Maps em Go são estruturas de dados que associam chaves a valores, funcionando como tabelas hash. Eles são extremamente úteis para armazenar e recuperar dados de forma eficiente, semelhante a dicionários em outras linguagens. Nesta aula, você aprenderá a criar, acessar, modificar e iterar sobre maps, além de entender o comportamento especial de iteração em ordem aleatória.

Diferente de arrays e slices, maps são tipos de referência: quando você atribui um map a outra variável ou passa para uma função, ambas compartilham a mesma estrutura de dados subjacente. Isso significa que modificações em um map refletem em todas as referências. Cuidado com isso para evitar efeitos colaterais inesperados.

Criação e acesso

Para criar um map, você pode usar a função make ou uma literal de map. A sintaxe básica é map[TipoChave]TipoValor. As chaves devem ser de um tipo comparável (como strings, inteiros, floats, booleanos ou structs com campos comparáveis). Valores podem ser de qualquer tipo, inclusive outros maps ou slices.

Exemplo de criação com make:

// Cria um map vazio com chaves string e valores int
idades := make(map[string]int)

// Adicionando valores
idades["Alice"] = 30
idades["Bob"] = 25

fmt.Println(idades) // map[Alice:30 Bob:25]

Exemplo com literal de map:

// Cria e inicializa um map
cores := map[string]string{
    "red":   "#FF0000",
    "green": "#00FF00",
    "blue":  "#0000FF",
}

fmt.Println(cores["red"]) // #FF0000

Para acessar um valor, use a chave entre colchetes. Se a chave não existir, o map retorna o valor zero do tipo do valor (por exemplo, 0 para int, "" para string, nil para slices/maps). Isso pode causar bugs se você não verificar a existência da chave.

fmt.Println(cores["yellow"]) // retorna "" (string vazia), pois "yellow" não existe

Comma-ok idiom

Para distinguir entre uma chave que existe com valor zero e uma chave que não existe, Go oferece o comma-ok idiom. Ao acessar um map, você pode atribuir dois valores: o valor (ou zero) e um booleano que indica se a chave estava presente.

valor, ok := cores["blue"]
if ok {
    fmt.Println("A chave blue existe:", valor)
} else {
    fmt.Println("Chave blue não encontrada")
}

valor, ok = cores["yellow"]
if !ok {
    fmt.Println("Chave yellow não existe")
}

Esse padrão é fundamental para evitar erros lógicos. Sempre que você precisar verificar a existência de uma chave, use o comma-ok idiom. Ele também é útil em operações como delete e ao iterar sobre maps.

delete

A função embutida delete remove uma chave de um map. Sua sintaxe é delete(map, chave). Se a chave não existir, a operação não faz nada (não gera erro).

delete(cores, "red")
fmt.Println(cores) // map[blue:#0000FF green:#00FF00]

delete(cores, "yellow") // não faz nada, pois "yellow" não existe
fmt.Println(cores) // map[blue:#0000FF green:#00FF00]

Após deletar uma chave, o map não encolhe automaticamente; a memória só é liberada quando o map é garbage collected. Se você precisar limpar todo o map, pode reatribuí-lo com um novo map vazio ou iterar e deletar todas as chaves.

Iteração (ordem aleatória)

Para iterar sobre um map, use o loop for range. A cada iteração, você obtém uma chave e seu valor correspondente. Importante: a ordem de iteração em maps é aleatória e não é garantida ser a mesma entre execuções. Isso é intencional para forçar os desenvolvedores a não dependerem de uma ordem específica.

for chave, valor := range cores {
    fmt.Printf("Chave: %s, Valor: %s\n", chave, valor)
}

Se você precisa de uma ordem consistente, deve ordenar as chaves manualmente. Por exemplo, extraia as chaves para um slice, ordene-o e então itere sobre o slice acessando o map.

import "sort"

// ...
chaves := make([]string, 0, len(cores))
for k := range cores {
    chaves = append(chaves, k)
}
sort.Strings(chaves)

for _, k := range chaves {
    fmt.Printf("Chave: %s, Valor: %s\n", k, cores[k])
}

Outra observação: durante a iteração, você não deve modificar o map (adicionar ou remover chaves) pois isso pode causar comportamento imprevisível. É seguro modificar o valor de uma chave existente, mas não adicionar ou deletar.

Boas práticas

  • Sempre use comma-ok idiom ao acessar valores para evitar surpresas com valor zero.
  • Não confie na ordem de iteração; se precisar de ordem, ordene as chaves.
  • Evite modificar o map (adicionar/remover) durante a iteração.
  • Use make para criar maps vazios, a menos que você tenha valores iniciais.
  • Lembre-se que maps são tipos de referência; cópia superficial não existe, atribuições compartilham o mesmo map.

Referências

Exercícios

  1. Crie um map chamado notas com chaves string (nomes de alunos) e valores float64 (notas). Adicione três alunos com notas 8.5, 7.0 e 9.2. Depois, imprima a nota do segundo aluno.

    ✓ Resposta:
    notas := map[string]float64{
        "Alice": 8.5,
        "Bob":   7.0,
        "Carol": 9.2,
    }
    fmt.Println(notas["Bob"]) // 7
  2. Usando o map do exercício anterior, verifique se a chave "David" existe. Se não existir, adicione-o com nota 6.5. Imprima o map.

    ✓ Resposta:
    if _, ok := notas["David"]; !ok {
        notas["David"] = 6.5
    }
    fmt.Println(notas)
  3. Remova o aluno "Alice" do map notas. Depois, tente remover "Eve" (que não existe). O que acontece?

    ✓ Resposta:
    delete(notas, "Alice")
    delete(notas, "Eve") // não faz nada, nenhum erro
    fmt.Println(notas) // map[Bob:7 Carol:9.2 David:6.5]
  4. Itere sobre o map notas e imprima cada aluno e sua nota. Execute o código duas vezes e observe se a ordem é a mesma.

    ✓ Resposta:
    for nome, nota := range notas {
        fmt.Printf("%s: %.1f\n", nome, nota)
    }
    // A ordem pode variar entre execuções.
  5. Crie um map contagem com chaves string e valores int. Conte quantas vezes cada letra aparece na string "banana". Dica: itere sobre a string e use comma-ok para incrementar.

    ✓ Resposta:
    s := "banana"
    contagem := make(map[string]int)
    for _, c := range s {
        letra := string(c)
        contagem[letra]++
    }
    fmt.Println(contagem) // map[a:3 b:1 n:2]