9

Самый эффективный способ группировки массивов объектов

5

Вопрос: Как эффективно сгруппировать объекты в массиве?

У меня есть массив объектов:

[ 
    { 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, которая полезна, но не решает поставленную задачу до конца. Мне нужно, чтобы значения не "разделялись", а "сливались", как в методе group by SQL.

Я ищу способ суммировать специфические значения (если это необходимо). Например, если я сгруппирую по 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 и затем самостоятельно проходить по результирующему объекту, чтобы суммировать значения?

5 ответ(ов)

13

Если вы хотите избежать использования внешних библиотек, вы можете лаконично реализовать "группировку по" (groupBy()) следующим образом:

var groupBy = function(xs, key) {
  return xs.reduce(function(rv, x) {
    (rv[x[key]] = rv[x[key]] || []).push(x);
    return rv;
  }, {});
};

console.log(groupBy(['one', 'two', 'three'], 'length'));

// => {"3": ["one", "two"], "5": ["three"]}

Этот код использует метод reduce() для обработки массива и группировки элементов по значению указанного ключа. Например, данный пример группирует строки по их длине, в результате чего вы получите объект, где ключами будут длины строк, а значениями — массивы строк соответствующей длины.

4

Вопрос: Как сгруппировать массив объектов с использованием объекта Map в ES6?

Ответ:

Вы можете использовать объект Map в ES6 для группировки массивов по определенному критерию. В приведенном ниже коде представлена функция groupBy, которая принимает массив и функцию для получения ключей, и возвращает Map, где ключами являются значения, возвращенные функцией, а значениями — массивы объектов, сгруппированные по этим ключам.

Вот реализация функции:

/**
 * @description
 * Принимает массив и функцию группировки,
 * и возвращает Map, сгруппированный по функции.
 *
 * @param list Массив типа V.
 * @param keyGetter Функция, которая принимает элемент массива типа V и возвращает значение типа K.
 *                  K обычно представляет собой ключ свойства V.
 *
 * @returns Map, сгруппированный по функции.
 */
function groupBy(list, keyGetter) {
    const map = new Map();
    list.forEach((item) => {
         const key = keyGetter(item);
         const collection = map.get(key);
         if (!collection) {
             map.set(key, [item]);
         } else {
             collection.push(item);
         }
    });
    return map;
}

Пример использования функции:

const pets = [
    {type:"Dog", name:"Spot"},
    {type:"Cat", name:"Tiger"},
    {type:"Dog", name:"Rover"}, 
    {type:"Cat", name:"Leo"}
];
    
const grouped = groupBy(pets, pet => pet.type);
    
console.log(grouped.get("Dog")); // -> [{type:"Dog", name:"Spot"}, {type:"Dog", name:"Rover"}]
console.log(grouped.get("Cat")); // -> [{type:"Cat", name:"Tiger"}, {type:"Cat", name:"Leo"}]

const odd = Symbol();
const even = Symbol();
const numbers = [1, 2, 3, 4, 5, 6, 7];

const oddEven = groupBy(numbers, x => (x % 2 === 1 ? odd : even));
    
console.log(oddEven.get(odd)); // -> [1, 3, 5, 7]
console.log(oddEven.get(even)); // -> [2, 4, 6]

В этом примере функция groupBy создает Map для группировки объектов pets по их типу. Также показано, как использовать символы для группировки чисел по четности.

Дополнительную информацию о Map можно найти в документации MDN.

1

Вот перевод вашей функции groupBy на русский в стиле ответа на StackOverflow:


Для группировки массива объектов по заданному ключу в JavaScript с использованием ES6, вы можете использовать функцию reduce. Вот пример реализации:

const groupBy = (items, key) => items.reduce(
  (result, item) => ({
    ...result,
    [item[key]]: [
      ...(result[item[key]] || []),
      item,
    ],
  }), 
  {},
);

Как это работает:

  1. Параметры: Функция принимает два аргумента - items (массив объектов) и key (ключ, по которому нужно группировать).

  2. reduce: Мы используем метод reduce, который перебирает все элементы массива. В качестве аргументов он принимает:

    • result — промежуточный объект, в который будут собираться сгруппированные элементы.
    • item — текущий элемент массива.
  3. Агрегация: Для каждого item мы обновляем result:

    • Используем оператор распространения ... для копирования существующих свойств result.
    • Добавляем новое свойство, ключом которого является значение item[key].
    • Для значения этого ключа создаём новый массив, который включает в себя существующие элементы (если они есть), а также текущий элемент item.
  4. Итог: В результате мы получаем объект, в котором ключи - это уникальные значения по указанному ключу, а значения - массивы элементов, сгруппированных по этим ключам.

Пример использования:

const data = [
  { id: 1, category: 'fruit', name: 'apple' },
  { id: 2, category: 'fruit', name: 'banana' },
  { id: 3, category: 'vegetable', name: 'carrot' },
];

const grouped = groupBy(data, 'category');
console.log(grouped);
/*
{
  fruit: [
    { id: 1, category: 'fruit', name: 'apple' },
    { id: 2, category: 'fruit', name: 'banana' }
  ],
  vegetable: [
    { id: 3, category: 'vegetable', name: 'carrot' }
  ]
}
*/

Надеюсь, это поможет! Если возникнут дополнительные вопросы, не стесняйтесь спрашивать.

1

ES2024 нативные методы Object.groupBy и Map.groupBy

С помощью методов Object.groupBy и Map.groupBy, добавленных в ES2024, вы можете удобно группировать элементы массива по определенному критерию. Вот пример использования:

Object.groupBy([1, 2, 3, 4, 5, 6, 7, 8, 9], v => (v % 2 ? "odd" : "even"));
// { odd: [1, 3, 5, 7, 9], even: [2, 4, 6, 8] };

Map.groupBy([1, 2, 3, 4, 5, 6, 7, 8, 9], v => (v % 2 ? "odd" : "even")).get('odd');
// [1, 3, 5, 7, 9]

Решение для группировки в одну строку с использованием ES2021

Если вам нужно решение, которое будет работать в более ранних версиях JavaScript, вот функция groupBy, реализованная с использованием метода reduce:

const groupBy = (x, f) => x.reduce((a, b, i) => ((a[f(b, i, x)] ||=[]).push(b), a), {});

TypeScript

Для TypeScript также можно использовать следующую реализацию:

const groupBy = <T>(array: T[], predicate: (value: T, index: number, array: T[]) => string) =>
  array.reduce((acc, value, index) => {
    (acc[predicate(value, index, array)] ||= []).push(value);
    return acc;
  }, {} as { [key: string]: T[] });

ПРИМЕРЫ

Вот несколько примеров использования функции groupBy:

groupBy([1, 2, 3, 4, 5, 6, 7, 8, 9], v => (v % 2 ? "odd" : "even"));
// { odd: [1, 3, 5, 7, 9], even: [2, 4, 6, 8] };

const colors = [
  "Apricot",
  "Brown",
  "Burgundy",
  "Cerulean",
  "Peach",
  "Pear",
  "Red",
];

groupBy(colors, v => v[0]); // группировка по первой букве цветовых названий
// {
//   A: ["Apricot"],
//   B: ["Brown", "Burgundy"],
//   C: ["Cerulean"],
//   P: ["Peach", "Pear"],
//   R: ["Red"],
// }

groupBy(colors, v => v.length); // группировка по длине названий цветов
// {
//   3: ["Red"],
//   4: ["Pear"],
//   5: ["Brown", "Peach"],
//   7: ["Apricot"],
//   8: ["Burgundy", "Cerulean"],
// }

const data = [
  { comment: "abc", forItem: 1, inModule: 1 },
  { comment: "pqr", forItem: 1, inModule: 1 },
  { comment: "klm", forItem: 1, inModule: 2 },
  { comment: "xyz", forItem: 1, inModule: 2 },
];

groupBy(data, v => v.inModule); // группировка по модулю
// {
//   1: [
//     { comment: "abc", forItem: 1, inModule: 1 },
//     { comment: "pqr", forItem: 1, inModule: 1 },
//   ],
//   2: [
//     { comment: "klm", forItem: 1, inModule: 2 },
//     { comment: "xyz", forItem: 1, inModule: 2 },
//   ],
// }

groupByToMap

Если вы хотите получить результат в виде Map, вы можете использовать функцию groupByToMap:

const groupByToMap = (x, f) =>
  x.reduce((a, b, i) => {
    const k = f(b, i);
    a.get(k)?.push(b) ?? a.set(k, [b]);
    return a;
  }, new Map());

TypeScript

А вот аналогичная реализация на TypeScript:

const groupByToMap = <T, Q>(array: T[], predicate: (value: T, index: number, array: T[]) => Q) =>
  array.reduce((map, value, index) => {
    const key = predicate(value, index);
    map.get(key)?.push(value) ?? map.set(key, [value]);
    return map;
  }, new Map<Q, T[]>());

Эти подходы позволяют вам удобно группировать данные в зависимости от ваших потребностей, используя как нативные методы ES2024, так и совместимые с более ранними версиями языка решения.

0

Конечно! Вот перевод вашего кода на русский в стиле ответа на StackOverflow:


Хотя это немного поздно, возможно, кто-то найдет это полезным.

В ES6 вы можете использовать функцию groupBy, чтобы сгруппировать массив объектов по определенному ключу. Вот пример, где мы группируем пользователей по их цвету:

const users = [{
    name: "Jim",
    color: "blue"
  },
  {
    name: "Sam",
    color: "blue"
  },
  {
    name: "Eddie",
    color: "green"
  },
  {
    name: "Robert",
    color: "green"
  },
];

const groupBy = (arr, key) => {
  const initialValue = {};
  return arr.reduce((acc, cval) => {
    const myAttribute = cval[key];
    acc[myAttribute] = [...(acc[myAttribute] || []), cval];
    return acc;
  }, initialValue);
};

const res = groupBy(users, "color");
console.log("группировка по цвету:", res);

В этом примере мы используем метод reduce, чтобы создать объект, в котором ключами будут значения цвета, а значениями — массивы пользователей с соответствующими цветами. В результате выполнения кода в консоль будет выведен объект, сгруппированный по цветам.

Если у вас есть дополнительные вопросы или вы хотите узнать о других способах группировки, не стесняйтесь спрашивать!

Чтобы ответить на вопрос, пожалуйста, войдите или зарегистрируйтесь