今回はHashMap型について見ていきたいと思います。
名前こそ違えど多言語にも存在するデータ構造ですのでわりと理解しやすいです。
今までと同様、自身の勉強のアウトプット・整理と同時に、同じようにRustを勉強している方や「公式ドキュメントはちょっと、、」というような方が気楽に見れるようにサンプルコードをメインに簡単にまとめてみました。
- HashMapの基本
- HashMapの便利なメソッドとその使い方
基本
HashMap
はキーと値のペア要素のコレクション型です。
例えば以下のコードではキーが数値型、値が文字列型のHashMap
となります。
use std::collections::HashMap; // HashMapの使用にはuseが必要なことに注意!
fn sample() {
let mut map: HashMap<i32, &str> = HashMap::new();
map.insert(1, "test");
map.insert(2, "test2");
println!("{:?}", map);
// -> {2: "test2", 1: "test"}
}
後述のメソッド紹介でも記載しますが、宣言と初期化を同時に行うにはfrom
が便利です。
fn sample() {
let map = HashMap::from([(1, "test"), (2, "test2")]);
println!("{:?}", map);
// -> {1: "test", 2: "test2"}
}
HashMap
において同一のキーは重複できません。
重複するキーが発生した場合の動作はメソッドにより異なります。
HashMapの操作
要素へのアクセス
HashMap
の要素へアクセスするには以下のようにします。
fn sample() {
let map = HashMap::from([(1, "test"), (2, "test2")]);
println!("{:?}", map[&1]); // -> "test"
println!("{:?}", map[&3]); // -> 存在しないキーによるアクセスはpanicとなる
println!("{:?}", map.get(&1)); // -> Some("test")
println!("{:?}", map.get(&3)); // -> None
}
インデックスアクセスも使用できますが、get
などのメソッドを使うほうが安全です。
ループ処理
コレクション型ですのでループ処理もサポートされています。
fn sample() {
let map = HashMap::from([(1, "test"), (2, "test2")]);
for (key, value) in map.iter() {
println!("key: {}, value: {}", key, value);
// -> key: 2, value: test2
// -> key: 1, value: test
}
}
注意が必要なこととして、HashMapでは順序が保証されません。
キーの昇順/降順で並べたい場合は同様のコレクション型であるBTreeMap
が使用できます。
便利なメソッド
以降はHashMap
で使用できる便利なメソッドをいくつかピックアップします。
from
タプル配列を元にHashMap
を初期化する。
fn sample() {
let map = HashMap::from([
(1, "test"),
(2, "test2"),
(3, "test3"),
(4, "test4"),
(5, "test5"),
]);
println!("{:?}", map);
// -> {3: "test3", 2: "test2", 5: "test5", 4: "test4", 1: "test"}
}
get
指定したキーに対応する値をResult
型で返す。
fn sample() {
let map = HashMap::from([(1, "test"), (2, "test2")]);
println!("{:?}", map.get(&1)); // -> Some("test")
println!("{:?}", map.get(&5)); // -> None
}
iter
- iter
-
イテレータを要素への参照として取得する。
fn sample() { let map = HashMap::from([(1, "test"), (2, "test2")]); for v in map.iter() { println!("{:?}", v); } // 複数回イテレータを使用可能 for v in map.iter() { println!("{:?}", v); } }
- into_iter
-
イテレータを要素の所有権を移動した状態で取得する
fn sample() { let map = HashMap::from([(1, "test"), (2, "test2")]); for v in map.into_iter() { println!("{:?}", v); } // 一度into_iterしたあとは所有権が移動しているためmapに対する操作はできない for v in map.into_iter() { println!("{:?}", v); } }
- iter_mut
-
イテレータを要素への可変参照として取得する。
fn sample() { let mut map = HashMap::from([(1, "test"), (2, "test2")]); for v in map.iter_mut() { *v.1 = "dummy"; println!("{:?}", v); } // iterと同様に複数回イテレータを使用可能 for v in map.iter_mut() { println!("{:?}", v); } }
insert
指定したキーの要素が存在しない場合は新しい要素を追加する。
すでに同一キーの要素が存在する場合はその要素の値を更新する。
fn sample() {
let mut map = HashMap::from([(1, "test"), (2, "test2")]);
let result = map.insert(3, "test3");
println!("{:?}", map); // -> {2: "test2", 3: "test3", 1: "test"}
println!("{:?}", result); // -> None
let result = map.insert(1, "updated!!");
println!("{:?}", map); // -> {2: "test2", 3: "test3", 1: "updated!!"}
println!("{:?}", result); // -> Some("test")
}
remove
指定したキーの要素が存在する場合はその要素を削除する。
fn sample() {
let mut map = HashMap::from([(1, "test"), (2, "test2")]);
let result = map.remove(&2);
println!("{:?}", map); // -> {1: "test"}
println!("{:?}", result); // -> Some("test2")
let result = map.remove(&5);
println!("{:?}", map); // -> {1: "test"}
println!("{:?}", result); // -> None
}
clear
すべての要素を削除する。
fn sample() {
let mut map = HashMap::from([(1, "test"), (2, "test2")]);
map.clear();
println!("{:?}", map);
// -> {}
}
is_empty
要素がひとつもない場合にtrue
、ひとつでもある場合はfalse
を返す。
fn sample() {
let mut map = HashMap::from([(1, "test"), (2, "test2")]);
println!("{:?}", map.is_empty());
// -> false
map.clear();
println!("{:?}", map.is_empty());
// -> true
}
contains_key
指定したキーが要素に含まれる場合にtrue
、含まれない場合にfalse
を返す。
fn sample() {
let map = HashMap::from([(1, "test"), (2, "test2")]);
println!("{:?}", map.contains_key(&1)); // -> true
println!("{:?}", map.contains_key(&5)); // -> false
}
drain
すべての要素をHashMap
から削除して、削除した要素へのイテレータを返す。
fn sample() {
let mut map = HashMap::from([(1, "test"), (2, "test2")]);
let iter = map.drain();
for v in iter {
println!("{:?}", v)
// -> (2, "test2")
// -> (1, "test")
}
}
retain
引数に指定した関数がtrue
を返す要素のみ残す。
fn sample() {
let mut map = HashMap::from([
(1, "test"),
(2, "test2"),
(3, "test3"),
(4, "test4"),
(5, "test5"),
]);
map.retain(|key, value| key % 2 == 0);
println!("{:?}", map);
// -> {2: "test2", 4: "test4"}
}
keys / values
要素のキー、値をそれぞれ取得する。
fn sample() {
let map = HashMap::from([
(1, "test"),
(2, "test2"),
(3, "test3"),
(4, "test4"),
(5, "test5"),
]);
println!("{:?}", map.keys());
// -> [1, 5, 3, 2, 4]
println!("{:?}", map.values());
// -> ["test4", "test3", "test2", "test5", "test"]
}
entry
指定したキーのEntry
型を取得する。
Entry
では、「指定した要素が存在する場合」「指定した要素が存在しない場合の処理」といった操作をメソッドチェーンで行うことができる。
fn sample() {
let mut map = HashMap::from([(1, "test"), (2, "test2")]);
// 指定したキーの要素がすでに存在する場合はinsertしない
map.entry(1).or_insert("duplicated");
println!("{:?}", map); // -> {1: "test", 2: "test2"}
// 指定したキーの要素が存在しない場合はinsertする
map.entry(3).or_insert("inserted");
println!("{:?}", map); // -> {2: "test2", 3: "inserted", 1: "test"}
}
最後に
HashMapはやはり使いやすいデータ構造だと思います。
基本的に他プログラミング言語と使用感も変わらないので別言語の経験があればその知識をほとんどそのまま使えます。
しかし、RustのHashMap
では挿入順が保証されないのには注意したいところです。