404 Not Found

404 Not Found


nginx

Rustのコレクション:HashMapとHashSetを詳しく見てみよう

HashMap と HashSet は、Rust の標準ライブラリで最もよく使われる ハッシュベースのコレクション です。HashMap はキーと値の対応関係を格納するのに対し、HashSet は一意の要素の集合を格納します。

Vecが「順序通りにデータを格納する」ものだとすれば、HashMapは「名前でデータを検索する」ものです。インデックスを覚えておく必要はなく、キーさえ分かっていればよいのです。一方、HashSetは、「この要素は存在するか?」という問いに対して、明確な答えを提供してくれます。


1. 学習内容


2. ある投票システムの物語

(1) 問題点:チケットの枚数を保存するために2つのVecを使用している

アンナはクラスでの投票システムを開発しており、各候補者が獲得した票数を集計する必要があります。

当初、彼女は2つのVecオブジェクトを使用していました:

RUST
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を用いたアプローチ

TEXT
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());
}

出力:

TEXT
Alice: 2 votes
Bob: 1 votes
Charlie: 1 votes
Alice the number of votes: 2

HashMap はキーと値の対応表です:key -> valueentry API は、「キーが存在しない場合はデフォルト値を挿入し、存在する場合は更新する」というシナリオを洗練された方法で処理します。get メソッドは、O(1) の時間計算量でキーを検索します。これにより、2つの同期化された Vec を維持する必要がなくなりました。


3. HashMap と HashSet の概要

(1) コンセプトマップ

TEXT
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 — 投票集計システム (難易度 ⭐⭐)

RUST
// ============================================
// 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);
    }
}

出力:

TEXT
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 行で「挿入または更新」操作を実行できます。getOption<&V>を返し、パニックを引き起こすことはありません。


(2) ▶ サンプル:HashMap の所有権ルールと値の型 (難易度 ⭐⭐⭐)

RUST
// ============================================
// 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);
}

出力:

TEXT
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 から重複要素を削除するおよび集合演算(難易度 ⭐⭐)

RUST
// ============================================
// 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);
}

出力:

TEXT
--- 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 の反復処理とコレクションの選択戦略 (難易度 ⭐⭐)

RUST
// ============================================
// 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;

出力:

TEXT
--- 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:総合演習――単語出現頻度分析とテキスト分析(難易度 ⭐⭐⭐)

RUST
// ============================================
// 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
=== 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_frequency entry().or_insert() の手法を用いてエレガントにカウントを行う;unique_words HashSet を使用して重複を自動的に除去する;intersection/difference/union 集合演算を実装する。HashMap と HashSet の組み合わせは、テキスト分析における黄金の組み合わせである。


❓ よくある質問

Q HashMapのキーはどのようなトレイトを満たす必要がありますか?
A キーはEq + Hashトレイトを実装している必要があります。プリミティブ型(i32、u32、String、bool)はすべてこれを実装しています。カスタム型には #[derive(Hash, Eq, PartialEq)] が必要です。f64 は Eq を実装していない(NaN != NaN であるため)ため、キーとして直接使用することはできません。
Q entry APIと直接のinsertの違いは何ですか?
A entryは既存の値を上書きしませんが、insertは直接上書きします。entry(key).or_insert(value)は、キーが存在しない場合にのみ挿入を行い、存在する場合は既存の値への参照を返します。insertは常に古い値を上書きし、Option<V>(古い値)を返します。entryは、「挿入または更新」を表す一般的な表記法です。
Q HashMapの反復順序は決まっていますか?
A いいえ、決まっていません!HashMapの反復順序は順不同です。実行のたびに異なる場合があります。順序付きキー・値のマッピングが必要な場合は、BTreeMap(キー順にソート済み)を使用できます。単に高速な検索が必要な場合は、HashMapのO(1)というパフォーマンスの方が優れています。
Q 重複要素の削除には、HashSetとVecのどちらが効率的ですか?
A 大規模なデータセットを扱う場合、HashSetの方がはるかに高速です。 Vec を使って重複を削除するには O(n²) の時間がかかります(各要素をそれ以前のすべての要素と比較する必要があるため)。一方、HashSet の insert 操作の平均時間計算量は O(1) です。ただし、HashSet はメモリを多く消費し、順序を保持しません。順序を保持する必要がある場合は、VecHashSet を組み合わせて使用することを検討してください。
Q HashMapentry メソッドは何を返しますか?
A Entry 列挙型を返します。この列挙型には、Occupied(Entry)Vacant(Entry) の 2 つのバリエーションがあります。 or_insert(default)は、スロットがVacantの場合にデフォルト値を挿入して参照を返し、Occupiedの場合は既存の値への参照を返します。and_modify(fn)は、スロットがOccupiedの場合に値を変更します。これらのメソッドはチェーンして呼び出すことができます。
Q HashMapとBTreeMapは、それぞれどのような場合に使用すべきですか?
A 高速な検索にはHashMap(O(1))を、順序付き探索にはBTreeMap(O(log n))を使用します。HashMapのキーは順不同ですが、検索は高速です。一方、BTreeMapのキーはソートされており(例えば、アルファベット順に表示されます)、検索は若干遅くなります。順序が重要な場合は、BTreeMapを使用してください。

📖 まとめ


📝 練習問題

  1. 難易度 ⭐:果物の価格(「リンゴ」=5、「バナナ」=3、「オレンジ」=4)を格納する変数 HashMap<String, u32> を作成してください。買い物かごの合計金額を計算する関数 fn total_cost(items: &[&str], prices: &HashMap<String, u32>) -> u32 を記述してください。メイン関数内で、買い物かご ["apple", "banana", "apple"] をテストしてください。

  2. 難易度 ⭐⭐:テキスト内の各単語の出現回数を数える関数 fn word_frequency(text: &str) -> HashMap<String, u32> を作成してください。entry API を使用してください。メイン関数内で、テキスト「the quick brown fox jumps over the lazy dog the fox」をテストし、結果を出力してください。

  3. 難易度 ⭐⭐⭐: 2つのクラス(HashSet<&str>)の生徒リストを作成してください。クラスAには ["Alice", "Bob", "Charlie", "David"] が、クラスBには ["Charlie", "David", "Eve", "Frank"] がいます。以下の出力を生成する関数 fn analyze_classes(a: &HashSet<&str>, b: &HashSet<&str>) を作成してください:両方のクラスに属する生徒(共通部分)、クラスAにのみ属する生徒(差分)、すべての異なる生徒(和集合)、およびどちらか一方のクラスにのみ属する生徒(対称差)。main関数内でこの関数を呼び出し、結果を出力してください。

Web-Tutorial.com

Web-Tutorial 技術チーム

複数の開発者によって共同維持されているプログラミングチュートリアルプラットフォーム。各チュートリアルは専門分野の開発者が執筆・レビューしています。正確で信頼性の高いコンテンツを目指しています — 問題を見つけた場合はお知らせください。

100%