Rust: مجموعات Rust: نظرة تفصيلية على HashMap و HashSet

آخر تحديث: 2026-08-26

تُعدّ HashMap وHashSet المجموعات القائمة على التجزئة الأكثر استخدامًا في المكتبة القياسية لـ Rust — حيث تُخزّن HashMap التعيينات بين المفاتيح والقيم، بينما تُخزّن HashSet مجموعة من العناصر الفريدة.

إذا كان الهدف من Vec هو «تخزين العناصر بترتيب»، فإن الهدف من HashMap هو «البحث عن العناصر بالاسم» — فلا داعي لتذكر الرقم التسلسلي؛ ما عليك سوى معرفة المفتاح. أما بالنسبة لـ HashSet، فهو يقدم الإجابة القاطعة على السؤال: «هل هذا العنصر موجود؟»


1. ما ستتعلمه



2. قصة نظام التصويت

(1) الألم: استخدام جهازي Vec لتخزين عدد التذاكر

تعمل آنا على تطوير نظام تصويت للفصل، وتحتاج إلى حساب عدد الأصوات التي يحصل عليها كل مرشح.

في البداية، استخدمت كائنين من نوع Vec:

RUST
let mut candidates = Vec::new();
let mut votes = Vec::new();

candidates.push("Alice");
votes.push(0);

candidates.push("Bob");
votes.push(0);

// Vote for Alice
let pos = candidates.iter().position(|&c| c == "Alice").unwrap();
votes[pos] += 1;

// Search Bob the number of votes
let pos = candidates.iter().position(|&c| c == "Bob").unwrap();
println!("Bob the number of votes: {}", votes[pos]);

تنطوي إدارة البيانات باستخدام متجهين متوازيين على مشكلة واضحة: الحفاظ على تزامن المتجهين أمر هش — فمن السهل نسيان تحديث أحد المتجهين عند إضافة مرشحين أو حذفهم. علاوة على ذلك، يتطلب البحث عن مرشح عملية بحث خطية من الدرجة O(n)، والتي تتباطأ مع زيادة عدد المرشحين. من حيث سهولة قراءة الكود، فإن العلاقة بين candidates[i] وvotes[i] غير صريحة، مما يجعل من الصعب على المطورين الجدد فهمها.

(2) نهج Rust HashMap

RUST
use std::collections::HashMap;

fn main() {
    let mut votes = HashMap::new();

    // Vote for a candidate
    *votes.entry("Alice").or_insert(0) += 1;
    *votes.entry("Bob").or_insert(0) += 1;
    *votes.entry("Alice").or_insert(0) += 1;  // Re-submit Alice
    *votes.entry("Charlie").or_insert(0) += 1;

    // Check the number of votes
    for (candidate, count) in &votes {
        println!("{}: {} votes", candidate, count);
    }

    // Search for a Specific Candidate
    println!("Alice the number of votes: {}", votes.get("Alice").unwrap());
}

الناتج:

TEXT 📖 للعرض فقط
Alice: 2 votes
Bob: 1 votes
Charlie: 1 votes
Alice the number of votes: 2

HashMap هو جدول ربط بين المفاتيح والقيم: key -> value. تتعامل واجهة برمجة التطبيقات entry ببراعة مع الحالات التي تنص على «إذا لم يكن المفتاح موجودًا، فقم بإدراج القيمة الافتراضية؛ وإذا كان موجودًا، فقم بتحديثه». وتقوم الطريقة get بالبحث عن المفاتيح في تعقيد زمني O(1). ولم تعد هناك حاجة إلى الحفاظ على مصفوفتين متزامنتين من نوع Vec.



3. نظرة عامة على HashMap وHashSet

(1) خريطة مفاهيمية

100%
graph TB
    A[Hash-Based Sets] --> B[HashMap<K, V>]
    A --> C[HashSet<T>]
    B --> B1[insert: Insert a key-value pair]
    B --> B2[get: Retrieving a Value by Key]
    B --> B3[entry: Elegant Insertion/Update]
    B --> B4[remove: Delete a key-value pair]
    B --> B5[contains_key: Check if a key exists]
    B --> B6[iter: Iterate through all key-value pairs]
    C --> C1[insert: Add an element]
    C --> C2[contains: Check if an element is included]
    C --> C3[union: Union Operation]
    C --> C4[intersection: Set Intersection]
    C --> C5[difference: Difference Set Operations]
    C --> C6[symmetric_difference: Symmetric difference set]

(2) مقارنة أنواع المجموعات

خاصية Vec<T> HashMap<K, V> HashSet<T>
التخزين التسلسلات المرتبة أزواج القيم والمفاتيح غير المرتبة العناصر الفريدة غير المرتبة
البحث البحث الخطي O(n) البحث التجزئي O(1) البحث التجزئي O(1)
الإدراج O(1) الإضافة إلى النهاية O(1) في المتوسط O(1) في المتوسط
إزالة التكرارات الفحص اليدوي الإزالة التلقائية للتكرارات في المفاتيح الإزالة التلقائية للتكرارات في العناصر
الذاكرة منخفضة (تخزين متجاور) متوسطة (عبء جدول التجزئة) متوسطة (عبء جدول التجزئة)
حالات الاستخدام الوصول التسلسلي، مجموعات البيانات الصغيرة التعيين بين المفتاح والقيمة، عمليات البحث السريعة عمليات المجموعات، إزالة التكرار

(3) مرجع سريع لأساليب HashMap الشائعة

الطريقة نوع القيمة المرجعة الوصف
insert(k, v) Option<V> إدراج زوج من المفتاح والقيمة وإرجاع القيمة القديمة
get(&k) Option<&V> البحث حسب المفتاح
get_mut(&k) Option<&mut V> البحث عن مراجع المتغيرات حسب المفتاح
remove(&k) Option<V> حذف زوج مفتاح-قيمة وإرجاع القيمة المحذوفة
contains_key(&k) bool هل المفتاح موجود؟
entry(k) Entry<K,V> استرداد سجل لإدراجه/تحديثه
keys() Keys<K,V> التمرير عبر جميع المفاتيح
values() Values<K,V> التكرار عبر جميع القيم
len() usize عدد أزواج المفتاح والقيمة
is_empty() bool هل هو فارغ؟
clear() () مسح جميع أزواج المفاتيح والقيم
drain() Drain<K,V> إزالة جميع أزواج المفاتيح والقيم وإرجاعها

(4) عمليات المجموعات في HashSet

العملية الطريقة الرمز الرياضي الوصف
الاتحاد union(&other) A ∪ B جميع عناصر المجموعتين
التقاطع intersection(&other) A ∩ B العناصر المشتركة بين المجموعتين
الفرق بين المجموعتين difference(&other) A - B العناصر الموجودة في A ولكنها غير موجودة في B
الفرق في التناظر symmetric_difference(&other) A △ B العناصر الموجودة في مجموعة واحدة فقط
المجموعة الفرعية is_subset(&other) A ⊆ B جميع عناصر A موجودة في B
مجموعة فائقة is_superset(&other) A ⊇ B A تحتوي على جميع عناصر B


4. أمثلة على HashMap و HashSet

(1) ▶ المثال:واجهة برمجة التطبيقات (API) الأساسية لـ HashMap — نظام فرز الأصوات (مستوى الصعوبة ⭐⭐)

RUST
// ============================================
// Voting Tally System:Display HashMap Basic API
// ============================================

use std::collections::HashMap;

fn main() {
    // Create a new empty HashMap
    let mut vote_counts: HashMap<String, u32> = HashMap::new();

    // --- insert ---
    // Insert key-value pairs (overwrites existing value)
    vote_counts.insert(String::from("Alice"), 0);
    vote_counts.insert(String::from("Bob"), 0);
    vote_counts.insert(String::from("Charlie"), 0);

    println!("After initial insert:");
    print_votes(&vote_counts);

    // --- get ---
    // Get a value by key (returns Option<&V>)
    let alice_votes = vote_counts.get("Alice");
    match alice_votes {
        Some(count) => println!("Alice's votes (via get): {}", count),
        None => println!("Alice not found"),
    }

    // --- entry API ---
    // The idiomatic way: insert or update
    // entry() returns an Entry enum, or_insert() inserts default if missing
    println!("\n--- Voting round ---");
    let candidates = ["Alice", "Bob", "Alice", "Charlie", "Alice", "Bob", "David"];
    for name in &candidates {
        let count = vote_counts.entry(String::from(*name)).or_insert(0);
        *count += 1;
        println!("Voted for {} (total: {})", name, count);
    }

    // --- contains_key ---
    println!("\n--- Checking candidates ---");
    for name in &["Alice", "David", "Eve"] {
        if vote_counts.contains_key(*name) {
            println!("{} is a candidate with {} votes", name, vote_counts.get(*name).unwrap());
        } else {
            println!("{} is NOT a candidate", name);
        }
    }

    // --- len and is_empty ---
    println!("\nTotal candidates: {}", vote_counts.len());
    println!("Is empty: {}", vote_counts.is_empty());

    // --- Final results ---
    println!("\n--- Final Results ---");
    print_votes(&vote_counts);
}

fn print_votes(votes: &HashMap<String, u32>) {
    // Note: HashMap iteration order is NOT guaranteed
    for (name, count) in votes {
        println!("  {}: {} votes", name, count);
    }
}

الناتج:

TEXT 📖 للعرض فقط
After initial insert:
  Alice: 0 votes
  Charlie: 0 votes
  Bob: 0 votes

Alice's votes (via get): 0

--- Voting round ---
Voted for Alice (total: 1)
Voted for Bob (total: 1)
Voted for Alice (total: 2)
Voted for Charlie (total: 1)
Voted for Alice (total: 3)
Voted for Bob (total: 2)
Voted for David (total: 1)

--- Checking candidates ---
Alice is a candidate with 3 votes
David is a candidate with 1 votes
Eve is NOT a candidate

Total candidates: 4
Is empty: false

--- Final Results ---
  Alice: 3 votes
  Charlie: 1 votes
  David: 1 votes
  Bob: 2 votes

entry(key).or_insert(default) هي الطريقة الاصطلاحية الأكثر شيوعًا لاستخدام HashMap — فإذا لم يكن المفتاح موجودًا، فإنها تُدرج قيمة افتراضية وتُرجع مرجعًا إليها؛ أما إذا كان المفتاح موجودًا، فإنها تُرجع ببساطة مرجعًا إليه. وبالاقتران مع *count += 1، تتيح لك هذه الطريقة تنفيذ عملية «إدراج أو تحديث» في سطر واحد. تُرجع get Option<&V> ولا تتسبب أبدًا في حدوث حالة ذعر.


(2) ▶ المثال:قواعد الملكية في HashMap وأنواع القيم (مستوى الصعوبة ⭐⭐⭐)

RUST
// ============================================
// HashMap Ownership Rules:What types are eligible? key/value
// ============================================

use std::collections::HashMap;

#[derive(Debug, Hash, Eq, PartialEq)]
struct ProductId(u32);

#[derive(Debug, Clone)]
struct Product {
    name: String,
    price: f64,
    stock: u32,
}

fn main() {
    // --- Rule 1: Owned types as keys ---
    // String (owned) can be a key; &str (borrowed) needs lifetime management
    let mut inventory: HashMap<String, Product> = HashMap::new();

    let product = Product {
        name: String::from("Rust Book"),
        price: 29.99,
        stock: 100,
    };

    // insert takes ownership of key and value
    inventory.insert(String::from("RB-001"), product);
    // println!("{:?}", product);  // ❌ product was moved into the HashMap

    // --- Rule 2: Inserting a reference ---
    // Borrowed keys need lifetime annotations on the HashMap
    // This works because the string literals have 'static lifetime
    let mut lookup: HashMap<&str, u32> = HashMap::new();
    lookup.insert("apple", 5);
    lookup.insert("banana", 3);
    println!("Lookup table: {:?}", lookup);

    // --- Rule 3: Getting values returns references ---
    // get() returns Option<&V>, not V
    let stock_ref = inventory.get("RB-001");
    match stock_ref {
        Some(p) => println!("Product: {}, price: {}", p.name, p.price),
        None => println!("Not found"),
    }
    // inventory is still valid (we only borrowed)

    // --- Rule 4: Custom types as keys ---
    // Keys must implement Eq + Hash
    let mut product_map: HashMap<ProductId, String> = HashMap::new();
    product_map.insert(ProductId(1), String::from("Laptop"));
    product_map.insert(ProductId(2), String::from("Mouse"));

    // --- Rule 5: Updating values with get_mut ---
    // get_mut() returns Option<&mut V> for mutable access
    if let Some(product) = inventory.get_mut("RB-001") {
        product.stock -= 1;  // Sell one unit
        println!("Updated stock: {}", product.stock);
    }

    // --- Rule 6: The entry API for sophisticated updates ---
    let mut word_count: HashMap<String, u32> = HashMap::new();
    let text = "hello world hello rust hello again";

    for word in text.split_whitespace() {
        // or_insert returns &mut V, which we dereference and increment
        let counter = word_count.entry(String::from(word)).or_insert(0);
        *counter += 1;
    }
    println!("\nWord count: {:?}", word_count);

    // Advanced: modify entry with and_modify + or_insert
    let mut scores: HashMap<String, u32> = HashMap::new();
    for team in &["red", "blue", "red", "green", "blue", "red"] {
        scores.entry(String::from(*team))
            .and_modify(|count| *count += 1)  // if exists, increment
            .or_insert(1);                     // if not, insert 1
    }
    println!("Scores: {:?}", scores);
}

الناتج:

TEXT 📖 للعرض فقط
Lookup table: {"banana": 3, "apple": 5}
Product: Rust Book, price: 29.99
Updated stock: 99

Word count: {"again": 1, "hello": 2, "rust": 1, "world": 1}
Scores: {"green": 1, "blue": 2, "red": 3}

قواعد ملكية HashMap: عند الإدراج، يتم نقل ملكية المفتاح والقيمة إلى HashMap. تُرجع get مرجعًا (&V) ولا تنقل الملكية. يجب أن ينفذ نوع المفتاح السمة Eq + Hash (تنفذ الأنواع الأولية وString هذه السمة افتراضيًّا). تعد الاستدعاءات المتسلسلة entry + and_modify + or_insert نمطًا أنيقًا فريدًا من نوعه في لغة Rust.


(3) ▶ المثال:إزالة التكرارات من HashSet وعمليات المجموعات (مستوى الصعوبة ⭐⭐)

RUST
// ============================================
// HashSet:Remove duplicates、Intersection、Union、Difference Set Operations
// ============================================

use std::collections::HashSet;

fn main() {
    // --- Basic HashSet: deduplication ---
    println!("--- HashSet Deduplication ---");
    let mut unique_numbers: HashSet<i32> = HashSet::new();

    let numbers = [3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5];
    for &n in &numbers {
        unique_numbers.insert(n);
    }
    println!("Original: {:?}", &numbers[..]);
    println!("Unique: {:?}", unique_numbers);
    println!("Count: {} (original: {})", unique_numbers.len(), numbers.len());

    // --- contains ---
    println!("\n--- Contains Check ---");
    for &n in &[1, 7, 9] {
        if unique_numbers.contains(&n) {
            println!("{} is in the set", n);
        } else {
            println!("{} is NOT in the set", n);
        }
    }

    // --- Set operations ---
    println!("\n--- Set Operations ---");

    let set_a: HashSet<i32> = [1, 2, 3, 4, 5].iter().cloned().collect();
    let set_b: HashSet<i32> = [4, 5, 6, 7, 8].iter().cloned().collect();

    println!("Set A: {:?}", set_a);
    println!("Set B: {:?}", set_b);

    // Union: elements in A OR B
    let union: HashSet<&i32> = set_a.union(&set_b).collect();
    println!("Union (A ∪ B): {:?}", union);

    // Intersection: elements in A AND B
    let intersection: HashSet<&i32> = set_a.intersection(&set_b).collect();
    println!("Intersection (A ∩ B): {:?}", intersection);

    // Difference: elements in A but NOT in B
    let diff_ab: HashSet<&i32> = set_a.difference(&set_b).collect();
    println!("Difference (A - B): {:?}", diff_ab);

    let diff_ba: HashSet<&i32> = set_b.difference(&set_a).collect();
    println!("Difference (B - A): {:?}", diff_ba);

    // Symmetric difference: elements in A or B but NOT both
    let sym_diff: HashSet<&i32> = set_a.symmetric_difference(&set_b).collect();
    println!("Symmetric Difference: {:?}", sym_diff);

    // --- Practical example: finding common friends ---
    println!("\n--- Practical: Common Friends ---");

    let alice_friends: HashSet<&str> =
        ["Bob", "Charlie", "David", "Eve"].iter().cloned().collect();
    let bob_friends: HashSet<&str> =
        ["Alice", "Charlie", "Eve", "Frank"].iter().cloned().collect();

    println!("Alice's friends: {:?}", alice_friends);
    println!("Bob's friends: {:?}", bob_friends);

    // Mutual friends (intersection)
    let mutual: HashSet<&&str> = alice_friends.intersection(&bob_friends).collect();
    println!("Mutual friends: {:?}", mutual);

    // Friends only Alice knows (difference)
    let alice_only: HashSet<&&str> = alice_friends.difference(&bob_friends).collect();
    println!("Only Alice knows: {:?}", alice_only);

    // All unique friends (union)
    let all_friends: HashSet<&&str> = alice_friends.union(&bob_friends).collect();
    println!("All unique friends: {:?}", all_friends);
}

الناتج:

TEXT 📖 للعرض فقط
--- HashSet Deduplication ---
Original: [3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5]
Unique: {3, 2, 1, 6, 4, 9, 5}
Count: 7 (original: 11)

--- Contains Check ---
1 is in the set
7 is NOT in the set
9 is in the set

--- Set Operations ---
Set A: {2, 3, 4, 5, 1}
Set B: {4, 7, 6, 5, 8}
Union (A ∪ B): {7, 2, 3, 6, 4, 5, 1, 8}
Intersection (A ∩ B): {4, 5}
Difference (A - B): {2, 3, 1}
Difference (B - A): {6, 7, 8}
Symmetric Difference: {1, 2, 3, 6, 7, 8}

--- Practical: Common Friends ---
Alice's friends: {"Charlie", "David", "Eve", "Bob"}
Bob's friends: {"Charlie", "Frank", "Alice", "Eve"}
Mutual friends: {"Charlie", "Eve"}
Only Alice knows: {"David", "Bob"}
All unique friends: {"Charlie", "David", "Frank", "Alice", "Eve", "Bob"}

العمليات الأربع الرئيسية للمجموعات في HashSet: union (التوحيد — جميع العناصر)، intersection (التقاطع — العناصر المشتركة)، difference (الفرق — العناصر الموجودة في A ولكنها غير موجودة في B)، symmetric_difference (الفرق المتماثل — العناصر غير الموجودة في أي من المجموعتين). تُرجع هذه الطرق مُكررات؛ وتحتاج إلى استخدام .collect() لتجميع النتائج في HashSet جديد.


(4) ▶ المثال:التكرار عبر HashMap واستراتيجيات الاختيار للمجموعات (مستوى الصعوبة ⭐⭐)

RUST
// ============================================
// Iterate HashMap + Comparison of Set Selection Strategies
// ============================================

use std::collections::HashMap;

fn main() {
    // --- Build a sample dataset ---
    let mut sales: HashMap<String, f64> = HashMap::new();
    sales.insert(String::from("Laptop"), 1200.0);
    sales.insert(String::from("Mouse"), 25.0);
    sales.insert(String::from("Keyboard"), 80.0);
    sales.insert(String::from("Monitor"), 350.0);
    sales.insert(String::from("Headphones"), 150.0);

    // --- Method 1: Iterate over key-value pairs ---
    println!("--- All Products (iter) ---");
    for (product, revenue) in &sales {
        println!("  {}: ${:.2}", product, revenue);
    }

    // --- Method 2: Iterate over keys only ---
    println!("\n--- Product Names (keys) ---");
    for product in sales.keys() {
        println!("  - {}", product);
    }

    // --- Method 3: Iterate over values only ---
    println!("\n--- Revenue Values (values) ---");
    let total: f64 = sales.values().sum();
    println!("  Total revenue: ${:.2}", total);
    println!("  Average: ${:.2}", total / sales.len() as f64);

    // --- Method 4: Mutable iteration over values ---
    println!("\n--- Apply 10% Discount (values_mut) ---");
    for revenue in sales.values_mut() {
        *revenue *= 0.9;  // Apply 10% discount
    }
    for (product, revenue) in &sales {
        println!("  {}: ${:.2}", product, revenue);
    }

    // --- Method 5: drain to consume the HashMap ---
    let mut backup = sales.clone();
    println!("\n--- Drain (consumes HashMap) ---");
    while let Some((product, revenue)) = backup.drain().next() {
        println!("  Removed: {} (${:.2})", product, revenue);
    }
    println!("  backup is empty: {}", backup.is_empty());

    // --- When to use what: Collection selection guide ---
    println!("\n--- Collection Selection Guide ---");

    // Scenario 1: Vec (ordered, indexed access)
    let mut todo_list: Vec<&str> = Vec::new();
    todo_list.push("Buy milk");
    todo_list.push("Write report");
    todo_list.push("Call mom");
    println!("Vec (ordered todo list):");
    for (i, item) in todo_list.iter().enumerate() {
        println!("  {}. {}", i + 1, item);
    }

    // Scenario 2: HashMap (key-value lookup)
    let mut phone_book: HashMap<&str, &str> = HashMap::new();
    phone_book.insert("Alice", "123-4567");
    phone_book.insert("Bob", "987-6543");
    println!("HashMap (phone book):");
    println!("  Alice's number: {}", phone_book.get("Alice").unwrap());

    // Scenario 3: HashSet (membership check)
    let mut admin_users: HashSet<&str> = HashSet::new();
    admin_users.insert("admin");
    admin_users.insert("root");
    let user = "admin";
    println!("HashSet (admin check):");
    println!("  Is '{}' admin? {}", user, admin_users.contains(user));
}

// Import HashSet for the last scenario
use std::collections::HashSet;

الناتج:

TEXT 📖 للعرض فقط
--- All Products (iter) ---
  Laptop: $1200.00
  Mouse: $25.00
  Keyboard: $80.00
  Monitor: $350.00
  Headphones: $150.00

--- Product Names (keys) ---
  - Laptop
  - Mouse
  - Keyboard
  - Monitor
  - Headphones

--- Revenue Values (values) ---
  Total revenue: $1805.00
  Average: $361.00

--- Apply 10% Discount (values_mut) ---
  Laptop: $1080.00
  Mouse: $22.50
  Keyboard: $72.00
  Monitor: $315.00
  Headphones: $135.00

--- Drain (consumes HashMap) ---
  Removed: Laptop ($1080.00)
  Removed: Mouse ($22.50)
  Removed: Keyboard ($72.00)
  Removed: Monitor ($315.00)
  Removed: Headphones ($135.00)
  backup is empty: true

--- Collection Selection Guide ---
Vec (ordered todo list):
  1. Buy milk
  2. Write report
  3. Call mom
HashMap (phone book):
  Alice's number: 123-4567
HashSet (admin check):
  Is 'admin' admin? true

طرق التكرار على HashMap: iter() التكرار على جميع أزواج المفاتيح والقيم؛ keys() التكرار على المفاتيح فقط؛ values() التكرار على القيم فقط؛ values_mut() التكرار على القيم بترتيب متغير؛ drain() استهلاك وإزالة جميع العناصر. عند اختيار نوع المجموعة: إذا كنت بحاجة إلى مجموعة مرتبة وقابلة للتكرار مع وصول قائم على الفهرس → استخدم Vec؛ إذا كنت بحاجة إلى تعيين المفاتيح والقيم وعمليات البحث السريعة → استخدم HashMap؛ إذا كنت بحاجة إلى إزالة التكرار وعمليات المجموعة والتحقق من العضوية → استخدم HashSet.


(5) ▶ المثال:تمرين شامل — تحليل تكرار الكلمات وتحليل النص (مستوى الصعوبة ⭐⭐⭐)

RUST
// ============================================
// Comprehensive Example:HashMap + HashSet Text Analysis
// ============================================

use std::collections::{HashMap, HashSet};

fn word_frequency(text: &str) -> HashMap<String, u32> {
    let mut freq: HashMap<String, u32> = HashMap::new();
    for word in text.split_whitespace() {
        let clean: String = word.chars()
            .filter(|c| c.is_alphabetic())
            .map(|c| c.to_lowercase().next().unwrap())
            .collect();
        if !clean.is_empty() {
            *freq.entry(clean).or_insert(0) += 1;
        }
    }
    freq
}

fn unique_words(text: &str) -> HashSet<String> {
    text.split_whitespace()
        .map(|w| w.to_lowercase())
        .collect()
}

fn top_n(freq: &HashMap<String, u32>, n: usize) -> Vec<(&str, u32)> {
    let mut entries: Vec<_> = freq.iter().map(|(k, &v)| (k.as_str(), v)).collect();
    entries.sort_by(|a, b| b.1.cmp(&a.1));
    entries.into_iter().take(n).collect()
}

fn main() {
    let text1 = "the cat sat on the mat and the cat slept on the mat";
    let text2 = "the dog ran on the grass and the dog slept on the rug";

    println!("=== Text 1 Word Frequency ===");
    let freq1 = word_frequency(text1);
    for (word, count) in top_n(&freq1, 5) {
        println!("  '{}': {} times", word, count);
    }

    println!("\n=== Text 2 Word Frequency ===");
    let freq2 = word_frequency(text2);
    for (word, count) in top_n(&freq2, 5) {
        println!("  '{}': {} times", word, count);
    }

    let words1 = unique_words(text1);
    let words2 = unique_words(text2);

    let common: HashSet<_> = words1.intersection(&words2).collect();
    println!("\nCommon Vocabulary: {:?}", common);

    let only1: HashSet<_> = words1.difference(&words2).collect();
    println!("Text1Exclusive: {:?}", only1);

    let only2: HashSet<_> = words2.difference(&words1).collect();
    println!("Text2Exclusive: {:?}", only2);

    let all: HashSet<_> = words1.union(&words2).collect();
    println!("Total number of words: {}", all.len());
}

الناتج:

TEXT 📖 للعرض فقط
=== Text 1 Word Frequency ===
  'the': 3 times
  'cat': 2 times
  'on': 2 times
  'mat': 2 times
  'sat': 1 times

=== Text 2 Word Frequency ===
  'the': 3 times
  'dog': 2 times
  'on': 2 times
  'grass': 1 times
  'ran': 1 times

Common Vocabulary: {"the", "and", "on", "slept"}
Text1Exclusive: {"mat", "cat", "sat"}
Text2Exclusive: {"ran", "rug", "grass", "dog"}

Total number of words: 11

word_frequency قم بالعد بطريقة أنيقة باستخدام طريقة entry().or_insert()؛ unique_words قم بإزالة التكرارات تلقائيًا باستخدام HashSet؛ intersection/difference/union قم بتنفيذ عمليات المجموعات. يُعد استخدام HashMap مع HashSet التركيبة المثالية لتحليل النصوص.



❓ أسئلة شائعة

س ما هي السمة التي يجب أن يستوفيها مفتاح HashMap؟
ج يجب أن يُنفِّذ المفتاح السمة Eq + Hash. وتنفِّذها جميع الأنواع الأولية (i32، u32، String، bool). أما الأنواع المخصصة فتتطلب #[derive(Hash, Eq, PartialEq)]. ولا يُنفذ f64 السمة Eq (لأن NaN != NaN)، لذا لا يمكن استخدامه مباشرة كمفتاح.
س ما الفرق بين واجهة برمجة التطبيقات entry والوظيفة المباشرة insert؟
ج لا تقوم entry بالكتابة فوق القيم الموجودة، بينما تقوم insert بالكتابة فوقها مباشرةً. entry(key).or_insert(value) تُدرج القيمة فقط إذا كان المفتاح غير موجود؛ أما إذا كان موجودًا، فإنها تُرجع مرجعًا إلى القيمة الموجودة. insert تقوم دائمًا بالكتابة فوق القيمة القديمة وتُرجع Option<V> (القيمة القديمة). entry هي الطريقة التقليدية لكتابة "الإدراج أو التحديث".
س هل ترتيب التكرار في HashMap ثابت؟
ج لا، ليس كذلك! ترتيب التكرار في HashMap غير مرتب. وقد يختلف من مرة إلى أخرى. إذا كنت بحاجة إلى تخطيط مرتب للمفاتيح والقيم، فيمكنك استخدام BTreeMap (مرتبة حسب المفتاح). أما إذا كنت تحتاج فقط إلى عمليات بحث سريعة، فإن أداء HashMap الذي يبلغ O(1) هو الأفضل.
س أيهما أكثر كفاءة في إزالة التكرارات: HashSet أم Vec؟
ج HashSet أسرع بكثير عند التعامل مع مجموعات البيانات الكبيرة. تستغرق إزالة التكرارات باستخدام Vec وقتًا من الدرجة O(n²) (نظرًا لأن كل عنصر يجب مقارنته بجميع العناصر السابقة)، في حين أن عملية insert في HashSet لها تعقيد زمني متوسط يبلغ O(1). ومع ذلك، يستهلك HashSet ذاكرة أكبر ولا يحافظ على الترتيب. إذا كنت بحاجة إلى الحفاظ على الترتيب، ففكر في استخدام Vec مع HashSet.
س ما الذي ترجعه الطريقة entry الخاصة بـ HashMap؟
ج ترجع قائمة Entry، التي تحتوي على نوعين: Occupied(Entry) و Vacant(Entry). تقوم or_insert(default) بإدراج قيمة افتراضية وتُرجع مرجعًا عندما تكون الخانة شاغرة، وتُرجع مرجعًا إلى القيمة الموجودة عندما تكون مشغولة. تقوم and_modify(fn) بتعديل القيمة عندما تكون الخانة مشغولة. يمكن تسلسل هذه الطرق.
س متى يجب استخدام HashMap، ومتى يجب استخدام BTreeMap؟
ج استخدم HashMap (O(1)) للبحث السريع، واستخدم BTreeMap (O(log n)) للتجول المرتب. المفاتيح في HashMap غير مرتبة لكن عمليات البحث سريعة؛ أما المفاتيح في BTreeMap فهي مرتبة (على سبيل المثال، تُطبع حسب الترتيب الأبجدي)، لكن عمليات البحث أبطأ قليلاً. إذا كان الترتيب مهمًا، فاستخدم BTreeMap.

📖 ملخص


📝 تمارين

  1. الصعوبة ⭐: أنشئ متغيرًا HashMap<String, u32> لتخزين أسعار الفاكهة ("apple"=5، "banana"=3، "orange"=4). اكتب دالة fn total_cost(items: &[&str], prices: &HashMap<String, u32>) -> u32 لحساب السعر الإجمالي لعربة التسوق. اختبر عربة التسوق ["apple", "banana", "apple"] في الدالة الرئيسية.

  2. الصعوبة ⭐⭐: اكتب دالة fn word_frequency(text: &str) -> HashMap<String, u32> تحسب عدد مرات ظهور كل كلمة في نص ما. استخدم واجهة برمجة التطبيقات entry. في الدالة الرئيسية، اختبر النص "the quick brown fox jumps over the lazy dog the fox" واعرض النتائج.

  3. الصعوبة ⭐⭐⭐: أنشئ قائمة بأسماء الطلاب في فصلين دراسيين (HashSet<&str>). يضم الفصل «أ» الطلاب ["Alice"، "Bob"، "Charlie"، "David"]، بينما يضم الفصل «ب» الطلاب ["Charlie"، "David"، "Eve"، "Frank"]. اكتب دالة fn analyze_classes(a: &HashSet<&str>, b: &HashSet<&str>) تعرض: أسماء الطلاب في كلا الفصلين (التقاطع)، وأسماء الطلاب في الفصل «أ» فقط (الفرق)، وأسماء جميع الطلاب الفريدين (الاتحاد)، وأسماء الطلاب في فصل واحد فقط (الفرق المتماثل). استدعِ الدالة في الدالة الرئيسية واعرض النتائج.

Web-Tutorial.com

فريق Web-Tutorial التقني

منصة دروس برمجية يديرها عدة مطورين. كل درس يتم كتابته ومراجعته بواسطة مطورين متخصصين في المجال. نعمل على ضمان دقة وموثوقية المحتوى — إذا لاحظت أي مشكلة، فيرجى إخبارنا.

100%