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

Преобразование коллекции

Преобразование берёт коллекцию, например slice или map, и строит из неё новую коллекцию. Исходная коллекция при этом не меняется.

Во многих языках у списков и словарей есть готовые методы вроде map, zip, associate и flatten. В Go таких встроенных методов нет. Вместо этого вы пишете небольшой цикл for или маленькую вспомогательную функцию, которую можно использовать снова и снова. Это и есть подход Go, и он получается коротким, как только вы запомните приём.

Начиная с версии 1.18 в Go есть дженерики. Обобщённая функция работает с любым типом элементов. Примеры на этой странице используют дженерики (Go 1.21 или новее), поэтому одна вспомогательная функция подходит и для чисел, и для строк, и для структур. Стандартные пакеты slices, maps и strings тоже помогают в частых задачах.

Синтаксис обобщённой вспомогательной функции Map

Так выглядит вспомогательная функция Map, которую можно использовать повторно. T — это тип входных элементов, а R — тип элементов результата. Функция f вызывается один раз для каждого элемента, и каждый ответ добавляется в новый slice.

main.go
func Map[T, R any](items []T, f func(T) R) []R {
  result := make([]R, 0, len(items))
  for _, v := range items {
    result = append(result, f(v))
  }
  return result
}

Map

Операция map применяет одну и ту же функцию к каждому элементу и собирает ответы в новый slice той же длины. Например, она может превратить [1 2 3] в [3 6 9].

В Go у слайсов нет метода map. Вы пишете цикл, который вызывает функцию для каждого элемента и с помощью append добавляет результат в новый slice. Если вы делаете так часто, вынесите цикл в обобщённую вспомогательную функцию.

Не путайте это с типом map в Go, который хранит пары ключ:значение. Здесь «map» означает «применить функцию к каждому элементу».

Map через вспомогательную функцию

Вспомогательная функция Map принимает slice и функцию. Она вызывает функцию для каждого элемента и возвращает новый slice. Здесь она умножает каждое число на 3.

Prompt
Напишите обобщённую вспомогательную функцию Map, которая применяет функцию к каждому элементу слайса и возвращает новый slice. Примените её к слайсу 1, 2, 3, чтобы умножить каждое число на 3, и выведите результат.
Вывод: программа на Go и захваченный вывод
Map with a helper function

Map с индексом

Иногда важна и позиция элемента. В Go range даёт и индекс, и значение, поэтому достаточно обычного цикла. Здесь каждое значение умножается на свой индекс.

Prompt
Пройдите по слайсу 1, 2, 3 с помощью range, используя и индекс, и значение. Постройте новый slice, где каждый элемент равен значение * индекс, и выведите его.
Вывод: программа на Go и захваченный вывод
Map with the index

Пропустить часть результатов

Иногда часть результатов нужно отбросить. У чисел в Go нет значения null, поэтому функция возвращает две вещи: результат и bool, который говорит, оставлять его или нет. Когда bool равен false, элемент пропускается.

Prompt
Напишите обобщённую вспомогательную функцию MapNotNil, которая принимает функцию, возвращающую значение и bool, и оставляет только те результаты, где bool равен true. Примените её к слайсу 1, 2, 3: пропустите число 2, а остальные умножьте на 3. Выведите результат.
Вывод: программа на Go и захваченный вывод
Skip some results

Преобразование ключей map

Чтобы изменить ключи map, создайте новый map и скопируйте в него каждую запись с новым ключом. Здесь strings.ToUpper переводит каждый ключ в верхний регистр. fmt.Println печатает map с отсортированными ключами, поэтому порядок вывода всегда одинаковый.

Prompt
Возьмите map, в котором "key1" соответствует 1, а "key2" — 2. Постройте новый map с теми же значениями, но с каждым ключом, переведённым в верхний регистр с помощью strings.ToUpper. Выведите новый map.
Вывод: программа на Go и захваченный вывод
Transform map keys

Преобразование значений map

Чтобы изменить значения, сделайте то же самое, но оставьте ключ и вычислите новое значение. Здесь каждое значение увеличивается на длину своего ключа: 1 + len("key1") равно 5.

Prompt
Возьмите map, в котором "key1" соответствует 1, а "key2" — 2. Постройте новый map с теми же ключами, где каждое значение равно старому значению плюс длина его ключа. Выведите новый map.
Вывод: программа на Go и захваченный вывод
Transform map values

Zip

Zip берёт два слайса и соединяет их элементы по позициям: первый с первым, второй со вторым и так далее. В результате получается столько пар, сколько элементов в более коротком слайсе.

В Go нет функции zip. Вы объявляете маленькую структуру для одной пары, находите меньшую длину встроенной функцией min и в цикле по индексу строите пары.

Zip двух слайсов

Структура Pair хранит один цвет и одно животное. Цикл идёт до меньшей длины и создаёт по одной Pair на каждый индекс. При печати слайса структур каждая пара показана в фигурных скобках.

Prompt
Объявите структуру Pair с полями Color и Animal. Соедините слайсы красный, коричневый, серый и лиса, медведь, волк поэлементно в slice пар, останавливаясь на более коротком слайсе. Выведите slice пар.
Вывод: программа на Go и захваченный вывод
Zip two slices

Zip с преобразованием

Сами пары нужны не всегда. Здесь цикл сразу использует оба элемента и строит из них строку. Слово превращается в []rune, чтобы работать с буквами, а не с байтами: runes[:1] — это первая буква, которую strings.ToUpper переводит в верхний регистр, а runes[1:] — остаток слова.

Prompt
Соедините слайсы красный, коричневый, серый и лиса, медведь, волк поэлементно. Для каждой пары выведите строку вида «Лиса — красный», где первая буква названия животного в верхнем регистре.
Вывод: программа на Go и захваченный вывод
Zip with a transformation

Unzip слайса пар

Unzip — обратная операция: один slice пар превращается в два слайса. Цикл читает каждую пару и добавляет её части в два отдельных слайса.

Prompt
Возьмите slice пар: one с 1, two с 2, three с 3. Разделите его на slice слов и slice чисел и выведите каждый slice на отдельной строке.
Вывод: программа на Go и захваченный вывод
Unzip a slice of pairs

Associate

Associate строит map из слайса. Каждый элемент даёт один ключ и одно значение. Map не может хранить один и тот же ключ дважды, поэтому если два элемента дают одинаковый ключ, побеждает более поздний.

В Go нет функций associate. Создайте пустой map с помощью make, затем пройдите по слайсу и присвойте result[key] = value для каждого элемента.

Элементы как ключи

Сам элемент становится ключом, а значение вычисляется из него. Здесь слова — это ключи, а значения — их длина в байтах, которую возвращает len: у one — 3, у three — 5.

Prompt
Постройте map из слов one, two, three, где каждое слово — ключ, а его длина — значение. Выведите map.
Вывод: программа на Go и захваченный вывод
Elements as keys

Элементы как значения

Элемент становится значением, а ключ вычисляется из него. Здесь ключ — это первая буква в верхнем регистре. И two, и three начинаются с T, поэтому three заменяет two, и в map остаётся только две записи.

Prompt
Постройте map из слов one, two, three, где ключ — первая буква слова в верхнем регистре, а значение — само слово. Выведите map и обратите внимание, что "three" заменяет "two" под ключом T.
Вывод: программа на Go и захваченный вывод
Elements as values

Вычислить и ключ, и значение

Из элемента можно вычислить и ключ, и значение. Здесь ключ — это первая буква, а значение — длина слова. Снова побеждает более поздняя запись с ключом T.

Prompt
Постройте map из слов one, two, three, где ключ — первая буква в верхнем регистре, а значение — длина слова. Выведите map.
Вывод: программа на Go и захваченный вывод
Compute both key and value

Ключ и значение из одного элемента

Иногда один элемент несёт обе части. strings.Fields разбивает полное имя по пробелам в slice слов. Фамилия становится ключом, а имя — значением.

Prompt
Возьмите полные имена "Alice Adams" и "Brian Brown". Разбейте каждое имя на слова с помощью strings.Fields и постройте map из фамилии в имя. Выведите map.
Вывод: программа на Go и захваченный вывод
Key and value from one element

Flatten

Flatten превращает вложенную коллекцию, например slice слайсов, в один плоский slice со всеми элементами по порядку.

В Go нет метода flatten, но append умеет добавить сразу целый slice: append(result, inner...). Три точки раскрывают все элементы inner в этом вызове.

Flatten слайса слайсов

Цикл проходит по каждому вложенному слайсу и добавляет все его элементы в result. Два вложенных слайса по три числа превращаются в один slice из шести.

Prompt
Возьмите slice слайсов: 1, 2, 3 и 4, 5, 6. Добавьте все вложенные элементы в один плоский slice с помощью append и оператора ..., затем выведите его.
Вывод: программа на Go и захваченный вывод
Flatten a slice of slices

Flatten значений из структур

Операция flat map сначала берёт slice из каждого элемента, а затем соединяет все эти слайсы вместе. В Go вы читаете нужное поле, c.Values, и добавляете его с тремя точками. Все значения из трёх контейнеров попадают в один slice.

Prompt
Объявите структуру Container с полем Values, которое является слайсом строк. Создайте три контейнера со значениями one, two, three; four, five; и six, семь, восемь. Соберите все их значения в один slice и выведите его.
Вывод: программа на Go и захваченный вывод
Flatten values from structs

Строковое представление

Часто коллекцию нужно показать как читаемый текст, например с запятыми между элементами.

В Go strings.Join принимает slice строк и разделитель и возвращает одну строку. Дополнения — префикс, постфикс, ограничение количества или преобразование — делаются обычной склейкой строк или коротким циклом перед объединением. Числа сначала нужно превратить в строки, например с помощью strconv.Itoa.

Объединение с разделителем

strings.Join ставит ", " между словами и возвращает одну строку.

Prompt
Объедините слова one, two, three в одну строку, поставив между ними ", ", с помощью strings.Join. Выведите строку.
Вывод: программа на Go и захваченный вывод
Join with a separator

Добавить префикс и постфикс

Префикс и постфикс — это просто текст вокруг объединённого результата. В Go он добавляется с помощью +.

Prompt
Объедините слова one, two, three, поставив между ними " | ", добавьте префикс "начало: " и постфикс ": конец" и выведите результат.
Вывод: программа на Go и захваченный вывод
Add a prefix and a postfix

Ограничить количество элементов

Для длинного слайса иногда нужно показать только первые несколько элементов. Возьмите первые 10 элементов через numbers[:10], превратите каждый в строку с помощью strconv.Itoa, а затем добавьте в конец "...>", чтобы показать, что остальные элементы отброшены.

Prompt
Создайте slice с числами от 1 до 100. Объедините только первые 10 из них через ", " и добавьте в конец "<...>", чтобы показать, что список обрезан. Выведите результат.
Вывод: программа на Go и захваченный вывод
Limit the number of elements

Преобразовать каждый элемент перед объединением

Чтобы изменить каждый элемент перед объединением, соберите в цикле новый slice строк, а затем вызовите на нём strings.Join. Здесь каждое слово превращается в "Элемент: " плюс само слово в верхнем регистре.

Prompt
Превратите каждое из слов one, two, three в "Элемент: " и следующее за ним слово в верхнем регистре. Объедините результаты через ", " и выведите строку.
Вывод: программа на Go и захваченный вывод
Transform each element before joining