مقدمه

آخرین کالکشن رایج در کتابخانه استاندارد Rust که به بررسی آن می‌پردازیم، «هش‌مپ» یا HashMap<K, V> است. هش‌مپ داده‌ها را به صورت مجموعه‌ای از زوج‌های «کلید-مقدار» (key-value) ذخیره می‌کند. این ساختار داده به شما اجازه می‌دهد تا با استفاده از یک کلید از نوع K، به مقدار متناظر آن از نوع V دسترسی پیدا کنید. هش‌مپ‌ها مانند وکتورها، داده‌های خود را در heap ذخیره می‌کنند و اندازه‌شان قابل رشد است.

هش‌مپ‌ها برای مواردی که نیاز به جستجوی سریع داده بر اساس یک شناسه منحصر به فرد دارید، بسیار مفید هستند. برخلاف وکتور که برای دسترسی به یک عنصر باید به صورت خطی آن را جستجو کرد، هش‌مپ با استفاده از یک «تابع هش» (hashing function)، می‌تواند به سرعت مکان یک مقدار را بر اساس کلید آن پیدا کند.

ایجاد و درج مقادیر در یک HashMap

برای استفاده از HashMap، ابتدا باید آن را از کتابخانه استاندارد به حوزه خود وارد کنیم: use std::collections::HashMap;. سپس می‌توانیم با HashMap::new() یک هش‌مپ خالی بسازیم و با متد insert()، مقادیر را در آن درج کنیم.

Copy Icon src/main.rs
use std::collections::HashMap;

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

    // Insert key-value pairs
    scores.insert(String::from("Blue"), 10);
    scores.insert(String::from("Yellow"), 50);
}

در این مثال، یک هش‌مپ می‌سازیم که نام تیم‌ها (String) را به امتیاز آنها (i32) نگاشت می‌کند. توجه داشته باشید که مانند Vec، برای افزودن داده باید هش‌مپ را به صورت تغییرپذیر (mutable) تعریف کنیم.

ساخت هش‌مپ از مجموعه‌ای از تاپل‌ها

یک روش دیگر برای ساخت هش‌مپ، استفاده از متد collect() روی یک وکتور از تاپل‌هاست. این روش زمانی مفید است که کلیدها و مقادیر شما از قبل در دو لیست مجزا وجود دارند.

Copy Icon src/main.rs
use std::collections::HashMap;

fn main() {
    let teams = vec![String::from("Blue"), String::from("Yellow")];
    let initial_scores = vec![10, 50];

    let mut scores: HashMap<_, _> =
        teams.into_iter().zip(initial_scores.into_iter()).collect();
}

در این کد، با استفاده از zip دو تکرارگر را با هم ترکیب کرده تا یک تکرارگر از تاپل‌ها بسازیم و سپس با collect آن را به یک هش‌مپ تبدیل می‌کنیم. علامت `_` به Rust می‌گوید که نوع کلید و مقدار را خودش استنتاج کند.

مالکیت و HashMap

برای نوع‌هایی که تریت Copy را پیاده‌سازی کرده‌اند (مانند i32)، مقادیر در هش‌مپ کپی می‌شوند. اما برای مقادیر مالک (owned values) مانند String، مالکیت آنها به هش‌مپ منتقل می‌شود. این یعنی پس از درج یک String در هش‌مپ، دیگر نمی‌توانید از آن متغیر استفاده کنید.

Copy Icon src/main.rs
let field_name = String::from("Favorite color");
let field_value = String::from("Blue");

let mut map = HashMap::new();
map.insert(field_name, field_value);

// The following line would not compile, as field_name's ownership was moved.
// println!("{}", field_name);

دسترسی و به‌روزرسانی مقادیر

برای دسترسی به یک مقدار در هش‌مپ، از متد get() استفاده می‌کنیم که یک رفرنس به مقدار را در داخل یک Option برمی‌گرداند. این روش امن است و در صورت عدم وجود کلید، None را برمی‌گرداند.

پیمایش روی هش‌مپ

با استفاده از یک حلقه for می‌توانیم روی تمام زوج‌های کلید-مقدار یک هش‌مپ پیمایش کنیم.

Copy Icon src/main.rs
use std::collections::HashMap;

fn main() {
    let mut scores = HashMap::new();
    scores.insert("Blue", 10);
    scores.insert("Yellow", 50);

    for (key, value) in &scores {
        println!("{}: {}", key, value);
    }
}

به‌روزرسانی مقادیر

هش‌مپ‌ها سه الگوی اصلی برای به‌روزرسانی داده‌ها ارائه می‌دهند:

  1. بازنویسی مقدار موجود: اگر یک کلید را که از قبل وجود دارد، دوباره insert کنیم، مقدار قبلی با مقدار جدید جایگزین می‌شود.
  2. درج در صورت عدم وجود: متد entry() به ما اجازه می‌دهد تا یک ورودی را بررسی کنیم. متد .or_insert() روی خروجی آن، مقدار جدید را تنها در صورتی درج می‌کند که کلید از قبل وجود نداشته باشد.
  3. به‌روزرسانی یک مقدار بر اساس مقدار قدیمی: با استفاده از الگوی entry().or_insert() می‌توانیم یک رفرنس تغییرپذیر به مقدار گرفته و آن را درجا ویرایش کنیم.
Copy Icon src/main.rs
let text = "hello world wonderful world";
let mut map = HashMap::new();

for word in text.split_whitespace() {
    // The or_insert method returns a mutable reference to the value for this key.
    let count = map.entry(word).or_insert(0);
    *count += 1; // Dereference to modify the value.
}

println!("{:?}", map); // {"world": 2, "hello": 1, "wonderful": 1}

این مثال کلاسیک، تعداد تکرار هر کلمه در یک متن را می‌شمارد. متد entry().or_insert(0) تضمین می‌کند که اگر کلمه برای اولین بار دیده می‌شود، مقدار آن برابر با صفر تنظیم شود و سپس یک رفرنس تغییرپذیر به آن مقدار برمی‌گرداند که ما می‌توانیم آن را افزایش دهیم.

در این درس با HashMap به عنوان یک کالکشن قدرتمند برای ذخیره‌سازی داده‌های کلید-مقدار آشنا شدیم. با این درس، فصل «کالکشن‌ها» به پایان می‌رسد. ما سه کالکشن اصلی کتابخانه استاندارد، یعنی Vec، String و HashMap را بررسی کردیم که ابزارهای بنیادی برای مدیریت داده‌های با اندازه متغیر در برنامه‌های Rust هستند. در فصل بعدی، به سراغ یکی از مهم‌ترین جنبه‌های نوشتن نرم‌افزار قوی، یعنی «مدیریت خطا در Rust» خواهیم رفت.