Charaken 技術系ブログ

技術系に関して学んだことや、気になったことを記事にしていく。

【Rust】がばがばRust独学 - 8. Collections - 3 Hash Maps

f:id:charaken:20191223210559p:plain

Rustの公式ドキュメントを通して、Rustを独学していく記事になります。(がば要素あり)

doc.rust-lang.org

今回も、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 を利用して keyvalue を取得することが可能です。この時、任意の順序であることに注意が必要です。

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 を取得して、対象の keyword ) に対して更新することが可能です。

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型を扱うときに有益になるのではないでしょうか。

また、PHParray もkey-value構成なので、とても馴染み深いです。PHPのarrayは自由度が高すぎるので、関数の引数や戻り値としてとりあえず突っ込んで、後は関数に任せちゃうみたいな使い方がありますが・・・。RustではStructやEnumで縛った方がいいですね。怖いですし。

次はエラー処理!