Related
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);
this.state = {
array: [1, 2, 3],
objects: [{ id: 1 }, { id: 2 }, { id: 3 }]
}
How can I change the specific value of an object or array in the state without setStating the whole array/object?
something like
this.setState({ array[2]: 5 })
this.setState({ object[0].id: 0 })
You could use a helper function to set an element at an index and return that newly updated array
const array = [1, 2, 3]
const object = [{id: 1}, {id: 2}, {id: 3}]
const setElementAtIndex = (index, value, array) => [
...array.slice(0, index),
value,
...array.slice(index + 1)
]
console.log(setElementAtIndex(0, 99, array))
console.log(setElementAtIndex(1, 99, array))
console.log(setElementAtIndex(2, 99, array))
console.log(setElementAtIndex(0, { ...object[0], id: 0 }, object))
this.setState({ array: setElementAtIndex(2, 5, array) })
this.setState({ object: setElementAtIndex(0, { ...object[0], id: 0 }, object) })
I would use map.
const state = {
array: [1,2,3],
objects: [{id: 1}, {id: 2}, {id: 3}]
}
const newArray = state.array.map((v, i) => i === 2 ? 5 : v);
const newObjects = state.objects.map((v, i) => i === 0 ? {...v, id: 0} : v);
console.log(newArray);
console.log(newObjects);
// this.setState({ ...this.state, array: newArray });
// this.setState({ ...this.state, objects: newObjects });
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);
My JSON array
var jData = [
{id: 1, parent: null},
{id: 2, parent: null},
{id: 3, parent: 1},
{id: 4, parent: 2},
{id: 5, parent: 2},
{id: 6, parent: 1}];
I want this be to sorted like the following ( by id then by the parent )
[
{id: 1, parent: null},
{id: 3, parent: 1},
{id: 6, parent: 1}
{id: 2, parent: null},
{id: 4, parent: 2},
{id: 5, parent: 2},
];
What is the best way to do it in JavaScript?
I tried, but no luck
jData .sort((a, b) => a.id - b.id ||a.parent- b.parent);
Help!!
You need a topological sorting first and then take the nodes in order of appearance.
function getData(array) {
return array.flatMap(({ data, children = [] }) => [data, ...getData(children)]);
}
var data = [{ id: 1, parent: null }, { id: 2, parent: null }, { id: 3, parent: 1 }, { id: 4, parent: 2 }, { id: 5, parent: 2 }, { id: 6, parent: 1 }],
tree = function (data, root) {
var t = {};
data.forEach(data => {
Object.assign(t[data.id] = t[data.id] || {}, { data });
t[data.parent] = t[data.parent] || {};
t[data.parent].children = t[data.parent].children || [];
t[data.parent].children.push(t[data.id]);
});
return t[root].children;
}(data, null),
result = getData(tree);
console.log(result);
console.log(tree); // just to show what's happening
.as-console-wrapper { max-height: 100% !important; top: 0; }
If parent is null we use the id as parent value and sort by parent first (otherwise we won't reach your result). If the parent value comparison results in zero, we sort by id.
var jData = [{id:5,parent:2},{id:1,parent:null},{id:4,parent:2},{id:2,parent:null},{id:3,parent:1},{id:6,parent:1}];
let res = jData.sort((a,b) => {
let ap = a.parent ? a.parent : a.id,
bp = b.parent ? b.parent : b.id;
return ap - bp || a.id - b.id;
});
console.log(res);
You can use reduce to group each array to its parent. Use 0 if parent is null. Use another reduce to contruct the final array.
var jData = [{"id":1,"parent":null},{"id":2,"parent":null},{"id":3,"parent":1},{"id":4,"parent":2},{"id":5,"parent":2},{"id":6,"parent":1}]
var temp = jData.reduce((c, v) => {
let p = v.parent || 0;
c[p] = c[p] || [];
c[p].push(v);
return c;
}, {});
var newjData = temp[0].reduce((c, v) => {
var o = temp[v.id] || [];
o.sort((a, b) => a.id - b.id); //Sort. Incase the IDs are not in order in the original array.
c.push(v, ...o);
return c;
}, []);
console.log(newjData);
I have an array like so:
var array = [
{
name: "a",
value: 1
},
{
name: "a",
value: 2
},
{
name: "a",
value: 3
},
{
name: "b",
value: 0
},
{
name: "b",
value: 1
}
];
And I need an array like this:
var newarray = [
{
name: "a",
value: 2
},
{
name: "b",
value: 0.5
}
]
Where the new array has each unique name as an object with the average value.
Is there an easy way to accomplish this?
You'll have to loop through the array, computing the sum and counts for each object. Here's a quick implementation:
function average(arr) {
var sums = {}, counts = {}, results = [], name;
for (var i = 0; i < arr.length; i++) {
name = arr[i].name;
if (!(name in sums)) {
sums[name] = 0;
counts[name] = 0;
}
sums[name] += arr[i].value;
counts[name]++;
}
for(name in sums) {
results.push({ name: name, value: sums[name] / counts[name] });
}
return results;
}
Demonstration
Note, this kind of thing can be made much easier if you use a library like Underscore.js:
var averages = _.chain(array)
.groupBy('name')
.map(function(g, k) {
return {
name: k,
value: _.chain(g)
.pluck('value')
.reduce(function(x, y) { return x + y })
.value() / g.length
};
})
.value();
Demonstration
var array = [
{
name: "a",
value: 1
},
{
name: "a",
value: 2
},
{
name: "a",
value: 3
},
{
name: "b",
value: 0
},
{
name: "b",
value: 1
}
];
var sum = {};
for(var i = 0; i < array.length; i++) {
var ele = array[i];
if (!sum[ele.name]) {
sum[ele.name] = {};
sum[ele.name]["sum"] = 0;
sum[ele.name]["count"] = 0;
}
sum[ele.name]["sum"] += ele.value;
sum[ele.name]["count"]++;
}
var result = [];
for (var name in sum) {
result.push({name: name, value: sum[name]["sum"] / sum[name]["count"]});
}
console.log(result);
You can do it with Alasql library with one line of code:
var newArray = alasql('SELECT name, AVG([value]) AS [value] FROM ? GROUP BY name',
[array]);
Here I put "value" in square brackets, because VALUE is a keyword in SQL.
Try this example at jsFiddle
Here is a ES2015 version, using reduce
let arr = [
{ a: 1, b: 1 },
{ a: 2, b: 3 },
{ a: 6, b: 4 },
{ a: 2, b: 1 },
{ a: 8, b: 2 },
{ a: 0, b: 2 },
{ a: 4, b: 3 }
]
arr.reduce((a, b, index, self) => {
const keys = Object.keys(a)
let c = {}
keys.map((key) => {
c[key] = a[key] + b[key]
if (index + 1 === self.length) {
c[key] = c[key] / self.length
}
})
return c
})
And a possible solution using ECMA5 (as we seem to be missing one)
var sums = {},
averages = Object.keys(array.reduce(function (previous, element) {
if (previous.hasOwnProperty(element.name)) {
previous[element.name].value += element.value;
previous[element.name].count += 1;
} else {
previous[element.name] = {
value: element.value,
count: 1
};
}
return previous;
}, sums)).map(function (name) {
return {
name: name,
average: this[name].value / this[name].count
};
}, sums);
On jsFiddle
October 2020, I think this is the shortest way (ES6+)
const getAveragesByGroup = (arr, key, val) => {
const average = (a, b, i, self) => a + b[val] / self.length;
return Object.values(
arr.reduce((acc, elem, i, self) => (
(acc[elem[key]] = acc[elem[key]] || {
[key]: elem[key],
[val]: self.filter((x) => x[key] === elem[key]).reduce(average, 0),
}),acc),{})
);
};
console.log(getAveragesByGroup(array, 'name', 'value'))
Try by yourself :)