Reduce array by its items key in ES6 - javascript

I've got array of fruits:
const fruits = [
{ name: 'apple', count: 2 },
{ name: 'apple', count: 4 },
{ name: 'banana', count: 5 },
{ name: 'apple', count: 3 },
{ name: 'apple', count: 4 },
{ name: 'apple', count: 1 },
{ name: 'banana', count: 3 },
{ name: 'apple', count: 1 },
{ name: 'orange', count: 2 }
];
What I try to achieve is to reduce this array so it look like this:
let fruitsReduces = [
{ name: 'apple', count: 15 },
{ name: 'banana', count: 8 },
{ name: 'orange', count: 2 }
];
I've done this using very ugly for loop:
for (let i = 0; i < fruits.length; i++) {
fruitItem = {};
fruitItem.name = fruits[i].name;
fruitItem.count = fruits[i].count;
const fruitAlreadyInTheArray = fruitsReduced.find(fruit => fruit.name === fruitItem.name);
if (!fruitAlreadyInTheArray) {
fruitsReduced.push(fruitItem);
} else {
fruitAlreadyInTheArray.count += fruitItem.count;
}
}
But I'm sure that same thing can be done somehow by using ES6 array.reduce. Can you help me?
I looked at JS (ES6): Reduce array based on object attribute but I can't figure it out.

You can us reduce() the array into an object. Use Object.values() to convert the object into an array.
const fruits = [{"name":"apple","count":2},{"name":"apple","count":4},{"name":"banana","count":5},{"name":"apple","count":3},{"name":"apple","count":4},{"name":"apple","count":1},{"name":"banana","count":3},{"name":"apple","count":1},{"name":"orange","count":2}];
var result = Object.values(fruits.reduce((c, {name,count}) => {
c[name] = c[name] || {name,count: 0};
c[name].count += count;
return c;
}, {}));
console.log(result);

You could use an array as accumulator and seach for an inserted object with a wanted name for adding the actual count. If not found add a new object.
const
fruits = [{ name: 'apple', count: 2 }, { name: 'apple', count: 4 }, { name: 'banana', count: 5 }, { name: 'apple', count: 3 }, { name: 'apple', count: 4 }, { name: 'apple', count: 1 }, { name: 'banana', count: 3 }, { name: 'apple', count: 1 }, { name: 'orange', count: 2 }],
result = fruits.reduce((r, { name, count }) => {
var item = r.find(o => o.name === name);
if (!item) {
item = { name, count: 0 };
r.push(item);
}
item.count += count;
return r;
}, []);
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
A different approach by collecting all counts first with a Map and render the wanted array with Array.from and a mapping function which builds new objects.
const
fruits = [{ name: 'apple', count: 2 }, { name: 'apple', count: 4 }, { name: 'banana', count: 5 }, { name: 'apple', count: 3 }, { name: 'apple', count: 4 }, { name: 'apple', count: 1 }, { name: 'banana', count: 3 }, { name: 'apple', count: 1 }, { name: 'orange', count: 2 }],
result = Array.from(
fruits.reduce(
(m, { name, count }) => m.set(name, (m.get(name) || 0) + count),
new Map
),
([name, count]) => ({ name, count })
);
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }

You can try to reduce() like
const fruits = [
{ name: 'apple', count: 2 },
{ name: 'apple', count: 4 },
{ name: 'banana', count: 5 },
{ name: 'apple', count: 3 },
{ name: 'apple', count: 4 },
{ name: 'apple', count: 1 },
{ name: 'banana', count: 3 },
{ name: 'apple', count: 1 },
{ name: 'orange', count: 2 }
];
function getReduced(total, {name,count}) {
total[name] = total[name] || {name,count: 0};
total[name].count += count;
return total;
}
let fruitsReduces = Object.values(fruits.reduce(getReduced,{}));
console.log(fruitsReduces);

Yet another (slightly different) solution
const fruits = [
{ name: 'apple', count: 2 },
{ name: 'apple', count: 4 },
{ name: 'banana', count: 5 },
{ name: 'apple', count: 3 },
{ name: 'apple', count: 4 },
{ name: 'apple', count: 1 },
{ name: 'banana', count: 3 },
{ name: 'apple', count: 1 },
{ name: 'orange', count: 2 } ];
const reduced = Object.entries (
fruits.reduce( (p, o) =>
p[o.name] ? (p[o.name] += o.count, p) : {...p, ...{[o.name]: o.count}}, {} ) )
.map(([key, value]) => ({name: key, count: value}));
console.log(reduced);

You might use a hashtable to build up the resultig array:
const result = [], hash = {};
for(const {name, count} of fruits) {
if(hash[name]) {
hash[name].count += count;
} else {
result.push(hash[name] = {name, count});
}
}

You can use a simple forEach method like this:
const res = {};
fruits.forEach((e) => res[e.name] = (res[e.name] || 0) + e.count);
res will contain your expected result.

Related

Reduce method os array of objects with a condition: is a number?

I have an array:
const products = [
{ product: 'banana', price: 3 },
{ product: 'mango', price: 6 },
{ product: 'potato', price: ' ' },
{ product: 'avocado', price: 8 },
{ product: 'coffee', price: 10 },
{ product: 'tea', price: '' },
]
And Iwould like to sum all prices. What I tried:
const sum = products.reduce(function(acc, cur){
if (Number.isInteger(cur.price))
return acc+cur.price
}, 0)
console.log(sum)
it returns undefined. I also tried that without the condition, it returns a string. Where do I make a mistake?
You need to return the accumulator if the condition is not true.
const
products = [{ product: 'banana', price: 3 }, { product: 'mango', price: 6 }, { product: 'potato', price: ' ' }, { product: 'avocado', price: 8 }, { product: 'coffee', price: 10 }, { product: 'tea', price: '' }],
sum = products.reduce(function(acc, cur) {
if (Number.isInteger(cur.price)) return acc + cur.price;
else return acc;
}, 0);
console.log(sum);
A shorter approach adds the value conditionally and returns only the accumulator at the end.
const
products = [{ product: 'banana', price: 3 }, { product: 'mango', price: 6 }, { product: 'potato', price: ' ' }, { product: 'avocado', price: 8 }, { product: 'coffee', price: 10 }, { product: 'tea', price: '' }],
sum = products.reduce(function(acc, cur) {
if (Number.isInteger(cur.price)) acc += cur.price;
return acc;
}, 0);
console.log(sum);
Try this:
const products = [
{ product: 'banana', price: 3 },
{ product: 'mango', price: 6 },
{ product: 'potato', price: ' ' },
{ product: 'avocado', price: 8 },
{ product: 'coffee', price: 10 },
{ product: 'tea', price: '' },
]
const sum = products.filter(x => typeof x.price === 'number').map(x => x.price).reduce((a, b) => a + b);
console.log(sum)
The result of your accumulator for the last element tea is undefined because you don't return anything, if price is not a number. And as this is the last call to the accumulator, also the result of reduce is undefined.
If you just want to ignore values with invalid price, you can either use
products.filter(x => Number.isInteger(x.price)).reduce (...)
to only sum up products with a valid price
or return for instance acc if cur.price is not a number.
const sum = products.reduce(function(acc, cur){
if (Number.isInteger(cur.price))
return acc+cur.price;
return acc;
}, 0)
console.log(sum)

Intersection of multiple arrays base on properties

I want to find the common elements of multiple array of objects based on a common property. In addition, if an element appears more than once, I want the resulting array to reflect the number of times it occurs in all the arrays.
I tried the following:
var arr = [
[
{ name: 'kiwi', value: 12 },
{ name: 'apple', value: 5 },
{ name: 'apple', value: 12 },
{ name: 'pizza', value: 33 },
{ name: 'pizza', value: 24 },
{ name: 'fish', value: 5 },
{ name: 'milk', value: 5 },
{ name: 'banana', value: 7 },
{ name: 'orange', value: 11 },
],
[
{ name: 'taco', value: 23 },
{ name: 'pizza', value: 78 },
{ name: 'apple', value: 12 },
{ name: 'pizza', value: 33 },
{ name: 'pizza', value: 24 },
{ name: 'fish', value: 5 },
{ name: 'pie', value: 1 },
{ name: 'cake', value: 3 },
{ name: 'banana', value: 7 },
{ name: 'beef', value: 123 },
{ name: 'lime', value: 72 },
{ name: 'pizza', value: 34 },
],
[
{ name: 'apple', value: 12 },
{ name: 'pizza', value: 33 },
{ name: 'pizza', value: 24 },
{ name: 'pizza', value: 23 },
{ name: 'fish', value: 5 },
{ name: 'banana', value: 7 },
{ name: 'banana', value: 77 },
]
];
function findArraysWithCommonName(arr) {
let arrays = [...arr];
var result = arrays.shift().reduce(function(res, v) {
if (arrays.every(function(a) {
return (a.filter(function(e) {
return e.name === v.name
}).length > 0);
})) res.push(v);
return res;
}, []);
return result;
}
console.log(findArraysWithCommonName(arr))
The result I got is:
[
{name: "apple", value: 5},
{name: "apple", value: 12},
{name: "pizza", value: 33},
{name: "pizza", value: 24},
{name: "fish", value: 5},
{name: "banana", value: 7}
]
I expect the output to be:
[
{name: "apple", value: 12},
{name: "pizza", value: 33},
{name: "pizza", value: 24},
{name: "fish", value: 5},
{name: "banana", value: 7}
]
or
[
{name: "apple", value: 5},
{name: "pizza", value: 33},
{name: "pizza", value: 24},
{name: "fish", value: 5},
{name: "banana", value: 7}
]
One approach would be to build a map that relates an object to it's "count" in the array (ie the number of times that object occours in arr).
This can be done via .reduce() where you serialize each object to a string via JSON.stringify(obj) - this string is a unique encoding of the corresponding object shape and state which is used as the key to identify the objects of this form in the mapping. The key is used to query and update the "count" value of the mapping, for each object encountered in the arr.
Once the mapping has been build, filter mapping entries by those with a "count" value greater than one.
Finally for any filtered entries, deserialize the corresponding keys of those entries via .map() to obtain an array of objects that occoured more that one in the original arr.
This approach could be implemented as:
var arr=[[{name:'kiwi',value:12},{name:'apple',value:5},{name:'apple',value:12},{name:'pizza',value:33},{name:'pizza',value:24},{name:'fish',value:5},{name:'milk',value:5},{name:'banana',value:7},{name:'orange',value:11}],[{name:'taco',value:23},{name:'pizza',value:78},{name:'apple',value:12},{name:'pizza',value:33},{name:'pizza',value:24},{name:'fish',value:5},{name:'pie',value:1},{name:'cake',value:3},{name:'banana',value:7},{name:'beef',value:123},{name:'lime',value:72},{name:'pizza',value:34}],[{name:'apple',value:12},{name:'pizza',value:33},{name:'pizza',value:24},{name:'pizza',value:23},{name:'fish',value:5},{name:'banana',value:7},{name:'banana',value:77}]];
/* Flatten array heirachy */
const flatArr = arr.flat();
/* Obtain a count mapping for each object's occourance in flatArr */
const mapObjectToCount = flatArr.reduce((map, item) => {
const key = JSON.stringify(item);
const count = (map[key] ? map[key] : 0) + 1;
return { ...map, [ key ] : count };
}, {})
/* Get key/value pair of the prior mapping, filter the objects by
those that occour more that one time, and obtain the original object
by parsing the key */
const result = Object.entries(mapObjectToCount)
.filter(([json, count]) => count > 1)
.map(([json]) => JSON.parse(json));
console.log(result)
I'd first transform each subarray into an object indexed by the number of occurences of each name. Then, iterate through each of those sub-objects created, creating a new object whose values are the minimum of the values found on the combined object, for every key.
Lastly, return a .filter of the first array, checking whether the occurence count of the name being iterated over on that object is greater than 0, reducing that count by one when found:
function findArraysWithCommonName(arr) {
const [oneArr, ...rest] = arr;
/* Transform each subarray into, eg:
{
"taco": 1,
"pizza": 4,
"apple": 1,
"fish": 1,
"pie": 1,
...
*/
const countsByName = rest.map(
subarr => subarr.reduce((a, { name }) => {
a[name] = (a[name] || 0) + 1;
return a;
}, {})
);
/* Combine the objects into one that contains only the minimum value for each property, eg:
{
"apple": 1,
"pizza": 3,
"fish": 1,
"banana": 1
}
*/
const combinedCountsByName = countsByName.reduce((a, countObj) => {
Object.entries(countObj).forEach(([key, val]) => {
countObj[key] = Math.min(a[key], val) || 0;
});
return countObj;
});
console.log(combinedCountsByName);
return oneArr.filter(({ name }) => {
if (combinedCountsByName[name] > 0) {
combinedCountsByName[name]--;
return true;
}
});
}
var arr = [
[
{ name: 'kiwi', value: 12 },
{ name: 'apple', value: 5 },
{ name: 'apple', value: 12 },
{ name: 'pizza', value: 33 },
{ name: 'pizza', value: 24 },
{ name: 'fish', value: 5 },
{ name: 'milk', value: 5 },
{ name: 'banana', value: 7 },
{ name: 'orange', value: 11 },
],
[
{ name: 'taco', value: 23 },
{ name: 'pizza', value: 78 },
{ name: 'apple', value: 12 },
{ name: 'pizza', value: 33 },
{ name: 'pizza', value: 24 },
{ name: 'fish', value: 5 },
{ name: 'pie', value: 1 },
{ name: 'cake', value: 3 },
{ name: 'banana', value: 7 },
{ name: 'beef', value: 123 },
{ name: 'lime', value: 72 },
{ name: 'pizza', value: 34 },
],
[
{ name: 'apple', value: 12 },
{ name: 'pizza', value: 33 },
{ name: 'pizza', value: 24 },
{ name: 'pizza', value: 23 },
{ name: 'fish', value: 5 },
{ name: 'banana', value: 7 },
{ name: 'banana', value: 77 },
]
];
console.log(findArraysWithCommonName(arr));

How to combine duplicate object value in the array then output a list?

I've been working on this couple hours and google around, see lots of example for remove duplicate, but not combined the value. so I hope someone can help me out here.
I want to check the item.name is the same, then add the price together then push to new list array.
const items = [
{ name: 'apple', price: '10' },
{ name: 'banana', price: '1' },
{ name: 'orange', price: '2' },
{ name: 'apple', price: '5' },
{ name: 'orange', price: '2.5' },
{ name: 'banana', price: '3' },
{ name: 'strawberry', price: '7' },
{ name: 'apple', price: '12' }
]
let newItem = []
const checkItem = items.map((prev, next) => {
if (prev.name === next.name) {
return newItem.push = {
name: next.name,
value: parseInt(prev.price) + parseInt(next.price)
}
}
});
console.log(newItem)
Big thanks for the help!
This will work.You can use reduce with Find.
const items = [{
name: 'apple',
price: '10'
},
{
name: 'banana',
price: '1'
},
{
name: 'orange',
price: '2'
},
{
name: 'apple',
price: '5'
},
{
name: 'orange',
price: '2.5'
},
{
name: 'banana',
price: '3'
},
{
name: 'strawberry',
price: '7'
},
{
name: 'apple',
price: '12'
}
]
let result = items.reduce((acc, el) => {
if (acc.filter(ele => ele.name == el.name).length == 0) {
acc.push(el);
} else {
let filtered = acc.find(ele => ele.name == el.name)
filtered.price = parseFloat(filtered.price) + parseFloat(el.price);
}
return acc;
}, [])
console.log(result)
var new_array = arr.map(function callback(currentValue[, index[, array]]) {
// Return element for new_array
}[, thisArg])
The Array​.prototype​.map()'s callback functions first two arguments are currentValue i.e item of the array and second value is it's index, & not prev and next elements.
What you are looking for is something like this.
const items = [
{ name: "apple", price: "10" },
{ name: "banana", price: "1" },
{ name: "orange", price: "2" },
{ name: "apple", price: "5" },
{ name: "orange", price: "2.5" },
{ name: "banana", price: "3" },
{ name: "strawberry", price: "7" },
{ name: "apple", price: "12" }
];
const combine = items.reduce((acc, item) => {
if (acc[item.name] !== undefined) {
acc[item.name] += Number(item.price);
} else acc[item.name] = Number(item.price);
return acc;
}, {});
const fruitKeys = Object.keys(combine);
newItem = fruitKeys.map(item => ({ name: item, price: combine[item] }));
console.log(newItem);
I have split the solution into two steps, namely combine and reconstruction of the object so that you can clearly see what's happening.
I highly recommend you to refer the documentation for reduce method to understand its working

How to sort array of objects and shift some type of strings to end of the list? [duplicate]

This question already has answers here:
Sort array of objects by string property value
(57 answers)
Closed 3 years ago.
I'm trying to sort array of objects by "name" key. Here some values starts with 'summer', after sorting need to place those objects to be end of the list. Have any idea look this sorting once.
This is my list:
var list = [
{ name: 'Summer_Mango', id: 20055 },
{ name: 'Orange', id: 20053 },
{ name: 'Apple', id: 45652 },
{ name: 'Grape', id: 20066 },
{ name: 'Summer_Watermelon', id: 20073 },
{ name: 'Banana', id: 20010 }
];
After sorting my list need to come like this
var list = [
{ name: 'Apple', id: 45652 },
{ name: 'Banana', id: 20010 }
{ name: 'Grape', id: 20066 },
{ name: 'Orange', id: 20053 },
{ name: 'Summer_Mango', id: 20055 },
{ name: 'Summer_Watermelon', id: 20073 },
];
You can customize sort condition for your requirement.
list = list.sort((a, b) => {
if (a.name.startsWith('Summer')) {return 1; }
if (b.name.startsWith('Summer')) {return -1;}
return a.name.localeCompare(b.name)
});
var list = [
{ name: 'Summer_Mango', id: 20055 },
{ name: 'Orange', id: 20053 },
{ name: 'Apple', id: 45652 },
{ name: 'Grape', id: 20066 },
{ name: 'Summer_Watermelon', id: 20073 },
{ name: 'Banana', id: 20010 }
];
list = list.sort((a, b) => {
if (a.name.startsWith('Summer')) {return 1; }
if (b.name.startsWith('Summer')) {return -1;}
return a.name.localeCompare(b.name)
});
console.log(list)

Group and sum with underscore

Input:
const data = [
{
id: 'RS11',
name: 'Road 1',
quantity: {
lengthVal: 50
}
},
{
id: 'RS11',
name: 'Road 1',
quantity: {
lengthVal: 100
}
},
{
id: 'RS11',
name: 'Road 2',
quantity: {
lengthVal: 20
}
}
]
Each property except quantity should be grouped. quantity.lengthVal should be summarized. A count property should also be added.
Expected output:
const expected = [
{
id: 'RS11',
name: 'Road 1',
count: 2,
summarizedLength: 150
},
{
id: 'RS11',
name: 'Road 2',
count: 1,
summarizedLength: 20
}
]
This is what i tried:
const groupAndSum = (arr) => {
return _.chain(arr)
.groupBy((obj) => {
// Some reduce here?
return _.values(_.without(obj), 'quantity').join('|')
})
.map((val) => {
val[0].count = val.length;
delete val[0].quantity;
return val[0];
})
.value();
}
Jsbin: https://jsbin.com/totujopume/edit?html,js,console
Dataset is quite big so performance is important.
Can do this with a fairly simple native reduce() that only makes one iteration through the array for whole procedure
const res = Object.values(
data.reduce((a, c) => {
const key = c.id + '|' + c.name;
const o = a[key] = a[key] || c;
o.count = (o.count || 0) +1;
o.summarizedLength = (o.summarizedLength || 0) + c.quantity.lengthVal;
delete c.quantity;
return a;
},{})
);
console.log(res)
.as-console-wrapper { max-height: 100%!important;}
<script>
const data = [{
id: 'RS11',
name: 'Road 1',
quantity: {
lengthVal: 50
}
},
{
id: 'RS11',
name: 'Road 1',
quantity: {
lengthVal: 100
}
},
{
id: 'RS11',
name: 'Road 2',
quantity: {
lengthVal: 20
}
}
]
</script>

Categories