Reputation: 43
I am trying to solve the next case without using a for loop... Meaning trying to use only filter, reduce, map or combination of them.
I got an array of Objects:
const items =
[ { label: 1, count: 22, isRefined: false }
, { label: 2, count: 10, isRefined: false }
, { label: 3, count: 3, isRefined: false }
, { label: 4, count: 1, isRefined: false }
]
const products =
[ { Locations: [{ LocationType: 1 } , { LocationType: 2 } , { LocationType: 3 } ]}
, { Locations: [{ LocationType: 1 } , { LocationType: 3 } ]}
, { Locations: [{ LocationType: 1 } , { LocationType: 3 } , { LocationType: 4 } ]}
, { Locations: [{ LocationType: 1 } ]}
, { Locations: [{ LocationType: 1 } , { LocationType: 2 } , { LocationType: 3 } ]}
]
The case is next:
I need to overwrite each item's inner Object count key with the value of corresponding products with the Objects in which the Locations objects contain the matching Location Type to the items object label...
For example, the output here should be
const output =
[ { label: 1, count: 5, isRefined: false }
, { label: 2, count: 2, isRefined: false }
, { label: 3, count: 4, isRefined: false }
, { label: 4, count: 1, isRefined: false }
]
I am totally stuck on trying to come to a solution without using a for loop or any kind of for loop, help would be much appreciated! Thank you
Upvotes: 0
Views: 151
Reputation: 596
In case forEach is not an option, here is another solution that uses map, reduce and filter.
const result = items.map(d => {
const locationCount = products.reduce((count, p) => count + p.Locations.filter(l => l.LocationType == d.label).length, 0);
let item = Object.assign({}, d);
item.count = locationCount;
return item;
})
For example:
const items =
[ { label: 1, count: 22, isRefined: false }
, { label: 2, count: 10, isRefined: false }
, { label: 3, count: 3, isRefined: false }
, { label: 4, count: 1, isRefined: false }
]
const products =
[ { Locations: [{ LocationType: 1 } , { LocationType: 2 } , { LocationType: 3 } ]}
, { Locations: [{ LocationType: 1 } , { LocationType: 3 } ]}
, { Locations: [{ LocationType: 1 } , { LocationType: 3 } , { LocationType: 4 } ]}
, { Locations: [{ LocationType: 1 } ]}
, { Locations: [{ LocationType: 1 } , { LocationType: 2 } , { LocationType: 3 } ]}
]
const result = items.map(d => {
const locationCount = products.reduce((count, p) => count + p.Locations.filter(l => l.LocationType == d.label).length, 0);
let item = Object.assign({}, d);
item.count = locationCount;
return item;
})
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
Upvotes: 0
Reputation: 22355
my way...
const countP = v => products.filter(p=>p.Locations.some(x=>x.LocationType===v)).length
const output = items.map(el=>({...el, count: countP(el.label)}))
proof:
const items =
[ { label: 1, count: 22, isRefined: false }
, { label: 2, count: 10, isRefined: false }
, { label: 3, count: 3, isRefined: false }
, { label: 4, count: 1, isRefined: false }
]
const products =
[ { Locations: [{ LocationType: 1 } , { LocationType: 2 } , { LocationType: 3 } ]}
, { Locations: [{ LocationType: 1 } , { LocationType: 3 } ]}
, { Locations: [{ LocationType: 1 } , { LocationType: 3 } , { LocationType: 4 } ]}
, { Locations: [{ LocationType: 1 } ]}
, { Locations: [{ LocationType: 1 } , { LocationType: 2 } , { LocationType: 3 } ]}
]
const countP = v => products.filter(p=>p.Locations.some(x=>x.LocationType===v)).length
const output = items.map(el=>({...el, count: countP(el.label)}))
console.log( output )
.as-console-wrapper { max-height: 100% !important; top: 0; }
.as-console-row { background-color: #87f1f1; }
.as-console-row::after { display:none !important; }
Upvotes: 0
Reputation: 1778
Simple, and elegant.
UPDATE
Thanks to @plichard for suggesting in the comments below to use the some()
method and object destructuring in the filter
callback. It's really neat that way !
const output = items.map(item => {
const matches = (location) => location.LocationType == item.label;
const filtered = products.filter(({ Locations }) => Locations.some(matches));
return { ...item, count: filtered.length };
});
Upvotes: 1
Reputation: 31712
First calculate the counts (the number of times each location type appears in the products
array), preferably as an object where the keys are the location types and the values are the counts. For that a simple reduce
/forEach
will do the job like so:
let counts = products.reduce((counts, locs) => {
locs.Locations.forEach(loc =>
counts[loc.LocationType] = (counts[loc.LocationType] || 0) + 1
);
return counts;
}, {});
Then simply map
the items
array into a new array where you shallow-copy the objects using a spread syntax and update the count
property using the counts
object calculated earlier:
const newItems = items.map(item => ({
...item,
count: counts[item.label] || 0
}));
And if you want to alter the original objects themselves instead, then a simple forEach
will suffice:
items.forEach(item => item.count = counts[item.label] || 0);
The || 0
part is a fallback value in case the item doesn't have any location types in the products
array, in which case counts[item.label]
will be undefined
and 0
will be used instead. This is called short-circuit evaluation.
Demo:
const items = [{label: 1, count: 22, isRefined: false}, {label: 2, count: 10, isRefined: false}, {label: 3, count: 3, isRefined: false},{label: 4, count: 1, isRefined: false}];
const products = [{Locations:[{LocationType:1},{LocationType:2},{LocationType:3}]},{Locations:[{LocationType:1},{LocationType:3}]},{Locations:[{LocationType:1},{LocationType:3},{LocationType:4}]},{Locations:[{LocationType:1}]},{Locations:[{LocationType:1},{LocationType:2},{LocationType:3}]}];
let counts = products.reduce((counts, locs) => {
locs.Locations.forEach(loc =>
counts[loc.LocationType] = (counts[loc.LocationType] || 0) + 1
);
return counts;
}, {});
const newItems = items.map(item => ({
...item,
count: counts[item.label] || 0
}));
console.log(newItems);
Upvotes: 0