404 Not Found

404 Not Found


nginx

一般的なRustのコレクション:VecDeque、BTreeMap、およびパフォーマンス比較

VecDeque は、両端での効率的な挿入と削除をサポートする両端キューです。BTreeMap は、範囲クエリをサポートする順序付きキーバリューマップです。これらは、Rust のコレクションライブラリに含まれる「特化型」の要素です。

Vecが「バスに乗るために列に並んでいる状態」(列の最後尾にしか加われない)だとすれば、VecDequeは「列に割り込む人」(どちらの端からも出入りできる)のようなものです。HashMapが「辞書」(ランダムな検索)のようなものだとすれば、BTreeMapは「連絡先リスト」(アルファベット順に並べられており、特定のページを開いて読み始めることができる)のようなものです。


1. 学習内容


2. 概念図

以下のMermaidによる比較図は、VecDequeという両端キューと、BTreeMapというB木マップのデータ構造の違いを示しています。

100%
graph LR
    subgraph VecDeque["VecDeque Double-ended queue"]
        direction LR
        V1["pop_front O(1)"] --> V2["Element1"]
        V2 --> V3["Element2"]
        V3 --> V4["..."]
        V4 --> V5["ElementN"]
        V5 --> V6["push_back O(1)"]
        V7["push_front O(1)"] -.-> V2
    end

    subgraph BTreeMap["BTreeMap BOrdered Map of Trees"]
        direction TB
        B0["Root Node"] --> B1["Left Branch<br/>Keys: A-M"]
        B0 --> B2["Right Branch<br/>Keys: N-Z"]
        B1 --> B3["(Alice, 95)"]
        B1 --> B4["(Bob, 72)"]
        B2 --> B5["(Charlie, 87)"]
        B2 --> B6["(David, 91)"]
    end

    VecDeque -.-> |Comparison of Data Structures| BTreeMap

3. 待ち行列・チケットシステムの物語

(1) 問題:Vec を使ってキューをシミュレートしているが、列に割り込む操作とキューから離れる操作の両方が遅い

トム(Tom)は、レストラン向けの待ち行列管理システムを開発しています。客は来店時に整理券を受け取り、店員がその番号を呼び出して席に案内します。

当初、彼はVecを使ってこれを実装しました:

RUST
let mut queue = Vec::new();
queue.push("Customer A");   // A Get a ticket,At the back of the line
queue.push("Customer B");   // B Get a ticket,At the back of the line
queue.push("Customer C");   // C Get a ticket,At the back of the line

// Take a seat when your number is called:Remove from the front of the line
let first = queue.remove(0);  // O(n) —— All elements that follow must be moved forward!
println!("{} Take a seat", first);

Vecのremove(0)はO(n)の操作です。最初の要素を削除した後、残りのすべての要素を1つずつ前方へずらす必要があります。列に10,000人が並んでいる場合、番号が呼ばれるたびに9,999個の要素をずらす必要があります。これは明らかに本番環境には適していません。

(2) より複雑な要件:VIP優先 + 番号による検索

さらに悪いことに、そのレストランのマネージャーはこう言った:

Vecは、こうした要件にはまったく対応できません。

(3) Rustのコレクションに関する解決策

RUST
use std::collections::VecDeque;
use std::collections::BTreeMap;

fn main() {
    // VecDeque:Accessible from both the front and the back
    let mut queue = VecDeque::new();
    queue.push_back("Customer A");   // Enter at the back of the line
    queue.push_back("Customer B");
    queue.push_front("VIP");         // VIP Cutting in line at the front of the line!

    println!("Next, please take a seat.: {}", queue.pop_front().unwrap());  // VIP
    println!("Next: {}", queue.pop_front().unwrap());    // Customer A

    // BTreeMap:Ordered Key-Value Store
    let mut customers = BTreeMap::new();
    customers.insert(1001, "Alice");
    customers.insert(1003, "Bob");
    customers.insert(1002, "Charlie");

    // Automatically Sort and Output Buttons
    for (id, name) in &customers {
        println!("Get a ticket #{}: {}", id, name);
    }
    // Output: #1001: Alice, #1002: Charlie, #1003: Bob
}

VecDequeの両端での操作は O(1) ですが、BTreeMap はキーを自動的にソートし、範囲検索をサポートしています。前者は「キューイング」の問題を解決し、後者は「順序付き検索」の問題を解決します。


4. 基本概念

(1) 4つの集合構造の比較

TEXT
graph TB
    A[Rust Collection Library] --> B[Vec: Contiguous Arrays]
    A --> C[VecDeque: Double-ended queue]
    A --> D[HashMap: Hash Table]
    A --> E[BTreeMap: B Ordered Map of Trees]

    B --> F["push / pop: O(1) Amortized"]
    B --> G["insert(0) / remove(0): O(n)"]

    C --> H["push_front / pop_front: O(1)"]
    C --> I["push_back / pop_back: O(1)"]

    D --> J["Insert / Search: O(1) Amortized"]
    D --> K["Disorder —— No order guarantee"]

    E --> L["Insert / Search: O(log n)"]
    E --> M["Orderly —— Support for Range Queries range"]

(2) 4種類のセットの性能比較

操作 Vec VecDeque HashMap BTreeMap
末尾挿入 O(1)(平均) O(1) O(1)(平均) O(log n)
先頭への挿入 O(n) O(1)
インデックスによるアクセス O(1) O(1)
キー検索 平均 O(1) O(log n)
範囲クエリ 未対応 未対応 未対応 O(log n + k)
順序付き探索 ソート済みである必要がある ソート済みである必要がある 順序なし O(n) (ソート済み)
メモリ使用量

(3) 選定戦略

シナリオ おすすめのコレクション 理由
キュー/バッファ(両端での操作) VecDeque 両端とも O(1) — これ以上に適したものはない
キー順の探索が必要 BTreeMap 自動的にソートされるため、追加のソート処理は不要
範囲クエリが必要 BTreeMap 範囲クエリをサポートする唯一のコレクション
テール操作のみが必要 Vec よりシンプルでメモリ効率が良い
キーと値の検索のみが必要 HashMap O(1)の検索速度で、BTreeMapよりも高速
中間の要素の頻繁な挿入・削除 LinkedList 理論上は O(1) だが、実際にはほとんど使われない

(4) VecDeque および BTreeMap の一般的なメソッドのクイックリファレンス

セット メソッド 説明 時間計算量
VecDeque push_front(val) 先頭への挿入 O(1)
VecDeque push_back(val) 末尾への挿入 O(1)
VecDeque pop_front() 配列の先頭要素を取り出す O(1)
VecDeque pop_back() 末尾から取り出す O(1)
VecDeque front() / back() 先頭要素と末尾要素を表示 O(1)
VecDeque len() / is_empty() 長さの問い合わせ O(1)
BTreeMap insert(k, v) キーと値のペアの挿入 O(log n)
BTreeMap get(&k) キーによる検索 O(log n)
BTreeMap remove(&k) キーによる削除 O(log n)
BTreeMap range(start..=end) 範囲検索 O(log n + k)
BTreeMap first_key_value() 最小のキー・値のペア数 O(log n)
BTreeMap last_key_value() キー・値のペアの最大数 O(log n)

5. 模範を示す

(1) ▶ サンプル:VecDeque 両端キュー — キューイングおよびチケット発行システム (難易度 ⭐)

RUST
// ============================================
// VecDeque:Basic Operations on a Double-Ended Queue
// ============================================

use std::collections::VecDeque;

fn main() {
    let mut queue: VecDeque<&str> = VecDeque::new();

    // Regular customers enter from the back of the line
    queue.push_back("Alice");
    queue.push_back("Bob");
    queue.push_back("Charlie");

    // VIP A customer cut in line from the front of the line
    queue.push_front("VIP-David");
    queue.push_front("VIP-Eve");

    println!("Current number of people in line: {}", queue.len());
    println!("At the head of the line: {:?}, Bringing up the rear: {:?}", queue.front(), queue.back());

    // Take a seat when your number is called:Remove from the front of the queue
    println!("\nCall Order:");
    while let Some(customer) = queue.pop_front() {
        println!("  {} Take a seat", customer);
    }
}

出力:

TEXT
Current number of people in line: 5
At the head of the line: Some("VIP-Eve"), Bringing up the rear: Some("Charlie")

Call Order:
  VIP-Eve Take a seat
  VIP-David Take a seat
  Alice Take a seat
  Bob Take a seat
  Charlie Take a seat

push_front()push_back() はどちらも O(1) の操作です。front()back() は、要素を削除することなく、それぞれキューの先頭と末尾への参照を返します。pop_front() は、キューの先頭にある要素を削除して、その要素を返します。


(2) ▶ サンプル:BTreeMap — 順序付きキー・バリュー型データ構造:成績順位(難易度 ⭐⭐)

RUST
// ============================================
// BTreeMap:Automatic Button Sorting
// ============================================

use std::collections::BTreeMap;

fn main() {
    let mut scores = BTreeMap::new();

    scores.insert("Charlie", 87);
    scores.insert("Alice", 95);
    scores.insert("Bob", 72);
    scores.insert("David", 91);

    // BTreeMap Button(Alphabetical order)Automatic Sorting
    println!("=== Transcript(Sort by Name)===");
    for (name, score) in &scores {
        println!("  {}: {}", name, score);
    }

    // Search by Key
    println!("\nAlice the results: {}", scores.get("Alice").unwrap());

    // Check for the presence of
    if scores.contains_key("Eve") {
        println!("Eve There are discrepancies in the scores");
    } else {
        println!("Eve No results yet");
    }

    // Get the first and last entries
    println!("\nFirst: {:?}", scores.first_key_value());
    println!("The last one: {:?}", scores.last_key_value());
}

出力:

TEXT
=== Transcript(Sort by Name)===
  Alice: 95
  Bob: 72
  Charlie: 87
  David: 91

Alice the results: 95
Eve No results yet

First: Some(("Alice", 95))
The last one: Some(("David", 91))

BTreeMap は、キーを Ord の順序で自動的にソートします。文字列のデフォルトのソート順序は辞書順であるため、「Alice」が先頭になり、「David」が最後になります。first_key_value() および last_key_value() を使用すると、先頭要素および末尾要素に効率的にアクセスできます。


(3) ▶ サンプル:BTreeMap の範囲クエリ — 日付範囲クエリ (難易度 ⭐⭐)

RUST
// ============================================
// BTreeMap Range Query:range Methods
// ============================================

use std::collections::BTreeMap;

fn main() {
    // Simulated Order System:Date -> Order Amount
    let mut orders = BTreeMap::new();

    orders.insert("2026-07-01", 120);
    orders.insert("2026-07-03", 85);
    orders.insert("2026-07-05", 200);
    orders.insert("2026-07-07", 150);
    orders.insert("2026-07-10", 95);

    // Search orders placed between July 3 and July 7 (inclusive)
    println!("=== July 3 ~ July 7 Orders ===");
    for (date, amount) in orders.range("2026-07-03"..="2026-07-07") {
        println!("  {}: ${}", date, amount);
    }

    // Search all orders on or after July 5
    println!("\n=== July 5 and After Orders ===");
    for (date, amount) in orders.range("2026-07-05"..) {
        println!("  {}: ${}", date, amount);
    }

    // Total Amount
    let total: i32 = orders.range("2026-07-03"..="2026-07-07")
        .map(|(_, amount)| amount)
        .sum();
    println!("\nJuly 3 ~ July 7 Total Amount: ${}", total);
}

出力:

TEXT
=== July 3 ~ July 7 Orders ===
  2026-07-03: $85
  2026-07-05: $200
  2026-07-07: $150

=== July 5 and After Orders ===
  2026-07-05: $200
  2026-07-07: $150
  2026-07-10: $95

July 3 ~ July 7 Total Amount: $435

range() は BTreeMap 独自のメソッドです。このメソッドは範囲式(開区間を表す ..、閉区間を表す ..=、半開区間を表す start..)を受け取り、イテレータを返します。これは HashMap ではできないことです。


(4) ▶ サンプル:種類のセットのパフォーマンス比較(難易度 ⭐⭐⭐)

RUST
// ============================================
// Comparison of the Performance of Four Types of Sets:Insert 10000 element
// ============================================

use std::collections::{BTreeMap, HashMap, VecDeque};
use std::time::Instant;

fn main() {
    let n = 10_000;

    // Vec Insert at the end
    let start = Instant::now();
    let mut vec = Vec::new();
    for i in 0..n {
        vec.push(i);
    }
    println!("Vec Insert at the end: {:?}", start.elapsed());

    // VecDeque Insert at the end
    let start = Instant::now();
    let mut deque = VecDeque::new();
    for i in 0..n {
        deque.push_back(i);
    }
    println!("VecDeque Insert at the end: {:?}", start.elapsed());

    // HashMap Insert
    let start = Instant::now();
    let mut hmap = HashMap::new();
    for i in 0..n {
        hmap.insert(i, i);
    }
    println!("HashMap Insert: {:?}", start.elapsed());

    // BTreeMap Insert
    let start = Instant::now();
    let mut bmap = BTreeMap::new();
    for i in 0..n {
        bmap.insert(i, i);
    }
    println!("BTreeMap Insert: {:?}", start.elapsed());

    // Search for Performance Comparisons
    println!("\n--- Search Performance ---");

    let start = Instant::now();
    let _ = vec.contains(&9999);
    println!("Vec Linear Search: {:?}", start.elapsed());

    let start = Instant::now();
    let _ = hmap.get(&9999);
    println!("HashMap Search: {:?}", start.elapsed());

    let start = Instant::now();
    let _ = bmap.get(&9999);
    println!("BTreeMap Search: {:?}", start.elapsed());
}

出力(例。実際の時間は機種によって異なる場合があります):

TEXT
Vec Insert at the end: 78.2µs
VecDeque Insert at the end: 82.1µs
HashMap Insert: 1.2ms
BTreeMap Insert: 2.8ms

--- Search Performance ---
Vec Linear Search: 42.5µs
HashMap Search: 138ns
BTreeMap Search: 312ns

パフォーマンスデータによると、検索(O(1))ではHashMapが最も高速で、BTreeMap(O(log n))がそれに続き、Vecは線形検索(O(n))において最も低速であることが示されています。挿入に関しては、末尾挿入ではVecとVecDequeが最も高速で、BTreeMapが最も低速です。これは、「万能の解決策など存在しない」ことを裏付けており、コレクションの選択は具体的なユースケースによって異なるということです。


(5) ▶ サンプル:総合演習—タスクスケジューリングシステム(難易度 ⭐⭐⭐)

RUST
// ============================================
// Comprehensive Example:VecDeque Queue + BTreeMap Priority
// ============================================

use std::collections::{VecDeque, BTreeMap};

#[derive(Debug, Clone)]
struct Task {
    id: u32,
    name: String,
    priority: u8,
}

fn main() {
    let mut queue: VecDeque<Task> = VecDeque::new();
    queue.push_back(Task { id: 1, name: "Check email".into(), priority: 3 });
    queue.push_back(Task { id: 2, name: "Fix bug #42".into(), priority: 1 });
    queue.push_back(Task { id: 3, name: "Write report".into(), priority: 2 });
    queue.push_back(Task { id: 4, name: "Urgent deploy".into(), priority: 0 });

    println!("=== FIFO Queue Processing ===");
    while let Some(task) = queue.pop_front() {
        println!("[P{}] #{}: {}", task.priority, task.id, task.name);
    }

    let mut priority_map: BTreeMap<u8, Vec<String>> = BTreeMap::new();
    let tasks = vec![
        ("Check email", 3u8), ("Fix bug #42", 1), ("Write report", 2),
        ("Urgent deploy", 0), ("Code review", 1), ("Team meeting", 3),
        ("Security patch", 0), ("Update docs", 2),
    ];

    for (name, pri) in tasks {
        priority_map.entry(pri).or_insert_with(Vec::new).push(name.to_string());
    }

    println!("\n=== Sort by priority ===");
    for (pri, task_list) in &priority_map {
        println!("P{}: {:?}", pri, task_list);
    }

    println!("\n=== High-Priority Tasks (P0-P1) ===");
    for (pri, task_list) in priority_map.range(0..=1) {
        println!("P{}: {:?}", pri, task_list);
    }

    let mut history: VecDeque<String> = VecDeque::with_capacity(3);
    for i in 0..5 {
        history.push_back(format!("task_{}", i));
        if history.len() > 3 {
            history.pop_front();
        }
    }
    println!("\nLast 3 history entries: {:?}", history);
}

出力:

TEXT
=== FIFO Queue Processing ===
[P3] #1: Check email
[P1] #2: Fix bug #42
[P2] #3: Write report
[P0] #4: Urgent deploy

=== Sort by priority ===
P0: ["Urgent deploy", "Security patch"]
P1: ["Fix bug #42", "Code review"]
P2: ["Write report", "Update docs"]
P3: ["Check email", "Team meeting"]

=== High-Priority Tasks (P0-P1) ===
P0: ["Urgent deploy", "Security patch"]
P1: ["Fix bug #42", "Code review"]

Last 3 history entries: ["task_2", "task_3", "task_4"]

VecDequeをFIFOキューとして使用する(push_back + pop_front)。BTreeMapは項目を優先度ごとにグループ化し、自動的にソートする。range(0..=1)は優先度の高いタスクのみを表示する。固定容量のVecDequeは、「最新のN件」の履歴機能を実装している。


❓ よくある質問

Q VecDequeとVecの違いは何ですか?VecDequeはどのような場合に使用すべきですか?
A VecDequeは両端での操作をO(1)でサポートしていますが、Vecは末尾での操作のみをO(1)でサポートしています。
Q BTreeMapとHashMapの違いは何ですか?
A BTreeMapはソート済みですが、HashMapはソートされていません。
Q なぜRustではLinkedListがほとんど使われないのですか?
A LinkedListの各ノードには追加のポインタオーバーヘッドが発生し、キャッシュ効率が悪いからです。
Q BTreeMapは、range操作においてページネーションをどのように実装していますか?
A next()nth()を使用して、カーソルベースのページネーションを実装しています。
Q 4種類のコレクションから適切なものを選ぶための簡単な覚え方はありますか?
A はい。「末尾での操作にはVecを、両端での操作にはVecDequeを、順不同の検索にはHashMapを、順不同の検索にはBTreeMapを使う」というものです。

📖 まとめ


📝 練習問題

  1. 難易度 ⭐: VecDeque を使用して「元に戻す履歴」を実装してください。push_back を使用して各操作を記録し、元に戻す際には pop_back を使用して最新の操作を削除してください。3 つの操作を行った後に 2 回元に戻す操作を行うシミュレーションを行い、各操作の内容を出力してください。
  2. 難易度 ⭐⭐:B-Treeマップを用いて「生徒の成績管理システム」を実装してください。5人の生徒の名前と成績を登録し、成績の降順で順位を出力してください(ヒント:B-Treeマップでは値を直接ソートすることはできません。成績をキー、名前を値として扱うか、iter().rev()を使用する必要があります)。
  3. 難易度 ⭐⭐⭐: 整数のスライスを入力として受け取り、VecDeque、HashMap、BTreeMapをそれぞれ使用して各桁の出現回数をカウントする関数 fn analyze_collections(data: &[i32]) を作成し、3つの手法のパフォーマンスの違いを比較してください(時間の測定には std::time::Instant を使用してください)。
Web-Tutorial.com

Web-Tutorial 技術チーム

複数の開発者によって共同維持されているプログラミングチュートリアルプラットフォーム。各チュートリアルは専門分野の開発者が執筆・レビューしています。正確で信頼性の高いコンテンツを目指しています — 問題を見つけた場合はお知らせください。

100%