Как проверить, содержит ли карта ключ в Go?

763

Я знаю, что могу перебрать карту m,

for k, v := range m { ... }

и искать ключ, но есть ли более эффективный способ проверки существования ключа на карте?

Я не мог найти ответ в спецификации языка .

grokus
источник
2
Вот где найти ответ в связанной спецификации: golang.org/ref/spec#Index_expressions
nobar

Ответы:

1476

Ответ в одну строку:

if val, ok := dict["foo"]; ok {
    //do something here
}

Объяснение:

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

  • инициализирует две переменные - valполучит либо значение «foo» с карты, либо «нулевое значение» (в данном случае пустую строку) и okполучит значение bool, которое будет установлено в том trueслучае, если «foo» действительно присутствовало на карте

  • оценивает ok, что будет, trueесли "foo" был на карте

Если «foo» действительно присутствует на карте, тело ifоператора будет выполнено и valбудет локальным для этой области.

продавец
источник
2
Это может быть лучше объяснено, как это работает (как другой комментарий от peterSO)
Chmouel Boudjnah
6
@Kiril var val string = ""останется прежним, val, ok :=создаст новую локальную переменную с тем же именем, которая видна только в этом блоке.
OneOfOne
1
отличный ответ, вы бы сказали, что сложность этого O (1) ??
Мени
1
@Mheni, я знаю, что немного опоздал, но в этом вопросе обсуждается сложность поиска. Большую часть времени амортизируемая сложность равна O (1), но стоит прочитать ответы на этот вопрос.
3
70
Такой запутанный синтаксис по сравнению с Python if key in dict.
Пранджал Миттал
131

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

Попытка получить значение карты с ключом, которого нет на карте, вернет нулевое значение для типа записей в карте. Например, если карта содержит целые числа, поиск несуществующего ключа вернет 0. Набор может быть реализован как карта со значением типа bool. Установите для записи карты значение true, чтобы поместить значение в набор, а затем протестируйте его с помощью простой индексации.

attended := map[string]bool{
    "Ann": true,
    "Joe": true,
    ...
}

if attended[person] { // will be false if person is not in the map
    fmt.Println(person, "was at the meeting")
}

Иногда вам нужно отличить отсутствующую запись от нулевого значения. Есть ли запись для «UTC» или это 0, потому что ее нет на карте вообще? Вы можете различать с помощью формы множественного назначения.

var seconds int
var ok bool
seconds, ok = timeZone[tz]

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

func offset(tz string) int {
    if seconds, ok := timeZone[tz]; ok {
        return seconds
    }
    log.Println("unknown time zone:", tz)
    return 0
}

Чтобы проверить наличие на карте, не беспокоясь о фактическом значении, вы можете использовать пустой идентификатор (_) вместо обычной переменной для значения.

_, present := timeZone[tz]
peterSO
источник
58

Поиск по списку рассылок и поиск решения, размещенного Питером Фрёлихом 15.11.2009.

package main

import "fmt"

func main() {
        dict := map[string]int {"foo" : 1, "bar" : 2}
        value, ok := dict["baz"]
        if ok {
                fmt.Println("value: ", value)
        } else {
                fmt.Println("key not found")
        }
}

Или, более компактно,

if value, ok := dict["baz"]; ok {
    fmt.Println("value: ", value)
} else {
    fmt.Println("key not found")
}

Обратите внимание, что с помощью этой формы ifзаявления, то valueи okпеременные видны только внутри ifусловий.

grokus
источник
21
Если вас действительно интересует, существует ли ключ или нет, и вам нет дела до значения, вы можете использовать его _, ok := dict["baz"]; ok. _Часть бросает значение прочь вместо создания временной переменной.
Мэтью Крамли
26

Короткий ответ

_, exists := timeZone[tz]    // Just checks for key existence
val, exists := timeZone[tz]  // Checks for key existence and retrieves the value

пример

Вот пример на игровой площадке Go .

Более длинный ответ

В разделе « Карты » Effective Go :

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

Иногда вам нужно отличить отсутствующую запись от нулевого значения. Есть ли запись для «UTC» или это пустая строка, потому что ее нет на карте вообще? Вы можете различать с помощью формы множественного назначения.

var seconds int
var ok bool
seconds, ok = timeZone[tz]

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

func offset(tz string) int {
    if seconds, ok := timeZone[tz]; ok {
        return seconds
    }
    log.Println("unknown time zone:", tz)
    return 0
}

Чтобы проверить наличие на карте, не беспокоясь о фактическом значении, вы можете использовать пустой идентификатор (_) вместо обычной переменной для значения.

_, present := timeZone[tz]
Мэтью Ранкин
источник
13

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

v, ok = a[x]
v, ok := a[x]
var v, ok = a[x]
var v, ok T = a[x]

Это красиво и чисто. Однако у него есть некоторые ограничения: это должно быть присвоение специальной формы. Выражение в правой части должно быть только выражением индекса карты, а список выражений в левой части должен содержать ровно 2 операнда: первый, которому присваивается тип значения, и второй, которому boolприсваивается значение. Первым значением результата этой специальной формы будет значение, связанное с ключом, а второе значение скажет, есть ли на карте запись с данным ключом (если ключ существует на карте). Список выражений в левой части также может содержать пустой идентификатор, если один из результатов не нужен.

Важно знать, что если индексированное значение карты является nilили не содержит ключ, индексное выражение оценивается как нулевое значение типа значения карты. Так, например:

m := map[int]string{}
s := m[1] // s will be the empty string ""
var m2 map[int]float64 // m2 is nil!
f := m2[2] // f will be 0.0

fmt.Printf("%q %f", s, f) // Prints: "" 0.000000

Попробуйте это на игровой площадке Go .

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

Например, если типом значения является string, и мы знаем, что мы никогда не храним записи на карте, где значением является пустая строка (нулевое значение для stringтипа), мы также можем проверить, находится ли ключ на карте, сравнивая не специальный форма (результат) выражения индекса до нулевого значения:

m := map[int]string{
    0: "zero",
    1: "one",
}

fmt.Printf("Key 0 exists: %t\nKey 1 exists: %t\nKey 2 exists: %t",
    m[0] != "", m[1] != "", m[2] != "")

Вывод (попробуйте на Go Playground ):

Key 0 exists: true
Key 1 exists: true
Key 2 exists: false

На практике во многих случаях мы не храним нулевое значение на карте, поэтому его можно использовать довольно часто. Например, интерфейсы и типы функций имеют нулевое значение nil, которое мы часто не храним в картах. Поэтому можно проверить, есть ли ключ на карте, сравнив его с nil.

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

Получение нулевого значения типа значения при индексации с несуществующим ключом также позволяет нам boolудобно использовать карты со значениями в виде наборов . Например:

set := map[string]bool{
    "one": true,
    "two": true,
}

fmt.Println("Contains 'one':", set["one"])

if set["two"] {
    fmt.Println("'two' is in the set")
}
if !set["three"] {
    fmt.Println("'three' is not in the set")
}

Он выводит (попробуйте на Go Playground ):

Contains 'one': true
'two' is in the set
'three' is not in the set

Смотрите связанные: Как я могу создать массив, который содержит уникальные строки?

icza
источник
1
что Tвнутри var v, ok T = a[x]? не okдолжно быть бул?
Кокиццу
2
@Kokizzu Это общая форма объявления переменных. Сначала мы можем подумать, что это будет работать (компилироваться) только если карта будет иметь тип map[bool]boolи Tесть bool, но это также работает, если карта имеет тип map[interface{}]boolи Tесть interface{}; Более того, он также работает с пользовательскими типами, имеющими boolбазовый тип, смотрите все на Go Playground . Так как эта форма действительна с заменой нескольких типов T, поэтому используется общее T. Тип okможет быть любым, для которого может быть назначен нетипизированныйbool .
icza
7

лучший способ здесь

if _, ok := dict["foo"]; ok {
    //do something here
}
Amazingandyyy
источник
4
    var d map[string]string
    value, ok := d["key"]
    if ok {
        fmt.Println("Key Present ", value)
    } else {
        fmt.Println(" Key Not Present ")
    }
чандра
источник
3
    var empty struct{}
    var ok bool
    var m map[string]struct{}
    m = make(map[string]struct{})
    m["somestring"] = empty


    _, ok = m["somestring"]
    fmt.Println("somestring exists?", ok) 
    _, ok = m["not"]
    fmt.Println("not exists?", ok)

Затем запустите maps.go существует ли какая-нибудь строка? правда не существует? ложный

Lady_Exotel
источник
Избавляется
Спасибо за вклад, но я думаю, что текущие ответы хорошо охватывают вопрос. Исходя из того, что вы здесь говорите, ваш ответ больше подходит для наилучшего способа реализации заданного вопроса типа Go .
Томаш
_, ok = m["somestring"]должно быть=_, ok := m["somestring"]
Elroy Jetson
3

Упоминается в разделе «Индексные выражения» .

Индексное выражение на карте a типа map [K] V, используемое в присваивании или инициализации специальной формы

v, ok = a[x] 
v, ok := a[x] 
var v, ok = a[x]

дает дополнительное нетипизированное логическое значение. Значение ok равно true, если ключ x присутствует на карте, и false в противном случае.

mroman
источник
1

Для этой цели можно использовать присвоение двух значений. Пожалуйста, проверьте мой пример программы ниже

package main

import (
    "fmt"
)

func main() {
    //creating a map with 3 key-value pairs
    sampleMap := map[string]int{"key1": 100, "key2": 500, "key3": 999}
    //A two value assignment can be used to check existence of a key.
    value, isKeyPresent := sampleMap["key2"]
    //isKeyPresent will be true if key present in sampleMap
    if isKeyPresent {
        //key exist
        fmt.Println("key present, value =  ", value)
    } else {
        //key does not exist
        fmt.Println("key does not exist")
    }
}
Фатх Рехман П
источник