sum array of objects inside an array of objects - javascript

I have an array of objects and inside the array there is another array of objects, I would like to sum the values. So the sum is based on the same picker_id. Then sum current_capacity, process_time_in_minutes, and picked_qty inside products which is array of objects.
Here is my data:
var arr = [
{
current_capacity: 6000,
picker_id: "icQrHPuE2fMZslceSG6liwuRar92",
process_time_in_minutes: 10,
products: [
{
product_id: 1,
picked_qty: 2
},
{
product_id: 2,
picked_qty: 3
}
]
},
{
current_capacity: 2500,
picker_id: "icQrHPuE2fMZslceSG6liwuRar92",
process_time_in_minutes: 20,
products: [
{
product_id: 1,
picked_qty: 10
}
]
},
{
current_capacity: 36000,
picker_id: "WIRzfIZALeftRk3DRGvh4nBdxQV2",
process_time_in_minutes: 15,
products: [
{
product_id: 1,
picked_qty: 2
},
{
product_id: 2,
picked_qty: 3
}
]
}
];
Here is my code:
var res = arr.reduce((acc, obj) => {
var existObj = acc.find((item) => item.picker_id === obj.picker_id);
if (existObj) {
let total_picked = obj.products.reduce((acc2, curr) => acc2 + curr);
// console.log("total_picked", total_picked);
existObj.current_capacity =
existObj.current_capacity + obj.current_capacity;
existObj.process_time_in_minutes =
existObj.process_time_in_minutes + obj.process_time_in_minutes;
existObj.total = existObj.total ? existObj.total : 0 + total_picked;
return acc;
}
acc.push(obj);
return acc;
}, []);
const formatted = res.map((el) => {
return {
picker_id: el.picker_id,
total_volume: el.current_capacity,
total_time: el.process_time_in_minutes,
total_products: el.total
};
});
The result is as below:
[
{
picker_id: "icQrHPuE2fMZslceSG6liwuRar92"
total_volume: 8500
total_time: 30
total_products: "0[object Object]"
},
{
picker_id: "WIRzfIZALeftRk3DRGvh4nBdxQV2"
total_volume: 36000
total_time: 15
total_products: undefined
}
]
Expected like below:
[
{
picker_id: "icQrHPuE2fMZslceSG6liwuRar92"
total_volume: 8500
total_time: 30
total_products: 15
},
{
picker_id: "WIRzfIZALeftRk3DRGvh4nBdxQV2"
total_volume: 36000
total_time: 15
total_products: 5
}
]

Using a little parameter destructuring, I think you can do a little further clean-up after you fix the problems others have described. My version might look like this:
const extract = (xs) => Object .values (xs .reduce (
(acc, {current_capacity, picker_id, process_time_in_minutes, products}) => {
const curr = acc [picker_id] || (acc [picker_id] = {
picker_id, total_volume: 0, total_time: 0, total_products: 0
})
curr .total_volume += current_capacity
curr .total_time += process_time_in_minutes
curr .total_products += products .reduce ((t, p) => t + p .picked_qty, 0)
return acc
},
{}
))
const arr = [{current_capacity: 6e3, picker_id: "icQrHPuE2fMZslceSG6liwuRar92", process_time_in_minutes: 10, products: [{product_id: 1, picked_qty: 2}, {product_id: 2, picked_qty: 3}]}, {current_capacity: 2500, picker_id: "icQrHPuE2fMZslceSG6liwuRar92", process_time_in_minutes: 20, products: [{product_id: 1, picked_qty: 10}]}, {current_capacity: 36e3, picker_id: "WIRzfIZALeftRk3DRGvh4nBdxQV2", process_time_in_minutes: 15, products: [{product_id: 1, picked_qty: 2}, {product_id: 2, picked_qty: 3}]}]
console .log (extract (arr))
.as-console-wrapper {max-height: 100% !important; top: 0}

You can also achieve your output by this
function getProductQty(arr){
let total = 0;
arr.forEach(prd => {
total += prd.picked_qty
})
return total;
}
const result = arr.reduce((acc,product) => {
if(!acc.hasOwnProperty(product.picker_id)){
acc[product.picker_id] = {
picker_id: product.picker_id,
total_volume: product.current_capacity,
total_time: product.process_time_in_minutes
}
acc[product.picker_id].total_products = getProductQty(product.products);
}else{
acc[product.picker_id].total_volume = acc[product.picker_id].total_volume + product.current_capacity
acc[product.picker_id].total_time = acc[product.picker_id].total_time + product.process_time_in_minutes
acc[product.picker_id].total_products = acc[product.picker_id].total_products + getProductQty(product.products);
}
return acc
},{})
console.log(Object.values(result),'result');

Issue with your implementation was if the existObj doesn't exit in your acc, you were directly pushing the obj instead you need to process the total first from the inner array of products.
I have updated your code to look cleaner and maintainable.
Approach:
build a dict for each picker_id which hold the computed data
convert dict to list
var result = arr.reduce((acc, obj) => {
if (!acc[obj.picker_id]) {
acc[obj.picker_id] = {
total_volume: 0,
total_time: 0,
total_products: 0
};
}
const selectedPicker = acc[obj.picker_id];
const total_picked = obj.products.reduce((acc2, item) => acc2 + item.picked_qty, 0);
selectedPicker.total_volume = selectedPicker.total_volume + obj.current_capacity;
selectedPicker.total_time =
selectedPicker.total_time + obj.process_time_in_minutes;
selectedPicker.total_products = selectedPicker.total_products + total_picked;
return acc;
}, {});
const formatted = Object.keys(result).reduce((acc, picker_id) => {
acc.push({
picker_id,
...result[picker_id]
})
return acc;
}, [])
console.log("formmated", formatted);

Related

Transform nested object of objects of arrays in JS

Besides the horrible name of the question my question is quite simple. I have this object:
let test = {
date1: [
{
time: 1,
value: 5,
},
{
time: 2,
value: 6,
},
],
date2: [
{
time: 1,
value: 20,
},
{
time: 2,
value: 10,
},
],
};
That I want to transform to something like this:
let result = {
date1: {
values: [5, 6],
times: [1, 2],
},
date2: {
values: [1, 2], // easier to summarise?!
times: [10, 20],
},
};
I actually want to do this in order to summarise the value-values for each date. I thought that if I have them in an array it would be easier to summarise them. I know there are other forms to do this (and I'd be happy to see any solutions).
My current approach does not what I want it to do. It looks like this:
let keys = Object.keys(test);
let red = keys.reduce((acc, curr) => {
return (acc[curr] = test[curr].map((e) => e.value));
}, {});
console.log(`red: `, red);
And produces this:
red: [ 20, 10 ]
This
return (acc[curr] = test[curr].map((e) => e.value));
is equivalent to
acc[curr] = test[curr].map((e) => e.value);
return acc[curr];
going inside a nested key of the accumulator on every iteration - which isn't the logic you want. Return the whole accumulator on a separate line, so previously assigned values don't get lost, and you also need to account for both the time and value properties of the array being iterated over - your => e.value only extracts one of the two properties you want.
let test = {
date1: [
{
time: 1,
value: 5,
},
{
time: 2,
value: 6,
},
],
date2: [
{
time: 1,
value: 20,
},
{
time: 2,
value: 10,
},
],
};
const keys = Object.keys(test);
const result = keys.reduce((acc, key) => {
acc[key] = {
values: test[key].map(({ value }) => value),
times: test[key].map(({ time }) => time),
};
return acc;
return acc;
}, {});
console.log(result);
or do
let test = {
date1: [
{
time: 1,
value: 5,
},
{
time: 2,
value: 6,
},
],
date2: [
{
time: 1,
value: 20,
},
{
time: 2,
value: 10,
},
],
};
const result = Object.fromEntries(
Object.entries(test).map(([key, arr]) => [
key,
{
values: arr.map(({ value }) => value),
times: arr.map(({ time }) => time),
}
])
);
console.log(result);
Try modifying it a little like this:
let result = Object.keys(test).reduce((acc, key) => {
test[key].forEach((item) => {
acc.push({
date: key,
time: item.time,
value: item.value,
});
});
return acc;
}
, []);
console.log(result);
Assuming all inner objects have the same keys and no date array is empty:
let test = {date1:[{time:1,value:5},{time:2,value:6},],date2:[{time:1,value:20},{time:2,value:10},]};
let keys = Object.keys(test);
let red = keys.reduce((acc, curr) => ({
...acc,
[curr]: Object.keys(test[curr][0])
.reduce((acc, key) => ({
...acc,
[key + 's']: test[curr].map(o => o[key])
}), {})
}), {});
console.log(`red: `, red);
There is no need to first create arrays when you want to sum up values from different objects. It looks like you want to achieve this result:
{
date1: 11
date2: 30
}
The idea to use reduce is fine (for summing up values). You can use Object.entries and Object.fromEntries on top of that, in order to create the new object structure:
const test = {date1: [{time: 1,value: 5,},{time: 2,value: 6,},],date2: [{time: 1,value: 20,},{time: 2,value: 10,},],};
const result = Object.fromEntries(
Object.entries(test).map(([key, arr]) =>
[key, arr.reduce((sum, {value}) => sum + value, 0)]
)
);
console.log(result);

Rewriting reduce callback to stop reassigning accumulator

I have a function written like so:
type ChartData = {
x?: number[],
y?: number[],
z?: number[],
};
const data = [{ x: 1, y: 2, z: 3 }, { x: 4, y: 7, z: 10 }];
const formattedData = data.reduce((acc, el) => {
Object.entries(el!).forEach(([k, v]) => {
acc = { ...acc, [k]: [...(acc[k as keyof ChartData] || []), v] };
});
return acc;
}, {} as ChartData);
This code does what I want, but I'm getting an eslint error: no-param-reassign.
I know that I could disable the rule, but I wondered if there's a better way of writing this reduce callback so that I am not reassigning acc.
You can assign to acc[k] directly:
const data = [{ x: 1, y: 2, z: 3 }, { x: 4, y: 7, z: 10 }];
const formattedData = data.reduce((acc, el) => {
Object.entries(el).forEach(([k, v]) => {
acc[k] = (acc[k] || []).concat(v);
});
return acc;
}, {});
console.log(formattedData);
More code but also more efficient (fewer allocations and copies):
const data = [{ x: 1, y: 2, z: 3 }, { x: 4, y: 7, z: 10 }];
const formattedData = data.reduce((acc, el) => {
Object.entries(el).forEach(([k, v]) => {
if (!acc[k]) {
acc[k] = [];
}
acc[k].push(v);
});
return acc;
}, {});
console.log(formattedData);
This 'no-param-reassign' warning mean that you have modified the function params inside the function. So here maybe a workaround for this warning.
type ChartData = {
x?: number[],
y?: number[],
z?: number[],
};
const data = [{ x: 1, y: 2, z: 3 }, { x: 4, y: 7, z: 10 }];
const formattedData = data.reduce((acc, el) => {
let newAcc = { ...acc };
Object.entries(el!).forEach(([k, v]) => {
newAcc = { ...newAcc , [k]: [...(newAcc [k as keyof ChartData] || []), v] };
});
return newAcc ;
}, {} as ChartData);

How can I build an exception handler?

I need to handle "null","yes"/"no" as exeptions that are = to 0 and strings with "4" as points. When they are ordered it should be printed out as it was given as input. I tried to use a for loop to reach the scores but it doesn't work. How can I build an exception handler?
function reorder(people) {
const arr = people;
for (let i in arr) {
for (let a in i.scores ) {
if (a === null || a === "yes" || a === "no") {
a = 0;
} else if (typeof a === "string") {
a = Number(a);
}
}
}
const sorted = arr
.map((e) => ({
...e,
sum: e.scores.reduce((total, score) => total + score, 0),
}))
.sort((a, b) => b.sum - a.sum || a.name.localeCompare(b.name))
.map(({ sum, ...e }) => e);
return sorted;
}
const input = [
{ name: "Joe", scores: [1, 2, "4.1"] },
{ name: "Jane", scores: [1, null, 3] },
{ name: "John", scores: [1, 2, 3] },
];
const output = reorder(input);
console.log(output);
You coud adjust reduce callback and convert the score to number or take zero for adding.
function reorder(people) {
return people
.map(e => ({ ...e, sum: e.scores.reduce((total, score) => total + (+score || 0), 0) }))
.sort((a, b) => b.sum - a.sum || a.name.localeCompare(b.name))
.map(({ sum, ...e }) => e);
}
const
input = [{ name: "Joe", scores: [1, 2, "4.1"] }, { name: "Jane", scores: [1, null, 3] }, { name: "John", scores: [1, 2, 3] }],
output = reorder(input);
console.log(output);
.as-console-wrapper { max-height: 100% !important; top: 0; }

Javascript flatten deeply nested Array with objects and renaming properties

I'm stuck again with some flattening and renaming of the following.
What I got:
test = [
{
date: '2020-03-30',
station: {
id: 0,
name: 'some description'
},
firstValues: [
{
result: 1,
type: 4,
max: 18,
min: 1,
},
{
result: 2,
type: 5,
max: 15,
min: 2,
}
],
lastValues: [
{
result: 1,
type: 3,
max: 17,
min: 1
},
{
result: 2,
type: 8,
max: 20,
min: 2
}
],
iD: 'xxx3',
count: 1
},
{
next object with same structure
}
]
What I try to achieve:
test = [
{
date: '2020-03-30',
station: 'some description',
first_E01_result: 1,
first_E01_type: 4,
first_E01_max: 18,
first_E01_min: 1,
first_E02_result: 2,
first_E02_type: 5,
first_E02_max: 15,
first_E02_min: 2,
last_E01_result: 1,
last_E01_type: 3,
last_E01_max: 17,
last_E01_min: 1,
last_E02_result: 2,
last_E02_type: 8,
last_E02_max: 20,
last_E02_min: 2,
iD: 'xxx3',
count: 1
},
{
next object with same structure
}
]
I'm quite aware that my approach isn't the right thing. I tried different things so far but couldn't get it working. I'm totally stuck again to find the right way because I do run into two main issues:
How can I make the difference between first and last values? (switch case or if and if else?)
and
How can I access the name property from the station object and assign it to the key of "station"
Here is my last approach which is still missing the right code for the mentioned problems:
convertTest(input) {
return input.map(obj => {
const obj1 = {};
const obj2 = {};
for (const prop in obj) {
if (obj.hasOwnProperty(prop) && Array.isArray(obj[prop])) {
for (let i = 0; i < obj[prop].length; i++) {
for (const [key, value] of Object.entries(obj[prop][i])) {
const name = 'first_EO' + (i + 1).toString() + '_' + key;
obj2[name] = value;
}
}
} else {
obj1[prop] = obj[prop];
}
const dataconverted = Object.assign({}, obj1, obj2);
return dataconverted;
}
});
}
You could take a recursive approach for all other nested objects except the first level with special cases.
var data = [{ date: '2020-03-30', station: { id: 0, name: 'some description' }, firstValues: [{ result: 1, type: 4, max: 18, min: 1 }, { result: 2, type: 5, max: 15, min: 2 }], lastValues: [{ result: 1, type: 3, max: 17, min: 1 }, { result: 2, type: 8, max: 20, min: 2 }], iD: 'xxx3', count: 1 }],
getPath = object => Object.entries(object).reduce((r, [k, v], i) => {
if (v && typeof v === 'object') {
r.push(...getPath(v).map(([left, right]) => [(Array.isArray(object) ? 'E' + (i + 1).toString().padStart(2, 0) : k) + '_' + left, right]));
} else {
r.push([k, v]);
}
return r;
}, []),
result = data.map(o => Object.fromEntries(Object.entries(o).reduce((r, [k, v]) => {
if (k === 'station') {
r.push([k, v.name]);
} else if (v && typeof v === 'object') {
if (k.endsWith('Values')) k = k.slice(0, -6);
r.push(...getPath(v).map(([left, right]) => [k + '_' + left, right]));
} else {
r.push([k, v]);
}
return r
}, [])));
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
You should use Map and Object.keys
var test = [{"date":"2020-03-30","station":{"id":0,"name":"some description"},"firstValues":[{"result":1,"type":4,"max":18,"min":1},{"result":2,"type":5,"max":15,"min":2}],"lastValues":[{"result":1,"type":3,"max":17,"min":1},{"result":2,"type":8,"max":20,"min":2}],"iD":"xxx3","count":1}]
console.log(flatten(test));
function flatten(arr) {
return arr.map(el => ModifyObject(el))
}
function ModifyObject(el) {
const obj = {};
obj.date = el.date;
obj.iD = el.iD;
obj.count = el.count;
obj.station = el.station.name;
flattenObjectByProperty(obj, el, 'firstValues')
flattenObjectByProperty(obj, el, 'lastValues')
return obj;
}
function flattenObjectByProperty(obj, el, property) {
(el[property] || []).map((child, i) => {
Object.keys(child).forEach(key => {
obj[property + '_E' + i + '_' + key] = child[key]
});
});
}
Please try this.
test = test.map((elem) => {
elem.firstValues.forEach((child, index) => {
for(let key in child){
let v = `first_E${index+1}_${key}`
elem[v] = child[key];
}
})
elem.lastValues.forEach((child, index) => {
for(let key in child){
let v = `last_E${index+1}_${key}`
elem[v] = child[key];
}
})
elem['station'] = elem.station.name;
delete elem.firstValues;
delete elem.lastValues;
return elem;
})
You can use Array.prototype.reduce to flatten as per your requirement
const test = [
{
date: '2020-03-30',
station: {
id: 0,
name: 'some description'
},
firstValues: [
{
result: 1,
type: 4,
max: 18,
min: 1,
},
{
result: 2,
type: 5,
max: 15,
min: 2,
}
],
lastValues: [
{
result: 1,
type: 3,
max: 17,
min: 1
},
{
result: 2,
type: 8,
max: 20,
min: 2
}
],
iD: 'xxx3',
count: 1
}
];
const result = test.reduce((acc, curr) => {
const { firstValues, lastValues, ...rest } = curr;
const modifiedFirstValues = firstValues.reduce((r, c, i) => {
Object.entries(c).forEach(([key, value]) => {
const modifiedKey = `first_E${i + 1}_${key}`;
r[modifiedKey] = value;
});
return r;
}, Object.create(null));
const modifiedLastValues = lastValues.reduce((r, c, i) => {
Object.entries(c).forEach(([key, value]) => {
const modifiedKey = `last_E${i + 1}_${key}`;
r[modifiedKey] = value;
});
return r;
}, Object.create(null));
const finalObj = {
...rest,
...modifiedFirstValues,
...modifiedLastValues
};
acc.push(finalObj);
return acc;
}, []);
console.log(result);

Merge arrays into one

I'm trying merge arrays into one in javascript.
I have this Array:
[{ID: 111, SEG: 4}, {ID: 111, SEG:
3}]
And I need this:
[{ID: 111, SEG: [3, 4]}]
This can be approximated to a solution, depending on the data:
var items = [
{
id: 1,
value: 5
},
{
id: 1,
value: 3
},
{
id: 2,
value: 40
},
{
id: 2,
value: 35
}
];
var group = function (arr, groupBy) {
var values = {};
arr.forEach(function (element) {
var item = element;
var index = item[groupBy];
if (!values[index]) {
values[index] = item;
}
else {
item.value += values[index].value;
}
values[index] = item;
});
return Object.keys(values).map(function (k) { return values[k]; });
};
console.log(group(items, 'id'));
The Problem can be solved using reduce.
let dataJ = [{ID: 111, SEG: 4}, {ID: 111, SEG: 3}]
let newData = dataJ.reduce(function(acc, curr) {
let index = acc.findIndex(item => item.ID === curr.ID);
if (index === -1) {
acc.push(curr);
} else {
if (!acc[index].SEG || !Array.isArray(acc[index].SEG)) {
acc[index].SEG = [];
}
acc[index].SEG.push(curr.SEG);
}
return acc;
}, []);
console.log(newData); // [{ID: 111, SEG: [3, 4]}]

Categories

Resources