Понимание карт в го

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

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

map[key]value{}

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

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

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

Ключи в примере карты:

  • "name"

  • "animal"

  • "color"

  • "location"

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

  • "Sammy"

  • "shark"

  • "blue"

  • "ocean"

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

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

Это будет ваш вывод:

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

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

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

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

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

fmt.Println(sammy["name"])

И получить значение в качестве вывода:

OutputSammy

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

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

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

fmt.Println(sammy["animal"])
// returns shark

fmt.Println(sammy["color"])
// returns blue

fmt.Println(sammy["location"])
// returns ocean

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

Ключи и ценности

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

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

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

Output"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"

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

keys := []string{}

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

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

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

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

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

sort.Strings(keys)

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

Output["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)

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

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

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

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

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

Output4

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

Проверка существования

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

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

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

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

Output0

Несмотря на то, что ключаsammy не было на карте, Go по-прежнему возвращал значение0. Это связано с тем, что тип данных значения -int, и поскольку Go имеет нулевое значение для всех переменных, он возвращает нулевое значение0.

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

count, ok := counts["sammy"]

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

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

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

Это приведет к следующему выводу:

OutputSammy was not found

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

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 на карте:

Outputmap[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:

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

Вы видите, что количество последователей подскочило с целого значения305 до342.

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

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

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

OutputEnter 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 := 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)

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

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

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

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

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

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

Вывод показывает, что теперь у вас есть пустая карта, лишенная пар ключ-значение:

Outputmap[]

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

Заключение

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