Como verificar se um mapa contém uma chave no Go?

762

Eu sei que posso iterar sobre um mapa m,

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

e procure uma chave, mas existe uma maneira mais eficiente de testar a existência de uma chave em um mapa?

Não consegui encontrar a resposta na especificação do idioma .

grokus
fonte
2
Aqui está onde encontrar a resposta na especificação vinculada: golang.org/ref/spec#Index_expressions
nobar 01/08/19

Respostas:

1474

Resposta em uma linha:

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

Explicação:

ifAs instruções no Go podem incluir uma condição e uma instrução de inicialização. O exemplo acima usa os dois:

  • inicializa duas variáveis ​​- valreceberá o valor de "foo" do mapa ou um "valor zero" (nesse caso, a string vazia) e okreceberá um bool que será definido como truese "foo" estivesse realmente presente no mapa

  • avalia ok, que será truese "foo" estava no mapa

Se "foo" estiver realmente presente no mapa, o corpo da ifinstrução será executado e valserá local nesse escopo.

comerciante
fonte
2
Isto pode também pode ser explicado como funciona (como outro comentário do peterSO)
Chmouel Boudjnah
6
O @Kiril var val string = ""permanecerá o mesmo, val, ok :=cria uma nova variável local com o mesmo nome, visível apenas nesse bloco.
OneOfOne 20/05
1
ótima resposta, você diria que a complexidade disso é O (1) ??
MJ #
1
@ Heni, eu sei que estou um pouco atrasado aqui, mas esta pergunta discute a complexidade da pesquisa em andamento . Na maioria das vezes, a complexidade amortizada é O (1), mas vale a pena ler as respostas sobre essa pergunta.
3ocene 09/10/19
70
Uma sintaxe tão confusa em comparação com a de python if key in dict.
Pranjal Mittal
130

Além da especificação da linguagem de programação The Go , você deve ler Go eficaz . Na seção de mapas , eles dizem, entre outras coisas:

Uma tentativa de buscar um valor do mapa com uma chave que não está presente no mapa retornará o valor zero para o tipo de entradas no mapa. Por exemplo, se o mapa contiver números inteiros, procurar uma chave inexistente retornará 0. Um conjunto pode ser implementado como um mapa com o tipo de valor bool. Defina a entrada do mapa como true para colocar o valor no conjunto e, em seguida, teste-o pela indexação simples.

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")
}

Às vezes, você precisa distinguir uma entrada ausente de um valor zero. Existe uma entrada para "UTC" ou é 0 porque não está no mapa? Você pode discriminar com uma forma de atribuição múltipla.

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

Por razões óbvias, isso é chamado de idioma "vírgula ok". Neste exemplo, se tz estiver presente, os segundos serão definidos adequadamente e ok será verdadeiro; caso contrário, os segundos serão definidos como zero e ok será falso. Aqui está uma função que o reúne com um bom relatório de erros:

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

Para testar a presença no mapa sem se preocupar com o valor real, você pode usar o identificador em branco (_) no lugar da variável usual para o valor.

_, present := timeZone[tz]
peterSO
fonte
57

Pesquisou na lista de e -mails desagradáveis e encontrou uma solução publicada por Peter Froehlich em 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")
        }
}

Ou, de forma mais compacta,

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

Observe que, usando este formulário da ifinstrução, as variáveis valuee oksão visíveis apenas dentro das ifcondições.

grokus
fonte
21
Se você realmente está apenas interessado em saber se a chave existe ou não, e não se importa com o valor, você pode usá-lo _, ok := dict["baz"]; ok. A _peça joga fora o valor em vez de criar uma variável temporária.
Matthew Crumley
26

Resposta curta

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

Exemplo

Aqui está um exemplo no Go Playground .

Resposta mais longa

De acordo com a seção Mapas do Effective Go :

Uma tentativa de buscar um valor do mapa com uma chave que não está presente no mapa retornará o valor zero para o tipo de entradas no mapa. Por exemplo, se o mapa contiver números inteiros, procurar uma chave inexistente retornará 0.

Às vezes, você precisa distinguir uma entrada ausente de um valor zero. Existe uma entrada para "UTC" ou essa é a string vazia porque não está no mapa? Você pode discriminar com uma forma de atribuição múltipla.

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

Por razões óbvias, isso é chamado de idioma "vírgula ok". Neste exemplo, se tz estiver presente, os segundos serão definidos adequadamente e ok será verdadeiro; caso contrário, os segundos serão definidos como zero e ok será falso. Aqui está uma função que o reúne com um bom relatório de erros:

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

Para testar a presença no mapa sem se preocupar com o valor real, você pode usar o identificador em branco (_) no lugar da variável usual para o valor.

_, present := timeZone[tz]
Matthew Rankin
fonte
13

Conforme observado por outras respostas, a solução geral é usar uma expressão de índice em uma atribuição do formulário especial:

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

Isso é legal e limpo. Porém, ele tem algumas restrições: deve ser uma atribuição de forma especial. A expressão do lado direito deve ser apenas a expressão do índice do mapa e a lista de expressões da esquerda deve conter exatamente 2 operandos, primeiro ao qual o tipo de valor é atribuível e um segundo ao qual um boolvalor é atribuível. O primeiro valor do resultado desse formulário especial será o valor associado à chave e o segundo valor informará se há realmente uma entrada no mapa com a chave especificada (se a chave existir no mapa). A lista de expressões do lado esquerdo também pode conter o identificador em branco se um dos resultados não for necessário.

É importante saber que, se o valor do mapa indexado é nilou não contém a chave, a expressão do índice é avaliada como o valor zero do tipo de valor do mapa. Então, por exemplo:

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

Experimente no Go Playground .

Portanto, se soubermos que não usamos o valor zero em nosso mapa, podemos tirar proveito disso.

Por exemplo, se o tipo de valor for stringe sabemos que nunca armazenamos entradas no mapa em que o valor é a sequência vazia (valor zero para o stringtipo), também podemos testar se a chave está no mapa comparando os itens não especiais forma da expressão de índice (resultado da) com o valor zero:

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] != "")

Saída (experimente no Go Playground ):

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

Na prática, existem muitos casos em que não armazenamos o valor zero no mapa, portanto, isso pode ser usado com bastante frequência. Por exemplo, interfaces e tipos de função têm um valor zero nil, que geralmente não armazenamos em mapas. Portanto, é possível testar se uma chave está no mapa comparando-a com nil.

O uso dessa "técnica" também tem outra vantagem: você pode verificar a existência de várias chaves de maneira compacta (não é possível fazer isso com o formulário especial "vírgula ok"). Mais sobre isso: verifique se existe chave em vários mapas em uma condição

Obter o valor zero do tipo de valor ao indexar com uma chave inexistente também nos permite usar mapas com boolvalores convenientemente como conjuntos . Por exemplo:

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")
}

Ele produz (experimente no Go Playground ):

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

Veja relacionados: Como posso criar uma matriz que contém seqüências de caracteres exclusivas?

icza
fonte
1
o que está Tdentro var v, ok T = a[x]? não okdeve ser booleano?
Kokizzu
2
@Kokizzu Essa é a forma geral da declaração da variável. A princípio, poderíamos pensar que só funcionaria (compilar) se o mapa fosse do tipo map[bool]boole Té bool, mas também funciona se o mapa for do tipo map[interface{}]boole Té interface{}; além disso, ele também funciona com tipos personalizados que têm boolcomo tipo subjacente, veja tudo no Go Playground . Portanto, como esse formulário é válido com vários tipos substituídos T, é por isso que o general Té usado. Tipo de okpode ser qualquer coisa à qual um tipo não digitadobool pode ser atribuído.
icza
7

melhor caminho aqui

if _, ok := dict["foo"]; ok {
    //do something here
}
Amazingandyyy
fonte
4
    var d map[string]string
    value, ok := d["key"]
    if ok {
        fmt.Println("Key Present ", value)
    } else {
        fmt.Println(" Key Not Present ")
    }
Chandra
fonte
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)

Então, vá em maps.go existe alguma restrição? verdadeiro não existe? falso

Lady_Exotel
fonte
Livra-se
Obrigado pela contribuição, mas acho que as respostas atuais cobrem bem a pergunta. Pelo que você está dizendo aqui, sua resposta seria mais adequada à melhor maneira de implementar o conjunto no tipo de pergunta Go .
tomasz
_, ok = m["somestring"]deveria ser #=_, ok := m["somestring"]
Elroy Jetson
3

É mencionado em "Expressões de índice" .

Uma expressão de índice em um mapa a do tipo map [K] V usado em uma atribuição ou inicialização do formulário especial

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

gera um valor booleano não digitado adicional. O valor de ok é verdadeiro se a chave x estiver presente no mapa e falso caso contrário.

mroman
fonte
1

Uma atribuição de dois valores pode ser usada para essa finalidade. Verifique meu programa de amostra abaixo

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")
    }
}
Fathah Rehman P
fonte