После изучения данного урока вы сможете:
- Использовать карты в качестве наборов неструктурированных данных;
- Объявлять, получать доступ и итерировать через карты;
- Использовать преимущества ассоциативного массива.
Карты могут пригодиться во время поиска, и речь идет не только о картах Google Maps. Go предоставляет коллекцию карт с ключами, что указывают на значения. В то время, как массивы и срезы индексируются через последовательные целые числа, ключи карт могут быть представлены практически любым типом.
Рекомендуем вам супер TELEGRAM канал по Golang где собраны все материалы для качественного изучения языка. Удивите всех своими знаниями на собеседовании! 😎
Мы публикуем в паблике ВК и Telegram качественные обучающие материалы для быстрого изучения Go. Подпишитесь на нас в ВК и в Telegram. Поддержите сообщество Go программистов.
Содержание статьи
- Объявление карты в Golang
- Копируются ли карты в Golang?
- Предварительное обозначение карты через make
- Использование карты для подсчета частоты использования элементов
- Группирование данных с картами и срезами Golang
- Множества в Golang
У коллекций подобного рода есть свои названия в разных языках: словари в Python, хеши в Ruby и объекты в JavaScript. Ассоциативные массивы в PHP и таблицы в Lua являются одновременно картами и обычными массивами.
Карты особенно полезны для неструктурированных данных, где ключи определяются во время работы программы. Программы, созданные через скриптовые языки, часто используют карты и для структурированных данных — тех данных, ключи которых известны заранее. В дальнейших уроках мы в подробностях рассмотрим структурированный тип, что лучше всего подходит для таких случаев.
Карты связывают ключ со значением, что удобно для индекса. Если вам известно название книги, итерация каждой книги в массиве займет много времени. Это похоже на просмотр каждой полки каждого стеллажа библиотеки или книжного магазина. Карта с ключом названия книги значительно ускоряет процесс.
Подумайте, в каких случаях карты с ключами к значениям могут быть полезны?
Объявление карты в Golang
Ключи карты могут быть практически любого типа, в отличие от массивов и срезов, где для ключей используется последовательность целых чисел. Тип для ключей и значений в Go нужно уточнять. Для объявления карты с ключами типа string и значениями типа int
используется синтаксис map[string]int
. Схема дается ниже.
Карта с ключами типа string
и значениями типа integer
Карта temperature, объявленная в Листинге 1, содержит средние значения температуры Планетарного информационного бюллетеня. Как и другие коллекционные типы, карты можно объявлять и инициализировать через композитные литералы. Для поиска значений по ключу, присваивания поверх существующих значений или добавления значений в карту используются квадратные скобки []
.
1 2 3 4 5 6 7 8 9 10 11 12 |
temperature := map[string]int{ "Земля": 15, // Пары ключ-значения являются композитными литералами для карт "Марс": -65, } temp := temperature["Земля"] fmt.Printf("Средняя температура на поверхности Земли составляет %v° C.\n", temp) // Выводит: Средняя температура на поверхности Земли составляет 15° C. temperature["Земля"] = 16 // Небольшое изменение климата temperature["Венера"] = 464 fmt.Println(temperature) // Выводит: map[Венера:464 Земля:16 Марс:-65] |
При попытке получить доступ к ключу, которого нет в карте, результатом будет нулевое значение типа (int
):
1 2 |
moon := temperature["Луна"] fmt.Println(moon) // Выводит: 0 |
В Go используется синтаксис comma, ok, что нужен для обозначения разницы между ключом "Луна"
, которого нет в карте, и тем, что находится в карте с температурой 0° C:
1 2 3 4 5 |
if moon, ok := temperature["Луна"]; ok { // Синтаксис comma, ok fmt.Printf("Средняя температура на поверхности Луны составляет %v° C.\n", moon) } else { fmt.Println("Где Луна?") // Выводит: Где Луна? } |
Переменная moon
должна содержать значение ключа "Луна"
или же нулевое значение. При наличии ключа значение дополнительной переменной ok
будет равно true, в противном случае — false
.
При использовании синтаксиса comma, ok можно использовать любые названия переменных:
1 |
temp, found := temperature["Венера"] |
Вопросы для проверки:
- Какой тип вы используете для объявления карты с ключами, представленными 64-битными вещественными числами, и значениями, что являются целыми числами?
- При модификации Листинга 1 таким образом, что ключ
"Луна"
был бы представлен со значением 0, каким будет результат использования синтаксиса comma, ok?
Копируются ли карты в Golang?
Ранее мы упоминали, что массивы копируются во время присваивания к новым переменным или передачи к функциям или методам. То же самое верное в отношении примитивных типов вроде int
или float64
.
Карты устроены иначе. В следующем примере planets
и planetsMarkII
делят одну и ту же базовую информацию. Как видите, изменения в одном, затрагивают и другой. Это не всегда кстати.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
planets := map[string]string{ "Земля": "Сектор ZZ9", "Марс": "Сектор ZZ9", } planetsMarkII := planets planets["Земля"] = "упс" fmt.Println(planets) // Выводит: map[Земля:упс Марс:Сектор ZZ9] fmt.Println(planetsMarkII) // Выводит: map[Земля:упс Марс:Сектор ZZ9] delete(planets, "Земля") // Земля удаляется из карты fmt.Println(planetsMarkII) // Выводит: map[Марс:Сектор ZZ9] |
Когда встроенная функция delete
удаляет элемент из карты, как planets
, так и planetsMarkII
изменяются. При передачи карты функции или методу содержимое карты может измениться. Такое поведение напоминает несколько срезов, что указывают на один и тот же базовый массив.
Вопросы для проверки:
- Почему изменения в
planets
из Листинга 2 также отражаются наplanetsMarkII
? - Что делает встроенная функция
delete
?
Предварительное обозначение карты через make
Некоторые аспекты карт напоминают срезы. Только если вы не инициализировали их через композитный литерал, карты нужно обозначить через встроенную функцию make
.
Для карт make
принимает один или два параметра. Второй предварительно обозначает место для количества ключей, что напоминает вместимость срезов. При использовании make
начальная длина карты всегда будет нулевой:
1 |
temperature := make(map[float64]int, 8) |
Вопрос для проверки:
В чем преимущество предварительного обозначения карты через make
?
Использование карты для подсчета частоты использования элементов
В Листинге 3 определяется частота упоминания температур, значения взяты из MAAS API. Если бы frequency
была срезом, ключи были бы целыми числами, а базовому массиву потребовалось бы зарезервировать место для подсчета температур, что в действительности не был произведен. Для таких случаев карта намного удобнее.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
temperatures := []float64{ -28.0, 32.0, -31.0, -29.0, -23.0, -29.0, -28.0, -33.0, } frequency := make(map[float64]int) for _, t := range temperatures { // Итерирует через срез (индекс, значение) frequency[t]++ } for t, num := range frequency { // Итерирует через карту (ключ, значение) fmt.Printf("%+.2f встречается %d раз(а) \n", t, num) } |
Итерация через ключевое слово range
работает одинаково со срезами, массивам и картами. Вместо индекса и значения, для итерации карты используется ключ и значение. Обратите внимание, что Go не гарантирует порядок ключей карты, поэтому выводы при различных запусках могут отличаться.
Вопрос для проверки:
При итерации через карту чем заполняются две переменные?
Группирование данных с картами и срезами Golang
Вместо определения частоты упоминания температур сгруппируем их вместе с разделением каждой в 10°. Для этого в следующем пример карты группируются в срез температур данной группы.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
temperatures := []float64{ -28.0, 32.0, -31.0, -29.0, -23.0, -29.0, -28.0, -33.0, } groups := make(map[float64][]float64) // Карта с ключами float64 и значениями []float64 for _, t := range temperatures { g := math.Trunc(t/10) * 10 // Округляет температуры вниз -20, -30 и так далее groups[g] = append(groups[g], t) } for g, temperatures := range groups { fmt.Printf("%v: %v\n", g, temperatures) } |
Результат программы:
1 2 3 |
30: [32] -30: [-31 -33] -20: [-28 -29 -23 -29 -28] |
Вопрос для проверки:
Какого типа ключи и значения в объявлении var groups map[string][]int
?
Множества в Golang
Множество является коллекцией, похожей на массив. Отличие в том, что каждый элемент должен повторяться только один раз. В Go коллекций множеств, но вы всегда можете сымпровизировать, используя карту, как показано в следующем примере. Значение не важно, но true
удобно для проверки того, является ли элемент частью множества. Если температура находится в карте, ее значение true
, это часть множества.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
var temperatures = []float64{ -28.0, 32.0, -31.0, -29.0, -23.0, -29.0, -28.0, -33.0, } set := make(map[float64]bool) // Создание карты с булевыми значениями for _, t := range temperatures { set[t] = true } if set[-28.0] { fmt.Println("часть множества") // Выводит: часть множества } fmt.Println(set) // Выводит: Prints map[-31:true -29:true -23:true -33:true -28:true 32:true] |
Видно, что карта содержит по одному ключу для каждой температуры, дубликаты удаляются. У ключей карты произвольный порядок, поэтому перед их сортировкой температуры нужно конвертировать обратно в срез:
1 2 3 4 5 6 7 |
unique := make([]float64, 0, len(set)) for t := range set { unique = append(unique, t) } sort.Float64s(unique) fmt.Println(unique) // Выводит: [-33 -31 -29 -28 -23 32] |
Вопрос для проверки:
Как проверить, является ли 32.0 частью множества set
?
Заключение
- Карты являются универсальными коллекциями для неструктурированных данных;
- Композитные литералы удобны для инициализации карт;
- Ключевое слово
range
может итерировать через карты; - Карты делят одинаковые базовые данные, когда присваиваются или передаются функциям;
- Коллекции становятся надежнее при объединении друг с другом.
Итоговое задание для проверки:
Напишите функцию для подсчета частоты упоминания слов в строке текста и возвращения карты со словами и числом, указывающем, сколько раз они употребляются. Функция должна конвертировать текст в нижний регистр и обрезать знаки препинания. Используйте пакет strings
. Функции, которые пригодятся для выполнения данного задания: Fields
, ToLower
и Trim
.
Используйте функцию для подсчета частоты слов следующего отрывка текста и последующего вывода числа употребления каждого слова, что встречается более одного раза.
As far as eye could reach he saw nothing but the stems of the great plants about him receding in the violet shade, and far overhead the multiple transparency of huge leaves filtering the sunshine to the solemn splendour of twilight in which he walked. Whenever he felt able he ran again; the ground continued soft and springy, covered with the same resilient weed which was the first thing his hands had touched in Malacandra. Once or twice a small red creature scuttled across his path, but otherwise there seemed to be no life stirring in the wood; nothing to fear—except the fact of wandering unprovisioned and alone in a forest of unknown vegetation thousands or millions of miles beyond the reach or knowledge of man.
C.S. Lewis, Out of the Silent Planet
Администрирую данный сайт с целью распространения как можно большего объема обучающего материала для языка программирования Go. В IT с 2008 года, с тех пор изучаю и применяю интересующие меня технологии. Проявляю огромный интерес к машинному обучению и анализу данных.
E-mail: vasile.buldumac@ati.utm.md
Образование
Технический Университет Молдовы (utm.md), Факультет Вычислительной Техники, Информатики и Микроэлектроники
- 2014 — 2018 Universitatea Tehnică a Moldovei, ИТ-Инженер. Тема дипломной работы «Автоматизация покупки и продажи криптовалюты используя технический анализ»
- 2018 — 2020 Universitatea Tehnică a Moldovei, Магистр, Магистерская диссертация «Идентификация человека в киберпространстве по фотографии лица»