Перевод статьи «Array Grouping in JavaScript (2024)».
Группировка массивов — это задача, которую вы, скорее всего, реализовывали в JavaScript. Она напоминает выполнение GROUP BY в SQL. Имея набор данных, мы можем составить набор более высокого уровня, поместив похожие данные в группы и присвоив группам идентификаторы.
В этой статье я рассмотрю новые функции группировки массивов, вышедшие в 2024 году, а именно Object.groupBy
и Map.groupBy
.
Что касается TypeScript, поддержка этих API доступна начиная с TypeScript 5.4. Однако вам придется настроить tsconfig.json на ESNext
. Когда они будут доступны в ES2024, вы сможете перейти на ES2024
или выше.
Группировка массивов до 2024 года
Группировка массивов в JavaScript — не новая концепция, мы уже реализовывали ее в различных вариантах. Например, можно применить циклы for
или foreach
, Array.prototype.reduce
или функции groupBy
в underscore.js или lodash.
Вот как мы группируем данные, используя функцию reduce()
, если у нас есть список сотрудников:
interface Employee { name: string; department: string; age: number; manager?: Employee; joined: Date } const ceo = { name: "John Doe", department: "engineering", age: 47, joined: new Date("10-04-2020") } const cfo = { name: "Anna Maria", department: "finance", age: 45, joined: new Date("10-05-2020") } const employees: Employee[] = [ ceo, { name: "Pop Jones Jr.", department: "finance", age: 30, manager: cfo, joined: new Date("10-04-2021") }, { name: "Sarah Clara", department: "engineering", age: 32, manager: ceo, joined: new Date("10-05-2021") }, cfo, { name: "Funmi Kola", department: "engineering", age: 20, manager: ceo, joined: new Date("10-05-2022") }, { name: "Julius Maria", department: "sales", age: 27, manager: cfo, joined: new Date("10-05-2022") } ] const groupByDepartment = employees.reduce<Record<string, Employee[]>>((acc, employee) => { const department = employee.department; if (acc[department] === undefined) { acc[department] = []; } acc[department].push(employee); return acc; }, {}); console.log(groupByDepartment);
Вывод для groupByDepartment
должен быть таким:
{ "engineering": [ { "name": "John Doe", "department": "engineering", "age": 47, "joined": "2020-10-03T22:00:00.000Z" }, { "name": "Sarah Clara", "department": "engineering", "age": 32, "manager": { "name": "John Doe", "department": "engineering", "age": 47, "joined": "2020-10-03T22:00:00.000Z" }, "joined": "2021-10-04T22:00:00.000Z" }, { "name": "Funmi Kola", "department": "engineering", "age": 20, "manager": { "name": "John Doe", "department": "engineering", "age": 47, "joined": "2020-10-03T22:00:00.000Z" }, "joined": "2022-10-04T22:00:00.000Z" } ], "finance": [ { "name": "Pop Jones Jr.", "department": "finance", "age": 30, "manager": { "name": "Anna Maria", "department": "finance", "age": 45, "joined": "2020-10-04T22:00:00.000Z" }, "joined": "2021-10-03T22:00:00.000Z" }, { "name": "Anna Maria", "department": "finance", "age": 45, "joined": "2020-10-04T22:00:00.000Z" } ], "sales": [ { "name": "Julius Maria", "department": "sales", "age": 27, "manager": { "name": "Anna Maria", "department": "finance", "age": 45, "joined": "2020-10-04T22:00:00.000Z" }, "joined": "2022-10-04T22:00:00.000Z" } ] }
Группировка с помощью Object.groupBy
Функция Object.groupBy
используется для группировки элементов итерируемого объекта по значению, которое возвращается предоставленной колбэк-функцией. Это значение должно быть чем-то, что может быть преобразовано в строку или символ.
Object.groupBy
возвращает объект, в котором каждое свойство представляет собой группу, содержащую массив элементов. Использование такого объекта позволяет эргономично проводить деструктуризацию и предотвращает случайные коллизии с глобальными свойствами Object
.
Используя тот же набор данных, с помощью Object.groupBy
мы можем сгруппировать сотрудников по их отделам:
const groupByDepartment = Object.groupBy(employees, ({department}) => department)
Это дает тот же результат, что и метод reduce
, но с меньшим количеством кода. Вывод для groupByDepartment
должен быть следующим:
{ "engineering": [ { "name": "John Doe", "department": "engineering", "age": 47, "joined": "2020-10-03T22:00:00.000Z" }, { "name": "Sarah Clara", "department": "engineering", "age": 32, "manager": { "name": "John Doe", "department": "engineering", "age": 47, "joined": "2020-10-03T22:00:00.000Z" }, "joined": "2021-10-04T22:00:00.000Z" }, { "name": "Funmi Kola", "department": "engineering", "age": 20, "manager": { "name": "John Doe", "department": "engineering", "age": 47, "joined": "2020-10-03T22:00:00.000Z" }, "joined": "2022-10-04T22:00:00.000Z" } ], "finance": [ { "name": "Pop Jones Jr.", "department": "finance", "age": 30, "manager": { "name": "Anna Maria", "department": "finance", "age": 45, "joined": "2020-10-04T22:00:00.000Z" }, "joined": "2021-10-03T22:00:00.000Z" }, { "name": "Anna Maria", "department": "finance", "age": 45, "joined": "2020-10-04T22:00:00.000Z" } ], "sales": [ { "name": "Julius Maria", "department": "sales", "age": 27, "manager": { "name": "Anna Maria", "department": "finance", "age": 45, "joined": "2020-10-04T22:00:00.000Z" }, "joined": "2022-10-04T22:00:00.000Z" } ] }
Группировка с помощью Map.groupBy
Функция Map.groupBy
аналогична Object.groupBy
, за исключением того, что она возвращает Map
, а заданный итерируемый массив может быть сгруппирован по любому произвольному значению. В данном случае набор данных можно сгруппировать с помощью объекта.
Возвращаясь к нашему набору данных, мы определили свойство manager
для некоторых сотрудников и присвоили ему объект ceo
или cfo
. Мы можем сгруппировать сотрудников по их руководителю (manager), что будет выглядеть следующим образом:
const managerWithTeammates = Map.groupBy(employees, ({manager}) => manager)
Вывод для managerWithTeammates
должен выглядеть так:
Map (3) {undefined => [{ "name": "John Doe", "department": "engineering", "age": 47, "joined": "2020-10-03T22:00:00.000Z" }, { "name": "Anna Maria", "department": "finance", "age": 45, "joined": "2020-10-04T22:00:00.000Z" }], { "name": "Anna Maria", "department": "finance", "age": 45, "joined": "2020-10-04T22:00:00.000Z" } => [{ "name": "Pop Jones Jr.", "department": "finance", "age": 30, "manager": { "name": "Anna Maria", "department": "finance", "age": 45, "joined": "2020-10-04T22:00:00.000Z" }, "joined": "2021-10-03T22:00:00.000Z" }, { "name": "Julius Maria", "department": "sales", "age": 27, "manager": { "name": "Anna Maria", "department": "finance", "age": 45, "joined": "2020-10-04T22:00:00.000Z" }, "joined": "2022-10-04T22:00:00.000Z" }], { "name": "John Doe", "department": "engineering", "age": 47, "joined": "2020-10-03T22:00:00.000Z" } => [{ "name": "Sarah Clara", "department": "engineering", "age": 32, "manager": { "name": "John Doe", "department": "engineering", "age": 47, "joined": "2020-10-03T22:00:00.000Z" }, "joined": "2021-10-04T22:00:00.000Z" }, { "name": "Funmi Kola", "department": "engineering", "age": 20, "manager": { "name": "John Doe", "department": "engineering", "age": 47, "joined": "2020-10-03T22:00:00.000Z" }, "joined": "2022-10-04T22:00:00.000Z" }]}
В этом примере у нас есть 3 группы:
- CEO и CFO (генеральный и финансовый директор), у которых нет руководителя, с ключом объекта
Map
—undefined
. - CFO, Anna Maria, у которой в подчинении два сотрудника. В качестве ключа объекта
Map
используется объектcfo
. - CEO, John Doe, в подчинении которого находятся два сотрудника. В качестве ключа объекта
Map
используется объектceo
.
Заключение
Разве не удивительны новые API, которые появляются в JavaScript? Функции Object.groupBy
и Map.groupBy
упрощают группировку данных в массиве или итерируемом файле, и они более эргономичны, чем традиционный метод reduce
. Они также уменьшают размер пакета, поскольку не требуют использования внешних библиотек, таких как lodash или underscore.js.
Напомним, что функция Map.groupBy
используется в основном для группировки элементов, связанных с любым произвольным объектом, особенно если этот объект может меняться со временем. Если объект неизменен, можно представить его в виде строки и сгруппировать элементы с помощью Object.groupBy()
. Если вы используете TypeScript, установите целевой параметр ESNext
(или ES2024
, когда он будет доступен).