はじめに
ベクターは、数値の位置によって値を整理します。一方、HashMap<K, V> は、型 K の各一意なキーに型 V の値を1つ関連付けます。そのため、カウントや設定など、キーで管理するデータに適しています。
この実験では、小さな単語数マップを作成します。その後、カウントの取得と置換、既存の単語と存在しない単語に対するentry APIの使用、マップで未指定のキー順序に依存しないすべての値の反復処理を行います。
キーに対応する値を挿入・取得する
このステップでは、2組のキーと値を挿入し、1つのカウントを安全に取得します。
プロジェクトに移動して、ソースファイルを開きます。
cd /home/labex/project/word-counts
nano src/main.rs
用意されているパス std::collections::HashMap は、Rust標準ライブラリのコレクションモジュールにある HashMap を指定しています。use 行によって、この長いパスにローカルで使える短い名前 HashMap が与えられます。ここではこれを準備済みのインポート処理として扱います。パスとインポートについては、後のモジュールLabで詳しく学びます。HashMap<String, u32> は、所有権を持つ文字列キーと符号なし整数のカウントを格納します。Step 1aのコメントを次のコードに置き換えます。
let _ = counts.insert(String::from("rust"), 1);
let _ = counts.insert(String::from("safe"), 1);
insert は、そのキーに以前格納されていた値があれば返します。ここで使うキーは新しいため、結果は None になります。let _ = ...; というパターンは、処理を実行したうえで、その結果を意図的に破棄します。この単独の _ は、_returned_id のような名前とは異なります。単独のワイルドカードは、後から利用できる束縛を作りません。
Step 1bのコメントを次のコードに置き換えます。
match counts.get("rust") {
Some(count) => println!("Rust count: {count}"),
None => println!("Rust count: missing"),
}
get は借用したテキスト "rust" を受け取り、キーやカウントをマップから移動させずに Option<&u32> を返します。保存して終了したら、確認して実行します。
cargo check
cargo run --quiet
Unique words: 2
Rust count: 1
1行目はキーが一意であることを示し、2行目は保存されたカウントを検索できたことを示します。
既存のカウントを置き換える
このステップでは、既存のキーに対して insert を使い、置き換えられた値を確認します。
ソースファイルを開きます。
nano src/main.rs
Step 2のコメントを次のコードに置き換えます。
let previous = counts.insert(String::from("rust"), 2);
println!("Previous rust count: {previous:?}");
マップは、1つのキーにつき1つの値だけを保持します。新しい値 2 は 1 を置き換え、返された Some(1) によって、その置換を確認できます。保存して終了したら、実行します。
cargo run --quiet
追加された行は次のようになります。
Previous rust count: Some(1)
置換によって別のキーが作成されることはないため、単語数は2のままです。
Entry APIでカウントを更新する
このステップでは、同じAPIを使って、既存の単語と存在しない単語のカウントを1つずつ増やします。
entry(key).or_insert(default) は、キーに対応する既存の値への可変参照を返します。値が存在しない場合はデフォルト値を挿入し、その値への可変参照を返します。ソースファイルを開きます。
nano src/main.rs
Step 3のコメントを次のコードに置き換えます。
*counts.entry(String::from("safe")).or_insert(0) += 1;
*counts.entry(String::from("fast")).or_insert(0) += 1;
println!("Safe count: {}", counts["safe"]);
println!("Fast count: {}", counts["fast"]);
この簡潔な更新処理を内側から順に読みます。まず、entry(key) は、そのキーに対応する場所を選択します。そこに値が存在する場合でも、存在しない場合でも同じように動作します。次に、or_insert(0) が値の存在を保証し、その値への可変参照 &mut u32 を返します。
先頭の * は、その参照が指している u32 にアクセスします。最後に、+= 1 が1を加算し、新しいカウントをマップに保存します。
そのため、safe は 2 になります。一方、存在しない fast は0から始まり、1 になります。角括弧を使うと、キーで値を読み取れます。キーが存在しない場合、マップのインデックス操作はパニックになります。ここで安全に使えるのは、直前の2つの entry 呼び出しによって両方のキーの存在が保証されているためです。キーが存在しない可能性がある場合は、引き続き get を使ってください。
保存して終了したら、実行します。
cargo run --quiet
最後の行は次のようになります。
Safe count: 2
Fast count: 1
1つの操作で、値が存在するキーと存在しないキーの両方を処理できました。
キーの順序に依存せずに反復処理する
このステップでは、保存されているすべてのカウントを反復処理し、順序に依存しない合計を計算します。
ハッシュマップは、キーの反復順序が一定であることを保証しません。そのため、初学者向けの確認では、キーが特定の順序で表示されることを前提にしないでください。values() メソッドは、保存されているすべての値を処理するため、合計を計算できます。
ソースファイルを開き、Step 4のコメントを置き換えます。
nano src/main.rs
let mut total = 0;
for count in counts.values() {
total += count;
}
println!("Total occurrences: {total}");
ここで count は共有参照です。+= を使うと、参照先の整数を加算できます。保存して終了したら、確認して実行します。
cargo check
cargo run --quiet
最後の行は次のようになります。
Total occurrences: 5
この合計は、rust = 2、safe = 2、fast = 1 を、これらの値の走査順序に依存せずに合算したものです。
まとめ
キーに対応するカウントを挿入、取得、置換、増加させた後、キーの順序が一定であることを前提にせず、マップ内のすべての値を反復処理しました。


