Pular para o conteúdo

HashMap em Rust

Um HashMap guarda dados como pares de chave e valor. Você busca as coisas por uma chave, e não pela posição, como faria com um array ou um vetor. Se você já usou um dicionário em Python ou um objeto como mapa em JavaScript, é a mesma ideia.

use std::collections::HashMap;
fn main() {
let mut ages = HashMap::new();
ages.insert("Alice", 30);
ages.insert("Bob", 25);
println!("{:?}", ages);
}

O use std::collections::HashMap; no topo traz o tipo para o escopo. Diferente de Vec e String, o HashMap não vem disponível por padrão, então essa linha é obrigatória.

▶ Experimente você mesmo
use std::collections::HashMap;
fn main() {
let mut ages = HashMap::new();
ages.insert("Alice", 30);
match ages.get("Alice") {
Some(age) => println!("Alice tem {age} anos"),
None => println!("Não encontrado"),
}
}
▶ Experimente você mesmo

.get() devolve um Option em vez do valor direto, porque a chave pode não existir. Vamos ver o Option com calma na página dele. Por enquanto, um match com Some/None é o jeito padrão de lidar com isso.

Inserir de novo com a mesma chave substitui o valor antigo:

use std::collections::HashMap;
fn main() {
let mut ages = HashMap::new();
ages.insert("Alice", 30);
ages.insert("Alice", 31); // substitui o 30
println!("{:?}", ages.get("Alice"));
}
▶ Experimente você mesmo

Se você só quer inserir um valor quando a chave ainda não existe, use .entry():

use std::collections::HashMap;
fn main() {
let mut ages = HashMap::new();
ages.insert("Alice", 30);
ages.entry("Alice").or_insert(99); // Alice já existe, então nada acontece
ages.entry("Bob").or_insert(25); // Bob não existe, então é inserido
println!("{:?}", ages);
}
▶ Experimente você mesmo
use std::collections::HashMap;
fn main() {
let mut ages = HashMap::new();
ages.insert("Alice", 30);
ages.insert("Bob", 25);
for (name, age) in &ages {
println!("{name} tem {age} anos");
}
}
▶ Experimente você mesmo

Use um HashMap sempre que quiser buscar coisas por um nome ou ID em vez de uma posição: contar quantas vezes cada palavra aparece, ligar nomes de usuário aos dados de cada usuário, guardar resultados em cache por chave. Se a ordem importa ou você só está trabalhando com uma lista simples, um Vec é a escolha melhor.

Guarde três nomes e telefones em um HashMap. Depois, procure "Ben" (que está lá) e "Dana" (que não está) e imprima uma linha amigável para cada um. Dica: percorra ["Ben", "Dana"] e faça um match em .get(name).

Ver solução
use std::collections::HashMap;
fn main() {
let mut phone_book = HashMap::new();
phone_book.insert("Ana", "555-1234");
phone_book.insert("Ben", "555-9876");
phone_book.insert("Chen", "555-4321");
for name in ["Ben", "Dana"] {
match phone_book.get(name) {
Some(number) => println!("{name}: {number}"),
None => println!("{name}: não está na agenda"),
}
}
}

Saída:

Ben: 555-9876
Dana: não está na agenda
▶ Experimente você mesmo

Percorrer os nomes que queremos, em vez do próprio mapa, também nos dá uma ordem fixa. É um truque útil sempre que você precisa de uma saída previsível a partir de um HashMap.

Você tem os votos ["vermelho", "azul", "vermelho", "verde", "azul", "vermelho"]. Conte quantos votos cada cor recebeu e imprima os totais de "vermelho", "verde", "azul" e "rosa" (que não recebeu nenhum). Dica: counts.entry(color).or_insert(0) dá a contagem atual daquela cor, começando em 0. Some um a ela com *count += 1.

Ver solução
use std::collections::HashMap;
fn main() {
let votes = ["vermelho", "azul", "vermelho", "verde", "azul", "vermelho"];
let mut counts = HashMap::new();
for color in votes {
let count = counts.entry(color).or_insert(0);
*count += 1;
}
for color in ["vermelho", "verde", "azul", "rosa"] {
match counts.get(color) {
Some(n) => println!("{color}: {n}"),
None => println!("{color}: 0"),
}
}
}

Saída:

vermelho: 3
verde: 1
azul: 2
rosa: 0
▶ Experimente você mesmo

O or_insert(0) devolve uma referência mutável para o valor guardado no mapa, e não uma cópia. O * em *count += 1 quer dizer “altere o número para o qual isto aponta”, então o próprio mapa é atualizado. Esse padrão de “entry, ou insira 0, depois some 1” é o jeito padrão de contar coisas em Rust.

  • HashMap<K, V> guarda pares de chave e valor; traga para o escopo com use std::collections::HashMap;.
  • .insert(key, value) adiciona ou substitui; .get(key) busca e devolve um Option.
  • .entry(key).or_insert(value) só insere se a chave ainda não estiver lá.
  • A ordem ao percorrer não é garantida. Não conte com ela.

Teste o que aprendeu

1. O que você precisa para trazer o HashMap para o escopo, ao contrário de Vec ou String?

2. O que .entry(key).or_insert(value) faz?

3. Dá para confiar na ordem em que as entradas aparecem ao percorrer um HashMap?

Pontuação: 0 / 3