مسیرها
/
Cairo
Cairo
/
تمرین‌ها
/
روز انتخابات
روز انتخابات

روز انتخابات

تمرین یادگیری

مقدمه

ساختار

ساختار نوع داده‌ای سفارشی است که به شما امکان می‌دهد چندین مقدار مرتبط را نام‌گذاری کنید و آن‌ها را در کنار هم بسته‌بندی کنید.

برای تعریف یک ساختار، از کلیدواژه‌ی struct استفاده کنید؛ پس از آن یک اسم و سپس آکولادهایی می‌آید که فیلدها را به همراه اسم و نوع‌شان در بر می‌گیرد:

#[derive(Drop)]
struct User {
    active: bool,
    username: ByteArray,
    email: ByteArray,
    sign_in_count: u64,
}

برای ساختن یک نمونه از struct، اسم struct را مشخص کنید و آکولادهایی با جفت‌های کلید-مقدار برای فیلدها به کار ببرید.

let user = User {
   active: true, username: "someusername123", email: "someone@example.com", sign_in_count: 1
};

برای دسترسی به فیلدهای یک نمونه‌ی struct تغییرپذیر یا تغییر آن‌ها، از نمادگذاری نقطه‌ای استفاده کنید.

let mut user = User {
    active: true, username: "someusername123", email: "someone@example.com", sign_in_count: 1
};
user.email = "anotheremail@example.com";

مالکیت

مقدارها در Cairo تغییرناپذیرند و همین تغییرناپذیری تضمین می‌کند که حتی اگر متغیرهای زیادی به یک مقدار یکسان اشاره کنند، خود مقدار ثابت و بدون تغییر بماند. مالک یک متغیر، کدی است که می‌تواند آن متغیر را بخواند (و اگر تغییرپذیر باشد، در آن بنویسد).

قواعد مالکیت در Cairo:

  1. هر متغیر در Cairo یک مالک دارد.
  2. در هر لحظه فقط یک مالک می‌تواند وجود داشته باشد.
  3. وقتی مالک از محدوده خارج می‌شود، متغیر از بین می‌رود.

«محدوده» بازه‌ای در یک برنامه است که یک متغیر در آن معتبر است.

fn main() {
                      // `x` is not valid here, it has not been declared yet
    let x: u32 = 3;   // `x` is declared, it is valid from this point forward
                      // do stuff with `x`

} // this scope ended here, `x` goes out of scope and is no longer valid

وقتی متغیری از محدوده خارج می‌شود، از بین می‌رود؛ این کار تضمین می‌کند که منبع مرتبط با آن به‌درستی آزاد شود. این کار با پیاده‌سازی یا مشتق کردن trait ‏Drop انجام می‌شود که به کامپایلر اعلام می‌کند آن نوع را می‌توان پس از بی‌استفاده شدن، با خیال راحت از بین برد.

#[derive(Drop)]
struct A {}

fn main() {
    A {}; // this value can be destroyed after going out of scope
}

در حال حاضر، پیاده‌سازی Drop را می‌توان برای همه‌ی نوع‌ها مشتق کرد، به‌جز دیکشنری‌ها (Felt252Dict) و نوع‌هایی که دیکشنری در خود دارند.

یک مقدار همچنین می‌تواند منتقل شود؛ این اتفاق وقتی می‌افتد که مقداری به تابع دیگری پاس داده شود: متغیر اصلی که به آن مقدار اشاره داشت از بین می‌رود (و بدین ترتیب بی‌استفاده می‌شود) و متغیر تازه‌ای برای نگه‌داشتن همان مقدار ساخته می‌شود.

fn foo(num: u32) { 
    // do something with p
}

fn main() {
    let num: u32 = 10;
    foo(num);
    // ownership of `num` was moved into `foo` and is thus no longer valid in this scope
}

بازگرداندن مقدارها معادل انتقال آن‌هاست.

مثال زیر تابعی را نشان می‌دهد که مقداری را برمی‌گرداند:

#[derive(Drop)]
struct A {}

fn main() {
    // main now owns a1
    let a1 = gives_ownership();

    // a2 comes into scope
    let a2 = A {};

    // a2 is moved into takes_and_gives_back, which also moves its return value into a3
    let a3 = takes_and_gives_back(a2);
} // Here, a3 and a1 go out of scope and are dropped. a2 was moved, so nothing happens.

// Moves its return value into the function that calls it
fn gives_ownership() -> A {
    let some_a = A {};
    some_a
}

// This function takes an instance some_a of A and returns it
fn takes_and_gives_back(some_a: A) -> A {
    some_a
}

چون متغیر پس از انتقال از بین می‌رود، کامپایلر اجازه نمی‌دهد بعد از آن دوباره از همان متغیر استفاده کنیم.

#[derive(Drop)]
struct Point {
    x: u128,
    y: u128,
}

fn foo(p: Point) { 
    // do something with p
}

fn main() {
    let p = Point { x: 5, y: 10 };
    foo(p);
    foo(p); // compiler error: Variable was previously moved.
}

در این حالت، می‌خواهیم یک مقدار یکسان (ساختار Point در متغیر p) را به هر دو فراخوانی تابع پاس بدهیم. به محض اینکه بار اول متغیر را به تابع foo پاس بدهیم، دیگر نمی‌توان از آن متغیر استفاده کرد.

یکی از راه‌های حل این مسئله، مشتق کردن trait ‏Copy روی ساختارمان است؛ این کار رفتار پاس دادن یک متغیر Copy به تابع را تغییر می‌دهد: دیگر مقدار منتقل نمی‌شود، بلکه متغیر تازه‌ای ساخته می‌شود که به همان مقدار اشاره می‌کند. این کار عملیاتی رایگان است، چون متغیرها یک انتزاع در Cairo هستند و مقدارها همیشه تغییرناپذیرند.

برای پیاده‌سازی trait ‏Copy روی نوع خودتان، حاشیه‌نویس #[derive(Copy)] را به تعریف نوع اضافه کنید.

#[derive(Copy, Drop)]
struct Point {
    x: u128,
    y: u128,
}

fn foo(p: Point) { 
    // do something with p
}

fn main() {
    let p = Point { x: 5, y: 10 };
    foo(p);
    foo(p); // this now works
}

ارجاع‌ها و اسنپ‌شات‌ها

Cairo دو امکان برای پاس دادن یک مقدار بدون از بین بردن یا منتقل کردن آن دارد که references و snapshots نامیده می‌شوند.

اسنپ‌شات‌ها نمایی تغییرناپذیر از یک مقدار در یک لحظه‌ی مشخص به شما می‌دهند؛ به این ترتیب می‌توانید مالکیت یک متغیر را نگه دارید و در همان حال به وضعیت آن دسترسی داشته باشید. هر تغییری در متغیر یک خانه‌ی حافظه‌ی تازه می‌سازد و نمای اسنپ‌شات بدون تغییر می‌ماند.

برای ساختن یک اسنپ‌شات، کافی است پیش از نمونه‌ی متغیر علامت @ بگذارید.

// this function takes in a snapshot using `@` with the data-type
fn get_len(arr: @Array<u128>) -> u32 { // 
    arr.len()
}

fn foo(arr: Array<u128>) {
    arr.pop_front();
}

fn main() {
    let mut new_arr: Array<u128> = array![1, 2, 3, 4, 5, 6, 7, 8];
    let snapshot = @new_arr; // using `@` to create a snapshot of a variable
    let len = get_len(snapshot); // pass the snapshot as argument, retaining ownership
    foo(new_arr); // `new_arr` is still valid up until here and no further
}

برای تبدیل یک اسنپ‌شات به یک متغیر معمولی، می‌توانید از عملگر desnap یعنی * استفاده کنید که نقطه‌ی مقابل عملگر @ است. فقط نوع‌هایی که trait ‏Copy را پیاده‌سازی کرده‌اند می‌توانند desnap شوند.

fn square(n: @u32) -> u32 {
    let n = *n; // we desnap the value
    n * n
}

fn main() {
    let num: u32 = 10;
    let squared = square(@num); // we use `@` to create a snapshot
    println!("{} squared = {}", num, squared);
}

ارجاع‌ها به شما اجازه می‌دهند مقدارهای تغییرپذیر را به تابع‌ها پاس بدهید و آن‌ها به‌طور ضمنی برگردانده شوند، در حالی که مالکیت در زمینه‌ی فراخوانی حفظ می‌شود. این کار با استفاده از اصلاح‌کننده‌ی ref ممکن می‌شود، به شرط اینکه متغیر با کلیدواژه‌ی mut به‌صورت تغییرپذیر تعریف شده باشد.

می‌توانیم مثال قبلی Point را طوری تغییر دهیم که به‌جای تکیه بر trait ‏Copy از ارجاع‌های تغییرپذیر استفاده کند، و باز هم کار می‌کند.

#[derive(Drop)]
struct Point {
    x: u128,
    y: u128,
}

fn foo(ref p: Point) { 
    // we can now update the passed values and the changes
    // will be visible in the calling context
    p.x += 1;
}

fn main() {
    let mut p = Point { x: 5, y: 10 };
    foo(ref p);
    foo(ref p); // this now works even without the Copy trait
    println!("p.x = {}", p.x); // p.x = 7 
}

دستورالعمل‌ها

یک مدرسه‌ی محلی نزدیک شما انجمن دانش‌آموزی بسیار فعالی دارد. انجمن دانش‌آموزی توسط یک رئیس اداره می‌شود و هر ۲ سال یک‌بار برای انتخاب رئیس جدید انتخابات برگزار می‌شود. در انتخابات امسال تصمیم گرفته شد که به یک سیستم دیجیتال جدید برای شمارش آرا نیاز است.

مدرسه برای ساخت این سیستم جدید به کمک شما نیاز دارد.

1. ساخت شمارنده‌ی آرا

یکی از نخستین مواردی که سیستم رأی‌گیری جدید به آن نیاز دارد، یک «شمارنده‌ی آرا» است. این شمارنده راهی است برای پیگیری آرای یک نامزد خاص.

تابعی به اسم new_vote_counter بسازید که تعداد آرای اولیه‌ی یک نامزد را می‌پذیرد و تصویری از یک ساختار VoteCounter را برمی‌گرداند که با تعداد آرای اولیه‌ی داده‌شده مقداردهی اولیه شده است.

let initial_votes: u32 = 2;
let counter: @VoteCounter = new_vote_counter(initial_votes);
// @VoteCounter {
//     value: 2,
// }

2. گرفتن تعداد آرا از یک شمارنده

حالا راهی برای ساخت شمارنده‌های جدید دارید! اما حالا متوجه می‌شوید که سیستم جدید به راهی هم برای گرفتن تعداد آرا از یک شمارنده نیاز خواهد داشت.

تابعی به اسم vote_count بسازید که یک شمارنده (@VoteCounter) را به عنوان ورودی می‌گیرد و تعداد آرا در شمارنده را برمی‌گرداند.

let initial_votes: u32 = 3;
let vote_counter: @VoteCounter = new_vote_counter(initial_votes);

let count = vote_count(vote_counter);
// => 3

3. افزایش آرای یک شمارنده

بالاخره وقت شمارش آرا رسید! حالا به راهی برای افزایش آرای یک شمارنده نیاز دارید.

تابعی به اسم increment_vote_count بسازید که یک شمارنده (@VoteCounter) و تعدادی رأی را به عنوان ورودی می‌گیرد و شمارنده را به اندازه‌ی آن تعداد رأی افزایش می‌دهد.

let votes: u32 = 3;
let vote_counter: @VoteCounter = new_vote_counter(initial_votes);
let vote_counter: VoteCounter = increment_vote_count(vote_counter, 2);

let count = vote_count(@vote_counter);
// => 5

4. ساخت نتایج انتخابات

حالا که همه‌ی آرا شمرده شده‌اند، وقت آن است که اعلام نتیجه برای تمام مدرسه را آماده کنید. برای این کار متوجه می‌شوید که داشتن فقط شمارنده برای آرا کافی نیست. باید راهی باشد که تعداد آرا را به یک نامزد خاص مرتبط کند.

تابعی به اسم new_election_result بسازید که اسم یک نامزد و تعداد آرای او را می‌گیرد و یک نتیجه‌ی انتخابات جدید برمی‌گرداند.

let result: ElectionResult = new_election_result("Peter", 3);
// ElectionResult {
//     name: "Peter",
//     votes: @VoteCounter {
//         value: 3,
//     },
// }

ساختار نتیجه‌ی انتخابات از قبل برای شما ساخته شده و به این صورت تعریف شده است:

struct ElectionResult {
    name: ByteArray,
    counter: @VoteCounter,
}

5. اعلام نتایج

وقت آن است که رئیس جدید را به مدرسه اعلام کنید! رئیس جدید در تابلوهای پیام دیجیتال کوچکی که مدرسه دارد اعلام می‌شود.

پیام باید اسم رئیس جدید و آرای او را به این قالب نشان دهد: <candidate_name> (<votes>). این نمونه‌ای از چنین پیامی است: "Peter (51)".

تابعی به اسم display_result بسازید که یک @ElectionResult را به عنوان ورودی می‌گیرد و یک ByteArray شامل پیامی که باید نمایش داده شود را برمی‌گرداند.

let result = ElectionResult {
    name: "John",
    votes: @VoteCounter {
        value: 32,
    },
};

let msg = display_result(@result);
// => "John (32)"

6. بازشماری آرا

برای اطمینان از درستی نتایج نهایی، آرا بازشماری شدند. در بازشماری مشخص شد که تعداد آرای برخی از نامزدها یک واحد اختلاف داشت.

تابعی به اسم decrement_votes_of_candidate بسازید که نتایج نهایی و اسم نامزدی را می‌گیرد که باید تعداد آرای او کاهش یابد. نتایج نهایی به شکل یک Felt252Dict<u32> داده می‌شوند، که کلیدهای آن اسم نامزدها و مقادیر آن مجموع آرای هر نامزد است.

let mut final_results: Felt252Dict<u32> = Default::default();
final_results.insert('Mary', 10);
final_results.insert('John', 51);

decrement_votes_of_candidate(ref final_results, 'Mary');

let decremented_votes = final_results.get('Mary');
// => 9
ویرایش از طریق GitHub این لینک در پنجره یا زبانه‌ی جدیدی باز می‌شود
Cairo Exercism

آماده‌اید روز انتخابات را شروع کنید؟

در Exercism ثبت‌نام کنید تا Cairo را همراه با 25 مفهوم68 تمرین و مربی‌گری انسانی واقعی یاد بگیرید و در آن استاد شوید، همه‌ی این‌ها رایگان.