Необходимо уменьшить массив объектов, чтобы вернуть наибольшее значение с тем же именем.
Нужно следующее,
[
{
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)
}
]
Вернуться
[
{
name: 'a',
value: 80
...(other object values)
},
{
name: 'b',
value: 90,
...(other object values)
}
]
У меня есть решение, но оно кажется слишком сложным. Интересно, есть ли более простой способ его достижения?
Ниже приведено решение, о котором я мог подумать:
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.info(arrayReducer(toReduce));
Вы можете вычислить максимум во время reduce()
, а не в отдельном цикле.
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.info(arrayReducer(toReduce));
Тот же подход работает для любого количества свойств.
Просто скопируйте их все в аккумулятор.
Если ваши изменения делают ответы недействительными, вам следует сделать это как новый вопрос. Несправедливо редактировать вопрос после получения действительных ответов на исходный вопрос.
Согласен, однако я попытался обновить вопросы, поскольку все ответы были ограничены фиксированной структурой объектов... Обновление вопроса принесет пользу всем, кто преследует этот подход.
Самый простой здесь, вероятно, простой цикл:
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)
Конечно, вы можете превратить это в одно утверждение с помощью reduce()
и так далее, но разборчивость пострадает, как вы можете видеть из всех остальных ответов.
Но вот версия .reduce()
, использующая тот же подход:
const output = Object.values(
input.reduce( (acc, cur) => {
if (cur.value > (acc[cur.name]?.value ?? 0)) {
acc[cur.name] = cur;
}
return acc;
}, {})
)
Спасибо за ваш ответ, я фактически отредактировал свой первоначальный вопрос, поскольку объект может иметь больше значений (динамически), помимо имени и значения, ваш ответ охватывает только объект с двумя значениями.
Это не работает, если значение может быть отрицательным.
@Barmar, если вам нужны отрицательные значения, используйте -Infinity
вместо 0
Это может помочь. Но это не за один раз.
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.info(result);
Другой вариант — использовать Map.groupBy() для группировки каждого объекта по name
. После группировки вы можете перебирать карту и для каждой группы объектов захватывать максимум value
(это делается с помощью Array.from() на карте с функцией сопоставления ниже):
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.info(res);
Если ваши объекты могут иметь другие ключи/свойства, вы можете найти максимальный объект из каждой группы и использовать его:
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.info(res);
Спасибо за ваш ответ, я фактически отредактировал свой первоначальный вопрос, поскольку объект может иметь больше значений (динамически), помимо имени и значения, ваш ответ охватывает только объект с двумя значениями.
@caiovisk Вместо запуска Math.max() вы можете использовать цикл (например, сокращение), чтобы найти максимальный объект и использовать его вместо этого. Смотрите обновленный ответ.
Функция keep
, приведенная ниже, представляет собой многоразовый способ свести список элементов на карту по их ключам и сравнить их значения с помощью компаратора.
Для параметров key
и value
вы можете передать метод доступа (получатель) или индекс (поле), чтобы сообщить функции, как определить, как «получить» значение из элемента.
Все это происходит за время O(n).
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.info(maxValues);
.as-console-wrapper { top: 0; max-height: 100% !important! }
попробуй это
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.info(result)
Хорошее использование groupBy
Простой способ добиться этого:
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.info(newObj);
Спасибо за ваш ответ, я фактически отредактировал свой первоначальный вопрос, поскольку объект может иметь больше значений (динамически), кроме имени и значения, ваш ответ охватывает только объект с двумя значениями.