Rust: Coleções em Rust
Última atualização: 2026-08-26
HashMap e HashSet são as coleções baseadas em hash mais utilizadas na biblioteca padrão do Rust — o HashMap armazena mapeamentos de chave-valor, enquanto o HashSet armazena um conjunto de elementos únicos.
Se o Vec serve para “armazenar itens em ordem”, o HashMap serve para “encontrar itens pelo nome” — você não precisa se lembrar do índice; basta saber a chave. Já o HashSet fornece a resposta definitiva para a pergunta: “Este item está presente?”
1. O que você vai aprender
- Use os pares chave-valor
HashMap::new(),inserteget - Use a API
entrypara lidar de forma adequada com a lógica de “inserção ou atualização” - Compreender as regras de propriedade do HashMap (quais tipos podem ser usados como chaves e valores)
- Use um HashSet para remover duplicatas e realizar operações de interseção, união e diferença entre conjuntos
- Várias maneiras de percorrer um HashMap
- Escolha o tipo de coleção adequado (Vec, HashMap ou HashSet) de acordo com o cenário
2. A história de um sistema eleitoral
(1) Dificuldade: Usar dois Vecs para armazenar o número de ingressos
Anna está desenvolvendo um sistema de votação para a turma e precisa contabilizar o número de votos que cada candidato recebe.
No início, ela usou dois objetos Vec:
let mut candidates = Vec::new();
let mut votes = Vec::new();
candidates.push("Alice");
votes.push(0);
candidates.push("Bob");
votes.push(0);
// Vote for Alice
let pos = candidates.iter().position(|&c| c == "Alice").unwrap();
votes[pos] += 1;
// Search Bob the number of votes
let pos = candidates.iter().position(|&c| c == "Bob").unwrap();
println!("Bob the number of votes: {}", votes[pos]);
Gerenciar dados com dois Vecs paralelos apresenta um problema óbvio: manter os dois Vecs sincronizados é complicado — é fácil esquecer de atualizar um dos Vecs ao adicionar ou remover candidatos. Além disso, a busca por um candidato requer uma busca linear de complexidade O(n), o que torna o processo mais lento à medida que o número de candidatos aumenta. Em termos de legibilidade do código, a relação entre
candidates[i]evotes[i]é implícita, o que dificulta a compreensão por parte de novos desenvolvedores.
(2) A abordagem do HashMap no Rust
use std::collections::HashMap;
fn main() {
let mut votes = HashMap::new();
// Vote for a candidate
*votes.entry("Alice").or_insert(0) += 1;
*votes.entry("Bob").or_insert(0) += 1;
*votes.entry("Alice").or_insert(0) += 1; // Re-submit Alice
*votes.entry("Charlie").or_insert(0) += 1;
// Check the number of votes
for (candidate, count) in &votes {
println!("{}: {} votes", candidate, count);
}
// Search for a Specific Candidate
println!("Alice the number of votes: {}", votes.get("Alice").unwrap());
}
Resultado:
Alice: 2 votes
Bob: 1 votes
Charlie: 1 votes
Alice the number of votes: 2
O HashMap é uma tabela de mapeamento chave-valor:
key -> value. A APIentrylida com elegância com cenários em que “se a chave não existir, insira o valor padrão; se existir, atualize-a”. O métodogetpesquisa chaves com complexidade de tempo O(1). Não há mais necessidade de manter dois Vecs sincronizados.
3. Visão geral do HashMap e do HashSet
(1) Mapa conceitual
graph TB
A[Hash-Based Sets] --> B[HashMap<K, V>]
A --> C[HashSet<T>]
B --> B1[insert: Insert a key-value pair]
B --> B2[get: Retrieving a Value by Key]
B --> B3[entry: Elegant Insertion/Update]
B --> B4[remove: Delete a key-value pair]
B --> B5[contains_key: Check if a key exists]
B --> B6[iter: Iterate through all key-value pairs]
C --> C1[insert: Add an element]
C --> C2[contains: Check if an element is included]
C --> C3[union: Union Operation]
C --> C4[intersection: Set Intersection]
C --> C5[difference: Difference Set Operations]
C --> C6[symmetric_difference: Symmetric difference set]
(2) Comparação entre tipos de conjuntos
| Característica | Vec<T> | HashMap<K, V> | HashSet<T> |
|---|---|---|---|
| Armazenamento | Sequências ordenadas | Pares chave-valor não ordenados | Elementos únicos não ordenados |
| Consulta | Pesquisa linear O(n) | Consulta em tabela hash O(1) | Consulta em tabela hash O(1) |
| Inserção | O(1) Adicionar ao final | O(1) em média | O(1) em média |
| Remoção de duplicatas | Verificação manual | Remoção automática de duplicatas de chaves | Remoção automática de duplicatas de elementos |
| Memória | Baixa (armazenamento contíguo) | Média (sobrecarga da tabela hash) | Média (sobrecarga da tabela hash) |
| Casos de uso | Acesso sequencial, pequenos conjuntos de dados | Mapeamento chave-valor, consultas rápidas | Operações em conjuntos, deduplicação |
(3) Referência rápida aos métodos comuns do HashMap
| Método | Tipo de retorno | Descrição |
|---|---|---|
insert(k, v) |
Option<V> |
Inserir um par chave-valor e retornar o valor anterior |
get(&k) |
Option<&V> |
Pesquisar por chave |
get_mut(&k) |
Option<&mut V> |
Localizar referências a variáveis por chave |
remove(&k) |
Option<V> |
Excluir um par chave-valor e retornar o valor excluído |
contains_key(&k) |
bool |
A chave existe? |
entry(k) |
Entry<K,V> |
Recuperar um registro para inserir/atualizar |
keys() |
Keys<K,V> |
Percorrer todas as chaves |
values() |
Values<K,V> |
Percorrer todos os valores |
len() |
usize |
Número de pares chave-valor |
is_empty() |
bool |
Está vazio? |
clear() |
() |
Limpar todos os pares chave-valor |
drain() |
Drain<K,V> |
Remover e devolver todos os pares chave-valor |
(4) Operações com conjuntos no HashSet
| Operação | Método | Símbolo matemático | Descrição |
|---|---|---|---|
| União | union(&other) |
A ∪ B | Todos os elementos dos dois conjuntos |
| Interseção | intersection(&other) |
A ∩ B | Elementos comuns aos dois conjuntos |
| Diferença entre conjuntos | difference(&other) |
A - B | Elementos que estão em A, mas não em B |
| Diferença de simetria | symmetric_difference(&other) |
A △ B | Elementos presentes em apenas um conjunto |
| Subconjunto | is_subset(&other) |
A ⊆ B | Todos os elementos de A estão em B |
| Supersérie | is_superset(&other) |
A ⊇ B | A contém todos os elementos de B |
4. Exemplos de HashMap e HashSet
▶ Exemplo 1: API básica do HashMap — Sistema de contagem de votos (Dificuldade ⭐⭐)
// ============================================
// Voting Tally System:Display HashMap Basic API
// ============================================
use std::collections::HashMap;
fn main() {
// Create a new empty HashMap
let mut vote_counts: HashMap<String, u32> = HashMap::new();
// --- insert ---
// Insert key-value pairs (overwrites existing value)
vote_counts.insert(String::from("Alice"), 0);
vote_counts.insert(String::from("Bob"), 0);
vote_counts.insert(String::from("Charlie"), 0);
println!("After initial insert:");
print_votes(&vote_counts);
// --- get ---
// Get a value by key (returns Option<&V>)
let alice_votes = vote_counts.get("Alice");
match alice_votes {
Some(count) => println!("Alice's votes (via get): {}", count),
None => println!("Alice not found"),
}
// --- entry API ---
// The idiomatic way: insert or update
// entry() returns an Entry enum, or_insert() inserts default if missing
println!("\n--- Voting round ---");
let candidates = ["Alice", "Bob", "Alice", "Charlie", "Alice", "Bob", "David"];
for name in &candidates {
let count = vote_counts.entry(String::from(*name)).or_insert(0);
*count += 1;
println!("Voted for {} (total: {})", name, count);
}
// --- contains_key ---
println!("\n--- Checking candidates ---");
for name in &["Alice", "David", "Eve"] {
if vote_counts.contains_key(*name) {
println!("{} is a candidate with {} votes", name, vote_counts.get(*name).unwrap());
} else {
println!("{} is NOT a candidate", name);
}
}
// --- len and is_empty ---
println!("\nTotal candidates: {}", vote_counts.len());
println!("Is empty: {}", vote_counts.is_empty());
// --- Final results ---
println!("\n--- Final Results ---");
print_votes(&vote_counts);
}
fn print_votes(votes: &HashMap<String, u32>) {
// Note: HashMap iteration order is NOT guaranteed
for (name, count) in votes {
println!(" {}: {} votes", name, count);
}
}
Resultado:
After initial insert:
Alice: 0 votes
Charlie: 0 votes
Bob: 0 votes
Alice's votes (via get): 0
--- Voting round ---
Voted for Alice (total: 1)
Voted for Bob (total: 1)
Voted for Alice (total: 2)
Voted for Charlie (total: 1)
Voted for Alice (total: 3)
Voted for Bob (total: 2)
Voted for David (total: 1)
--- Checking candidates ---
Alice is a candidate with 3 votes
David is a candidate with 1 votes
Eve is NOT a candidate
Total candidates: 4
Is empty: false
--- Final Results ---
Alice: 3 votes
Charlie: 1 votes
David: 1 votes
Bob: 2 votes
entry(key).or_insert(default)é a forma idiomática mais comum de usar o HashMap — se a chave não existir, ele insere um valor padrão e retorna uma referência a ele; se a chave existir, ele simplesmente retorna uma referência a ela. Combinado com*count += 1, permite realizar uma operação de “inserir ou atualizar” em uma única linha.getretornaOption<&V>e nunca causa um panic.
▶ Exemplo 2: Regras de propriedade do HashMap e tipos de valores (Dificuldade ⭐⭐⭐)
// ============================================
// HashMap Ownership Rules:What types are eligible? key/value
// ============================================
use std::collections::HashMap;
#[derive(Debug, Hash, Eq, PartialEq)]
struct ProductId(u32);
#[derive(Debug, Clone)]
struct Product {
name: String,
price: f64,
stock: u32,
}
fn main() {
// --- Rule 1: Owned types as keys ---
// String (owned) can be a key; &str (borrowed) needs lifetime management
let mut inventory: HashMap<String, Product> = HashMap::new();
let product = Product {
name: String::from("Rust Book"),
price: 29.99,
stock: 100,
};
// insert takes ownership of key and value
inventory.insert(String::from("RB-001"), product);
// println!("{:?}", product); // ❌ product was moved into the HashMap
// --- Rule 2: Inserting a reference ---
// Borrowed keys need lifetime annotations on the HashMap
// This works because the string literals have 'static lifetime
let mut lookup: HashMap<&str, u32> = HashMap::new();
lookup.insert("apple", 5);
lookup.insert("banana", 3);
println!("Lookup table: {:?}", lookup);
// --- Rule 3: Getting values returns references ---
// get() returns Option<&V>, not V
let stock_ref = inventory.get("RB-001");
match stock_ref {
Some(p) => println!("Product: {}, price: {}", p.name, p.price),
None => println!("Not found"),
}
// inventory is still valid (we only borrowed)
// --- Rule 4: Custom types as keys ---
// Keys must implement Eq + Hash
let mut product_map: HashMap<ProductId, String> = HashMap::new();
product_map.insert(ProductId(1), String::from("Laptop"));
product_map.insert(ProductId(2), String::from("Mouse"));
// --- Rule 5: Updating values with get_mut ---
// get_mut() returns Option<&mut V> for mutable access
if let Some(product) = inventory.get_mut("RB-001") {
product.stock -= 1; // Sell one unit
println!("Updated stock: {}", product.stock);
}
// --- Rule 6: The entry API for sophisticated updates ---
let mut word_count: HashMap<String, u32> = HashMap::new();
let text = "hello world hello rust hello again";
for word in text.split_whitespace() {
// or_insert returns &mut V, which we dereference and increment
let counter = word_count.entry(String::from(word)).or_insert(0);
*counter += 1;
}
println!("\nWord count: {:?}", word_count);
// Advanced: modify entry with and_modify + or_insert
let mut scores: HashMap<String, u32> = HashMap::new();
for team in &["red", "blue", "red", "green", "blue", "red"] {
scores.entry(String::from(*team))
.and_modify(|count| *count += 1) // if exists, increment
.or_insert(1); // if not, insert 1
}
println!("Scores: {:?}", scores);
}
Resultado:
Lookup table: {"banana": 3, "apple": 5}
Product: Rust Book, price: 29.99
Updated stock: 99
Word count: {"again": 1, "hello": 2, "rust": 1, "world": 1}
Scores: {"green": 1, "blue": 2, "red": 3}
Regras de propriedade do HashMap: Ao inserir, a propriedade da chave e do valor é transferida para o HashMap.
getretorna uma referência (&V) e não transfere a propriedade. O tipo da chave deve implementar o traitEq + Hash(os tipos primitivos eStringimplementam isso por padrão). As chamadas encadeadasentry+and_modify+or_insertsão um padrão elegante exclusivo do Rust.
▶ Exemplo 3: Remoção de duplicatas de um HashSet e operações com conjuntos (Dificuldade ⭐⭐)
// ============================================
// HashSet:Remove duplicates、Intersection、Union、Difference Set Operations
// ============================================
use std::collections::HashSet;
fn main() {
// --- Basic HashSet: deduplication ---
println!("--- HashSet Deduplication ---");
let mut unique_numbers: HashSet<i32> = HashSet::new();
let numbers = [3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5];
for &n in &numbers {
unique_numbers.insert(n);
}
println!("Original: {:?}", &numbers[..]);
println!("Unique: {:?}", unique_numbers);
println!("Count: {} (original: {})", unique_numbers.len(), numbers.len());
// --- contains ---
println!("\n--- Contains Check ---");
for &n in &[1, 7, 9] {
if unique_numbers.contains(&n) {
println!("{} is in the set", n);
} else {
println!("{} is NOT in the set", n);
}
}
// --- Set operations ---
println!("\n--- Set Operations ---");
let set_a: HashSet<i32> = [1, 2, 3, 4, 5].iter().cloned().collect();
let set_b: HashSet<i32> = [4, 5, 6, 7, 8].iter().cloned().collect();
println!("Set A: {:?}", set_a);
println!("Set B: {:?}", set_b);
// Union: elements in A OR B
let union: HashSet<&i32> = set_a.union(&set_b).collect();
println!("Union (A ∪ B): {:?}", union);
// Intersection: elements in A AND B
let intersection: HashSet<&i32> = set_a.intersection(&set_b).collect();
println!("Intersection (A ∩ B): {:?}", intersection);
// Difference: elements in A but NOT in B
let diff_ab: HashSet<&i32> = set_a.difference(&set_b).collect();
println!("Difference (A - B): {:?}", diff_ab);
let diff_ba: HashSet<&i32> = set_b.difference(&set_a).collect();
println!("Difference (B - A): {:?}", diff_ba);
// Symmetric difference: elements in A or B but NOT both
let sym_diff: HashSet<&i32> = set_a.symmetric_difference(&set_b).collect();
println!("Symmetric Difference: {:?}", sym_diff);
// --- Practical example: finding common friends ---
println!("\n--- Practical: Common Friends ---");
let alice_friends: HashSet<&str> =
["Bob", "Charlie", "David", "Eve"].iter().cloned().collect();
let bob_friends: HashSet<&str> =
["Alice", "Charlie", "Eve", "Frank"].iter().cloned().collect();
println!("Alice's friends: {:?}", alice_friends);
println!("Bob's friends: {:?}", bob_friends);
// Mutual friends (intersection)
let mutual: HashSet<&&str> = alice_friends.intersection(&bob_friends).collect();
println!("Mutual friends: {:?}", mutual);
// Friends only Alice knows (difference)
let alice_only: HashSet<&&str> = alice_friends.difference(&bob_friends).collect();
println!("Only Alice knows: {:?}", alice_only);
// All unique friends (union)
let all_friends: HashSet<&&str> = alice_friends.union(&bob_friends).collect();
println!("All unique friends: {:?}", all_friends);
}
Resultado:
--- HashSet Deduplication ---
Original: [3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5]
Unique: {3, 2, 1, 6, 4, 9, 5}
Count: 7 (original: 11)
--- Contains Check ---
1 is in the set
7 is NOT in the set
9 is in the set
--- Set Operations ---
Set A: {2, 3, 4, 5, 1}
Set B: {4, 7, 6, 5, 8}
Union (A ∪ B): {7, 2, 3, 6, 4, 5, 1, 8}
Intersection (A ∩ B): {4, 5}
Difference (A - B): {2, 3, 1}
Difference (B - A): {6, 7, 8}
Symmetric Difference: {1, 2, 3, 6, 7, 8}
--- Practical: Common Friends ---
Alice's friends: {"Charlie", "David", "Eve", "Bob"}
Bob's friends: {"Charlie", "Frank", "Alice", "Eve"}
Mutual friends: {"Charlie", "Eve"}
Only Alice knows: {"David", "Bob"}
All unique friends: {"Charlie", "David", "Frank", "Alice", "Eve", "Bob"}
As quatro principais operações de conjuntos do HashSet:
union(união — todos os elementos),intersection(interseção — elementos comuns),difference(diferença — elementos presentes em A, mas não em B),symmetric_difference(diferença simétrica — elementos ausentes em ambos os conjuntos). Esses métodos retornam iteradores; é necessário usar.collect()para coletar os resultados em um novo HashSet.
▶ Exemplo 4: Iteração por um HashMap e estratégias de seleção para coleções (Dificuldade ⭐⭐)
// ============================================
// Iterate HashMap + Comparison of Set Selection Strategies
// ============================================
use std::collections::HashMap;
fn main() {
// --- Build a sample dataset ---
let mut sales: HashMap<String, f64> = HashMap::new();
sales.insert(String::from("Laptop"), 1200.0);
sales.insert(String::from("Mouse"), 25.0);
sales.insert(String::from("Keyboard"), 80.0);
sales.insert(String::from("Monitor"), 350.0);
sales.insert(String::from("Headphones"), 150.0);
// --- Method 1: Iterate over key-value pairs ---
println!("--- All Products (iter) ---");
for (product, revenue) in &sales {
println!(" {}: ${:.2}", product, revenue);
}
// --- Method 2: Iterate over keys only ---
println!("\n--- Product Names (keys) ---");
for product in sales.keys() {
println!(" - {}", product);
}
// --- Method 3: Iterate over values only ---
println!("\n--- Revenue Values (values) ---");
let total: f64 = sales.values().sum();
println!(" Total revenue: ${:.2}", total);
println!(" Average: ${:.2}", total / sales.len() as f64);
// --- Method 4: Mutable iteration over values ---
println!("\n--- Apply 10% Discount (values_mut) ---");
for revenue in sales.values_mut() {
*revenue *= 0.9; // Apply 10% discount
}
for (product, revenue) in &sales {
println!(" {}: ${:.2}", product, revenue);
}
// --- Method 5: drain to consume the HashMap ---
let mut backup = sales.clone();
println!("\n--- Drain (consumes HashMap) ---");
while let Some((product, revenue)) = backup.drain().next() {
println!(" Removed: {} (${:.2})", product, revenue);
}
println!(" backup is empty: {}", backup.is_empty());
// --- When to use what: Collection selection guide ---
println!("\n--- Collection Selection Guide ---");
// Scenario 1: Vec (ordered, indexed access)
let mut todo_list: Vec<&str> = Vec::new();
todo_list.push("Buy milk");
todo_list.push("Write report");
todo_list.push("Call mom");
println!("Vec (ordered todo list):");
for (i, item) in todo_list.iter().enumerate() {
println!(" {}. {}", i + 1, item);
}
// Scenario 2: HashMap (key-value lookup)
let mut phone_book: HashMap<&str, &str> = HashMap::new();
phone_book.insert("Alice", "123-4567");
phone_book.insert("Bob", "987-6543");
println!("HashMap (phone book):");
println!(" Alice's number: {}", phone_book.get("Alice").unwrap());
// Scenario 3: HashSet (membership check)
let mut admin_users: HashSet<&str> = HashSet::new();
admin_users.insert("admin");
admin_users.insert("root");
let user = "admin";
println!("HashSet (admin check):");
println!(" Is '{}' admin? {}", user, admin_users.contains(user));
}
// Import HashSet for the last scenario
use std::collections::HashSet;
Resultado:
--- All Products (iter) ---
Laptop: $1200.00
Mouse: $25.00
Keyboard: $80.00
Monitor: $350.00
Headphones: $150.00
--- Product Names (keys) ---
- Laptop
- Mouse
- Keyboard
- Monitor
- Headphones
--- Revenue Values (values) ---
Total revenue: $1805.00
Average: $361.00
--- Apply 10% Discount (values_mut) ---
Laptop: $1080.00
Mouse: $22.50
Keyboard: $72.00
Monitor: $315.00
Headphones: $135.00
--- Drain (consumes HashMap) ---
Removed: Laptop ($1080.00)
Removed: Mouse ($22.50)
Removed: Keyboard ($72.00)
Removed: Monitor ($315.00)
Removed: Headphones ($135.00)
backup is empty: true
--- Collection Selection Guide ---
Vec (ordered todo list):
1. Buy milk
2. Write report
3. Call mom
HashMap (phone book):
Alice's number: 123-4567
HashSet (admin check):
Is 'admin' admin? true
Formas de iterar sobre um HashMap:
iter()Itera sobre todos os pares chave-valor;keys()Itera apenas sobre as chaves;values()Itera apenas sobre os valores;values_mut()Itera sobre os valores em ordem variável;drain()Consome e remove todos os elementos. Ao escolher um tipo de coleção: se você precisar de uma coleção ordenada e duplicável com acesso baseado em índice → Vec; se precisar de mapeamento chave-valor e consultas rápidas → HashMap; se precisar de deduplicação, operações de coleção e verificações de pertencimento → HashSet.
▶ Exemplo 5: Exercício abrangente — Análise de frequência de palavras e análise de texto (Dificuldade ⭐⭐⭐)
// ============================================
// Comprehensive Example:HashMap + HashSet Text Analysis
// ============================================
use std::collections::{HashMap, HashSet};
fn word_frequency(text: &str) -> HashMap<String, u32> {
let mut freq: HashMap<String, u32> = HashMap::new();
for word in text.split_whitespace() {
let clean: String = word.chars()
.filter(|c| c.is_alphabetic())
.map(|c| c.to_lowercase().next().unwrap())
.collect();
if !clean.is_empty() {
*freq.entry(clean).or_insert(0) += 1;
}
}
freq
}
fn unique_words(text: &str) -> HashSet<String> {
text.split_whitespace()
.map(|w| w.to_lowercase())
.collect()
}
fn top_n(freq: &HashMap<String, u32>, n: usize) -> Vec<(&str, u32)> {
let mut entries: Vec<_> = freq.iter().map(|(k, &v)| (k.as_str(), v)).collect();
entries.sort_by(|a, b| b.1.cmp(&a.1));
entries.into_iter().take(n).collect()
}
fn main() {
let text1 = "the cat sat on the mat and the cat slept on the mat";
let text2 = "the dog ran on the grass and the dog slept on the rug";
println!("=== Text 1 Word Frequency ===");
let freq1 = word_frequency(text1);
for (word, count) in top_n(&freq1, 5) {
println!(" '{}': {} times", word, count);
}
println!("\n=== Text 2 Word Frequency ===");
let freq2 = word_frequency(text2);
for (word, count) in top_n(&freq2, 5) {
println!(" '{}': {} times", word, count);
}
let words1 = unique_words(text1);
let words2 = unique_words(text2);
let common: HashSet<_> = words1.intersection(&words2).collect();
println!("\nCommon Vocabulary: {:?}", common);
let only1: HashSet<_> = words1.difference(&words2).collect();
println!("Text1Exclusive: {:?}", only1);
let only2: HashSet<_> = words2.difference(&words1).collect();
println!("Text2Exclusive: {:?}", only2);
let all: HashSet<_> = words1.union(&words2).collect();
println!("Total number of words: {}", all.len());
}
Resultado:
=== Text 1 Word Frequency ===
'the': 3 times
'cat': 2 times
'on': 2 times
'mat': 2 times
'sat': 1 times
=== Text 2 Word Frequency ===
'the': 3 times
'dog': 2 times
'on': 2 times
'grass': 1 times
'ran': 1 times
Common Vocabulary: {"the", "and", "on", "slept"}
Text1Exclusive: {"mat", "cat", "sat"}
Text2Exclusive: {"ran", "rug", "grass", "dog"}
Total number of words: 11
word_frequencyConte de forma elegante usando o métodoentry().or_insert();unique_wordsremova automaticamente as duplicatas usando um HashSet;intersection/difference/unionimplementem operações de conjunto. HashMap + HashSet é a combinação perfeita para análise de texto.
❓ Perguntas Frequentes
P: Que característica uma chave do HashMap deve satisfazer? R: Uma chave deve implementar a característica
Eq + Hash. Todos os tipos primitivos (i32, u32, String, bool) a implementam. Tipos personalizados exigem#[derive(Hash, Eq, PartialEq)]. f64 não implementaEq(porque NaN != NaN), portanto não pode ser usado diretamente como chave.
P: Qual é a diferença entre a API
entrye uminsertdireto? R:entrynão sobrescreve os valores existentes, enquantoinsertos sobrescreve diretamente.entry(key).or_insert(value)insere apenas se a chave não existir; se ela existir, retorna uma referência ao valor existente.insertsempre sobrescreve o valor antigo e retornaOption<V>(o valor antigo).entryé a forma convencional de escrever “inserir ou atualizar”.
P: A ordem de iteração de um HashMap é fixa? R: Não, não é! A ordem de iteração de um HashMap não é ordenada. Ela pode variar a cada execução. Se você precisar de um mapeamento chave-valor ordenado, pode usar
BTreeMap(ordenado por chave). Se você precisar apenas de consultas rápidas, o desempenho O(1) de um HashMap é melhor.
P: O que é mais eficiente para remover duplicatas: HashSet ou Vec? R: O HashSet é muito mais rápido ao lidar com grandes conjuntos de dados. A remoção de duplicatas com o Vec leva tempo O(n²) (já que cada elemento precisa ser comparado com todos os anteriores), enquanto a operação
insertno HashSet tem uma complexidade de tempo média de O(1). No entanto, o HashSet consome mais memória e não preserva a ordem. Se você precisar preservar a ordem, considere usarVeccombinado comHashSet.
P: O que o método
entrydeHashMapretorna? R: Ele retorna a enumeraçãoEntry, que possui duas variantes:Occupied(Entry)eVacant(Entry).or_insert(default)insere um valor padrão e retorna uma referência quando o slot está Vago, e retorna uma referência ao valor existente quando ele está Ocupado.and_modify(fn)modifica o valor quando o slot está Ocupado. Esses métodos podem ser encadeados.
P: Quando se deve usar um HashMap e quando se deve usar um BTreeMap? R: Use um HashMap (O(1)) para consultas rápidas e um BTreeMap (O(log n)) para percorrer a estrutura de forma ordenada. As chaves em um HashMap não estão ordenadas, mas as consultas são rápidas; as chaves em um BTreeMap estão ordenadas (por exemplo, exibidas em ordem alfabética), mas as consultas são um pouco mais lentas. Se a ordem for importante, use um BTreeMap.
📖 Resumo
HashMap<K, V>Armazena mapeamentos de chave-valor e permite consultas com uma complexidade temporal média de O(1)- API de entrada é uma expressão idiomática específica do Rust para “inserir ou atualizar” (
entry(k).or_insert(v)) - Ao inserir em um HashMap, a propriedade é transferida; a chave deve implementar o trait
Eq + Hash HashSet<T>é, essencialmente,HashMap<T, ()>, utilizado para deduplicação e operações com conjuntos- O HashSet suporta união, interseção, diferença e diferença simétrica
- Estratégias de seleção de coleções: Classificadas/indexadas → Vec, consulta por chave-valor → HashMap, deduplicação/verificação de pertencimento → HashSet
📝 Exercícios
-
Dificuldade ⭐: Crie uma variável
HashMap<String, u32>para armazenar os preços das frutas (“maçã”=5, “banana”=3, “laranja”=4). Escreva uma funçãofn total_cost(items: &[&str], prices: &HashMap<String, u32>) -> u32para calcular o preço total do carrinho de compras. Teste o carrinho de compras["apple", "banana", "apple"]na função principal. -
Dificuldade ⭐⭐: Escreva uma função
fn word_frequency(text: &str) -> HashMap<String, u32>que conte o número de vezes que cada palavra aparece em um texto. Use a APIentry. Na função principal, teste o texto “the quick brown fox jumps over the lazy dog the fox” e imprima os resultados. -
Dificuldade ⭐⭐⭐: Crie uma lista de alunos de duas turmas (
HashSet<&str>). A Turma A tem ["Alice", "Bob", "Charlie", "David"], e a Turma B tem ["Charlie", "David", "Eve", "Frank"]. Escreva uma funçãofn analyze_classes(a: &HashSet<&str>, b: &HashSet<&str>)que imprima: os alunos de ambas as turmas (interseção), os alunos apenas da Turma A (diferença), todos os alunos únicos (união) e os alunos presentes em apenas uma turma (diferença simétrica). Chame a função na função principal e imprima os resultados.