Entendendo mapas em Go

A maioria das linguagens de programação modernas tem o conceito de um tipo dicionário ou um tipo hash. Esses tipos são comumente usados para armazenar dados em pares com uma chave que mapeia um valor.

Em Go, o tipo de dados mapa é o tipo no qual a maioria dos programadores pensaria como o tipo dicionário. Esse tipo de dados mapeia chaves para valores, criando pares chave-valor que são uma maneira útil de armazenar dados em Go. Um mapa é construído usando a palavra-chave map, seguida do tipo de dados chave entre colchetes [ ], seguidos do tipo de dados do valor. Então, os pares chave-valor são colocados dentro de um par de chaves em um dos lados { }:

map[key]value{} 

Normalmente, os mapas são usados em Go para reter dados relacionados, como as informações contidas em um ID. Um mapa com dados se parece com este:

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

Além das chaves, há também os dois pontos ao longo do mapa que conectam os pares chave-valor. As palavras à esquerda dos dois pontos são as chaves. As chaves podem ser qualquer tipo comparável em Go, como strings, ints e assim por diante.

As chaves no mapa exemplo são:

  • "name"
  • "animal"
  • "color"
  • "location"

As palavras à direita dos dois pontos são os valores. Os valores podem ser qualquer tipo de dados. Os valores no mapa exemplo são:

  • "Sammy"
  • "shark"
  • "blue"
  • "ocean"

Assim como os outros tipos de dados, é possível armazenar o mapa dentro de uma variável e imprimi-lo:

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

Este seria seu resultado:

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

A ordem dos pares chave-valor pode ter alterada. Em Go, o tipo de dados do mapa é desordenado. Independentemente da ordem, os pares chave-valor permanecerão intactos, permitindo que você acesse dados baseados em seu significado relacional.

Acessando itens do mapa

Você pode chamar os valores de um mapa referenciando as chaves relacionadas. Como os mapas oferecem pares chave-valor para armazenar dados, eles podem ser itens úteis e importantes no seu programa em Go.

Caso queira isolar o nome de usuário Sammy, você pode chamar sammy["name"]; a variável que detém o seu mapa e a chave relacionada. Vamos imprimir isso:

fmt.Println(sammy["name"]) 

E recebemos o valor como resultado:

OutputSammy 

Os mapas se comportam como um banco de dados; em vez de chamar um número inteiro para obter um valor de índice específico – como você faria com uma fatia, você atribui um valor a uma chave e chama a chave para obter seu valor relacionado.

Ao invocar a chave "name", você recebe o valor daquela chave, que é "Sammy".

De forma similar, você pode chamar os valores restantes no mapa sammy usando o mesmo formato:

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

Ao usar os pares chave-valor em tipos de dados mapa, você pode referenciar chaves para recuperar valores.

Chaves e valores

Ao contrário de algumas linguagens de programação, em Go não tem nenhuma função de conveniência para listar as chaves ou valores de um mapa. Um exemplo disso seria o método .keys() do Python para dicionários. No entanto, a linguagem permite a iteração, usando o operador range:

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

Ao usar o método de intervalo em um mapa em Go, ele retornará dois valores. O primeiro valor será a chave e o segundo valor será o valor. Go criará essas variáveis com o tipo de dados correto. Nesse caso, a chave do mapa foi uma string, então key [chave] também será uma string. O value [valor] também é uma string:

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" 

Para obter uma lista apenas das chaves, você pode usar o operador de intervalos novamente. Você pode declarar apenas uma variável para acessar as chaves:

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

O programa começa declarando uma fatia para armazenar suas chaves.

O resultado mostrará apenas as chaves do seu mapa:

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

Novamente, as chaves não estão classificadas. Se quiser classificá-las, use a função sort.Strings do pacote sort:

sort.Strings(keys) 

Com esta função, você receberá o seguinte resultado:

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

Você pode usar o mesmo padrão para recuperar apenas os valores em um mapa. No próximo exemplo, você fará a alocação prévia da fatia para evitar as alocações e, dessa forma, tornará o programa mais eficiente:

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) 

Primeiro, declare uma fatia onde irá armazenar suas chaves; como você sabe o número de itens de que necessita, você poderá evitar possíveis alocações de memória, definindo a fatia exatamente do mesmo tamanho. Então, declare sua variável de índice. Como você não quer a chave, use o operador _ ao iniciar seu loop, de maneira a ignorar o valor da chave. Seu resultado seria o seguinte:

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

Para determinar o número de itens em um mapa, use a função integrada len:

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

O resultado mostra o número de itens em seu mapa:

Output4 

Embora a linguagem Go não venha com funções de conveniência para obter chaves e valores, só são necessárias algumas linhas de código para recuperar as chaves e valores, quando necessário.

Verificando a existência

Os mapas em Go retornarão o valor zero para o tipo de valor do mapa quando a chave solicitada estiver em falta. Por conta disso, você precisa de uma maneira alternativa para diferenciar um zero armazenado, em relação a uma chave faltante.

Vamos procurar um valor em um mapa que você sabe que não existe e examinar o valor retornado:

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

Você verá o seguinte resultado:

Output0 

Embora a chave sammy não estivesse no mapa, o Go ainda retornou o valor de 0. Isso acontece porque o tipo de dados valor é um int e, como o Go tem um valor zero para todas as variáveis, ele retorna o valor zerado de 0.

Em muitos casos, isso é indesejável e levaria a um bug em seu programa. Ao procurar o valor em um mapa, o Go pode retornar um segundo valor opcional. Esse segundo valor é um bool e será true [verdadeiro] se a chave for encontrada, ou false [falso] se a chave não for encontrada. Em Go, isso é conhecido como a expressão ok. Embora possa dar o nome que quiser para a variável que captura o segundo argumento, em Go você sempre a nomeia de ok:

count, ok := counts["sammy"] 

Se a chave sammy existir no mapa counts, então ok será true. Caso contrário, ok será falso.

Você pode usar a variável ok para decidir o que fazer em seu programa:

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

Isso resultaria no seguinte:

OutputSammy was not found 

Em Go, você pode combinar a declaração de variável e checagem condicional com um bloco if/else. Isso permite que você use uma única instrução para esta verificação:

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

Ao recuperar um valor de um mapa em Go, é sempre bom verificar sua existência para evitar bugs em seu programa.

Modificando os mapas

Os mapas possuem uma estrutura de dados mutável, então você pode modificá-los. Vamos examinar como adicionar e excluir itens do mapa nesta seção.

Adicionando e mudando itens do mapa

Sem usar um método ou função, é possível adicionar pares chave-valor aos mapas. Você faz isso usando o nome de variável dos mapas, seguido do valor chave entre colchetes [ ] e usando o operador igual = para definir um novo valor:

map[key] = value 

Na prática, você pode ver isso funcionando, adicionando um par chave-valor em um mapa chamado usernames:

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

O resultado exibirá o novo par chave-valor Drew:squidly no mapa:

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

Como os mapas são retornados desordenados, esse par pode ocorrer em qualquer lugar na saída do mapa. Se usar o mapa usernames mais tarde em seu arquivo de programa, ele incluirá o par chave-valor adicional.

Você também pode usar essa sintaxe para modificar o valor atribuído a uma chave. Nesse caso, você faz referência a uma chave existente e envia um valor diferente para ela.

Considere um mapa chamado followers que rastreia seguidores de usuários em uma determinada rede. O usuário "drew" teve uma onda de seguidores hoje, então você precisa atualizar o valor inteiro enviado para a chave "drew". Você usará a função Println() para verificar se o mapa foi modificado:

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

Seu resultado mostrará o valor atualizado para drew:

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

É visível que o número de seguidores saltou do valor inteiro 305 para 342.

Você pode usar esse método para adicionar pares chave-valor aos mapas com as entradas do usuário. Vamos escrever um programa rápido chamado usernames.go que executa na linha de comando e permite que as entradas do usuário adicionem mais nomes e nomes de usuário associados:

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 %qn", 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.")     } } 

Em usernames.go, primeiro você define o mapa original. Então, configura um loop para iterar sobre os nomes. Solicita que seu usuário digite um nome e declare uma variável onde armazená-lo. Em seguida, verifica se houve algum erro; caso tenha havido, o programa sairá com um pânico. Como Scanln captura a entrada inteira, incluindo o retorno de carro, você precisa remover qualquer espaço da entrada; faça isso com a função strings.TrimSpace.

O bloco if verifica se o nome está presente no mapa e imprime um feedback. Se o nome estiver presente, então continua de volta para o topo do loop. Se o nome não estiver no mapa, ele dá feedback ao usuário e então solicita um novo nome de usuário para o nome associado. O programa verifica novamente se há um erro. Sem erros, ele arruma o retorno de carro, atribui o valor de nome de usuário à chave name e, em seguida, imprime feedback de que os dados foram atualizado.

Vamos executar o programa na linha de comando:

  • go run usernames.go

Você verá o seguinte resultado:

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: 

Quando terminar os testes, pressione CTRL + C para sair do programa.

Isso mostra como você pode modificar mapas interativamente. Com esse programa em particular, assim que sair do programa com CTRL + C, você perderá todos os seus dados, a menos que implemente uma maneira de gerenciar a leitura e a gravação de arquivos.

Resumindo, é possível adicionar itens aos mapas ou modificar valores com a sintaxe map[key] = value.

Excluindo itens do mapa

Assim como você pode adicionar pares chave-valor e alterar valores dentro do tipo de dados mapa, você também pode excluir itens dentro de um mapa.

Para remover um par chave-valor de um mapa, você pode usar a função integrada delete(). O primeiro argumento é o mapa do qual está excluindo. O segundo argumento é a chave que está excluindo:

delete(map, key) 

Vamos definir um mapa de permissões:

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

Você não precisa mais da permissão modify, então você a removerá do seu mapa. Depois, você imprimirá o mapa para confirmar que a permissão foi removida:

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

O resultado confirmará a exclusão:

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

A linha delete(permissions, 16) remove o par chave-valor 16:"modify" do mapa permissions.

Se quiser limpar um mapa de todos os seus valores, poderá fazer isso definindo-o igual a um mapa vazio do mesmo tipo. Isso criará um novo mapa vazio para ser usado e o antigo mapa será limpo da memória pelo coletor de lixo.

Vamos remover todos os itens dentro do mapa permissions:

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

O resultado mostra que você tem agora um mapa vazio destituído de pares chave-valor:

Outputmap[] 

Como os mapas são tipos de dados mutáveis, neles você pode adicionar dados, modificar dados e remover e limpar itens.

Conclusão

Este tutorial explorou a estrutura de dados de mapa em Go. Os mapas são constituídos por pares chave-valor e fornecem uma maneira de armazenar dados sem depender de indexação. Isso nos permite recuperar valores baseados em seu significado e relação com outros tipos de dados.