コンテンツにスキップ

RustのHashMap

HashMap は、データをキーと値のペアとして保存します。配列やベクタのように位置で探すのではなく、キーを使って値を探します。Pythonの辞書(dict)や、JavaScriptでオブジェクトをマップとして使ったことがあれば、それと同じ考え方です。

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

先頭の use std::collections::HashMap; で、この型をスコープに持ち込んでいます。Vec や String と違って HashMap は最初から使えるわけではないので、この行が必要です。

▶ 実際に試してみよう
use std::collections::HashMap;
fn main() {
let mut ages = HashMap::new();
ages.insert("Alice", 30);
match ages.get("Alice") {
Some(age) => println!("Aliceは{age}歳です"),
None => println!("見つかりません"),
}
}
▶ 実際に試してみよう

.get() は値をそのまま返すのではなく、Option を返します。キーが存在しないかもしれないからです。Option については専用のページできちんと説明します。今のところは、match で Some と None を処理するのが基本の書き方だと覚えておいてください。

同じキーでもう一度 insert すると、古い値が上書きされます。

use std::collections::HashMap;
fn main() {
let mut ages = HashMap::new();
ages.insert("Alice", 30);
ages.insert("Alice", 31); // 30を上書きする
println!("{:?}", ages.get("Alice"));
}
▶ 実際に試してみよう

キーがまだ存在しないときだけ値を追加したいなら、.entry() を使います。

use std::collections::HashMap;
fn main() {
let mut ages = HashMap::new();
ages.insert("Alice", 30);
ages.entry("Alice").or_insert(99); // Aliceはすでにあるので、何もしない
ages.entry("Bob").or_insert(25); // Bobはまだないので、追加される
println!("{:?}", ages);
}
▶ 実際に試してみよう
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}は{age}歳です");
}
}
▶ 実際に試してみよう

位置ではなく、名前やIDで何かを探したいときは HashMap の出番です。たとえば、単語の出現回数を数える、ユーザー名とユーザー情報を対応づける、計算結果をキーごとにキャッシュする、といった場面です。順番が大事なときや、ただのリストを順に処理するだけなら、Vec のほうが向いています。

3人の名前と電話番号を HashMap に保存します。次に、電話帳に載っている "Ben" と、載っていない "Dana" を探して、それぞれについてわかりやすい1行を表示してください。ヒント:["Ben", "Dana"] をループして、.get(name) に対して match します。

解答を見る
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}: 電話帳に載っていません"),
}
}
}

実行結果:

Ben: 555-9876
Dana: 電話帳に載っていません
▶ 実際に試してみよう

マップではなく、探したい名前のほうをループしているので、表示の順番も決まります。HashMap から予測どおりの出力を得たいときに便利なテクニックです。

["赤", "青", "赤", "緑", "青", "赤"] という票があります。それぞれの色が何票入ったかを数えて、"赤"、"緑"、"青"、そして1票も入らなかった "ピンク" の合計を表示してください。ヒント:counts.entry(color).or_insert(0) で、その色の今の票数が得られます(最初は0)。*count += 1 で1を足します。

解答を見る
use std::collections::HashMap;
fn main() {
let votes = ["赤", "青", "赤", "緑", "青", "赤"];
let mut counts = HashMap::new();
for color in votes {
let count = counts.entry(color).or_insert(0);
*count += 1;
}
for color in ["赤", "緑", "青", "ピンク"] {
match counts.get(color) {
Some(n) => println!("{color}: {n}"),
None => println!("{color}: 0"),
}
}
}

実行結果:

赤: 3
緑: 1
青: 2
ピンク: 0
▶ 実際に試してみよう

or_insert(0) が返すのは、マップに保存されている値のコピーではなく、その値への可変参照です。*count += 1 の * は「これが指している数を変更する」という意味なので、マップそのものが更新されます。この「entryで探し、なければ0を入れて、1を足す」という書き方は、Rustで何かを数えるときの定番です。

  • HashMap<K, V> はキーと値のペアを保存します。use std::collections::HashMap; でスコープに持ち込みます。
  • .insert(key, value) は追加または上書きをします。.get(key) は値を探して Option を返します。
  • .entry(key).or_insert(value) は、キーがまだないときだけ値を追加します。
  • ループの順番は保証されません。順番に頼らないようにしましょう。

理解度チェック

1. Vec や String と違って、HashMap をスコープに持ち込むには何が必要ですか?

2. .entry(key).or_insert(value) は何をしますか?

3. HashMap をループで回したときの表示順に頼ってよいですか?

スコア: 0 / 3