please see attempt and examples below, I am looking for an efficient way in JS to get the number of fruits matched. Currently I am using filter to find matches within items
array, but I am wondering if there is a better approach (without having to filter through the fruits
array on each iteration ~ imagining it is a much bigger array of fruits).
const items = [
{
id: '111',
name: 'apple',
},
{
id: '222',
name: 'apple',
},
{
id: '333',
name: 'kiwi',
},
];
const fruits = [
{
id: 'fruit-1',
name: 'apple',
},
{
id: 'fruit-2',
name: 'banana',
},
{
id: 'fruit-3',
name: 'kiwi',
},
];
// attempted
const numberOfFruitsInItems = fruits.map((fruit) => {
const itemsWithFruit = items.filter(({ name }) => fruit.name === name);
return {
...fruit,
total: itemsWithFruit.length,
};
});
console.log(numberOfFruitsInItems);
// desired response
[
{
id: 'fruit-1',
name: 'apple',
total: 2,
},
{
id: 'fruit-2',
name: 'banana',
total: 0,
},
{
id: 'fruit-3',
name: 'kiwi',
total: 1,
},
];
CodePudding user response:
As many questions and answers on this site, you should group the array of objects by name
, counting each and storing in a grouped
object with keys that are names and values that are count.
const items = [{id:"111",name:"apple"},{id:"222",name:"apple"},{id:"333",name:"kiwi"},];
const fruits = [{id:"fruit-1",name:"apple"},{id:"fruit-2",name:"banana"},{id:"fruit-3",name:"kiwi"},];
var grouped = items.reduce(function(agg, item) {
agg[item.name] = (agg[item.name] || 0) 1
return agg
}, {})
fruits.forEach(function(item) {
item.total = grouped[item.name] || 0
})
console.log(fruits)
.as-console-wrapper {max-height: 100% !important}
CodePudding user response:
You will need to cache the item frequencies before your map each of the fruits.
const
items = [
{ id: '111', name: 'apple' },
{ id: '222', name: 'apple' },
{ id: '333', name: 'kiwi' }
],
fruits = [
{ id: 'fruit-1', name: 'apple' },
{ id: 'fruit-2', name: 'banana' },
{ id: 'fruit-3', name: 'kiwi' }
];
const main = () => {
const reducedFruits = fruitReducer(fruits, items);
console.log(reducedFruits);
};
const computeFrequency = (items, accessor) =>
items.reduce((acc, item) =>
(key =>
acc.set(key, (acc.get(key) ?? 0) 1))
(accessor ? accessor(item) : item), new Map);
const fruitReducer = (fruits, items) =>
((freqMap) =>
fruits.map(({ id, name }) =>
({ id, name, total: freqMap.get(name) ?? 0 })))
(computeFrequency(items, ({ name }) => name))
main();
.as-console-wrapper { top: 0; max-height: 100% !important; }
CodePudding user response:
You can accomplish this in one iteration over items
that updates a global counter of corresponding fruit.
// Use a hashed object to keep count of totals per fruit name.
// totals[FRUIT NAME] = ...number of items having name=FRUIT NAME
const totals = {}
// Fill the totals hash in one single iteration of items
items.forEach(item => {
totals[item.name] = (totals[item.name] || 0) 1
})
const numberOfFruitsInItems = fruits.map((fruit) => {
return {
...fruit,
total: totals[fruit.name] || 0,
};
});
CodePudding user response:
It would be better to separate out the two mechanisms to remove the nested iteration. First iterate over the items
array using reduce
to create a dictionary of key/value pairs (e.g. { apple: 2 }
, and then loop over the fruits
array adding in the correct count information from the dictionary.
const items=[{id:"111",name:"apple"},{id:"222",name:"apple"},{id:"333",name:"kiwi"}],fruits=[{id:"fruit-1",name:"apple"},{id:"fruit-2",name:"banana"},{id:"fruit-3",name:"kiwi"}];
// `reduce` over the items.
// If the item name isn't a key on the object
// set it to zero, then add one
const itemCount = items.reduce((acc, { name }) => {
fruits[name] ??= 0;
fruits[name];
return fruits;
}, {});
// Update the `fruits` using the dictionary
// count information - note this mutates the
// `fruits` array so if you want a new array
// use `map` instead of `forEach`
fruits.forEach(fruit => {
fruit.count = itemCount[fruit.name] || 0;
});
console.log(fruits);
Additional information
CodePudding user response:
A version which does not mutate your original data -- after all, we're not barbarians, right? -- could start by creating copies of your fruit
array, adding a count
property to each, storing them as an object keyed off the fruit name, then folding the items
list into it by incrementing the count
property of the relevant node. At the end, we just collect and return the values
of this object.
The code is pretty simple:
const countFruits = (fruits, items) => Object .values (items .reduce (
(a, {name}) => ((a [name] .count = 1), a),
fruits .reduce ((a, f) => ((a [f .name] = {...f , count: 0}), a), {})
))
const items = [{id: '111', name: 'apple'}, {id: '222', name: 'apple'}, {id: '333', name: 'kiwi'}]
const fruits = [{id: 'fruit-1', name: 'apple', }, {id: 'fruit-2', name: 'banana', }, {id: 'fruit-3', name: 'kiwi', }]
console .log (countFruits (fruits, items))
.as-console-wrapper {max-height: 100% !important; top: 0}
Note that there is no error-checking, and you may need it. What if items
included {id" 444', name: 'papaya'}
, even though paypaya
was not in the list of fruits. This should not be too hard to handle, and we can leave it as an exercise.