Mapas (map): dicionários em Go
Em Go, map é uma estrutura de dados que associa uma chave a um valor (como um dicionário). Você escolhe o tipo da chave e do valor: map[K]V. As chaves precisam ser de um tipo comparável (por exemplo: string, int, ponteiros, structs comparáveis). Slices, maps e funções não podem ser chaves.
Criando mapas
Existem duas formas comuns: usando make (map vazio pronto para uso) ou um literal (já com valores).
package main
import "fmt"
func main() {
// 1) make: cria um map vazio (não-nil)
idades := make(map[string]int)
idades["Ana"] = 28
idades["Bruno"] = 31
// 2) literal: cria e inicializa
capitais := map[string]string{
"BR": "Brasília",
"PT": "Lisboa",
}
fmt.Println(idades, capitais)
}Um detalhe importante: o valor zero de um map é nil. Um map nil não pode receber atribuições (vai causar panic), mas pode ser lido (retorna valor zero do tipo do valor).
var m map[string]int
fmt.Println(m["x"]) // 0 (leitura ok)
// m["x"] = 1 // panic: assignment to entry in nil mapLeitura, escrita, remoção e tamanho
idades := map[string]int{"Ana": 28}
idades["Ana"] = 29 // atualiza
idades["Bruno"] = 31 // insere
fmt.Println(idades["Ana"]) // lê
fmt.Println(len(idades)) // quantidade de pares
delete(idades, "Bruno") // remove (se não existir, não dá erro)Verificando existência: padrão value, ok
Ao ler uma chave que não existe, você recebe o valor zero do tipo do valor. Isso pode ser ambíguo quando o valor zero é um valor válido (ex.: 0 em contagens). Para resolver, use o padrão value, ok.
idades := map[string]int{"Ana": 0}
v1 := idades["Ana"]
v2 := idades["Carla"]
fmt.Println(v1, v2) // 0 0 (ambíguo)
if v, ok := idades["Ana"]; ok {
fmt.Println("Ana existe, valor:", v)
}
if v, ok := idades["Carla"]; !ok {
fmt.Println("Carla não existe, valor lido (zero):", v)
}Iteração e implicações de ordem
Você pode iterar com range. Porém, a ordem de iteração em mapas não é garantida e pode variar entre execuções. Isso é intencional: não escreva código que dependa da ordem de um map.
- Ouça o áudio com a tela desligada
- Ganhe Certificado após a conclusão
- + de 5000 cursos para você explorar!
Baixar o aplicativo
capitais := map[string]string{"BR": "Brasília", "PT": "Lisboa", "AR": "Buenos Aires"}
for k, v := range capitais {
fmt.Println(k, v)
}Quando você precisa de ordem determinística (por exemplo, para exibir ou testar), extraia as chaves, ordene e depois acesse o map.
package main
import (
"fmt"
"sort"
)
func main() {
capitais := map[string]string{"BR": "Brasília", "PT": "Lisboa", "AR": "Buenos Aires"}
keys := make([]string, 0, len(capitais))
for k := range capitais {
keys = append(keys, k)
}
sort.Strings(keys)
for _, k := range keys {
fmt.Println(k, capitais[k])
}
}Strings, bytes e runes
Em Go, string é uma sequência imutável de bytes. Normalmente esses bytes representam texto em UTF-8. Isso traz duas consequências práticas: (1) índices em string acessam bytes, não “caracteres”; (2) alguns caracteres (como “ç”, “á”, “你”, “🙂”) ocupam mais de 1 byte.
Bytes vs runes
byte é um alias de uint8 e representa um byte. rune é um alias de int32 e costuma representar um ponto de código Unicode. Ao iterar uma string com range, Go decodifica UTF-8 e entrega runes.
s := "café"
fmt.Println(len(s)) // tamanho em bytes
fmt.Println(s[0]) // byte (uint8) do primeiro byte
for i := 0; i < len(s); i++ {
fmt.Printf("byte[%d]=%d\n", i, s[i])
}
for idx, r := range s {
fmt.Printf("rune em byteIndex=%d: %c (U+%04X)\n", idx, r, r)
}Note que len(s) conta bytes, não runes. Para contar runes, você pode converter para []rune (custo extra) ou usar utf8.RuneCountInString.
package main
import (
"fmt"
"unicode/utf8"
)
func main() {
s := "café"
fmt.Println("bytes:", len(s))
fmt.Println("runes:", utf8.RuneCountInString(s))
fmt.Println("runes via []rune:", len([]rune(s)))
}Iteração correta para texto
Para processar texto “por caractere”, use for _, r := range s. Se você usar indexação direta (s[i]), estará processando bytes, o que pode quebrar caracteres multibyte.
// Exemplo: transformar letras em maiúsculas (Unicode-aware)
package main
import (
"fmt"
"strings"
)
func main() {
s := "Olá, mundo"
fmt.Println(strings.ToUpper(s))
}Quando você precisa manipular “caracteres” manualmente (por exemplo, remover acentos, filtrar letras), normalmente você trabalha com runes e pacotes como unicode.
package main
import (
"fmt"
"unicode"
)
func main() {
s := "Go 1.22!"
out := make([]rune, 0, len(s))
for _, r := range s {
if unicode.IsLetter(r) {
out = append(out, r)
}
}
fmt.Println(string(out)) // "Go"
}Conversões comuns
string→[]byte: útil para I/O e manipulação em nível de bytes.string→[]rune: útil para indexar por “caractere” (rune), com custo de alocação.[]byte/[]rune→string: cria uma nova string.
s := "café"
bs := []byte(s)
rs := []rune(s)
fmt.Println(bs) // bytes UTF-8
fmt.Println(rs) // pontos de código
fmt.Println(string(bs))
fmt.Println(string(rs))Construção eficiente de texto com strings.Builder
Concatenar strings repetidamente com + em loops pode gerar muitas alocações, porque strings são imutáveis: cada concatenação cria uma nova string. Para montar texto incrementalmente, prefira strings.Builder.
Passo a passo: montando um relatório
package main
import (
"fmt"
"strings"
)
func main() {
linhas := []string{"item=maçã", "item=banana", "item=café"}
var b strings.Builder
b.Grow(64) // opcional: reserva capacidade para reduzir realocações
b.WriteString("Relatório\n")
for i, ln := range linhas {
b.WriteString(fmt.Sprintf("%d) %s\n", i+1, ln))
}
texto := b.String()
fmt.Println(texto)
}Observação: fmt.Sprintf é conveniente, mas pode ser mais custoso. Quando possível, escreva partes diretamente no builder (por exemplo, usando strconv.AppendInt em um buffer de bytes, ou fmt.Fprintf(&b, ...) quando a clareza for prioridade).
package main
import (
"fmt"
"strings"
)
func main() {
var b strings.Builder
fmt.Fprintf(&b, "Olá %s, você tem %d mensagens.\n", "Ana", 3)
fmt.Print(b.String())
}Tarefas práticas: parsing e contagem de frequência
Tarefa 1: parsing simples de pares chave=valor
Objetivo: receber uma lista de linhas no formato chave=valor, ignorar linhas inválidas e construir um map[string]string. Você vai praticar: strings.SplitN, strings.TrimSpace, map e o padrão value, ok.
Passo a passo
- Crie um
mapcommake. - Para cada linha: remova espaços com
TrimSpace. - Ignore vazias e comentários (por exemplo, começando com
#). - Use
SplitN(linha, "=", 2)para separar em no máximo 2 partes. - Se não houver 2 partes, a linha é inválida.
- Faça
TrimSpaceem chave e valor. - Se a chave já existir, decida se sobrescreve ou mantém (mostrado abaixo: sobrescreve).
package main
import (
"fmt"
"strings"
)
func parseKV(lines []string) map[string]string {
out := make(map[string]string)
for _, line := range lines {
line = strings.TrimSpace(line)
if line == "" || strings.HasPrefix(line, "#") {
continue
}
parts := strings.SplitN(line, "=", 2)
if len(parts) != 2 {
continue
}
k := strings.TrimSpace(parts[0])
v := strings.TrimSpace(parts[1])
if k == "" {
continue
}
out[k] = v
}
return out
}
func main() {
lines := []string{
"host = localhost",
"port= 5432",
"# comentário",
"invalida",
"user=ana",
"user=bruno", // sobrescreve
}
cfg := parseKV(lines)
if v, ok := cfg["user"]; ok {
fmt.Println("user:", v)
}
fmt.Println(cfg)
}Tarefa 2: contagem de frequência de palavras
Objetivo: dado um texto, contar quantas vezes cada palavra aparece. Você vai praticar: normalização de texto, iteração correta, map com contadores e ordenação para exibir resultados.
Decisões importantes
- Normalização: converter para minúsculas para não separar “Go” de “go”.
- Separação: você pode usar
strings.Fields(separação por espaços) ou um scanner mais robusto. Aqui vamos fazer uma tokenização simples por runes: manter letras e números e trocar o resto por espaço. - Contagem: use
map[string]inte incremente:freq[w]++. - Ordem: para imprimir em ordem, ordene as chaves.
Passo a passo com tokenização por runes
package main
import (
"fmt"
"sort"
"strings"
"unicode"
)
func normalizeToWords(s string) []string {
// 1) minúsculas
s = strings.ToLower(s)
// 2) substituir pontuação por espaços, preservando letras e números
var b strings.Builder
b.Grow(len(s))
for _, r := range s {
if unicode.IsLetter(r) || unicode.IsNumber(r) {
b.WriteRune(r)
} else {
b.WriteByte(' ')
}
}
// 3) separar por espaços (colapsa múltiplos espaços)
return strings.Fields(b.String())
}
func wordFrequency(text string) map[string]int {
freq := make(map[string]int)
for _, w := range normalizeToWords(text) {
freq[w]++
}
return freq
}
func main() {
text := "Go é Go. Café, café! GO? 2024 é 2024."
freq := wordFrequency(text)
keys := make([]string, 0, len(freq))
for k := range freq {
keys = append(keys, k)
}
sort.Strings(keys)
for _, k := range keys {
fmt.Printf("%s: %d\n", k, freq[k])
}
}Extensões sugeridas (para praticar mais)
- Mostrar as N palavras mais frequentes (exige ordenar por valor; você pode criar uma slice de pares e ordenar).
- Manter também o total de palavras e o total de palavras únicas.
- Tratar hífen como parte da palavra (por exemplo, “auto-escola”).
- Contar caracteres (runes) e bytes do texto e comparar.
- Gerar uma saída formatada usando
strings.Builder(por exemplo, uma tabela em texto).