Rust: مجموعات Rust: نظرة تفصيلية على HashMap و HashSet
آخر تحديث: 2026-08-26
تُعدّ HashMap وHashSet المجموعات القائمة على التجزئة الأكثر استخدامًا في المكتبة القياسية لـ Rust — حيث تُخزّن HashMap التعيينات بين المفاتيح والقيم، بينما تُخزّن HashSet مجموعة من العناصر الفريدة.
إذا كان الهدف من Vec هو «تخزين العناصر بترتيب»، فإن الهدف من HashMap هو «البحث عن العناصر بالاسم» — فلا داعي لتذكر الرقم التسلسلي؛ ما عليك سوى معرفة المفتاح. أما بالنسبة لـ HashSet، فهو يقدم الإجابة القاطعة على السؤال: «هل هذا العنصر موجود؟»
1. ما ستتعلمه
- استخدم أزواج القيم والمفاتيح
HashMap::new()وinsertوget - استخدم واجهة برمجة التطبيقات
entryللتعامل بسلاسة مع منطق «الإدراج أو التحديث» - فهم قواعد الملكية الخاصة بـ HashMap (ما هي الأنواع التي يمكن استخدامها كمفاتيح وقيم)
- استخدم HashSet لإزالة التكرارات وإجراء عمليات التقاطع والاتحاد والفرق بين المجموعات
- طرق متنوعة للتنقل عبر خريطة التجزئة (HashMap)
- اختر نوع المجموعة المناسب (Vec أم HashMap أم HashSet) وفقًا للسيناريو
2. قصة نظام التصويت
(1) الألم: استخدام جهازي Vec لتخزين عدد التذاكر
تعمل آنا على تطوير نظام تصويت للفصل، وتحتاج إلى حساب عدد الأصوات التي يحصل عليها كل مرشح.
في البداية، استخدمت كائنين من نوع Vec:
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
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());
}
الناتج:
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) خريطة مفاهيمية
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 — نظام فرز الأصوات (مستوى الصعوبة ⭐⭐)
// ============================================
// 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);
}
}
الناتج:
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، تتيح لك هذه الطريقة تنفيذ عملية «إدراج أو تحديث» في سطر واحد. تُرجعgetOption<&V>ولا تتسبب أبدًا في حدوث حالة ذعر.
(2) ▶ المثال:قواعد الملكية في HashMap وأنواع القيم (مستوى الصعوبة ⭐⭐⭐)
// ============================================
// 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);
}
الناتج:
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 وعمليات المجموعات (مستوى الصعوبة ⭐⭐)
// ============================================
// 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);
}
الناتج:
--- 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 واستراتيجيات الاختيار للمجموعات (مستوى الصعوبة ⭐⭐)
// ============================================
// 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;
الناتج:
--- 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) ▶ المثال:تمرين شامل — تحليل تكرار الكلمات وتحليل النص (مستوى الصعوبة ⭐⭐⭐)
// ============================================
// 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 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 التركيبة المثالية لتحليل النصوص.
❓ أسئلة شائعة
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 هي الطريقة التقليدية لكتابة "الإدراج أو التحديث".BTreeMap (مرتبة حسب المفتاح). أما إذا كنت تحتاج فقط إلى عمليات بحث سريعة، فإن أداء HashMap الذي يبلغ O(1) هو الأفضل.insert في HashSet لها تعقيد زمني متوسط يبلغ O(1). ومع ذلك، يستهلك HashSet ذاكرة أكبر ولا يحافظ على الترتيب. إذا كنت بحاجة إلى الحفاظ على الترتيب، ففكر في استخدام Vec مع HashSet.entry الخاصة بـ HashMap؟Entry، التي تحتوي على نوعين: Occupied(Entry) و Vacant(Entry). تقوم or_insert(default) بإدراج قيمة افتراضية وتُرجع مرجعًا عندما تكون الخانة شاغرة، وتُرجع مرجعًا إلى القيمة الموجودة عندما تكون مشغولة. تقوم and_modify(fn) بتعديل القيمة عندما تكون الخانة مشغولة. يمكن تسلسل هذه الطرق.📖 ملخص
HashMap<K, V>يخزن التعيينات بين القيم والمفاتيح ويوفر عمليات البحث بمتوسط تعقيد زمني يبلغ O(1)- واجهة برمجة التطبيقات (API) للإدخال هي عبارة عن أسلوب خاص بلغة Rust لـ«الإدراج أو التحديث» (
entry(k).or_insert(v)) - عند الإدراج في HashMap، يتم نقل الملكية؛ ويجب أن يُنفذ المفتاح السمة
Eq + Hash HashSet<T>هو في الأساسHashMap<T, ()>، ويُستخدم لإزالة التكرار وعمليات المجموعات- يدعم HashSet عمليات الاتحاد والتقاطع والفرق والفرق المتماثل
- استراتيجيات اختيار المجموعات: مرتبة/مفهرسة → Vec، البحث عن القيمة باستخدام المفتاح → HashMap، إزالة التكرارات/التحقق من العضوية → HashSet
📝 تمارين
-
الصعوبة ⭐: أنشئ متغيرًا
HashMap<String, u32>لتخزين أسعار الفاكهة ("apple"=5، "banana"=3، "orange"=4). اكتب دالةfn total_cost(items: &[&str], prices: &HashMap<String, u32>) -> u32لحساب السعر الإجمالي لعربة التسوق. اختبر عربة التسوق["apple", "banana", "apple"]في الدالة الرئيسية. -
الصعوبة ⭐⭐: اكتب دالة
fn word_frequency(text: &str) -> HashMap<String, u32>تحسب عدد مرات ظهور كل كلمة في نص ما. استخدم واجهة برمجة التطبيقاتentry. في الدالة الرئيسية، اختبر النص "the quick brown fox jumps over the lazy dog the fox" واعرض النتائج. -
الصعوبة ⭐⭐⭐: أنشئ قائمة بأسماء الطلاب في فصلين دراسيين (
HashSet<&str>). يضم الفصل «أ» الطلاب ["Alice"، "Bob"، "Charlie"، "David"]، بينما يضم الفصل «ب» الطلاب ["Charlie"، "David"، "Eve"، "Frank"]. اكتب دالةfn analyze_classes(a: &HashSet<&str>, b: &HashSet<&str>)تعرض: أسماء الطلاب في كلا الفصلين (التقاطع)، وأسماء الطلاب في الفصل «أ» فقط (الفرق)، وأسماء جميع الطلاب الفريدين (الاتحاد)، وأسماء الطلاب في فصل واحد فقط (الفرق المتماثل). استدعِ الدالة في الدالة الرئيسية واعرض النتائج.