I have an array that looks like this:
const values = [
{
value: 2000,
items: [
{
value: 300,
},
],
},
]
I want to sum the total of all of the values in values.value, and if a values.items.value exists I also want to include it in my sum.
values.reduce((total, obj) => obj.value + total, 0);
What would be the correct way to key into the nested array so it sums both the top and nested value key in my reduce function? The output I'd like is 2300 but right now I can only get one level deep, and it's outputting 2000.
You could nest your reduce approach to handle the inner array while ensuring that the key exists in the inner array objects.
const values = [
{
value: 2000,
items: [
{
value: 300,
},
],
},
];
const total = values.reduce((acc, obj) => {
acc += obj.value;
acc += obj.items.reduce((a, o) => 'value' in o ? o.value + a : a, 0);
return acc;
}, 0);
console.log(total);
// 2300
You can use reduce add value and check for items if it's there then add the value of items arrays as well
const values = [{value: 2000,items: [{value: 300,},],},]
let op = values.reduce((op,{value,items}) => {
op+= value
if(items && items.length) {
items.forEach(({value})=> op+=value )
}
return op
},0)
console.log(op)
I will add an inner reduce() to get the accumulated sum for the items array. Also, I will add some checks with isNaN() and Array.isArray() just for safety:
const values = [
{value: 2000, items: [{value: 300}]},
{value: 3000},
{value: 2000, items: [{value: 300}, {foo: 20}]},
{nothing: "nothing"}
];
let res = values.reduce((acc, {value, items}) =>
{
acc += isNaN(value) ? 0 : value;
acc += Array.isArray(items) ?
items.reduce((sum, {value}) => sum + (isNaN(value) ? 0 : value), 0) :
0;
return acc;
}, 0);
console.log(res);
.as-console {background-color:black !important; color:lime;}
.as-console-wrapper {max-height:100% !important; top:0;}
You could iterate keys/values of the object and sum the a nested object and take the wanted key or zero as start value.
function sum(object, key) {
return Object.entries(object).reduce((s, [k, v]) => {
if (v && typeof v === 'object') return s + sum(v, key);
return s;
}, key in object ? object[key] : 0);
}
const values = [{ value: 2000, items: [{ value: 300 }] }];
console.log(sum(values, 'value'));
Related
i'm trying to duplicate objects based on two properties that have multiple values differentiated by a comma.
For example:
I have an object
const obj = {
id: 1
date: "2021"
tst1: "111, 222"
tst2: "AAA, BBB"
}
And I would like the result to be an array of 2 objects in this case (because there are 2 values in tst1 OR tst2, these 2 properties will always have the same nr of values differentiated by a comma)
[{
id: 1,
date: "2021",
tst1: "111",
tst2: "AAA",
},
{
id: 1,
date: "2021",
tst1: "222",
tst2: "BBB",
}]
What I tried is this:
I created a temporary object
const tempObject = {
id: obj.id,
date: obj.date,
}
And then I would split and map the property that has multiple values, like this:
cont newObj = obj.tst1.split(",").map(function(value) {
let finalObj = {}
return finalObj = {
id: tempObject.id,
date: tempObject.date,
tst1: value,
})
And now, the newObj is an array of objects and each object contains a value of tst1.
The problem is I still have to do the same for the tst2...
And I was wondering if there is a simpler method to do this...
Thank you!
Here is an example that accepts an array of duplicate keys to differentiate. It first maps them to arrays of entries by splitting on ',' and then trimming the entries, then zips them by index to create sub-arrays of each specified property, finally it returns a result of the original object spread against an Object.fromEntries of the zipped properties.
const mapDuplicateProps = (obj, props) => {
const splitProps = props.map((p) =>
obj[p].split(',').map((s) => [p, s.trim()])
);
// [ [[ 'tst1', '111' ], [ 'tst1', '222' ]], [[ 'tst2', 'AAA' ], [ 'tst2', 'BBB' ]] ]
const dupeEntries = splitProps[0].map((_, i) => splitProps.map((p) => p[i]));
// [ [[ 'tst1', '111' ], [ 'tst2', 'AAA' ]], [[ 'tst1', '222' ], [ 'tst2', 'BBB' ]] ]
return dupeEntries.map((d) => ({ ...obj, ...Object.fromEntries(d) }));
};
const obj = {
id: 1,
date: '2021',
tst1: '111, 222',
tst2: 'AAA, BBB',
};
console.log(mapDuplicateProps(obj, ['tst1', 'tst2']));
Not sure if that's what you're searching for, but I tried making a more general use of what you try to do:
const duplicateProperties = obj => {
const properties = Object.entries(obj);
let acc = [{}];
properties.forEach(([key, value]) => {
if (typeof value === 'string' && value.includes(',')) {
const values = value.split(',');
values.forEach((v, i) => {
if (!acc[i]) {
acc[i] = {};
}
acc[i][key] = v.trim();
});
} else {
acc.forEach(o => o[key] = value);
}
});
return acc;
};
const obj = {
id: 1,
date: '2021',
tst1: '111, 222',
tst2: 'AAA, BBB',
};
console.log(duplicateProperties(obj));
You could start by determining the length of the result using Math.max(), String.split() etc.
Then you'd create an Array using Array.from(), returning the correct object for each value of the output index.
const obj = {
id: 1,
date: "2021",
tst1: "111, 222",
tst2: "AAA, BBB",
}
// Determine the length of our output array...
const length = Math.max(...Object.values(obj).map(s => (s + '').split(',').length))
// Map the object using the relevant index...
const result = Array.from({ length }, (_, idx) => {
return Object.fromEntries(Object.entries(obj).map(([key, value]) => {
const a = (value + '').split(/,\s*/);
return [key, a.length > 1 ? a[idx] : value ]
}))
})
console.log(result)
.as-console-wrapper { max-height: 100% !important; }
I have an array of objects, objects that contain an order: number; property.
I want for each object in that array that has the order property higher than a specific value to have it decreased it by one.
Any simple way to achieve this?
myArray.forEach(x => x.order >= someValue)...
You can map the result, updating the value when >= someValue
const someValue = 3;
const result = [
{ order: 1 },
{ order: 2 },
{ order: 3 },
{ order: 4 }
].map(x => x.order >= someValue ? { ...x, order: x.order - 1 } : x);
console.log(result);
With array.reduce
const value = 2;
const testArray = [{ order: 1 }, { order: 1 }, { order: 3 }];
const result = testArray.reduce((acc, curr) => {
if (curr.order > value) {
return [...acc, { ...curr, order: curr.order - 1 }];
}
return [...acc, curr];
}, []);
console.log(result);
Several options to reach this:
Adding functionality to Array.prototype.
if(!Array.prototype.decreaseOrderIfBiggerThan) {
Array.prototype.decreaseOrderIfBiggerThan = function(value) {
if(!value || typeof value != 'number') return this;
return this.map((el) => ({
...el,
order: el?.order > value ? --el.order : el.order
}))
}
}
Simply mapping:
myArray = myArray.map((el) => ({
...el,
order: el?.order > value ? --el.order : el.order
}))
Mutating original array: it's basically #2 method but without assigning new array to original one and with forEach.
Hopes this is what you needed.
I have arr array of objects, I need to pivot it with product,calorie and apply (grouping & sum) on remaining parameters.
And then require data in single object.
I tried below code, it works fine but I divided code in 3 parts.
Could I have better code than this or it is ok.
var arr = [{
"product": "Jam",
"calorie": 2000,
"A": 300,
"B": 500,
"type": "Daily"
},
{
"product": "Sugar",
"calorie": 1000,
"A": 100,
"B": 200,
"type": "Daily"
}
]
var a1 = {}
var a2 = {}
//Step-1 Pivot
for (let i = 0; i < arr.length; i++) {
a1[arr[i]['product']] = arr[i]['calorie'];
}
//Step-2 Group and sum
a2 = groupAndSum(arr, ['type'], ['A', 'B'])[0];
//Step-3 merging.
console.log({ ...a1,
...a2
})
//General grouping and summing function that accepts an
//#Array:Array of objects
//#groupKeys: An array of keys to group by,
//#sumKeys - An array of keys to sum.
function groupAndSum(arr, groupKeys, sumKeys) {
return Object.values(
arr.reduce((acc, curr) => {
const group = groupKeys.map(k => curr[k]).join('-');
acc[group] = acc[group] || Object.fromEntries(groupKeys.map(k => [k, curr[k]]).concat(sumKeys.map(k => [k, 0])));
sumKeys.forEach(k => acc[group][k] += curr[k]);
return acc;
}, {})
);
}
Here a single function which takes 3 params:
const func = (arr, pivot_vals, sum_vals) => {
return arr.reduce((a, v) => {
pivot_vals.forEach((pivot) => {
a[v[pivot[0]]] = v[pivot[1]];
});
sum_vals.forEach((key) => {
if (!a[key]) a[key] = 0;
a[key] += v[key];
});
return a;
},{});
};
arr
containing the data
sum_vals
array with all props you want do be summed
pivot_vals
nested array with the props which should be linked
I wans't sure what to do with the type, since it is a string it can`t be summed. Did you want to count the amount of types ?
let arr = [
{
product: "Jam",
calorie: 2000,
A: 300,
B: 500,
type: "Daily",
},
{
product: "Sugar",
calorie: 1000,
A: 100,
B: 200,
type: "Daily",
},
];
let sum_vals = ["A","B"]
let pivot_vals = [["product", "calorie"]];
const func = (arr, pivot_vals, sum_vals) => {
return arr.reduce((a, v) => {
pivot_vals.forEach((pivot) => {
a[v[pivot[0]]] = v[pivot[1]];
});
sum_vals.forEach((key) => {
if (!a[key]) a[key] = 0;
a[key] += v[key];
});
return a;
},{});
};
console.log(func(arr, pivot_vals, sum_vals));
I have an object with multiple keys (e.g. idOne, idTwo, idThree, idFour) ... each key contains an array of objects. I would like to return and output the key with minimum price. In this example, idThree contains the minimum price of id and therefore should output idThree. I have code that returns the minimum price found ... but my goal is to return key (idThree). Is there a simpler/cleaner way?
const object = {
idOne: [{ price: 300 }],
idTwo: [{ price: 200 }, { price: 100 }],
idThree: [{ price: 90 }, { price: 100 }],
idFour: [{ price: 99 }, { price: 210 }]
}
Current Code
const arrayOfMinValues = []
for (const [key, value] of Object.entries(object)) {
const minimumEntry = Math.min(...value.map(item => item.price))
arrayOfMinValues.push(minimumEntry)
}
console.log('MIN VALUE IS: ', Math.min(...arrayOfMinValues)) // how can I return key?
If you first turn the object into an array of entries, and turn each subarray into the single lowest price in the array, you can then .reduce to iterate over all of those lowest prices and pick out the entry with the lowest one:
const object = {
idOne: [{ price: 300 }],
idTwo: [{ price: 200 }, { price: 100 }],
idThree: [{ price: 90 }, { price: 100 }],
idFour: [{ price: 99 }, { price: 210 }]
}
const minEntry = Object.entries(object)
.map(([key, arr]) => [key, Math.min(...arr.map(obj => obj.price))])
.reduce((a, b) => a[1] > b[1] ? b : a);
console.log('Min entry:', minEntry);
To access a property of an array, use [index] where index is the index you want to access:
const key = minEntry[0]
You can use nested reduce calls to get an object with the minimum key and value, and destructure the key:
const object = {"idOne":[{"price":300}],"idTwo":[{"price":200},{"price":100}],"idThree":[{"price":90},{"price":100}],"idFour":[{"price":99},{"price":210}]}
const { key } = Object.entries(object)
.reduce((acc, [key, values]) =>
values.reduce((r, { price }) => price < r.price ? { key, price } : r, acc)
, { key: null, price: Infinity })
console.log(key)
Another variation of reduce() using find()
const object = {"idOne":[{"price":300}],"idTwo":[{"price":200},{"price":100}],"idThree":[{"price":90},{"price":100}],"idFour":[{"price":99},{"price":210}]}
const [key, lp] = Object.entries(object).reduce((a, [k, v])=>{
const low = v.find(o => o.price < a[1]);
return low ? [k, low.price] : a;
},[null,Infinity])
console.log(key, ' has low price of ',lp )
Simple approach to use with steps:
Create sumPrices() function to get sum of prices.
function sumPrices(arr){
sum = 0;
for(const price of arr){
sum += price["price"]
}
return sum;
}
Create variable keys has all keys.
Create two vars minKey the key of lowest prices.
and minSum sum of lowest prices.
const keys = Object.keys(object);
let minKey = null,
minSum = Number.MAX_SAFE_INTEGER;
iterate over array keys
get each sum of each inner array
and compare currentSum with minSum if less than minimum.
keep track the minSum with thier recpective key.
for(const key of keys){
const currentSum = sumPrices(object[key])
if(currentSum <= minSum){
minKey = key;
minSum = currentSum;
}
}
console.log(minKey);
I have the following array:
var array = []
array.push({payslipId: 1759});
array.push({payDate: "2019-04-19T00:00:00+00:00"});
array.push({periodsPerYear: 52});
array.push({taxPeriod: 2});
array.push({payslipId: 1760});
array.push({payDate: "2019-04-19T00:00:00+00:00"});
array.push({periodsPerYear: 52});
array.push({taxPeriod: 2});
What I am hoping to be able to do is create an array of distinct names, and where the values are numeric, the total off all in the above array would be shown.
The only way I can think to do this is to create a new array, and add each name to it, then loop around the above array, and add to the value to the name, if the value is numeric.
I am not overly familiar with javascript, so am hoping there is a neater way, a bit like linq in C#?
Desired output:
[{ payslipId: 3519 }, { periodsPerYear: 104} , { taxPeriod: 4}]
You could take a Map and reduce the array by taking the only key/value pair of the object.
function group(array) {
return Array.from(
array.reduce((m, o) => {
var [k, v] = Object.entries(o)[0];
return typeof v === 'number'
? m.set(k, (m.get(k) || 0) + v)
: m;
}, new Map),
([k, v]) => ({ [k]: v })
);
}
var array = [{ payslipId: 1759 }, { payDate: "2019-04-19T00:00:00+00:00" }, { periodsPerYear: 52 }, { taxPeriod: 2 }, { payslipId: 1760 }, { payDate: "2019-04-19T00:00:00+00:00" }, { periodsPerYear: 52 }, { taxPeriod: 2 }],
result = group(array);
console.log(result);
As I have mentioned in the comment to the original post, you can use reduce() function for aggregation. You may apply any condition you need to for aggregation. As you have mentioned, I have considered number fields only.
Here you go:
var array = []
array.push({ payslipId: 1759 });
array.push({ payDate: "2019-04-19T00:00:00+00:00" });
array.push({ periodsPerYear: 52 });
array.push({ taxPeriod: 2 });
array.push({ payslipId: 1760 });
array.push({ payDate: "2019-04-19T00:00:00+00:00" });
array.push({ periodsPerYear: 52 });
array.push({ taxPeriod: 2 });
//console.log(array);
result = array.reduce((aggr, item) => {
itemKey = Object.keys(item)[0];
if (Number.isInteger(item[itemKey])) {
if (!aggr[itemKey]) {
aggr[itemKey] = 0;
}
aggr[itemKey] = parseInt(aggr[itemKey]) + parseInt(item[itemKey]);
}
return aggr;
}, {});
console.log(result);
Another option could be:
let array2 = [array[0]];
array.shift();
array.forEach(obj => {
if(typeof(obj[Object.keys(obj)[0]]) !== 'number') return;
//test if the object with the same property name already exists to array2 or not. If it doesn't exist, it will be added to array2. If it does only the value will be added to the existing element
let test = array2.reduce((accum,obj2) => {
return accum && (Object.keys(obj2)[0] !== Object.keys(obj)[0]);
}, true);
if(test) array2.push(obj);
else array2.find(obj2 => obj2.hasOwnProperty(Object.keys(obj)[0]))[Object.keys(obj)[0]] += obj[Object.keys(obj)[0]];
});
console.log(array2);