Группировка коллекции
Группировка — это раскладывание элементов по корзинам. Вы выбираете ключ для каждого элемента, и все элементы с одинаковым ключом попадают в одну корзину.
Ключ вычисляет небольшая функция, которая принимает один элемент и возвращает его ключ. Например, первую букву слова.
Map.groupBy() делает всю работу. Передайте ему массив и функцию ключа, и он вернёт Map, сопоставляющий ключ массиву элементов с этим ключом.
Map хранит ключи в том порядке, в котором они были добавлены впервые, поэтому напечатанный результат всегда выглядит одинаково. console.log() выводит Map так: Map(1) { 'a' => [ 1 ] } — сначала количество ключей, затем каждый ключ и его значение. Если одна строка получилась бы слишком длинной, ключи выводятся по одному на строку.
Есть также Object.groupBy(), который возвращает обычный объект вместо Map. В примерах здесь используется Map.groupBy(), потому что Map принимает ключи любого типа и выводит, сколько у него ключей.
Группировка по ключу
Самая простая группировка оставляет элементы такими, какие они есть, и только решает, в какую корзину попадёт каждый из них.
Передайте массив и функцию ключа в Map.groupBy(). Результат — Map, сопоставляющий ключ массиву элементов с этим ключом. Элементы внутри группы сохраняют тот порядок, который был у них во входных данных.
Иногда нужно изменить элементы во время группировки, например сохранить их в верхнем регистре. У Map.groupBy() нет параметра для этого. Напишите цикл сами: вычислите ключ, вычислите новое значение и добавьте новое значение в группу. groups.get(key) ?? [] даёт группу, которая уже есть, или новый пустой массив для ключа, который вы ещё не встречали.
Группировка слов по первой букве
Функция ключа берёт первую букву слова с помощью w[0] и переводит её в верхний регистр методом toUpperCase(). Слова, начинающиеся с одной и той же буквы, попадают в один массив. Ключи выводятся в том порядке, в котором были впервые встречены: 'O', 'T', 'F'.
Вывод: программа на TypeScript и захваченный вывод
const words: string[] = ["one", "two", "three", "four", "five"]
// Map.groupBy() вызывает функцию ключа для каждого слова и собирает группы.
const byFirstLetter = Map.groupBy(words, w => w[0].toUpperCase())
console.log(byFirstLetter)
Группировка с изменением значений
Ключ — первая буква в нижнем регистре. Сохраняемое значение — всё слово в верхнем регистре. Цикл for...of выполняет оба шага и добавляет новое значение в массив, полученный из groups.get(key) ?? [].
Вывод: программа на TypeScript и захваченный вывод
const words: string[] = ["one", "two", "three", "four", "five"]
// Map.groupBy() не может изменять значения, поэтому строим Map циклом.
const groups = new Map<string, string[]>()
for (const w of words) {
const key = w[0] // первая буква в нижнем регистре
const value = w.toUpperCase() // значение, сохраняемое в группе
const group = groups.get(key) ?? []
group.push(value)
groups.set(key, group)
}
console.log(groups)
Подсчёт элементов каждой группы
Часто вам нужны не сами элементы каждой группы, а только их количество.
Метода eachCount() не существует. Используйте вместо него Map<string, number>. Чтение ключа, которого нет в Map, даёт undefined, а не 0, поэтому пишите (counts.get(key) ?? 0) + 1. Оператор ?? подставляет начальное значение 0 для первого элемента группы.
Подсчёт слов по первой букве
Каждое слово добавляет 1 к счётчику своей первой буквы. В итоге Map содержит по одному числу на букву: 'o' => 1, 't' => 2 и 'f' => 2.
Вывод: программа на TypeScript и захваченный вывод
const words: string[] = ["one", "two", "three", "four", "five"]
// get() возвращает undefined для нового ключа, поэтому ?? 0 начинает счёт с 0.
const counts = new Map<string, number>()
for (const w of words) {
const key = w[0]
counts.set(key, (counts.get(key) ?? 0) + 1)
}
console.log(counts)
Свёртка и редукция по группам
Свернуть группу — значит шаг за шагом объединить её элементы в одно значение. Классический пример — текущая сумма: начать с 0 и прибавлять каждый элемент.
reduce() делает именно это. Он проносит одно значение — аккумулятор — через весь массив. Здесь аккумулятор — это Map, который хранит текущее значение каждой группы. Для каждого элемента вы вычисляете его ключ и обновляете значение, сохранённое под этим ключом.
Когда у текущего значения есть начальное значение, например 0 для суммы, ?? 0 подставляет его для ключа, которого ещё нет. Когда же первый элемент сам должен задать начальное значение, сравните результат get() с undefined, чтобы различить эти два случая.
Сумма каждой группы
Каждое число помещается в группу "even" или "odd" и прибавляется к сумме этой группы выражением acc.set(key, (acc.get(key) ?? 0) + n). Новый ключ начинается с 0, поэтому особый случай не нужен. Первое число — 3, поэтому 'odd' — первый ключ этого Map.
Вывод: программа на TypeScript и захваченный вывод
const numbers: number[] = [3, 4, 5, 6, 7, 8]
// reduce() сворачивает массив в один Map с текущими суммами.
const sums = numbers.reduce((acc, n) => {
const key = n % 2 === 0 ? "even" : "odd"
return acc.set(key, (acc.get(key) ?? 0) + n)
}, new Map<string, number>())
console.log(sums)
Построение строки для каждой группы
Первое число группы начинает строку. Каждое следующее число присоединяется через "+". acc.get(key) даёт undefined для группы, которая ещё пуста, и это позволяет различить два случая. String(n) превращает число в текст.
Вывод: программа на TypeScript и захваченный вывод
const numbers: number[] = [3, 4, 5, 6, 7, 8]
const chains = numbers.reduce((acc, n) => {
const key = n % 2 === 0 ? "even" : "odd"
const current = acc.get(key)
// undefined означает, что это первое число группы.
return acc.set(key, current === undefined ? String(n) : current + "+" + n)
}, new Map<string, string>())
console.log(chains)