Группировка объектов и суммирование значений в JS для оптимизации
Узнайте эффективные способы группировки объектов в массивах JavaScript и суммирования их значений. Используйте reduce(), Map и библиотеки для производительности и кода.
Какой самый эффективный способ сгруппировать объекты в массиве и посчитать сумму их значений?
Например, у меня есть такой массив объектов:
[
{ Phase: "Phase 1", Step: "Step 1", Task: "Task 1", Value: "5" },
{ Phase: "Phase 1", Step: "Step 1", Task: "Task 2", Value: "10" },
{ Phase: "Phase 1", Step: "Step 2", Task: "Task 1", Value: "15" },
{ Phase: "Phase 1", Step: "Step 2", Task: "Task 2", Value: "20" },
{ Phase: "Phase 2", Step: "Step 1", Task: "Task 1", Value: "25" },
{ Phase: "Phase 2", Step: "Step 1", Task: "Task 2", Value: "30" },
{ Phase: "Phase 2", Step: "Step 2", Task: "Task 1", Value: "35" },
{ Phase: "Phase 2", Step: "Step 2", Task: "Task 2", Value: "40" }
]
Я выводю эту информацию в таблицу и хочу группировать по разным свойствам, одновременно суммируя значения. Я использую Underscore.js из‑за функции groupBy, но она не даёт полного решения: я хочу объединить результаты, а не разделять их, как в SQL‑е при GROUP BY.
Ожидаемый результат при группировке по Phase:
[
{ Phase: "Phase 1", Value: 50 },
{ Phase: "Phase 2", Value: 130 }
]
А при группировке по Phase и Step:
[
{ Phase: "Phase 1", Step: "Step 1", Value: 15 },
{ Phase: "Phase 1", Step: "Step 2", Value: 35 },
{ Phase: "Phase 2", Step: "Step 1", Value: 55 },
{ Phase: "Phase 2", Step: "Step 2", Value: 75 }
]
Существует ли готовый скрипт или библиотека, которая бы делала это эффективно, или лучше использовать Underscore.js и вручную обрабатывать результаты для подсчёта сумм?
Самый эффективный способ группировки объектов в массиве и суммирования их значений в JavaScript – использовать Array.prototype.reduce() с аккумуляторным объектом или Map, что обеспечивает сложность O(n) и чистый, читаемый код. Для вашего конкретного случая с Underscore.js понадобится сочетать groupBy() с последующим шагом агрегации, но решение на основе reduce() обычно оказывается более эффективным и гибким.
Содержание
- Использование Array.prototype.reduce()
- Группировка по нескольким свойствам
- Использование Map для лучшей производительности
- Решения библиотек
- Сравнение производительности
- Полные примеры реализации
Использование Array.prototype.reduce()
Метод reduce() – самый прямолинейный и эффективный подход для группировки и суммирования. Вот как реализовать его для вашего конкретного случая:
const data = [
{ Phase: "Phase 1", Step: "Step 1", Task: "Task 1", Value: "5" },
{ Phase: "Phase 1", Step: "Step 1", Task: "Task 2", Value: "10" },
{ Phase: "Phase 1", Step: "Step 2", Task: "Task 1", Value: "15" },
{ Phase: "Phase 1", Step: "Step 2", Task: "Task 2", Value: "20" },
{ Phase: "Phase 2", Step: "Step 1", Task: "Task 1", Value: "25" },
{ Phase: "Phase 2", Step: "Step 1", Task: "Task 2", Value: "30" },
{ Phase: "Phase 2", Step: "Step 2", Task: "Task 1", Value: "35" },
{ Phase: "Phase 2", Step: "Step 2", Task: "Task 2", Value: "40" }
];
// Группировка по одному свойству и суммирование
const groupAndSum = (array, groupByProperty, sumProperty) => {
return array.reduce((acc, obj) => {
const key = obj[groupByProperty];
const value = parseFloat(obj[sumProperty]) || 0;
if (!acc[key]) {
acc[key] = { [groupByProperty]: key, [sumProperty]: 0 };
}
acc[key][sumProperty] += value;
return acc;
}, {});
};
// Преобразовать объект в массив
const result = Object.values(groupAndSum(data, 'Phase', 'Value'));
console.log(result);
// Output: [ { Phase: "Phase 1", Value: 50 }, { Phase: "Phase 2", Value: 130 } ]
Этот подход эффективен, потому что обрабатывает массив за один проход (сложность O(n)) и сохраняет чистую разделённость логики группировки и суммирования.
Группировка по нескольким свойствам
Для группировки по нескольким свойствам (например, Phase и Step) можно создать составной ключ:
const groupAndSumMultiple = (array, groupByProperties, sumProperty) => {
return array.reduce((acc, obj) => {
// Создать составной ключ из нескольких свойств
const key = groupByProperties.map(prop => obj[prop]).join('-');
const value = parseFloat(obj[sumProperty]) || 0;
if (!acc[key]) {
const newObj = {};
groupByProperties.forEach(prop => newObj[prop] = obj[prop]);
newObj[sumProperty] = 0;
acc[key] = newObj;
}
acc[key][sumProperty] += value;
return acc;
}, {});
};
const result2 = Object.values(groupAndSumMultiple(data, ['Phase', 'Step'], 'Value'));
console.log(result2);
/* Output: [
{ Phase: "Phase 1", Step: "Step 1", Value: 15 },
{ Phase: "Phase 1", Step: "Step 2", Value: 35 },
{ Phase: "Phase 2", Step: "Step 1", Value: 55 },
{ Phase: "Phase 2", Step: "Step 2", Value: 75 }
] */
Это решение динамически обрабатывает несколько свойств группировки и сохраняет структуру, необходимую для отображения таблицы.
Использование Map для лучшей производительности
Для больших наборов данных использование Map может обеспечить лучшую производительность, чем обычные объекты:
const groupAndSumWithMap = (array, groupByProperty, sumProperty) => {
const resultMap = new Map();
array.forEach(obj => {
const key = obj[groupByProperty];
const value = parseFloat(obj[sumProperty]) || 0;
if (resultMap.has(key)) {
resultMap.get(key)[sumProperty] += value;
} else {
const newObj = { [groupByProperty]: key, [sumProperty]: value };
resultMap.set(key, newObj);
}
});
return Array.from(resultMap.values());
};
const result3 = groupAndSumWithMap(data, 'Phase', 'Value');
console.log(result3);
Согласно обсуждениям на Stack Overflow, объекты Map обычно работают быстрее, чем обычные объекты при операциях группировки, особенно при работе с большими наборами данных.
Решения библиотек
Хотя вы упомянули Underscore.js, существуют специализированные библиотеки, которые решают эту задачу:
1. Пакет group-by-with-sum npm
const groupBy = require('group-by-with-sum');
const arr = [
{ name: 'Vasya', who: 'man', money: 100 },
{ name: 'Vasya', who: 'man', money: 263 },
{ name: 'Kolya', who: 'man', money: 98 },
{ name: 'Katya', who: 'woman', money: 290 }
];
const result = groupBy(arr, 'who', 'money');
console.log(result);
Этот специализированный npm‑пакет предназначен именно для вашего случая и предоставляет чистую, оптимизированную функциональность для группировки и суммирования.
2. Расширенный подход с Underscore.js
Если вы предпочитаете оставаться на Underscore.js, можно комбинировать несколько функций Underscore:
const _ = require('underscore');
const result4 = _.chain(data)
.groupBy('Phase')
.map((group, phase) => ({
Phase: phase,
Value: _.reduce(group, (sum, obj) => sum + parseFloat(obj.Value), 0)
}))
.value();
console.log(result4);
Этот подход использует цепочку Underscore, но требует нескольких проходов по данным.
Сравнение производительности
Ниже приведено сравнение различных подходов на основе найденных исследований:
| Метод | Сложность | Память | Читаемость | Гибкость |
|---|---|---|---|---|
| reduce() с объектом | O(n) | Средняя | Высокая | Высокая |
| reduce() с Map | O(n) | Средняя | Высокая | Высокая |
| цепочка Underscore.js | O(n) | Высокая | Средняя | Средняя |
| пользовательская функция | O(n) | Низкая | Средняя | Высокая |
Согласно обсуждениям в LambdaTest Community, подходы на основе reduce() обычно являются наиболее эффективными для большинства случаев.
Полные примеры реализации
Ниже приведена полностью готовая к использованию реализация, которая покрывает все ваши требования:
class GroupBySum {
constructor(data) {
this.data = data;
}
// Группировка по одному свойству
groupBySingle(property, sumProperty = 'Value') {
return this.data.reduce((acc, obj) => {
const key = obj[property];
const value = parseFloat(obj[sumProperty]) || 0;
if (!acc[key]) {
acc[key] = { [property]: key, [sumProperty]: 0 };
}
acc[key][sumProperty] += value;
return acc;
}, {});
}
// Группировка по нескольким свойствам
groupByMultiple(properties, sumProperty = 'Value') {
return this.data.reduce((acc, obj) => {
const key = properties.map(prop => obj[prop]).join('-');
const value = parseFloat(obj[sumProperty]) || 0;
if (!acc[key]) {
const newObj = {};
properties.forEach(prop => newObj[prop] = obj[prop]);
newObj[sumProperty] = 0;
acc[key] = newObj;
}
acc[key][sumProperty] += value;
return acc;
}, {});
}
// Получить результаты как массив
getResults(properties, sumProperty = 'Value') {
const isMultiple = Array.isArray(properties);
const grouped = isMultiple
? this.groupByMultiple(properties, sumProperty)
: this.groupBySingle(properties, sumProperty);
return Object.values(grouped);
}
}
// Пример использования
const groupBySum = new GroupBySum(data);
// Группировка только по Phase
const phaseResults = groupBySum.getResults('Phase');
console.log('Group by Phase:', phaseResults);
// Группировка по Phase и Step
const phaseStepResults = groupBySum.getResults(['Phase', 'Step']);
console.log('Group by Phase and Step:', phaseStepResults);
Эта реализация предоставляет чистый, переиспользуемый подход, который обрабатывает как одиночные, так и множественные свойства группировки, сохраняя отличные характеристики производительности.
Источники
- Most efficient method to groupby on an array of objects - Stack Overflow
- How to group by and sum an array of objects - Stack Overflow
- What’s the most efficient way to group by objects in an array in JavaScript and sum specific values - LambdaTest Community
- Group objects by multiple properties in array then sum up their values - Stack Overflow
- group-by-with-sum npm package
- JavaScript - group objects by multiple properties in array then sum up their values - Dirask
Заключение
На основании исследований и примеров реализации ключевые выводы по эффективной группировке и суммированию объектов в JavaScript:
- Используйте reduce() для лучшей производительности – метод
Array.prototype.reduce()обеспечивает сложность O(n) и является самым эффективным подходом для большинства случаев. - Рассмотрите Map для больших наборов данных – при работе с очень большими данными использование
Mapвместо обычного объекта может дать лучшую производительность. - Специализированные библиотеки могут помочь – если вы не хотите реализовывать собственную логику, рассмотрите пакет
group-by-with-sum, специально созданный для этой функциональности. - Underscore.js требует дополнительных шагов – хотя Underscore.js полезен, его функция
groupBy()сама по себе не обеспечивает полного решения; понадобится добавить дополнительные шаги агрегации. - Создайте переиспользуемое решение – для продакшн‑кода рассмотрите создание переиспользуемого класса или функции, которая обрабатывает как одиночные, так и множественные свойства группировки.
Самый рекомендуемый подход – использовать пользовательскую реализацию на основе reduce(), которая даёт полный контроль над логикой группировки и суммирования при сохранении отличных характеристик производительности.