Rust でハッシュマップ型 (HashMap) を扱う

ハッシュマップの基本

Rust の標準ライブラリとして、キー&バリューのコレクションを扱うハッシュマップ型 (HashMap<K, V>) が用意されています。

下記は、空のハッシュマップを作成し、キー&バリューを追加/取得する例です。

use std::collections::HashMap;

let mut map = HashMap::new();
map.insert(String::from("AAA"), 100);
map.insert(String::from("BBB"), 200);
map.insert(String::from("CCC"), 300);
println!("{:?}", map.get("AAA"));  //=> Some(100)
println!("{:?}", map.get("ZZZ"));  //=> None

上記の例では、HashMap の型パラメーターを省略していますが、insert しているデータから、キーの型は String、値の型は i32 と推測されます。 HashMap のキーの型が String であっても、get メソッドの引数には &str を渡せるようになっています。

ハッシュマップの生成方法

空のハッシュマップを生成する (new, with_capacity)

let mut map: HashMap<String, i32> = HashMap::new();
let mut map: HashMap<String, i32> = HashMap::with_capacity(100)

空のマップは new で生成できますが、あらかじめ格納する要素数を想定できるときは with_capacity を使うと効率的です。 HashMap 型には、Vec 型の vec! のようなインスタンス生成用のマクロは用意されていないので、これらの関数を使ってインスタンスを生成する必要があります。 空のハッシュマップには何らかのデータを詰める必要があるので、通常は mutable な変数として定義します。

コンパイラーが、格納するデータから型を推測してくれるので、ほとんどのケースで型情報 (: HashMap<String, i32>) を省略できます。

let mut map = HashMap::new();
let mut map = HashMap::with_capacity(100)

タプル(キーと値)のベクターから生成する

use std::collections::HashMap;

let kv_pairs = vec![
    (String::from("AAA"), 100),
    (String::from("BBB"), 200),
    (String::from("CCC"), 300),
];
let map: HashMap<_, _> = kv_pairs.into_iter().collect();

このケースでは、collect メソッドに、HashMap 型としてまとめ上げることを知らせるために、HashMap<_, _> という型情報の指定が必要になります。 キーの型と値の型はコンパイラに推測してもらうので、_ とだけ記述しておけば OK です。

HashMapextend メソッドを使う方法もあります。

use std::collections::HashMap;

let vec = vec![("AAA", 100), ("BBB", 200), ("CCC", 300)];
let mut map = HashMap::new();
map.extend(vec);

キーのベクターと値のベクターから生成する

use std::collections::HashMap;

let keys = vec![String::from("AAA"), String::from("BBB"), String::from("CCC")];
let values = vec![100, 200, 300];
let map: HashMap<_, _> = keys.iter().zip(values.iter()).collect();

要素の追加と削除 (insert, remove)

HashMap への要素の追加は insert、削除は remove メソッドで行います。 すでに存在するキーに対して insert を実行すると、古い値が上書きされます。 存在しないキーに対して remove を実行した場合は無視されます。

use std::collections::HashMap;

let mut map = HashMap::new();
map.insert(String::from("AAA"), 100);
map.insert(String::from("AAA"), 0); // 上書きする
map.insert(String::from("BBB"), 200);
map.remove("BBB"); // 削除する(BBB が削除される)
map.remove("CCC"); // 削除する(何も起こらない)

println!("{:?}", map); //=> {"AAA": 0}

remove メソッドは、Option 型 を返すようになっており、削除されたキーに対応する値を取得することができます。 存在しないキーを指定した場合(何も削除されなかった場合)は、None を返します。

if let Some(val) = map.remove("AAA") {
    println!("削除された値は {} です", val);
}

キーに対応する値を取得する (get)

HashMap#get メソッドで、指定したキーに対応する値を取得することができます。 戻り値は Option 型 で返されるため、指定されたキーが存在するかを調べつつ値を取り出すことができます。

// HashMap を作成する
let mut map = HashMap::new();
map.insert(String::from("AAA"), 100);

// キーに対応する値を取得する
let key = String::from("AAA");
if let Some(val) = map.get(&key) {
    println!("{} の値は {} です", &key, val);
} else {
    println!("{} が見つかりません", &key);
}

全ての要素をループ処理する

HashMap の要素(キー&バリュー)を 1 つずつ取り出して処理したいときは、iter メソッドを使用します。

let mut map = HashMap::new();
map.insert(String::from("AAA"), 100);
map.insert(String::from("BBB"), 200);
map.insert(String::from("CCC"), 300);

// HashMap::iter() は (&'a key, &'a value) を順不同で返す
for (key, value) in map.iter() {
    println!("{} => {}", key, value);
}
実行結果
CCC => 300
AAA => 100
BBB => 200

ループしながら値を書き換えたいときは、iter の代わりに iter_mut を使用して、値への可変参照を取得します。

// HashMap::iter_mut() は (&'a key, &'a mut value) を順不同で返す
for (key, value) in map.iter_mut() {
    *value += 1;
    println!("{} の値に 1 を足して {} にしました", key, value);
}
実行結果
BBB の値に 1 を足して 201 にしました
CCC の値に 1 を足して 301 にしました
AAA の値に 1 を足して 101 にしました

キーが見つからない場合に値をセットする (or_insert)

// HashMap の作成
let mut map = HashMap::new();
map.insert(String::from("AAA"), 100);

// 指定したキーが存在しなければ新しい値をセットする
map.entry(String::from("AAA")).or_insert(0);
map.entry(String::from("BBB")).or_insert(0);

println!("{}", map.get("AAA").unwrap()); //=> 100
println!("{}", map.get("BBB").unwrap()); //=> 0

or_insert は可変参照を返すので、取り出した値を利用して自分自身の値を書き換えることができます。 次の例では、単語の出現数をカウントしています。

let text = "AAA BBB CCC BBB AAA";
let mut word_count = HashMap::new();
for word in text.split_whitespace() {
    let count = word_count.entry(word).or_insert(0);
    *count += 1;
}
println!("{:?}", word_count);  //=> {"AAA": 2, "BBB": 2, "CCC": 1}

整数型 (i32) のデフォルト値は 0 なので、or_insert(0) の代わりに or_default() を使うこともできます。