Перевод статьи «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, когда он будет доступен).


