Rustのコレクション:HashMapとHashSetを詳しく見てみよう
HashMap と HashSet は、Rust の標準ライブラリで最もよく使われる ハッシュベースのコレクション です。HashMap はキーと値の対応関係を格納するのに対し、HashSet は一意の要素の集合を格納します。
Vecが「順序通りにデータを格納する」ものだとすれば、HashMapは「名前でデータを検索する」ものです。インデックスを覚えておく必要はなく、キーさえ分かっていればよいのです。一方、HashSetは、「この要素は存在するか?」という問いに対して、明確な答えを提供してくれます。
1. 学習内容
HashMap::new()、insert、およびgetのキーと値のペアを使用してくださいentryAPI を使用して、「挿入または更新」のロジックを適切に処理する- HashMap の所有権に関するルール(キーや値として使用できる型)を理解する
- HashSet を使用して重複を削除し、集合の積、和、差の演算を実行する
- HashMapを反復処理するさまざまな方法
- シナリオに応じて、適切なコレクションの種類(Vec、HashMap、HashSet)を選択する
2. ある投票システムの物語
(1) 問題点:チケットの枚数を保存するために2つのVecを使用している
アンナはクラスでの投票システムを開発しており、各候補者が獲得した票数を集計する必要があります。
当初、彼女は2つの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]);
2つの並列なVecを使ってデータを管理する場合、明らかな問題があります。それは、2つのVecの同期を維持することが困難であるということです。候補を追加したり削除したりする際に、一方のVecの更新を忘れてしまいがちです。さらに、候補の検索にはO(n)の線形検索が必要であり、候補の数が増えるにつれて処理速度が低下します。コードの可読性の観点からは、
candidates[i]とvotes[i]の関係が暗黙的であるため、新規の開発者が理解するのは困難です。
(2) RustのHashMapを用いたアプローチ
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());
}
出力:
Alice: 2 votes
Bob: 1 votes
Charlie: 1 votes
Alice the number of votes: 2
HashMap はキーと値の対応表です:
key -> value。entryAPI は、「キーが存在しない場合はデフォルト値を挿入し、存在する場合は更新する」というシナリオを洗練された方法で処理します。getメソッドは、O(1) の時間計算量でキーを検索します。これにより、2つの同期化された Vec を維持する必要がなくなりました。
3. HashMap と HashSet の概要
(1) コンセプトマップ
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) 集合の種類の比較
| 機能 | Vec<T> | HashMap<K, V> | HashSet<T> |
|---|---|---|---|
| 保存形式 | 順序付きシーケンス | 順序なしのキー・値ペア | 順序なしの一意な要素 |
| 検索 | O(n) の線形検索 | O(1) のハッシュ検索 | O(1) のハッシュ検索 |
| 挿入 | O(1) 末尾への追加 | 平均 O(1) | 平均 O(1) |
| 重複の削除 | 手動チェック | キーの重複の自動削除 | 要素の重複の自動削除 |
| メモリ | 低 (連続した領域) | 中 (ハッシュテーブルのオーバーヘッド) | 中 (ハッシュテーブルのオーバーヘッド) |
| ユースケース | 順次アクセス、小規模なデータセット | キーと値の対応付け、高速な検索 | 集合演算、重複排除 |
(3) よく使われる HashMap メソッドのクイックリファレンス
| メソッド | 戻り値の型 | 説明 |
|---|---|---|
insert(k, v) |
Option<V> |
キーと値のペアを挿入し、元の値を返す |
get(&k) |
Option<&V> |
キーワードで検索 |
get_mut(&k) |
Option<&mut V> |
キーによる変数の参照を検索 |
remove(&k) |
Option<V> |
キーと値のペアを削除し、削除された値を返す |
contains_key(&k) |
bool |
そのキーは存在しますか? |
entry(k) |
Entry<K,V> |
挿入・更新するエントリを取得 |
keys() |
Keys<K,V> |
すべてのキーを順に処理する |
values() |
Values<K,V> |
すべての値を順に処理する |
len() |
usize |
キーと値のペアの数 |
is_empty() |
bool |
空ですか? |
clear() |
() |
すべてのキーと値のペアをクリア |
drain() |
Drain<K,V> |
すべてのキーと値のペアを削除して返します |
(4) HashSet の集合演算
| 演算 | 方法 | 数学記号 | 説明 |
|---|---|---|---|
| 和集合 | union(&other) |
A ∪ B | 2つの集合のすべての要素 |
| 交点 | intersection(&other) |
A ∩ B | 両集合に共通する要素 |
| 集合の差 | difference(&other) |
A - B | Aに含まれるがBに含まれない要素 |
| 対称性の違い | symmetric_difference(&other) |
A △ B | いずれか一方の集合にのみ属する要素 |
| 部分集合 | is_subset(&other) |
A ⊆ B | A のすべての要素が B に含まれる |
| スーパーセット | is_superset(&other) |
A ⊇ B | A は B のすべての要素を含む |
4. HashMap と HashSet の例
(1) ▶ サンプル:基本的な HashMap API — 投票集計システム (難易度 ⭐⭐)
// ============================================
// 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);
}
}
出力:
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)は、HashMap を使用する際にもっとも一般的な慣用的な書き方です。キーが存在しない場合はデフォルト値を挿入してその参照を返し、キーが存在する場合は単にその参照を返します。*count += 1と組み合わせることで、1 行で「挿入または更新」操作を実行できます。getはOption<&V>を返し、パニックを引き起こすことはありません。
(2) ▶ サンプル:HashMap の所有権ルールと値の型 (難易度 ⭐⭐⭐)
// ============================================
// 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);
}
出力:
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}
HashMap の所有権に関するルール:挿入時に、キーと値の所有権は HashMap に譲渡されます。
getは参照 (&V) を返し、所有権を譲渡しません。キーの型はEq + Hashトレイトを実装している必要があります(プリミティブ型およびStringはデフォルトでこれを実装しています)。entry+and_modify+or_insertという連鎖呼び出しは、Rust ならではの洗練されたパターンです。
(3) ▶ サンプル:HashSet から重複要素を削除するおよび集合演算(難易度 ⭐⭐)
// ============================================
// 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);
}
出力:
--- 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"}
HashSet の 4 つの主要な集合演算:
union(和集合—すべての要素)、intersection(共通集合—共通の要素)、difference(差集合—A に含まれ、B に含まれない要素)、symmetric_difference(対称差集合—どちらの集合にも含まれない要素)。これらのメソッドはイテレータを返すため、結果を新しいHashSetに格納するには.collect()を使用する必要があります。
(4) ▶ サンプル:HashMap の反復処理とコレクションの選択戦略 (難易度 ⭐⭐)
// ============================================
// 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;
出力:
--- 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
HashMap を反復処理する方法:
iter()すべてのキーと値のペアを反復処理する;keys()キーのみを反復処理する;values()値のみを反復処理する;values_mut()順不同で値を反復処理する;drain()すべての要素を消費して削除する。コレクションの型を選択する際:順序付きで重複可能な、インデックスベースのアクセスが必要な場合は → Vec;キーと値の対応関係と高速な検索が必要な場合は → HashMap;重複排除、コレクション操作、および要素の有無の確認が必要な場合は → HashSet。
(5) ▶ サンプル:題 5:総合演習――単語出現頻度分析とテキスト分析(難易度 ⭐⭐⭐)
// ============================================
// 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());
}
出力:
=== 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_frequencyentry().or_insert()の手法を用いてエレガントにカウントを行う;unique_wordsHashSet を使用して重複を自動的に除去する;intersection/difference/union集合演算を実装する。HashMap と HashSet の組み合わせは、テキスト分析における黄金の組み合わせである。
❓ よくある質問
Eq + Hashトレイトを実装している必要があります。プリミティブ型(i32、u32、String、bool)はすべてこれを実装しています。カスタム型には #[derive(Hash, Eq, PartialEq)] が必要です。f64 は Eq を実装していない(NaN != NaN であるため)ため、キーとして直接使用することはできません。entry APIと直接のinsertの違いは何ですか?entryは既存の値を上書きしませんが、insertは直接上書きします。entry(key).or_insert(value)は、キーが存在しない場合にのみ挿入を行い、存在する場合は既存の値への参照を返します。insertは常に古い値を上書きし、Option<V>(古い値)を返します。entryは、「挿入または更新」を表す一般的な表記法です。BTreeMap(キー順にソート済み)を使用できます。単に高速な検索が必要な場合は、HashMapのO(1)というパフォーマンスの方が優れています。insert 操作の平均時間計算量は O(1) です。ただし、HashSet はメモリを多く消費し、順序を保持しません。順序を保持する必要がある場合は、Vec と HashSet を組み合わせて使用することを検討してください。HashMap の entry メソッドは何を返しますか?Entry 列挙型を返します。この列挙型には、Occupied(Entry) と Vacant(Entry) の 2 つのバリエーションがあります。 or_insert(default)は、スロットがVacantの場合にデフォルト値を挿入して参照を返し、Occupiedの場合は既存の値への参照を返します。and_modify(fn)は、スロットがOccupiedの場合に値を変更します。これらのメソッドはチェーンして呼び出すことができます。📖 まとめ
HashMap<K, V>キーと値の対応関係を格納し、平均時間計算量 O(1) で検索を実行します。- entry API は、Rust 特有の「挿入または更新」のイディオムです (
entry(k).or_insert(v)) - HashMap に要素を追加する際、所有権が移転されます。キーは
Eq + Hashトレイトを実装している必要があります。 HashSet<T>は本質的にHashMap<T, ()>と同じであり、重複排除や集合演算に使用されます- HashSet は 和集合、共通部分、差集合、および 対称差 をサポートしています
- コレクションの選択戦略:ソート・インデックス化 → Vec、キー・値の検索 → HashMap、重複排除・メンバーシップチェック → HashSet
📝 練習問題
-
難易度 ⭐:果物の価格(「リンゴ」=5、「バナナ」=3、「オレンジ」=4)を格納する変数
HashMap<String, u32>を作成してください。買い物かごの合計金額を計算する関数fn total_cost(items: &[&str], prices: &HashMap<String, u32>) -> u32を記述してください。メイン関数内で、買い物かご["apple", "banana", "apple"]をテストしてください。 -
難易度 ⭐⭐:テキスト内の各単語の出現回数を数える関数
fn word_frequency(text: &str) -> HashMap<String, u32>を作成してください。entryAPI を使用してください。メイン関数内で、テキスト「the quick brown fox jumps over the lazy dog the fox」をテストし、結果を出力してください。 -
難易度 ⭐⭐⭐: 2つのクラス(
HashSet<&str>)の生徒リストを作成してください。クラスAには ["Alice", "Bob", "Charlie", "David"] が、クラスBには ["Charlie", "David", "Eve", "Frank"] がいます。以下の出力を生成する関数fn analyze_classes(a: &HashSet<&str>, b: &HashSet<&str>)を作成してください:両方のクラスに属する生徒(共通部分)、クラスAにのみ属する生徒(差分)、すべての異なる生徒(和集合)、およびどちらか一方のクラスにのみ属する生徒(対称差)。main関数内でこの関数を呼び出し、結果を出力してください。



