【Rust】がばがばRust独学 - 8. Collections - 3 Hash Maps
Rustの公式ドキュメントを通して、Rustを独学していく記事になります。(がば要素あり)
今回も、Collectionsの Hash Maps について学びつつ、気になった点をコード確認した結果を記事にしています。
- versions
$ rustc --version rustc 1.40.0 (73528e339 2019-12-16)
Hash Maps
Storing Keys with Associated Values in Hash Maps - The Rust Programming Language
HashMap<K, V>
として定義されており、key -> valueとして利用することが可能です。
Hash Mapの作成
use std::collections::HashMap; fn main() { let mut map: HashMap<String, usize> = HashMap::new(); map.insert(String::from("A"), 10); map.insert(String::from("B"), 50); println!("map -> {:#?}", map); }
map -> { "A": 10, "B": 50, }
また、vectorを利用して生成することも可能です。逆順になっていることに注意が必要です。
use std::collections::HashMap; fn main() { let keys: Vec<String> = vec![String::from("A"), String::from("B")]; let values: Vec<usize> = vec![10, 50]; let map: HashMap<_, _> = keys.iter().zip(values.iter()).collect(); println!("map -> {:#?}", map); }
map -> { "B": 50, "A": 10, }
上記の場合、 HashMap<_, _>
により、型推論されます。
型推論を利用しない場合、 collect()
は std::iter::Iterator<Item=(&std::string::String, &usize)>
を返すため、エラーとなります。
use std::collections::HashMap; fn main() { let keys: Vec<String> = vec![String::from("A"), String::from("B")]; let values: Vec<usize> = vec![10, 50]; let map: HashMap<String, usize> = keys.iter().zip(values.iter()).collect(); println!("map -> {:#?}", map); }
error[E0277]: a collection of type `std::collections::HashMap<std::string::String, usize>` cannot be built from an iterator over elements of type `(&std::string::String, &usize)` --> src/demo.rs:7:67 | 7 | let map: HashMap<String, usize> = keys.iter().zip(values.iter()).collect(); | ^^^^^^^ a collection of type `std::collections::HashMap<std::string::String, usize>` cannot be built from `std::iter::Iterator<Item=(&std::string::String, &usize)>` | = help: the trait `std::iter::FromIterator<(&std::string::String, &usize)>` is not implemented for `std::collections::HashMap<std::string::String, usize>`
owner
下記のように、key, valueを用意していた場合に、map.insert
を実施後は key1
key2
value1
value2
は、mapに所有権が譲渡されるため使用できません。
use std::collections::HashMap; fn main() { let key1: String = String::from("A"); let key2: String = String::from("B"); let value1: usize = 10; let value2: usize = 50; let mut map: HashMap<String, usize> = HashMap::new(); map.insert(key1, value1); map.insert(key2, value2); println!("map -> {:#?}", map); // NOTE: value borrowed here after move // println!("key1 -> {}", key1); // println!("key2 -> {}", key2); // println!("value1 -> {}", value1value1); // println!("value2 -> {}", kvalue2ey1); }
map -> { "A": 10, "B": 50, }
アクセス
get
により Option<&V>
としてアクセス可能です。
use std::collections::HashMap; fn main() { let key1: String = String::from("A"); let key2: String = String::from("B"); let value1: usize = 10; let value2: usize = 50; let mut map: HashMap<String, usize> = HashMap::new(); map.insert(key1, value1); map.insert(key2, value2); println!("map -> {:#?}", map); let a = map.get(&String::from("A")); let z = map.get(&String::from("Z")); println!("a -> {:#?}", a); println!("z -> {:#?}", z); }
map -> { "B": 50, "A": 10, } a -> Some( 10, ) z -> None
get
はポインタを利用するため、注意が必要です。
use std::collections::HashMap; fn main() { let key1: usize = 1; let value1: usize = 10; let mut map: HashMap<usize, usize> = HashMap::new(); map.insert(key1, value1); println!("map -> {:#?}", map); let one = map.get(&1); let ten = map.get(&10); println!("one -> {:#?}", one); println!("ten -> {:#?}", ten); }
map -> { 1: 10, } one -> Some( 10, ) ten -> None
反復処理
for
を利用して key
、 value
を取得することが可能です。この時、任意の順序であることに注意が必要です。
use std::collections::HashMap; fn main() { let key1: String = String::from("A"); let key2: String = String::from("B"); let value1: usize = 10; let value2: usize = 50; let mut map: HashMap<String, usize> = HashMap::new(); map.insert(key1, value1); map.insert(key2, value2); for (key, value) in map { println!("key -> {:#?}, value -> {:#?}", key, value); } }
key -> "B", value -> 50 key -> "A", value -> 10
更新
上書き
同名keyを指定することによって、上書きが可能です。
use std::collections::HashMap; fn main() { let mut map: HashMap<String, usize> = HashMap::new(); map.insert(String::from("A"), 10); map.insert(String::from("B"), 50); map.insert(String::from("B"), 100); println!("map -> {:#?}", map); }
map -> { "B": 100, "A": 10, }
keyがない場合のみ挿入
entry
により Entry
型として取り扱い、 or_insert
でkeyがない場合にのみアクセスして挿入することができます。
use std::collections::HashMap; fn main() { let mut map: HashMap<String, usize> = HashMap::new(); map.insert(String::from("A"), 10); map.insert(String::from("B"), 50); map.entry(String::from("B")).or_insert(100); map.entry(String::from("C")).or_insert(100); println!("map -> {:#?}", map); }
map -> { "A": 10, "C": 100, "B": 50, }
古い値に基づいて更新
公式ドキュメントから少し変更して、下記のようにした場合、 count
に対して &mut usize
を取得して、対象の key
( word
) に対して更新することが可能です。
use std::collections::HashMap; fn main() { let text = "hello world wonderful world"; let mut map = HashMap::new(); for word in text.split_whitespace() { let count: &mut usize = map.entry(word).or_insert(0); *count += 1; } println!("map -> {:#?}", map); }
map -> { "wonderful": 1, "world": 2, "hello": 1, }
最後に
Hash Mapが使えるのは喜ばしいことです。例えば、都道府県のコードと名前を紐付けたりや、NoSQLでのkey-value型を扱うときに有益になるのではないでしょうか。
また、PHPの array
もkey-value構成なので、とても馴染み深いです。PHPのarrayは自由度が高すぎるので、関数の引数や戻り値としてとりあえず突っ込んで、後は関数に任せちゃうみたいな使い方がありますが・・・。RustではStructやEnumで縛った方がいいですね。怖いですし。
次はエラー処理!