Основы работы с картами в Go

Большинство современных языков программирования поддерживают такие типы данных, как словарь или хэш. Эти типы обычно используются для хранения данных в парах, где ключ сопоставляется со значением.

В Go есть похожий тип данных – карты, большинство программистов считают его аналогом словаря. Он сопоставляет ключи со значениями, создавая пары «ключ-значение», это удобный способ хранения данных в Go. Карта создается с помощью ключевого слова map, за которым следует ключевой тип данных в квадратных скобках [] и значения. Пары «ключ-значение» помещаются с обеих сторон в фигурные скобки {}, вот так:

map[key]value{}

Обычно карты в Go используются для хранения связанных данных, например, идентификаторов. Карта с данными выглядит так:

map[string]string{"name": "Sammy", "animal": "shark", "color": "blue", "location": "ocean"}

Кроме фигурных скобок карта также содержит двоеточия, которые соединяют ключ и значение в пару. Слова слева от двоеточия являются ключами. Ключи в Go могут выражаться данными любого сопоставимого типа, например, строками, целыми числами и т.п.

В этом случае ключами являются:

  • «name»
  • «animal»
  • «color»
  • «location»

Слова справа от двоеточия являются значениями. Значения могут выражаться данными любого типа. В этом случае значениями являются:

  • «Sammy»
  • «shark»
  • «blue»
  • «ocean»

Как и другие типы данных, карту можно сохранить внутри переменной и вывести ее на экран:

sammy := map[string]string{"name": "Sammy", "animal": "shark", "color": "blue", "location": "ocean"}

Вы получите такой вывод:

map[animal:shark color:blue location:ocean name:Sammy]

Порядок пар ключ-значение мог измениться. В Go карты – неупорядоченный тип данных. Независимо от порядка сами пары ключ-значение не меняются, что позволяет получать доступ к данным на основе их реляционного значения.

Доступ к элементам карты

Вызвать значение карты можно, сославшись на связанный ключ.

Вернемся к нашему примеру. Если вы хотите изолировать имя пользователя, вы можете сделать это, вызвав sammy[«name»]; — то есть переменную, содержащую карту и связанный ключ. Давайте сделаем это:

fmt.Println(sammy["name"])

Карты ведут себя примерно как база данных; чтобы получить конкретное значение индекса, не нужно вызывать целое число, как в срезах, можно присвоить ключу значение и вызвать этот ключ, чтобы получить связанное с ним значение.

Вызывая ключ «name», вы получаете значение этого ключа — «Sammy».

Точно так же можно вызвать оставшиеся значения в карте sammy, используя тот же формат:

fmt.Println(sammy["animal"])
// returns shark
fmt.Println(sammy["color"])
// returns blue
fmt.Println(sammy["location"])
// returns ocean

В картах можно ссылаться на ключи для получения значений.

Ключи и значения

В отличие от некоторых языков программирования, в Go нет удобных функций для получения ключей или значений карты. Например, в Python для словарей есть метод .keys(). Хотя Go допускает итерацию с помощью оператора range:

for key, value := range sammy {
fmt.Printf("%q is the key for the value %q\n", key, value)
}

При итерации по карте Go вернет два значения. Первым будет ключ, а вторым – значение. Go создаст эти переменные в правильном типе данных. В этом случае ключевым типом карты является строка, поэтому и ключ, и значение также будет выражаться строкой.

"animal" is the key for the value "shark"
"color" is the key for the value "blue"
"location" is the key for the value "ocean"
"name" is the key for the value "Sammy"

Чтобы получить список ключей, вы можете снова использовать оператор range. Вы можете объявить одну переменную, чтобы получить доступ только к ключам:

keys := []string{}
for key := range sammy {
keys = append(keys, key)
}
fmt.Printf("%q", keys)

Программа начинается с объявления среза для хранения ключей.

Вывод покажет только ключи вашей карты:

["color" "location" "name" "animal"]

Опять же, ключи не отсортированы. Если вы хотите отсортировать их, используйте функцию sort.Strings из пакета sort:

sort.Strings(keys)

С помощью этой функции вы получите следующий вывод:

["animal" "color" "location" "name"]

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

sammy := map[string]string{"name": "Sammy", "animal": "shark", "color": "blue", "location": "ocean"}
items := make([]string, len(sammy))
var i int
for _, v := range sammy {
items[i] = v
i++
}
fmt.Printf("%q", items)

Сначала нужно объявить фрагмент для хранения ключей; так как вы знаете, сколько элементов вам нужно, вы можете избежать потенциального выделения памяти, определив срез необходимого размера. Затем объявляется переменная индекса. Поскольку ключи вам не нужны, вы можете использовать оператор _ при запуске цикла, чтобы игнорировать значение ключа. Вывод будет выглядеть так:

["ocean" "Sammy" "shark" "blue"]

Чтобы определить количество элементов в карте, вы можете использовать встроенную функцию len:

sammy := map[string]string{"name": "Sammy", "animal": "shark", "color": "blue", "location": "ocean"}
fmt.Println(len(sammy))

Вывод покажет количество элементов в карте:

4

Хотя в Go нет удобных функций для получения списков ключей и значений, при необходимости достаточно написать всего несколько строк кода.

Проверка наличия данных

Карты в Go возвращают нулевое значение, если запрошенный ключ отсутствует. Поэтому нужен альтернативный способ, который позволил бы отличить действительное значение 0 от ключа, которого нет в карте.

Запросите такое значение в карте, которого не существует, и посмотрите на полученный результат:

counts := map[string]int{}
fmt.Println(counts["sammy"])

Вы увидите следующий вывод:

0

Ключа sammy не было в карте, а Go все равно выдал его значение — 0. Это связано с тем, что типом данных для значений является int, а Go присваивает нулевое значение всем переменным.

Во многих случаях это поведение нежелательно и может привести к ошибке в программе. При поиске значения в карте Go может вернуть второе, опциональное значение. Это второе значение является логическим (bool) – это будет true, если ключ был найден, и false, если нет. В Go это называется идиомой ok. Несмотря на то, что назвать переменную, которая захватывает второй аргумент, можно было бы как угодно, в Go она всегда называется ok:

count, ok := counts["sammy"]

Если ключ sammy существует в карте counts, тогда ok будет true. В противном случае будет false.

Вы можете использовать переменную ok, чтобы решить, что делать с вашей программой:

if ok {
fmt.Printf("Sammy has a count of %d\n", count)
} else {
fmt.Println("Sammy was not found")
}

Это вернет:

Sammy was not found

В Go вы можете объединить объявление переменной и условную проверку с блоком if/else. Go позволяет использовать для этой проверки один оператор:

if count, ok := counts["sammy"]; ok {
fmt.Printf("Sammy has a count of %d\n", count)
} else {
fmt.Println("Sammy was not found")
}

При извлечении значения из карты в Go всегда рекомендуется проверять его наличие, чтобы избежать ошибок в вашей программе.

Изменение карт

Карты являются изменяемой структурой данных, поэтому вы можете вносить в них поправки. Рассмотрим добавление и удаление элементов карты.

Добавление и изменение элементов карты

В карты можно добавлять пары ключ-значение без метода или функции. Это делается с помощью имени переменной карты, за которым следует значение ключа в квадратных скобках [], и с помощью оператора = для установки нового значения:

map[key] = value

Попробуйте для практики добавить в карту пару ключ-значение usernames:

usernames := map[string]string{"Sammy": "sammy-shark", "Jamie": "mantisshrimp54"}
usernames["Drew"] = "squidly"
fmt.Println(usernames)

В выводе появится новая пара ключ-значение Drew:squidly:

map[Drew:squidly Jamie:mantisshrimp54 Sammy:sammy-shark]

Поскольку карты неупорядоченные, эта пара может встретиться в любом месте вывода карты. Если вы позже используете карту usernames  в файле программы, она будет включать дополнительную пару ключ-значение.

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

Рассмотрим карту под названием followers, которая отслеживает подписчиков пользователей в данной сети. У пользователя «drew» сегодня добавились подписчики, поэтому нужно обновить целочисленное значение, передаваемое ключу «drew». Используйте функцию Println(), чтобы проверить, была ли карта изменена:

followers := map[string]int{"drew": 305, "mary": 428, "cindy": 918}
followers["drew"] = 342
fmt.Println(followers)

Ваш вывод покажет обновленное значение для drew:

map[cindy:918 drew:342 mary:428]

Как можно видеть, число подписчиков подскочило с 305 до 342.

Вы можете использовать этот метод для добавления в карты пар ключ-значение с пользовательским вводом. Давайте напишем быструю программу под названием usernames.go, которая запускается в командной строке и позволяет вводить настоящие имена пользователей и связывать их с учетными данными:

package main
import (
"fmt"
"strings"
)
func main() {
usernames := map[string]string{"Sammy": "sammy-shark", "Jamie": "mantisshrimp54"}
for {
fmt.Println("Enter a name:")
var name string
_, err := fmt.Scanln(&name)
if err != nil {
panic(err)
}
name = strings.TrimSpace(name)
if u, ok := usernames[name]; ok {
fmt.Printf("%q is the username of %q\n", u, name)
continue
}
fmt.Printf("I don't have %v's username, what is it?\n", name)
var username string
_, err = fmt.Scanln(&username)
if err != nil {
panic(err)
}
username = strings.TrimSpace(username)
usernames[name] = username
fmt.Println("Data updated.")
}
}

В usernames.go мы сначала определили исходную карту. Затем мы настроили цикл для перебора имен. Программа просит пользователя ввести имя и объявляет переменную для его сохранения. Затем мы проверяем, не возникла ли ошибка; если да, программа прекратит работу (panic). Поскольку Scanln захватывает весь ввод, включая возврат каретки, вам необходимо удалить из ввода все пробелы; это делается с помощью функции strings.TrimSpace.

Блок if проверяет, присутствует ли имя в карте, и возвращает результат. Если имя присутствует, блок затем возвращается к началу цикла. Если имя отсутствует на карте, программа предоставляет обратную связь пользователю, а затем запросит новое имя пользователя для заданного имени. Программа снова проверит, есть ли ошибка. Если ошибок нет, она обрезает возврат каретки, присваивает значение имени пользователя ключу, а затем сообщает о том, что данные были обновлены.

go run usernames.go

Вы получите такой вывод:

Enter a name:
Sammy
"sammy-shark" is the username of "Sammy"
Enter a name:
Jesse
I don't have Jesse's username, what is it?
JOctopus
Data updated.
Enter a name:

Когда вы закончите тестирование программы, нажмите CTRL+C, чтобы выйти.

Эта программа показывает, как вы можете изменять карты в интерактивном режиме. В этом конкретном случае как только вы выйдете из программы с помощью клавиш CTRL+C, вы потеряете все свои данные, если не настроите способ чтения и записи файлов.

Итак, теперь вы можете добавлять элементы в карты или изменять значения с помощью синтаксиса map[key] = value.

Удаление элементов карты

Вы также можете удалять элементы из карты.

Чтобы удалить пару ключ-значение, вы можете использовать встроенную функцию delete(). Первый аргумент – карта, из которой вы хотите удалить значение. Второй аргумент — это ключ, который вы удаляете:

delete(map, key)

Давайте определим карту permissions:

permissions := map[int]string{1: "read", 2: "write", 4: "delete", 8: "create", 16:"modify"}

Допустим, право modify вам больше не нужно, поэтому его можно удалить со своей карты. Затем можно отобразить карту, чтобы убедиться, что пара была удалена:

permissions := map[int]string{1: "read", 2: "write", 4: "delete", 8: "create", 16: "modify"}
delete(permissions, 16)
fmt.Println(permissions)

Вывод подтвердит удаление:

map[1:read 2:write 4:delete 8:create]

Строка delete(permissions, 16) удаляет пару ключ-значение 16:»modify» из карты permissions.

Если вы хотите полностью очистить карту от значений, вы можете приравнять ее к пустой карте того же типа. Это создаст новую пустую карту, а старая карта будет удалена из памяти сборщиком мусора.

Давайте удалим все элементы в карте permissions:

permissions = map[int]string{}
fmt.Println(permissions)

Вывод показывает, что теперь у вас есть пустая карта без пар:

map[]

Поскольку карты являются изменяемым типом данных, в них можно добавлять, изменять и удалять элементы.

Читайте также: Установка Go и настройка локальной среды разработки в Ubuntu 18.04

Tags: ,