Rust 100 Ex 🏃【26/37】 HashMap・順序・BTreeMap ～Rustの辞書型～

前の記事

- [【0】 準備](https://qiita.com/namn1125/items/bd1b4cd028874189a3c1) ← 初回
- ...
- [【25】 インデックス・可変インデックス ～インデックスもトレイト！～](https://qiita.com/namn1125/items/bd43375362af10ea8b42) ← 前回
- 【26】 HashMap・BTreeMap ～Rustの辞書型～ ← 今回

<details><summary>全記事一覧</summary>

- [【0】 準備](https://qiita.com/namn1125/items/bd1b4cd028874189a3c1)
- [【1】 構文・整数・変数](https://qiita.com/namn1125/items/c776768baec89b8a0438)
- [【2】 if・パニック・演習](https://qiita.com/namn1125/items/13870965e8273bc87bce)
- [【3】 可変・ループ・オーバーフロー](https://qiita.com/namn1125/items/4f57f8aede2f5201016f)
- [【4】 キャスト・構造体 (たまにUFCS)](https://qiita.com/namn1125/items/227ed5bbedbb775c5d27)
- [【5】 バリデーション・モジュールの公開範囲 ～ → カプセル化！～](https://qiita.com/namn1125/items/46721cceccbfa9ec3c7a)
- [【6】 カプセル化の続きと所有権とセッター ～そして不変参照と可変参照！～](https://qiita.com/namn1125/items/442de7798d49f777384c)
- [【7】 スタック・ヒープと参照のサイズ ～メモリの話～](https://qiita.com/namn1125/items/780d80e4340f0e34dbf6)
- [【8】 デストラクタ(変数の終わり)・トレイト ～終わりと始まり～](https://qiita.com/namn1125/items/7247edde3b6bd0b85b25)
- [【9】 Orphan rule (孤児ルール)・演算子オーバーロード・derive ～Empowerment 💪 ～](https://qiita.com/namn1125/items/e565713b423301a80c76)
- [【10】 トレイト境界・文字列・Derefトレイト ～トレイトのアレコレ～](https://qiita.com/namn1125/items/9060faee2965f23e0e3d)
- [【11】 Sized トレイト・From トレイト・関連型 ～おもしろトレイトと関連型～](https://qiita.com/namn1125/items/b68990712d379a3d6a49)
- [【12】 Clone・Copy・Dropトレイト ～覚えるべき主要トレイトたち～](https://qiita.com/namn1125/items/c6f34945f331c7229746)
- [【13】 トレイトまとめ・列挙型・match式 ～最強のトレイトの次は、最強の列挙型～](https://qiita.com/namn1125/items/6b2cc31d6a47f9ffaff6)
- [【14】 フィールド付き列挙型とOption型 ～チョクワガタ～](https://qiita.com/namn1125/items/6fe69d1cab6c7936c698)
- [【15】 Result型 ～Rust流エラーハンドリング術～](https://qiita.com/namn1125/items/698aafaf22ec4bfd1bde)
- [【16】 Errorトレイトと外部クレート ～依存はCargo.tomlに全部お任せ！～](https://qiita.com/namn1125/items/9dae7b728fa0668e4102)
- [【17】 thiserror・TryFrom ～トレイトもResultも自由自在！～](https://qiita.com/namn1125/items/d67006a29b7ff5d6148c)
- [【18】 Errorのネスト・慣例的な書き方 ～Rustらしさの目醒め～](https://qiita.com/namn1125/items/705829027661a38e4a4f)
- [【19】 配列・動的配列 ～スタックが使われる配列と、ヒープに保存できる動的配列～](https://qiita.com/namn1125/items/9c49fd4c08f3e61896c7)
- [【20】 動的配列のリサイズ・イテレータ ～またまたトレイト登場！～](https://qiita.com/namn1125/items/6e4dede789c6de97f396)
- [【21】 イテレータ・ライフタイム ～ライフタイム注釈ようやく登場！～](https://qiita.com/namn1125/items/9a1224abf733f29a980a)
- [【22】 コンビネータ・RPIT ～ :dragon: 「 `Iterator` トレイトを実装してるやつ」～](https://qiita.com/namn1125/items/27fe20f2d60803c94357)
- [【23】 `impl Trait` ・スライス ～配列の欠片～](https://qiita.com/namn1125/items/7601d0268293ce7fcf28)
- [【24】 可変スライス・下書き構造体 ～構造体で状態表現～](https://qiita.com/namn1125/items/a9acc7dc2ca7c4c55cc3)
- [【25】 インデックス・可変インデックス ～インデックスもトレイト！～](https://qiita.com/namn1125/items/bd43375362af10ea8b42)
- [【26】 HashMap・順序・BTreeMap ～Rustの辞書型～](https://qiita.com/namn1125/items/a2806cded12fa719a1cb)
- [【27】 スレッド・'staticライフタイム ～並列処理に見るRustの恩恵～](https://qiita.com/namn1125/items/5a4a09172834063268b1)
- [【28】 リーク・スコープ付きスレッド ～ライフタイムに技あり！～](https://qiita.com/namn1125/items/34a0aacd4d40f9234aef)
- [【29】 チャネル・参照の内部可変性 ～Rustの虎の子、mpscと `Rc<RefCell<T>>`～](https://qiita.com/namn1125/items/861e72dbb4b837508948)
- [【30】 双方向通信・リファクタリング ～返信用封筒を入れよう！～](https://qiita.com/namn1125/items/46f03de967d0de46737e)
- [【31】 上限付きチャネル・PATCH機能 ～パンクしないように制御！～](https://qiita.com/namn1125/items/d7ae8a205ee969339401)
- [【32】 `Send`・排他的ロック(`Mutex`)・非対称排他的ロック(`RwLock`) ～真打 `Arc<Mutex<T>>` 登場～](https://qiita.com/namn1125/items/eee890dc8672e975e70c)
- [【33】 チャネルなしで実装・Syncの話 ～考察回です～](https://qiita.com/namn1125/items/cc8bee3871b727e564e3)
- [【34】 `async fn`・非同期タスク生成 ～Rustの非同期入門～](https://qiita.com/namn1125/items/9c32b730a283ad76c891)
- [【35】 非同期ランタイム・Futureトレイト ～非同期のお作法～](https://qiita.com/namn1125/items/c840d48f8f558613ec10)
- [【36】 ブロッキング・非同期用の実装・キャンセル ～ラストスパート！～](https://qiita.com/namn1125/items/39c139da3f1f26264353)
- [【37】 Axumでクラサバ！ ～最終回～](https://qiita.com/namn1125/items/bae492f823fbe60e9974)
- [【おまけ1】 Rustで勘違いしていたこと3選 🏄🌴 【100 Exercises To Learn Rust 🦀 完走記事 🏃】](https://qiita.com/namn1125/items/2a59f486cce30fd2b889)
- [【おまけ2】 【🙇 懺悔 🙇】Qiitanグッズ欲しさに1日に33記事投稿した話またはQiita CLIとcargo scriptを布教する的な何か](https://qiita.com/namn1125/items/8e552694b389c5b13b5e)
</details>

[100 Exercise To Learn Rust](https://rust-exercises.com/) 演習第26回になります。配列・イテレータ周りが多かった6章も今回が最後です！

今回の関連ページ

- [6.15. HashMap](https://rust-exercises.com/06_ticket_management/15_hashmap)
- [6.16. BTreeMap](https://rust-exercises.com/06_ticket_management/16_btreemap)

# [[06_ticket_management/15_hashmap](https://github.com/mainmatter/100-exercises-to-learn-rust/blob/main/exercises/06_ticket_management/15_hashmap/src/lib.rs)] HashMap (いわゆる辞書型)

- [6.15. HashMap](https://rust-exercises.com/06_ticket_management/15_hashmap)

問題はこちらです。

```rust:lib.rs (長いのでTODO周辺のみ抜粋)
// TODO: Replace `todo!()`s with the correct implementation.
//  Implement additional traits on `TicketId` if needed.

use std::collections::HashMap;
use std::ops::{Index, IndexMut};
use ticket_fields::{TicketDescription, TicketTitle};

#[derive(Clone)]
pub struct TicketStore {
    tickets: HashMap<TicketId, Ticket>,
    counter: u64,
}

#[derive(Clone, Copy, Debug, PartialEq)]
pub struct TicketId(u64);

#[derive(Clone, Debug, PartialEq)]
pub struct Ticket {
    pub id: TicketId,
    pub title: TicketTitle,
    pub description: TicketDescription,
    pub status: Status,
}

// ...省略...

impl TicketStore {
    pub fn new() -> Self {
        Self {
            tickets: todo!(),
            counter: 0,
        }
    }

    pub fn add_ticket(&mut self, ticket: TicketDraft) -> TicketId {
        let id = TicketId(self.counter);
        self.counter += 1;
        let ticket = Ticket {
            id,
            title: ticket.title,
            description: ticket.description,
            status: Status::ToDo,
        };
        todo!();
        id
    }

    // Index<TicketId> for TicketStore に関わる
    pub fn get(&self, id: TicketId) -> Option<&Ticket> {
        todo!()
    }

    // IndexMut<TicketId> for TicketStore に関わる
    pub fn get_mut(&mut self, id: TicketId) -> Option<&mut Ticket> {
        todo!()
    }
}
```

<details><summary>テストを含めた全体</summary>

```rust:lib.rs
// TODO: Replace `todo!()`s with the correct implementation.
//  Implement additional traits on `TicketId` if needed.

use std::collections::HashMap;
use std::ops::{Index, IndexMut};
use ticket_fields::{TicketDescription, TicketTitle};

#[derive(Clone)]
pub struct TicketStore {
    tickets: HashMap<TicketId, Ticket>,
    counter: u64,
}

#[derive(Clone, Copy, Debug, PartialEq)]
pub struct TicketId(u64);

#[derive(Clone, Debug, PartialEq)]
pub struct Ticket {
    pub id: TicketId,
    pub title: TicketTitle,
    pub description: TicketDescription,
    pub status: Status,
}

#[derive(Clone, Debug, PartialEq, Eq)]
pub struct TicketDraft {
    pub title: TicketTitle,
    pub description: TicketDescription,
}

#[derive(Clone, Debug, Copy, PartialEq, Eq)]
pub enum Status {
    ToDo,
    InProgress,
    Done,
}

impl TicketStore {
    pub fn new() -> Self {
        Self {
            tickets: todo!(),
            counter: 0,
        }
    }

    pub fn add_ticket(&mut self, ticket: TicketDraft) -> TicketId {
        let id = TicketId(self.counter);
        self.counter += 1;
        let ticket = Ticket {
            id,
            title: ticket.title,
            description: ticket.description,
            status: Status::ToDo,
        };
        todo!();
        id
    }

    pub fn get(&self, id: TicketId) -> Option<&Ticket> {
        todo!()
    }

    pub fn get_mut(&mut self, id: TicketId) -> Option<&mut Ticket> {
        todo!()
    }
}

impl Index<TicketId> for TicketStore {
    type Output = Ticket;

    fn index(&self, index: TicketId) -> &Self::Output {
        self.get(index).unwrap()
    }
}

impl Index<&TicketId> for TicketStore {
    type Output = Ticket;

    fn index(&self, index: &TicketId) -> &Self::Output {
        &self[*index]
    }
}

impl IndexMut<TicketId> for TicketStore {
    fn index_mut(&mut self, index: TicketId) -> &mut Self::Output {
        self.get_mut(index).unwrap()
    }
}

impl IndexMut<&TicketId> for TicketStore {
    fn index_mut(&mut self, index: &TicketId) -> &mut Self::Output {
        &mut self[*index]
    }
}

#[cfg(test)]
mod tests {
    use crate::{Status, TicketDraft, TicketStore};
    use ticket_fields::test_helpers::{ticket_description, ticket_title};

    #[test]
    fn works() {
        let mut store = TicketStore::new();

        let draft = TicketDraft {
            title: ticket_title(),
            description: ticket_description(),
        };
        let id = store.add_ticket(draft.clone());
        let ticket = &store[id];
        assert_eq!(draft.title, ticket.title);
        assert_eq!(draft.description, ticket.description);
        assert_eq!(ticket.status, Status::ToDo);

        let ticket = &mut store[id];
        ticket.status = Status::InProgress;

        let ticket = &store[id];
        assert_eq!(ticket.status, Status::InProgress);
    }
}
```
</details>

`TicketStore` の定義部に今回の問題要旨が詰まっています。

```rust:Rust
#[derive(Clone)]
pub struct TicketStore {
    tickets: HashMap<TicketId, Ticket>,
    counter: u64,
}
```

`Ticket` を保存しておくフィールド `tickets` が `Vec<Ticket>` から `HashMap<TicketId, Ticket>` に変わっています！

[前々回](https://qiita.com/namn1125/items/a9acc7dc2ca7c4c55cc3) にて「 `O(N)` アクセスになっている件は次回以降の話題」と話しましたが、今回がその話題になります。[ハッシュテーブル](https://ja.wikipedia.org/wiki/%E3%83%8F%E3%83%83%E3%82%B7%E3%83%A5%E3%83%86%E3%83%BC%E3%83%96%E3%83%AB) を用いることで、 `tickets` へのアクセス(理想)時間計算量を `O(1)` にしましょう！

## 解説

改変するところが多いですが、難しい部分はないです。 `TicketId` には `Eq` トレイト及び `Hash` トレイトを実装する必要があるため、その記述の追加と、その他は [`HashMap`](https://doc.rust-lang.org/std/collections/struct.HashMap.html) に存在するメソッドに既存の実装を差し替える形になります。

```diff_rust:lib.rs (超抜粋)
+ use std::hash::Hash;

- #[derive(Clone, Copy, Debug, PartialEq)]
+ #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct TicketId(u64);

// 以降は todo!() 埋め

impl TicketStore {
    pub fn new() -> Self {
        Self {
            tickets: HashMap::new(),
            counter: 0,
        }
    }

    pub fn add_ticket(&mut self, ticket: TicketDraft) -> TicketId {
        let id = TicketId(self.counter);
        self.counter += 1;
        let ticket = Ticket {
            id,
            title: ticket.title,
            description: ticket.description,
            status: Status::ToDo,
        };
        self.tickets.insert(id, ticket);
        id
    }

    pub fn get(&self, id: TicketId) -> Option<&Ticket> {
        self.tickets.get(&id)
    }

    pub fn get_mut(&mut self, id: TicketId) -> Option<&mut Ticket> {
        self.tickets.get_mut(&id)
    }
}
```

[`Eq` トレイト](https://doc.rust-lang.org/std/cmp/trait.Eq.html)は、語弊を恐れずに言うと(マサカリ要素)、「 `a == b` なら `b == a` が成り立つ」、みたいな、 `PartialEq` の実装が直感に反しないものになっていること[^relation]を保証するよ、というマーカートレイトです。 [`Hash` トレイト](https://doc.rust-lang.org/std/hash/trait.Hash.html) は今回の場合 `TicketId` を何かしらのハッシュ関数に掛けられることを示します。この両トレイトを実装していないと、ハッシュテーブルのキーとなる型になる資格はありません！ハッシュテーブルの仕組みを考えると当然に見えますね。

[^relation]: 本来なら数学の[二項関係](https://mathlandscape.com/binary-relations/)とかの話をするべきなのかもしれませんが、正直「数学的な関係を満たしている」というのもそれはそれで誤りな理解な気がしていて、あえて「直感に反しない」という表現にしました。マサカリはコメント欄へ

そしてこの両トレイトを意図通り実装していても多分抜ける `HashMap` のキーの必要条件に「要素が『等しい』なら、ハッシュ同士を比較しても『等し』くなければならない」というものがあります[^hash]。まぁ `Eq` トレイトを要求している時点で「変な実装はするなよ」という牽制になっているでしょうから気にする必要はないかもしれませんが、例えば「比較に際して構造体が持つ特定のフィールドを無視する」といったことをすると(それでも対称律等は守られるはずです。)、ハッシュの実装にて全フィールドを見るようにしてしまっている場合結果に不整合が出ることになります。こういったヘンテコな実装が必要な場合、 `PartialEq` の手動実装と `Hash` の手動実装は不整合が起きないように気を配る必要がありそうです。

[^hash]: 筆者は[book](https://rust-exercises.com/100-exercises/06_ticket_management/15_hashmap)を誤読していましたが、逆(ハッシュが同一でも全体は等しくない)は別に問題ないようです。

# [[06_ticket_management/16_btreemap](https://github.com/mainmatter/100-exercises-to-learn-rust/blob/main/exercises/06_ticket_management/16_btreemap/src/lib.rs)] 順序・BTreeMap (順序付き辞書型)

- [6.16. BTreeMap](https://rust-exercises.com/06_ticket_management/16_btreemap)

問題はこちらです。

```rust:lib.rs (重要な部分のみ抜粋)
// TODO: Replace `todo!()`s with the correct implementation.
//  Implement `IntoIterator` for `&TicketStore`. The iterator should yield immutable
//  references to the tickets, ordered by their `TicketId`.
//  Implement additional traits on `TicketId` if needed.

use std::collections::BTreeMap;
use std::ops::{Index, IndexMut};
use ticket_fields::{TicketDescription, TicketTitle};

#[derive(Clone)]
pub struct TicketStore {
    tickets: BTreeMap<TicketId, Ticket>,
    counter: u64,
}

// ...省略...

#[cfg(test)]
mod tests {
    use crate::{Status, TicketDraft, TicketId, TicketStore};
    use ticket_fields::test_helpers::{ticket_description, ticket_title};

    #[test]
    fn works() {
        // ...省略...

        let ids: Vec<TicketId> = (&store).into_iter().map(|t| t.id).collect();
        let sorted_ids = {
            let mut v = ids.clone();
            v.sort();
            v
        };
        assert_eq!(ids, sorted_ids);
    }
}
```

<details><summary>問題ソースコード全体</summary>

```rust:lib.rs
// TODO: Replace `todo!()`s with the correct implementation.
//  Implement `IntoIterator` for `&TicketStore`. The iterator should yield immutable
//  references to the tickets, ordered by their `TicketId`.
//  Implement additional traits on `TicketId` if needed.

use std::collections::BTreeMap;
use std::ops::{Index, IndexMut};
use ticket_fields::{TicketDescription, TicketTitle};

#[derive(Clone)]
pub struct TicketStore {
    tickets: BTreeMap<TicketId, Ticket>,
    counter: u64,
}

#[derive(Clone, Copy, Debug, PartialEq)]
pub struct TicketId(u64);

#[derive(Clone, Debug, PartialEq)]
pub struct Ticket {
    pub id: TicketId,
    pub title: TicketTitle,
    pub description: TicketDescription,
    pub status: Status,
}

#[derive(Clone, Debug, PartialEq, Eq)]
pub struct TicketDraft {
    pub title: TicketTitle,
    pub description: TicketDescription,
}

#[derive(Clone, Debug, Copy, PartialEq, Eq)]
pub enum Status {
    ToDo,
    InProgress,
    Done,
}

impl TicketStore {
    pub fn new() -> Self {
        Self {
            tickets: todo!(),
            counter: 0,
        }
    }

    pub fn add_ticket(&mut self, ticket: TicketDraft) -> TicketId {
        let id = TicketId(self.counter);
        self.counter += 1;
        let ticket = Ticket {
            id,
            title: ticket.title,
            description: ticket.description,
            status: Status::ToDo,
        };
        todo!();
        id
    }

    pub fn get(&self, id: TicketId) -> Option<&Ticket> {
        todo!()
    }

    pub fn get_mut(&mut self, id: TicketId) -> Option<&mut Ticket> {
        todo!()
    }
}

impl Index<TicketId> for TicketStore {
    type Output = Ticket;

    fn index(&self, index: TicketId) -> &Self::Output {
        self.get(index).unwrap()
    }
}

impl Index<&TicketId> for TicketStore {
    type Output = Ticket;

    fn index(&self, index: &TicketId) -> &Self::Output {
        &self[*index]
    }
}

impl IndexMut<TicketId> for TicketStore {
    fn index_mut(&mut self, index: TicketId) -> &mut Self::Output {
        self.get_mut(index).unwrap()
    }
}

impl IndexMut<&TicketId> for TicketStore {
    fn index_mut(&mut self, index: &TicketId) -> &mut Self::Output {
        &mut self[*index]
    }
}

#[cfg(test)]
mod tests {
    use crate::{Status, TicketDraft, TicketId, TicketStore};
    use ticket_fields::test_helpers::{ticket_description, ticket_title};

    #[test]
    fn works() {
        let mut store = TicketStore::new();

        let n_tickets = 5;

        for i in 0..n_tickets {
            let draft = TicketDraft {
                title: ticket_title(),
                description: ticket_description(),
            };
            let id = store.add_ticket(draft.clone());
            let ticket = &store[id];
            assert_eq!(draft.title, ticket.title);
            assert_eq!(draft.description, ticket.description);
            assert_eq!(ticket.status, Status::ToDo);

            let ticket = &mut store[id];
            ticket.status = Status::InProgress;

            let ticket = &store[id];
            assert_eq!(ticket.status, Status::InProgress);
        }

        let ids: Vec<TicketId> = (&store).into_iter().map(|t| t.id).collect();
        let sorted_ids = {
            let mut v = ids.clone();
            v.sort();
            v
        };
        assert_eq!(ids, sorted_ids);
    }
}
```
</details>

先程は `HashMap` にしていた部分を [`BTreeMap`](https://doc.rust-lang.org/std/collections/struct.BTreeMap.html) (B木[^btree]式のマップ) に差し替えた問題となっています！

[^btree]: 競プロ雑魚なので間違った理解をしているかもしれませんが、二分探索とかがしやすい木と認識していました。

```rust:Rust
#[derive(Clone)]
pub struct TicketStore {
    tickets: BTreeMap<TicketId, Ticket>,
    counter: u64,
}
```

## 解説

先程の問題とほぼ同じです！ `TicketId` に対して `Hash` の代わりに `PartialOrd, Ord` を付ける点と、 `IntoIterator` トレイトを実装している点が異なります。

```diff_rust:lib.rs
- #[derive(Clone, Copy, Debug, PartialEq)]
+ #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub struct TicketId(u64);

// HashMap のエクササイズと同様の改変を加えた上で、更にIntoIteratorについて

use std::array::IntoIter;

impl<'a> IntoIterator for &'a TicketStore {
    type Item = &'a Ticket;
    type IntoIter = Values<'a, TicketId, Ticket>;

    fn into_iter(self) -> Self::IntoIter {
        self.tickets.values()
    }
}
```

二分探索等で要素にアクセスするようになるがゆえに、ハッシュ値を取らないため `Hash` がなくなっています。平均アクセス時間計算量が `O(1)` からは離れましたが、順番を維持したまま要素を保存し、かつ高速にアクセスできるということで配列とハッシュテーブルの良いとこ取りになっていますね！ `HashMap` と合わせて使いこなせるようになりたいデータ構造です。

では次の問題に行きましょう！

次の記事: [【27】 スレッド・'staticライフタイム ～並列処理に見るRustの恩恵～](https://qiita.com/namn1125/items/5a4a09172834063268b1)