Rustのハッシュマップ、気になっていませんか?
プログラミングを始めたばかりだと、たくさんのデータをどうやって整理したらいいか、迷うこともありますよね。例えば、ユーザー名とその人の得点、商品コードとその在庫数みたいに、「名前」と「情報」をセットで扱いたい場面、結構あると思います。
この記事では、Rustでとっても便利なデータ構造、HashMap(ハッシュマップ)の基本から、実際にコードでどうやって使うのか、気をつけたいポイントまで見ていきます。
読み終わるころには、「なるほど、HashMapってこう使うのか!」と、あなたのRustスキルが一段アップしているはずです!
この記事でわかること
- HashMapって何? どういう時に使うの?
- RustコードでのHashMapの作り方
- データの追加、取り出し、削除のやり方
- HashMapの中身を順番に見ていく方法
- 使うときに知っておきたいルール(所有権とか!)
Rustのハッシュマップ (HashMap) とは?
さて、HashMapとは一体何者なんでしょう?
一言でいうと、「名前(キー)」と「値(バリュー)」をペアにして保管しておく箱みたいなものです。ちょうど、本物の辞書で単語(キー)を引くと意味(値)がわかるのと同じ感覚ですね。
大量のデータがあっても、キーさえわかっていれば、目的の値をものすごく速く見つけ出せるのが、HashMapのすごいところ。商品コード(キー)を入れたら、すぐに値段(値)が出てくる自動販売機をイメージすると分かりやすいかもしれません。
Rustでは、標準ライブラリの `std::collections::HashMap` として用意されています。わざわざ自分で作らなくても、すぐに使える便利なやつなんです。
Rustでのハッシュマップの作成方法
じゃあ、実際にRustのコードでHashMapを使ってみましょう! まずは箱、つまりHashMap自体を作る方法から見ていきますね。
use宣言と`HashMap::new()`
HashMapを使うには、まず「これからHashMapを使いますよー」という宣言が必要です。コードの最初にこれを書きます。
use std::collections::HashMap;
次に、空っぽのHashMapを作ってみましょう。`HashMap::new()`を使います。後でデータを追加することがほとんどなので、`mut`をつけて変更可能にしておくのが一般的です。
use std::collections::HashMap; fn main() { let mut scores = HashMap::new(); // 空のHashMapを作成 // ここに後でデータを追加していく scores.insert(String::from("数学"), 85); scores.insert(String::from("英語"), 92); println!("作成したHashMap: {:?}", scores); // 中身を確認 // 出力例: {"英語": 92, "数学": 85} (順序は変わることがあります) }
上のコードでは、`scores`という名前でHashMapを作っています。まだキーも値も何も入っていない、空の状態ですね。`HashMap::new()`で、まずは空の箱を用意する、と覚えておきましょう。
Rustは賢いので、後で入れるデータから型を推測してくれますが、はっきりさせたい場合は `let mut scores: HashMap<String, i32> = HashMap::new();` のように型を指定することもできますよ。
`collect()`を使った初期化
最初からいくつかデータを入れた状態でHashMapを作りたい時もありますよね。そんな時に便利なのが `collect()` というメソッドです。
例えば、こんな風にキーと値のペアのリスト(ベクタ)を用意しておいて…
use std::collections::HashMap; fn main() { let teams_list = vec![ (String::from("赤組"), 100), (String::from("白組"), 150), ]; // teams_listからHashMapを作成 // 型注釈 HashMap<_, _> は「キーと値の型は推測してね」という意味 let teams_map: HashMap<_, _> = teams_list.into_iter().collect(); println!("Collectで作ったHashMap: {:?}", teams_map); // 出力例: {"白組": 150, "赤組": 100} (順序は変わることがあります) }
上のコードでは、`teams_list` というベクタ(配列みたいなもの)に入っているタプル(ペア)を、`into_iter().collect()` を使って一気にHashMapに変換しています。あらかじめデータがまとまっている場合は `collect()` が楽ちんです。
型指定の部分で `HashMap<_, _>` とアンダースコアを使っているのは、「型はRust君、いい感じに推測してね!」という意味合いです。もちろん `HashMap<String, i32>` と書いてもOKです。
Rustハッシュマップの基本的な使い方 (挿入・取得・削除)
HashMapの箱が作れたら、次はデータの出し入れです! ここでは、データを追加したり、取り出したり、削除したりする基本的な操作を見ていきましょう。
値の挿入・更新 (`insert`)
HashMapに新しいキーと値のペアを追加するには `insert` メソッドを使います。
use std::collections::HashMap; fn main() { let mut book_reviews = HashMap::new(); // データ追加 (キー: 本のタイトル(String), 値: 評価(i32)) book_reviews.insert(String::from("面白い小説"), 5); book_reviews.insert(String::from("ためになる技術書"), 4); println!("追加後: {:?}", book_reviews); // 同じキーで再度insertすると、値が上書きされる(更新) // "面白い小説"の評価を5から4に変更 book_reviews.insert(String::from("面白い小説"), 4); println!("更新後: {:?}", book_reviews); // 出力例 (更新後): {"ためになる技術書": 4, "面白い小説": 4} (順序は変わることがあります) }
`insert` はとってもシンプルですね。キーと値を指定して呼び出すだけです。もし既に同じキーが存在していた場合は、新しい値で上書きされる、という点を覚えておいてください。これが更新の役割も果たします。
値の取得 (`get`)
キーを指定して、それに対応する値を取り出したい時は `get` メソッドを使います。ここで少し注意点があります。
`get` メソッドは、値を直接返すのではなく、`Option` という特別な型で値を包んで返します。なぜかというと、指定したキーが存在しない可能性もあるからです。
- キーが存在する場合 → `Some(値への参照)` という形で返ってくる
- キーが存在しない場合 → `None` という形で返ってくる
なので、`get` の結果を使うときは、「ちゃんと値が入っていたか?」を確認する処理が必要です。`match` や `if let` を使うのが安全でおすすめです。
use std::collections::HashMap; fn main() { let mut user_points = HashMap::new(); user_points.insert(String::from("Alice"), 100); user_points.insert(String::from("Bob"), 75); let alice_key = String::from("Alice"); let charlie_key = String::from("Charlie"); // 存在しないキー // Aliceのポイントを取得 (存在するキー) // getにはキーの「参照」(&key) を渡すのが基本 match user_points.get(&alice_key) { Some(points) => { // Some(値) で受け取る println!("Aliceさんのポイント: {}", points); // 出力: Aliceさんのポイント: 100 } None => { // None ならキーが存在しない println!("Aliceさんのデータはありません。"); } } // Charlieのポイントを取得 (存在しないキー) - if let を使う場合 if let Some(points) = user_points.get(&charlie_key) { println!("Charlieさんのポイント: {}", points); } else { println!("Charlieさんのデータはありません。"); // 出力: Charlieさんのデータはありません。 } }
このように、`Option` をきちんと処理することで、キーがなくてもプログラムがエラーで止まるのを防げます。
`get`を使うときは `Option` を意識するのがコツです。そして、`get`に渡すキーは、`&` を付けた参照(`&key`)にするのが基本、という点も思い出してください。
値の削除 (`remove`)
HashMapから特定のキーとその値のペアを削除したい場合は `remove` メソッドを使います。これもキーを指定して呼び出します。
use std::collections::HashMap; fn main() { let mut permissions = HashMap::new(); permissions.insert(String::from("admin"), true); permissions.insert(String::from("guest"), false); println!("削除前: {:?}", permissions); // 出力例: {"guest": false, "admin": true} // "guest" キーとその値を削除 // removeに渡すキーも参照(&key)が基本だが、文字列リテラルの場合は直接書ける permissions.remove("guest"); println!("削除後: {:?}", permissions); // 出力例: {"admin": true} // 削除されたか確認 (getでNoneが返るはず) match permissions.get("guest") { Some(_) => println!("guest はまだ存在します。"), None => println!("guest は削除されました。"), // 出力: guest は削除されました。 } }
`remove` を使うと、指定したキーのデータがごっそり消えます。削除されたかどうかは、後で `get` を使って確認するとわかりますね。`None` が返ってくれば、ちゃんと削除されています。
Rustハッシュマップの反復処理 (ループ)
HashMapに入っているデータを、一つずつ順番に取り出して処理したいこともありますよね。そんなときは `for` ループが使えます。
use std::collections::HashMap; fn main() { let mut fruit_stock = HashMap::new(); fruit_stock.insert(String::from("りんご"), 10); fruit_stock.insert(String::from("みかん"), 25); fruit_stock.insert(String::from("バナナ"), 12); println!("在庫一覧:"); // &fruit_stock でループすることで所有権を移動させない // ループで得られるキーと値も参照(&)になることが多い for (fruit_name, stock_count) in &fruit_stock { println!("{} は {} 個あります。", fruit_name, stock_count); } // 出力例 (順序は実行ごとに変わる可能性あり): // 在庫一覧: // みかん は 25 個あります。 // りんご は 10 個あります。 // バナナ は 12 個あります。 }
このように `for (キー変数, 値変数) in &HashMap変数` という形で書くと、HashMapの中身をぐるっと一周見て回れます。
注意点として、HashMapの中身がどの順番で出てくるかは決まっていません。実行するたびに順番が変わる可能性もあります。もし順番が厳密に決まっていてほしい場合は、HashMap以外のデータ構造(例えば `Vec` や `BTreeMap` など)を検討する必要があるかもしれません。
ループの際に `&fruit_stock` のように `&` を付けているのは、「HashMapそのものを貸してね」という意味です。`&` を付けないと、HashMapの所有権がループの中に移動してしまい、ループの後で `fruit_stock` が使えなくなってしまうので気をつけてください。
Rustのハッシュマップ利用時の注意点 (所有権とキーの型)
HashMapはとても便利ですが、使う上でいくつか知っておくと良いルールがあります。特に「所有権」と「キーにできる型」について見ていきましょう。
これらはRustならではの考え方ですが、一度理解すれば大丈夫です!
所有権について
Rustには「所有権」という大事なルールがあります。HashMapに値を入れるとき、その値の型によって動きが変わります。
- `String` のように `Copy` ではない型の場合:
値を `insert` すると、その値の所有権(持ち主である権利)がHashMapに移ります。元の変数は使えなくなります。これはまるで、友達にプレゼントをあげたら、もう自分の物ではなくなるのと同じ感じです。 - `i32` のような `Copy` できる型の場合:
値がコピーされてHashMapに入ります。元の変数はそのまま使い続けられます。これは、メモ帳の数字を友達に教えてあげるようなもので、教えてあげても自分のメモは消えませんよね。
特に `String` を値として使うときは、`insert` した後に元の変数が使えなくなる点に注意が必要です。所有権が移動する型か、コピーされる型か、を意識するのがRustプログラミングのコツの一つです。これはキーとして `String` を使う場合も同じルールが適用されます。
キーの型制約 (`Hash` + `Eq`)
HashMapの「キー」には、どんな型でも使えるわけではありません。実は、キーとして使える型には条件があります。
その条件とは、`Hash` と `Eq` という2つの「トレイト」を実装していることです。
- `Hash`トレイト:キーからハッシュ値という内部的な目印(整理番号みたいなもの)を計算できるようにするため。
- `Eq`トレイト:キー同士が「まったく同じものか?」を比較できるようにするため。
「トレイトって何?難しそう…」と感じるかもしれませんが、安心してください。
普段よく使う `String` や `i32`, `u64` などの数値型、`bool` 型(true/false)、文字(`char`)などは、最初からこれらのトレイトを持っています。ですから、これらの型をキーにする場合は、特に何も気にする必要はありません。
use std::collections::HashMap; fn main() { // String をキーにする (OK) let mut map1 = HashMap::new(); map1.insert(String::from("apple"), 1); // i32 をキーにする (OK) let mut map2 = HashMap::new(); map2.insert(10, "Ten"); // bool をキーにする (OK) let mut map3 = HashMap::new(); map3.insert(true, "Yes"); map3.insert(false, "No"); println!("map1: {:?}", map1); // {"apple": 1} println!("map2: {:?}", map2); // {10: "Ten"} println!("map3: {:?}", map3); // {false: "No", true: "Yes"} (順不同) }
もし自分で作った構造体(struct)をキーにしたい場合は、一手間加えて `Hash` と `Eq` を実装(または自動で実装させるおまじないを書く)必要がありますが、入門段階ではまず「よく使う型はだいたいキーにできる」と覚えておけば大丈夫でしょう。
【まとめ】Rustハッシュマップの基礎をマスター
お疲れ様でした! Rustの `HashMap` について、基本的なところから使い方、そしてちょっとした注意点まで見てきました。
今回のポイントをもう一度おさらいしましょう。
- HashMapはキーと値のペアでデータを効率よく管理する箱
- `HashMap::new()` で空の箱を、`collect()` で初期値入りの箱を作れる
- `insert` で追加・更新、`get` で取得(返り値は`Option`!)、`remove` で削除
- `for` ループで中身を順番(ただし順序は保証されない)に見れる
- `String`みたいなCopyじゃない型を入れると所有権が移動する点に注意
- キーには `Hash` と `Eq` が必要(でも普段使う型は心配無用!)
HashMapは、キーを使って素早くデータにアクセスしたい時に本当に役立ちます。データベースのような使い方から、設定情報の管理、一時的なデータの整理まで、活躍の場面はたくさんあります。
最初は所有権や `Option` の扱いで少し戸惑う部分もあるかもしれませんが、実際にコードを書いて、動かして、時にはエラーを出してみて、「なるほど、こう動くのか!」と体験してみるのが一番の近道です。
この記事が、あなたのRust学習の一助となれば嬉しいです。ぜひ、あなたのRustプロジェクトでもHashMapを活用して、もっと便利なプログラムを作ってみてくださいね!
0 件のコメント:
コメントを投稿
注: コメントを投稿できるのは、このブログのメンバーだけです。