Reputation: 3819
Need to reduce an array of Objects to return the highest value of the same name.
Need the following,
[
{
name: 'a',
value: 20,
...(other object values)
},
{
name: 'a',
value: 80
...(other object values)
},
{
name: 'b',
value: 90,
...(other object values)
},
{
name: 'b',
value: 50,
...(other object values)
}
]
To return
[
{
name: 'a',
value: 80
...(other object values)
},
{
name: 'b',
value: 90,
...(other object values)
}
]
I have a solution, but it seems a little to much, wondering if there is a more straightforward way to achieve it?
Below is the solution I could think of:
var toReduce = [
{
'name': 'a',
'value': 20,
'other': 'any',
},
{
'name': 'a',
'value': 80,
'other': 'value',
},
{
'name': 'b',
'value': 90,
'other': 'extra',
},
{
'name': 'b',
'value': 50,
'other': 'super',
}
];
function arrayReducer(arrayToReduce) {
// Created an object separating by name
let reduced = arrayToReduce.reduce((accumulator, currentValue) => {
(accumulator[currentValue.name] =
accumulator[currentValue.name] || []).push(currentValue);
return accumulator;
}, {});
// Reduce object to the highest value
for (let quotes of Object.keys(reduced)) {
reduced[quotes] = reduced[quotes].reduce(
(accumulator, currentValue) => {
return accumulator && accumulator.value > currentValue.value
? accumulator
: currentValue;
}
);
}
// return only object values
return Object.values(reduced);
}
console.log(arrayReducer(toReduce));
Upvotes: 0
Views: 125
Reputation: 782166
You can calculate the maximum during reduce()
rather than in a separate loop.
var toReduce =
[ { 'name': 'a', 'value': 20 }
, { 'name': 'a', 'value': 80 }
, { 'name': 'b', 'value': 90 }
, { 'name': 'b', 'value': 50 }
];
function arrayReducer(arrayToReduce)
{
// Created an object separating by name
let reduced = arrayToReduce.reduce((accumulator, currentValue) =>
{
accumulator[currentValue.name] =
{ name : currentValue.name
, value : accumulator.hasOwnProperty(currentValue.name)
? Math.max(accumulator[currentValue.name].value, currentValue.value)
: currentValue.value
};
return accumulator;
}
, {});
// return only object values
return Object.values(reduced);
}
console.log(arrayReducer(toReduce));
Upvotes: 0
Reputation: 1
A simple way to achieve it:
const newObj = [toReduce[0]];
const objKeyArray = [toReduce[0].name];
for (i = 1; i < toReduce.length; i++) {
for (j = 0; j < newObj.length; j++) {
if (toReduce[i].name === newObj[j].name) {
// If you just want to change the data of the specific property
// newObj[j].value = Math.max(toReduce[i].value, newObj[j].value);
// If you want to replace the object.
newObj[j] = toReduce[i];
} else {
if (!objKeyArray.includes(toReduce[i].name)) {
objKeyArray.push(toReduce[i].name);
newObj.push(toReduce[i]);
}
}
}
}
console.log(newObj);
Upvotes: 0
Reputation: 379
try this
const data = [
{
name: 'a',
value: 20,
...(other object values)
},
{
name: 'a',
value: 80
...(other object values)
},
{
name: 'b',
value: 90,
...(other object values)
},
{
name: 'b',
value: 50,
...(other object values)
}
]
const obj = Object.groupBy(data, ({name}) => name)
const result = Object.keys(obj).map(key => {
const max = obj[key].toSorted((a, b) => b.value - a.value)[0]
return max
})
console.log(result)
Upvotes: 1
Reputation: 50914
Another option is to use Map.groupBy()
to group each object by name
. Once grouped, you can iterate through the map, and for each group of objects grab the maximum value
(this is done using Array.from()
on the Map with a mapping function below):
const arr = [ { name: 'a', value: 20 }, { name: 'a', value: 80 }, { name: 'b', value: 90 }, { name: 'b', value: 50 } ];
const res = Array.from(
Map.groupBy(arr, obj => obj.name),
([name, grouped]) => ({name, value: Math.max(...grouped.map(obj => obj.value))})
);
console.log(res);
If your objects can have other keys/properties, then you can find the max object from each group and use that:
const arr = [{ 'name': 'a', 'value': 20, 'other': 'any', }, { 'name': 'a', 'value': 80, 'other': 'value', }, { 'name': 'b', 'value': 90, 'other': 'extra', }, { 'name': 'b', 'value': 50, 'other': 'super', } ];
const findMax = arr => arr.reduce((max, curr) => curr.value > max.value ? curr : max);
const res = Array.from(
Map.groupBy(arr, obj => obj.name).values(),
findMax
);
console.log(res);
Upvotes: 1
Reputation: 99745
The easiest here is probably a simple loop:
const result = {};
for (const item of input) {
if (item.value > (result[item.name]?.value ?? 0)) {
result[item.name] = item;
}
}
// To turn it back into the original array format:
const output = Object.values(result)
Of course you can turn this into a single statement with reduce()
and so on, but legibility will suffer as you can see from all the other answers.
But here's the .reduce()
version using the same approach:
const output = Object.values(
input.reduce( (acc, cur) => {
if (cur.value > (acc[cur.name]?.value ?? 0)) {
acc[cur.name] = cur;
}
return acc;
}, {})
)
Upvotes: 1
Reputation: 48713
The keep
function below is a reusable way to reduce a list of items into a map by their keys, and compare their values using a comparator.
For the key
and value
options, you can pass an accessor (getter) or a index (field) to tell the function how to determine how to "get" the value from the item.
This all happens in O(n) time.
const original = [
{ name: 'a', value: 20 },
{ name: 'a', value: 80 },
{ name: 'b', value: 90 },
{ name: 'b', value: 50 }
];
const get = (item, keyOrFn) => {
if (!keyOrFn) return item;
return typeof keyOrFn === 'function'
? keyOrFn(item)
: item[keyOrFn];
};
const keep = (arr, comparator, { key, value }) => {
return [...arr.reduce((acc, item) => {
const k = get(item, key);
const existing = acc.get(k);
if (!existing) return acc.set(k, item);
const a = get(item, value);
const b = get(existing, value);
if (comparator(a, b) > 0) return acc.set(k, item);
return acc;
}, new Map()).values()];
};
const maxValues = keep(
original,
(a, b) => a - b,
{ key: 'name', value: 'value' },
// OR: { key: x => x.name, value: x => x.value }
);
console.log(maxValues);
.as-console-wrapper { top: 0; max-height: 100% !important! }
Upvotes: 0
Reputation: 203
This might help. But it's not in one Go.
var toReduce = [
{
name: "a",
value: 20,
},
{
name: "a",
value: 80,
},
{
name: "b",
value: 90,
},
{
name: "b",
value: 50,
},
];
const itemByMaxValue = toReduce.reduce((acc, item) => {
if (acc[item.name]) {
acc[item.name] = Math.max(item.value, acc[item.name]);
} else {
acc[item.name] = item.value;
}
return acc;
}, {});
const result = Object.entries(itemByMaxValue).map(([name, value]) => ({
name,
value,
}));
console.log(result);
Upvotes: 0