Reputation: 123
If I have the following Array:
var myArray = [
{sa67g:{id:'sa67g', name: 'Leo', value: 50}},
{sa86w:{id:'sa86w', name: 'Amy', value: 40}},
{sa33p:{id:'sa33p', name: 'Alex', value: 30}},
{sa74x:{id:'sa74x', name: 'John', value: 20}},
{sa67g:{id:'sa67g', name: 'Leo', value: 10}},
{sa33p:{id:'sa33p', name: 'Alex', value: 15}},
]
What is the best one to get a single array with no-repeated items, with the sum of their value(s) and ordered by descending value for all the repeated items from another array using lodash?
Te expected result should be something like this:
result = [{sa67g:{id:'sa67g', name: 'Leo', value: 60}},
{sa33p:{id:'sa33p', name: 'Alex', value: 45}},
{sa86w:{id:'sa86w', name: 'Amy', value: 40}},
{sa74x:{id:'sa74x', name: 'John', value: 20}}
]
Upvotes: 0
Views: 82
Reputation: 191976
You can use lodash's _.mergeWith()
to combine all objects into a single object. Since _.mergeWith
is recursive, the inner properties will be merged as well, and we can use this to sum the value
property. Afterwards, we convert the object back to an array using _.map()
:
const myArray = [{"sa67g":{"id":"sa67g","name":"Leo","value":50}},{"sa86w":{"id":"sa86w","name":"Amy","value":40}},{"sa33p":{"id":"sa33p","name":"Alex","value":30}},{"sa74x":{"id":"sa74x","name":"John","value":20}},{"sa67g":{"id":"sa67g","name":"Leo","value":10}},{"sa33p":{"id":"sa33p","name":"Alex","value":15}}];
const result = _.map(
// merge all objects into a single object, and sum the value property
_.mergeWith({}, ...myArray, (objValue = 0, srcValue = 0, key) => key === 'value' ? objValue + srcValue : undefined),
// split back into an array of objects
(v, k) => ({ [k]: v })
)
console.log(result);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.4/lodash.min.js"></script>
Upvotes: 1
Reputation: 135227
Object properties should never be a value (ie, a key or id) – to create a key-value association, use a Map instead.
const concat = ({ id: id1, name: name1, value: v1 }, { id: id2, name: name2, value: v2 }) =>
({ id: id1, name: name1, value: v1 + v2 })
const main = xs =>
xs.map (x => Object.entries (x) [ 0 ])
.reduce ((acc, [ key, value ]) =>
acc.has (key)
? acc.set (key, concat (value, acc.get (key)))
: acc.set (key, value), new Map)
const myArray =
[ {sa67g:{id:'sa67g', name: 'Leo', value: 50}}
, {sa86w:{id:'sa86w', name: 'Amy', value: 40}}
, {sa33p:{id:'sa33p', name: 'Alex', value: 30}}
, {sa74x:{id:'sa74x', name: 'John', value: 20}}
, {sa67g:{id:'sa67g', name: 'Leo', value: 10}}
, {sa33p:{id:'sa33p', name: 'Alex', value: 15}}
]
console.log (main (myArray))
// => Map {
// 'sa67g' => { id: 'sa67g', name: 'Leo', value: 60 },
// 'sa86w' => { id: 'sa86w', name: 'Amy', value: 40 },
// 'sa33p' => { id: 'sa33p', name: 'Alex', value: 45 },
// 'sa74x' => { id: 'sa74x', name: 'John', value: 20 } }
If you want to convert the Map back to an object, you can use this
const mapToObject = m =>
Array.from(m.entries ()).reduce ((acc, [ k, v ]) =>
Object.assign (acc, { [ k ]: v }), {})
Upvotes: 0
Reputation: 386600
You could take a step by step approach and take the resutl for the next function with a pipe.
var array = [{ sa67g: { id: 'sa67g', name: 'Leo', value: 50 } }, { sa86w: { id: 'sa86w', name: 'Amy', value: 40 } }, { sa33p: { id: 'sa33p', name: 'Alex', value: 30 } }, { sa74x: { id: 'sa74x', name: 'John', value: 20 } }, { sa67g: { id: 'sa67g', name: 'Leo', value: 10 } }, { sa33p: { id: 'sa33p', name: 'Alex', value: 15 } }],
pipe = (...fn) => arg => fn.reduce((x, f) => f(x), arg),
objects = array => array.map(o => Object.assign({}, Object.values(o)[0])),
sum = array => array.reduce((m, o) => {
if (m.has(o.id)) {
m.get(o.id).value += o.value;
} else {
m.set(o.id, o);
}
return m;
}, new Map),
values = map => Array.from(map.values()),
sort = array => array.sort((a, b) => b.value - a.value),
map = array => array.map(o => ({ [o.id]: o })),
path = pipe(objects, sum, values, sort, map),
result = path(array);
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
Upvotes: 0
Reputation: 4010
I'm sure there are a bunch of ways to do it. Off the top of my head, I would use reduce
to convert your array of objects into one object with summed values. Using chain
to combine with some other lodash methods for ordering and transforming, it would look like this:
const result = _.chain(myArray)
.map(person => _.values(person))
.flatten()
.reduce((summed, person) => {
if (!summed[person.id]) {
summed[person.id] = person
} else {
summed[person.id].value += person.value
}
return summed
}, {})
.values()
.orderBy(['value'], ['desc'])
.map(person => ({ [person.id]: person }))
.value()
Upvotes: 1
Reputation: 171669
Using native methods and assuming es8 support:
var myArray = [
{sa67g:{id:'sa67g', name: 'Leo', value: 50}},
{sa86w:{id:'sa86w', name: 'Amy', value: 40}},
{sa33p:{id:'sa33p', name: 'Alex', value: 30}},
{sa74x:{id:'sa74x', name: 'John', value: 20}},
{sa67g:{id:'sa67g', name: 'Leo', value: 10}},
{sa33p:{id:'sa33p', name: 'Alex', value: 15}},
]
var tmp = myArray.map(o => Object.values(o)[0])
.reduce((a, c) => {
const obj = a[c.name]
if (obj) {
obj.id = c.id > obj.id ? c.id : obj.id;// set highest id
obj.value += c.value; //increment vlues
} else {
a[c.name] = Object.assign({},c);
}
return a;
}, {});
var res = Object.values(tmp)
.sort((a, b) => b.value - a.value)
.map(o => ({[o.id]:o }))
console.log(res)
.as-console-wrapper { max-height: 100%!important;}
Upvotes: 0