Ма

Мапи у Go

2 вправи

Про концепцію Мапи

У Go map — це вбудований тип даних, що представляє хеш-таблицю. В інших мовах програмування те саме може називатися dict, асоціативним масивом або сховищем ключ-значення. Якщо така концепція нам незнайома, map можна уявити як словник, де кожне слово — це key, а визначення — element цього map.

Перш ніж почнемо, радимо зазирнути до специфікації Go для map та блогу Go про map, щоб глибше розібратися з map.

Синтаксично map має такий вигляд:

map[KeyType]ElementType

KeyType має бути будь-яким порівнюваним типом, а ElementType може бути будь-яким припустимим типом у Go, тобто можна зберігати будь-що, від примітивної змінної до слайса.

Важливо памʼятати, що map у Go невпорядкований: якщо спробувати перебрати map циклом і надрукувати елементи, можна здивуватися, побачивши, що вони виводяться у випадковому порядку (спробуйте, якщо хочеться).

Також важливо знати, що кожен ключ унікальний: присвоєння того самого ключа вдруге перезапише значення, що відповідає цьому ключу.

map — це посилальний тип: якщо передати його далі, Go не копіюватиме map повністю. Натомість Go скопіює вказівник на map, і саме тому передавати map у функцію чи змінну дешево. Значення неініціалізованого map — це nil.

Визначити map можна так (це ще називають nil map);

  var foo map[string]int

Щоб ініціалізувати map, можна зробити так:

  // With map literal
  foo := map[string]int{}

або

  // or with make function
  foo := make(map[string]int)

nil map відрізняється від ініціалізованого map: запис у nil map спричинить помилку під час виконання

panic: assignment to entry in nil map

Тому важливо ініціалізувати map перед використанням

Ось деякі операції, які можна виконувати з map:

  // Add a value in a map with the `=` operator:
  foo["bar"] = 42
  // Here we update the element of `bar`
  foo["bar"] = 73
  // To retrieve a map value, you can use
  baz := foo["bar"]
  // To delete an item from a map, you can use
  delete(foo, "bar")

Якщо спробувати отримати значення для ключа, якого немає в map, повернеться нульове значення відповідного типу. Це може заплутати, особливо якщо типове значення ElementType (наприклад, 0 для int) є припустимим значенням. Щоб перевірити, чи існує ключ у map, можна скористатися таким:

  value, exists := foo["baz"]
  // If the key "baz" does not exist,
  // value: 0; exists: false

Як ми вже бачили, визначити, чи ініціалізовано map, легко: достатньо порівняти його з nil. Але що з іншими map? Оскільки map не є порівнюваним типом, оператор == тут не підходить, але на допомогу приходить пакет reflect із функцією DeepEqual, яку можна використати так:

import "reflect"

// ....

equal := reflect.DeepEqual(map[string]int{}, map[string]int{})
fmt.Println(equal)
// Output: true

Але тут виникає питання: якщо map не порівнюваний, чому його можна порівнювати з nil? Специфікація зробила для цього виняток, див. розділ про порівнюваність

І останнє: якщо писати в map з кількох горутин, це активує детектор гонок, див. документацію Go та блог Go. Або можна використати sync.Map, atomic чи mutex, щоб обійти цю проблему.

Редагувати через GitHub Посилання відкривається в новому вікні або вкладці

Вивчити концепцію Мапи

Практика заблокована

Розблокуйте ще 3 вправи, щоб практикувати концепцію Мапи