I'm facing an array of objects where it is possible to have more than one object with the same value of id. When the value of id is the same, the value of price will also be the same.
Here's an example:
[{
'id': 'A', 'price': 1, 'quantity': 1
}, {
'id': 'A', 'price': 1, 'quantity': 2
}, {
'id': 'B', 'price': 1, 'quantity': 1
}]
The result should be an array where only one object appears for every id. Moreover, quantity must be populated with the sum of the quantities in the repeated values of id.
Here's the expected result for the example above:
[{
'id': 'A','price': 1, 'quantity': 3
}, {
'id': 'B','price': 1, 'quantity': 1
}]
Actually, I need to pivot.
I would like to avoid jQuery and external calls. Is it possible to accomplish this using only JS functions?
Arrays of objects are a little messy to work with, but this is do-able.
const list = [
{'id': 'A', 'price': 1, 'quantity': 1},
{'id': 'A', 'price': 1, 'quantity': 2},
{'id': 'B', 'price': 1, 'quantity': 1}
];
// Objects are easier to work with. An intermediate step to combine entries by Id.
const intermediate = list.reduce((a, { id, price, quantity }) => {
a[id] = a[id] || { id, price, quantity: 0}; // Copy id & price if the entry doesn't exist yet.
a[id].quantity += quantity; // Add quantity.
return a;
}, {});
// Map it all back to an array of objects.
const result = Object.keys(intermediate).map(id => intermediate[id]);
console.log(result);
Here's a ES5 version:
var list = [
{'id': 'A', 'price': 1, 'quantity': 1},
{'id': 'A', 'price': 1, 'quantity': 2},
{'id': 'B', 'price': 1, 'quantity': 1}
];
// Objects are easier to work with. An intermediate step to combine entries by Id.
var intermediate = list.reduce(function(a, curr) {
a[curr.id] = a[curr.id] || { id: curr.id, price: curr.price, quantity: 0}; // Copy id & price if the entry doesn't exist yet.
a[curr.id].quantity += curr.quantity; // Add quantity.
return a;
}, {});
// Map it all back to an array of objects.
var result = Object.keys(intermediate).map(function(id){
return intermediate[id];
});
console.log(result);
You can try this code, it's very simple to understand:
var objs = [
{ 'id': 'A', 'price': 1, 'quantity': 1 },
{ 'id': 'A', 'price': 1, 'quantity': 2 },
{ 'id': 'B', 'price': 1, 'quantity': 1 }
];
const results = objs.reduce(function(acc, current) {
var obj = acc.find(function(o) {
return o.id === current.id && o.price === current.price
});
if (obj) {
obj.quantity += current.quantity;
} else {
acc.push(current);
}
return acc;
}, []);
console.log(results);
maybe not efficient, but does its work.
const array = [{
'id': 'A', 'price': 1, 'quantity': 1
},{
'id': 'A', 'price': 1, 'quantity': 2
},{
'id': 'B', 'price': 1, 'quantity': 1
}];
const aggregated = array.reduce(function (p, c) {
if (!p[c.id]) p[c.id] = {quantity: c.quantity, id: c.id, price: c.price};
else p[c.id].quantity += c.quantity;
return p;
}, {});
const final = Object.keys(aggregated).map(function(k) { return aggregated[k]; });
console.log(final);
Related
Got a bit of a puzzle here...I want to loop through allItems and return allItems but replace with any newItems that matches its id. How can I look for a match on id and then replace it with the correct object into the array?
const allItems = [
{
'id': 1,
'category_id': 1,
'text': 'old',
},
{
'id': 2,
'category_id': 1,
'text': 'old'
}
]
const newItems = [
{
'id': 1,
'category_id': 1,
'text': 'new',
'more_info': 'abcd'
},
{
'id': 2,
'category_id': 1,
'text': 'new',
'more_info': 'abcd'
}
]
What I tried so far:
for(let i = 0; i < allItems.length; i++) {
if(newItems.indexOf(allItems[i].id) > -1){
allItems[i] = newItems
}
}
How can I get the position of the object in newItems and then replace it into allItems?
Use Array.map and Array.find():
const allItems = [
{ 'id': 1, 'category_id': 1, 'text': 'old' },
{ 'id': 2, 'category_id': 1, 'text': 'old' }
];
const newItems = [
{ 'id': 1, 'category_id': 1, 'text': 'new', 'more_info': 'abcd' },
{ 'id': 2, 'category_id': 1, 'text': 'new', 'more_info': 'abcd' }
];
const result = allItems.map(x => {
const item = newItems.find(({ id }) => id === x.id);
return item ? item : x;
});
console.log(result);
This can even be shortened by using a logical or to return the original item when the call to find returns undefined:
const result = allItems.map(x => newItems.find(({ id }) => id === x.id) || x);
Regarding your code, you can't use indexOf since it only compares primitive values or references in the case of arrays and objects.
Just use map like so:
const allItems = [{
'id': 1,
'category_id': 1,
'text': 'old',
},
{
'id': 2,
'category_id': 1,
'text': 'old'
}
];
const newItems = [{
'id': 1,
'category_id': 1,
'text': 'new',
'more_info': 'abcd'
},
{
'id': 2,
'category_id': 1,
'text': 'new',
'more_info': 'abcd'
}
];
const replacedItems = allItems.map(e => {
if (newItems.some(({ id }) => id == e.id)) {
return newItems.find(({ id }) => id == e.id);
}
return e;
});
console.log(replacedItems);
Just using a simple Array.map and a method to check the other array.
const allItems = [
{
'id': 1,
'category_id': 1,
'text': 'old',
},
{
'id': 2,
'category_id': 1,
'text': 'old'
},
{
'id': 3,
'category_id': 1,
'text': 'old_same'
}
]
const newItems = [
{
'id': 1,
'category_id': 1,
'text': 'new',
'more_info': 'abcd'
},
{
'id': 2,
'category_id': 1,
'text': 'new',
'more_info': 'abcd'
}
]
const findNewItem = (oldItem) => newItems.find(item => item.id === oldItem.id);
let arr = allItems.map(item => findNewItem(item)||item);
console.log(arr);
Depending on how large your input arrays are, you might consider adding an intermediate step to build a "map" of your newItems, where the key of this map is the id of the item.
Using a mapping such as this would allow for much faster reconciliation (and replacement) of items from the allItems array with items in the newItems array:
function replaceItemsOnId(items, replacement) {
/*
Create a map where the key is the id of replacement items.
This map will speed up the reconciling process in the
subsequent "map" stage
*/
const replacementMap = replacement.reduce((map, item) => {
map[ item.id ] = item
return map;
}, {})
/*
Map the items to a new array where items in the result array
are either clones of the orignals, or replaced by items of
"replacement" array where thier id matches the item being mapped
*/
return items.map(item => {
const use = replacementMap[ item.id ] || item
return { ...use }
})
}
const allItems = [
{
'id': 1,
'category_id': 1,
'text': 'old',
},
{
'id': 2,
'category_id': 1,
'text': 'old'
}
]
const newItems = [
{
'id': 1,
'category_id': 1,
'text': 'new',
'more_info': 'abcd'
},
{
'id': 2,
'category_id': 1,
'text': 'new',
'more_info': 'abcd'
}
]
console.log(replaceItemsOnId(allItems, newItems))
You could get a reference to the object you are looking for by id but that would not be enough because then you would need the index in order to replace it in allItem (allItem[index] = newItem), so I suggest finding that index instead first like this:
for (let item of newItems) {
let indexNewItem = allItems.map(function (e) { return e.id }).indexOf(item.id);
allItems[indexNewItem] = item
}
this should work
The answers for this question mostly have the code iterating through all the items in the original array to see which should be replaced. If the original array is large, and you don't need to replace many, it might be better performance to change the logic. I only needed to replace one item in an array (customers). I did it like this:
const index = customers.findIndex(x => x.Id == editedCust.Id);
if (index >= 0)
customers[index] = editedCust;
You could iterate through all the items in newItems and do the above for each.
I have large array, which looks like this:
let arr = [{
'name': '1',
'val': '12'
},{
'name': '4',
'val': '52'
},
{
'name': '11',
'val': '15'
},
{
'name': '4',
'val': '33'
},
...]
I want find all objects with same name values and push them into separated arrays .
P.S. I don't know possible name values in array.
You can easily group by name to get what you need. Below is a quite general function to group any array of objects to a Map, by specified property. Then simply make it an array and you're done
function group(arr, propertyToGroupBy) {
return arr.reduce(function(a, b) {
return a.set(b[propertyToGroupBy], (a.get(b[propertyToGroupBy]) || []).concat(b));
}, new Map);
}
const map = group(arr, 'name');
console.log(Array.from(map));
<script>
let arr = [{
'name': '1',
'val': '12'
}, {
'name': '4',
'val': '52'
},
{
'name': '11',
'val': '15'
},
{
'name': '4',
'val': '33'
}
];
</script>
You can group your array based on name in an object accumulator. Then using Object.values() you can get all the values.
let arr = [{ 'name': '1', 'val': '12' },{ 'name': '4', 'val': '52' }, { 'name': '11', 'val': '15' }, { 'name': '4', 'val': '33' }],
result = Object.values(arr.reduce((r,o) => {
r[o.name] = r[o.name] || [];
r[o.name].push(o);
return r;
},{}));
console.log(result);
let arr=[{'name':'1','val':'12'},{'name':'4','val':'52'},{'name':'11','val':'15'},{'name':'4','val':'33'}];
let groups = arr.reduce((obj, el) => ({...obj, [el.name]: [...obj[el.name] || [], el] }), {});
console.log(groups);
Group with reduce and map out the output to be an array:
let arr = [{'name': '1','val': '12'},{'name': '4','val': '52'}, {'name': '11','val': '15'},{'name': '4','val': '33'}];
// group object by same name
const dataObj = arr.reduce((all, {name, val}) => {
if (!all.hasOwnProperty(name)) all[name] = [];
all[name].push({name, val});
return all;
}, {});
// map out the result into an array
const result = Object.keys(dataObj).map(k => (dataObj[k]));
console.log(result);
Let say I have an array of tree type data
[ { 'id': 1, 'name': 'root', 'parent_id': 1 },
{ 'id': 2, 'name': 'level 1', 'parent_id': 1 },
{ 'id': 3, 'name': 'level 2', 'parent_id': 1 },
{ 'id': 4, 'name': 'level 2.1', 'parent_id': 3 } ]
is it possible to update the array to
[ { 'id': 1, 'name': 'root', 'parent_id': 1, 'parent_name': 'root' },
{ 'id': 2, 'name': 'level 1', 'parent_id': 1, 'parent_name': 'root' },
{ 'id': 3, 'name': 'level 2', 'parent_id': 1, 'parent_name': 'root' },
{ 'id': 4, 'name': 'level 2.1', 'parent_id': 3, 'parent_name': 'level 2' } ]
using lodash forEach and find?
_.forEach(arr, function (o) {
let item = _.find(arr, {'id': o.parent_id})
o.parent_name = item.name
})
my problem is inside the function of forEach, it has no idea what arr is, I got it working replacing the forEach with a plain for loop.
for (i = 0; i < arr.length; i++) {
let item = _.find(arr, {'id': arr[i].parent_id})
arr[i].parent_name = item.name
}
So wondering if there're any more elegant way to accomplish this
Here's another way to solve your problem using a hash to improve performance:
let hash = _.keyBy(data, 'id');
This will create an object where the keys are the id:
{
1: { id: 1, name: 'root' ... },
2: { id: 2, name: 'level 1' ... },
...
}
Then just iterate over the data to set the parent_name using the hash:
_.each(data, item => item.parent_name = _.get(hash, [item.parent_id, 'name']));
Try reading the documentation on _.forEach.
The iteratee is invoked with three arguments: (value, index|key, collection).
You need to setup your forEach like so:
_.forEach(arr, function (o, idx, arr) {
let item = _.find(arr, {'id': o.parent_id})
o.parent_name = item.name
});
you can use .map to change object as-
const arr =[ { 'id': 1, 'name': 'root', 'parent_id': 1 },
{ 'id': 2, 'name': 'level 1', 'parent_id': 1 },
{ 'id': 3, 'name': 'level 2', 'parent_id': 1 },
{ 'id': 4, 'name': 'level 2.1', 'parent_id': 3 } ];
const newArr = arr.map((item) => {
if(name === 'root') return Object.assign({}, item, {parent_name: 'root'});
const findIndexOfParent = arr.findIndex((pItem) => pItem.id == item.parent_id);
if(findIndexOfParent > -1){
return Object.assign({}, item, {parent_name: arr[findIndexOfParent].name});
}
});
console.log(newArr);
Supposing I have the below:
var allFoods = Immutable.List();
var frenchFood = Immutable.List([
{
'type': 'french fries',
'price': 3
},
{
'type': 'petit gateau',
'price': 40
},
{
'type': 'croissant',
'price': 20
},
]);
var fastFood = Immutable.List([
{
'type': 'cheeseburger',
'price': 5
},
{
'type': 'vegan burger',
'price': 20
},
{
'type': 'french fries',
'price': 3
}
]);
I want to merge both lists, in a way that I also remove dupes (in this case, french fries), so the expected result would be:
{
'type': 'french fries', // keep the first french fries
'price': 3
},
{
'type': 'petit gateau',
'price': 40
},
{
'type': 'croissant',
'price': 20
},
{
'type': 'cheeseburger',
'price': 5
},
{
'type': 'vegan burger',
'price': 20
}
What I'm trying (doesn't remove dupes):
allFoods = frenchFood.concat(fastFood);
allFoods = allFoods.filter(function(item, pos) {
return allFoods.indexOf(item) === pos;
});
Which returns arrays merged, but still duplicated.
What am I missing?
const allFoods = frenchFood.concat(fastFood.filter((item) =>
frenchFood.indexOf(item) < 0
));
I would use reduce
var result = frenchFood.concat(fastFood).reduce( (reduction, food) => {
if(reduction[food.type]) {
return reduction;
} else {
return reduction.set([food.type], food);
}
}, new Immutable.Map()).valueSeq().toList();
I would highly encourage you to not nest js objects inside immutable structures. Better to wrap those objects in an Immutable.Map() or do Immutable.fromJS(yourJsObj).
Least amount of code
const results = Immutable.Set(frenchFood).union(Immutable.Set(fastFood));
However #rooftop answer fastest
https://jsperf.com/union-vs-concat-immutable
I found a best solution (for me) on medium, link to origin answer is dead: https://medium.com/#justintulk/merging-and-deduplicating-data-arrays-with-array-reduce-efaa4d7ef7b0
const arr1 = [
{ id: 1, name: 'Array 1-1' },
{ id: 2, name: 'Array 1-2' },
{ id: 3, name: 'Array 1-3' }
]
const arr2 = [
{ id: 1, name: 'Array 2-1' },
{ id: 3, name: 'Array 2-3' },
{ id: 4, name: 'Array 2-4' }
]
const mergeArrObjectsUnique = (currentArr, newArr) => {
let obj = {}
currentArr.forEach(item => {
obj[item.id] = item
})
newArr.forEach(item => {
obj[item.id] = item
})
let result = [];
for(let p in obj) {
if(obj.hasOwnProperty(p))
result.push(obj[p])
}
console.log('result: ', result)
return result
}
mergeArrObjectsUnique(arr1, arr2)
Well, I need to add price of products with same name and modify the array accordingly.
Input: records=[{'name':'A', 'price':200},{'name':'B', 'price':350},{'name':'A', 'price':150},{'name':'B', 'price':300}]
Output: records=[{'name':'A', 'price':350},{'name':'B', 'price':650}]
Will be glad if the solution is provided using javascript forEach function.
You could build a new array with the wanted objects and add the price to the grouped item, with a reference from this object.
var records = [{ 'name': 'A', 'price': 200 }, { 'name': 'B', 'price': 350 }, { 'name': 'A', 'price': 150 }, { 'name': 'B', 'price': 300 }],
result = [];
records.forEach(function (a) {
if (!this[a.name]) {
this[a.name] = { name: a.name, price: 0 };
result.push(this[a.name]);
}
this[a.name].price += a.price;
}, {});
document.write('<pre>' + JSON.stringify(result, 0, 4) + '</pre>');
Another way of doing it
records.map(function(element, index, array){
return {
name: element.name,
price: (array
.reduceRight(function(previousvalue, currentvalue, currentindex){
var dup = currentvalue.name === element.name;
var cp = dup? currentvalue.price: 0;
dup? array.splice(currentindex, 1):0;
return previousvalue + cp;
}, 0))}
}).filter(function(element){ return typeof element !== "undefined";});
Fiddle
https://jsfiddle.net/sfy2p1rf/1/