Перейти к основному содержимому

Группировка коллекции

Группировка — это распределение элементов по корзинам. Вы выбираете ключ для каждого элемента, и все элементы с одинаковым ключом попадают в одну корзину.

Ключ вычисляет небольшая функция, которая берёт один элемент и возвращает его ключ. Например, первую букву слова.

В Go нет встроенного метода groupBy у слайсов. Естественный способ группировки в Go — это map, ключами которого являются ключи групп, а значениями — слайсы элементов: map[K][]T. Вы заполняете его циклом for и функцией append() или один раз пишете небольшой обобщённый помощник, показанный ниже, и используете его повторно.

У map нет фиксированного порядка, но fmt.Println() печатает map, отсортированный по ключу. Поэтому выведенный результат всегда выглядит одинаково.

Синтаксис обобщённого помощника GroupBy

T — это тип элемента, а K — тип ключа. K должен быть comparable, потому что ключи map должны поддерживать ==. Для каждого элемента помощник вызывает key() и добавляет элемент в слайс, сохранённый под этим ключом. Добавление по ключу, которого ещё нет, просто начинает новый слайс.

main.go
func GroupBy[T any, K comparable](items []T, key func(T) K) map[K][]T {
  groups := make(map[K][]T)
  for _, v := range items {
    k := key(v)
    groups[k] = append(groups[k], v)
  }
  return groups
}

Группировка по ключу

Самая простая группировка оставляет элементы такими, какие они есть, и только решает, в какую корзину попадёт каждый из них.

Передайте слайс и функцию ключа помощнику GroupBy. Результат — это map из ключа в слайс элементов с этим ключом. Внутри группы элементы сохраняют тот порядок, который был во входных данных.

Иногда нужно изменить элементы прямо во время группировки, например сохранить их в верхнем регистре. В Go нет параметра valueTransform для этого. Напишите цикл сами: вычислите ключ, вычислите новое значение и добавьте новое значение в группу.

Группировка слов по первой букве

Функция ключа берёт первую букву слова с помощью w[:1] и переводит её в верхний регистр функцией strings.ToUpper(). Слова, начинающиеся с одной и той же буквы, оказываются в одном слайсе.

Prompt
Напишите обобщённый (generic) помощник GroupBy, который принимает слайс и функцию ключа и возвращает map из ключа в слайс элементов с этим ключом. Примените его к словам one, two, three, four, five, взяв в качестве ключа первую букву в верхнем регистре. Выведите map.
Вывод: программа на Go и захваченный вывод
Group words by first letter

Группировка с изменением значений

Ключ — это первая буква в нижнем регистре. Сохраняемое значение — это всё слово в верхнем регистре. Обычный цикл for делает оба шага и добавляет значение в groups[key].

Prompt
Сгруппируйте слова one, two, three, four, five по их первой букве в нижнем регистре. Сохраняйте каждое слово в его группе в верхнем регистре, а не как есть. Используйте цикл и map из строки в слайс строк, затем выведите map.
Вывод: программа на Go и захваченный вывод
Group and change the values

Подсчёт в каждой группе

Часто вам нужны не сами элементы каждой группы, а только их количество.

В Go нет groupingBy() или eachCount(). Вместо этого используйте map[string]int. Чтение ключа, которого нет в map, даёт 0, поэтому counts[key]++ работает даже для первого элемента группы.

Подсчёт слов по первой букве

Каждое слово прибавляет 1 к счётчику своей первой буквы. В итоге в map оказывается по одному числу для каждой буквы.

Prompt
Посчитайте, сколько слов из one, two, three, four, five начинается с каждой буквы. Используйте map из первой буквы в int и увеличивайте счётчик для каждого слова. Выведите map.
Вывод: программа на Go и захваченный вывод
Count words per first letter

Свёртка (fold и reduce) по группам

Свернуть группу — значит шаг за шагом объединить её элементы в одно значение. Классический пример — накопительная сумма: начните с 0 и прибавляйте каждый элемент.

В Go нет fold() или aggregate() для групп. Способ Go — это map, который хранит текущее значение каждой группы. Для каждого элемента вычислите его ключ и обновите значение, сохранённое под этим ключом.

Когда у текущего значения есть начальное значение, например 0 для суммы, отсутствующий ключ уже даёт вам это начало. Когда значение должно начинаться с самого первого элемента, проверьте с помощью шаблона value, ok := m[key], существует ли уже такой ключ.

Сумма каждой группы

Каждое число попадает в группу "чётные" или "нечётные" и прибавляется к сумме этой группы с помощью sums[key] += n. Новый ключ начинается с 0, поэтому особый случай не нужен.

Prompt
Возьмите числа 3, 4, 5, 6, 7, 8 и поместите каждое из них в группу «чётные» или «нечётные». Вместо того чтобы хранить сами числа, прибавляйте каждое число к текущей сумме его группы в map из строки в int. Выведите map.
Вывод: программа на Go и захваченный вывод
Sum each group

Сборка строки для каждой группы

Первое число группы начинает строку. Каждое следующее число присоединяется через "+". Флаг exists из chains[key] различает эти два случая. strconv.Itoa() превращает число в текст.

Prompt
Возьмите числа 3, 4, 5, 6, 7, 8 и поместите каждое из них в группу «чётные» или «нечётные». Для каждой группы соберите строку шаг за шагом: первое число начинает строку, а каждое следующее добавляется со знаком «+» перед ним, что даёт текст вида 3+5+7. Выведите map из имени группы в строку.
Вывод: программа на Go и захваченный вывод
Build a string per group