Διαδρομές
/
Cairo
Cairo
/
Ασκήσεις
/
Ημέρα των Εκλογών
Ημέρα των Εκλογών

Ημέρα των Εκλογών

Άσκηση εκμάθησης

Εισαγωγή

Δομές

Οι δομές είναι προσαρμοσμένοι τύποι δεδομένων που σου επιτρέπουν να ονομάσεις και να ομαδοποιήσεις μαζί πολλές σχετικές τιμές.

Για να ορίσεις μια δομή, χρησιμοποίησε τη λέξη-κλειδί struct ακολουθούμενη από ένα όνομα και άγκιστρα που περιέχουν τα πεδία με τα ονόματα και τους τύπους τους:

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

Για να δημιουργήσεις ένα στιγμιότυπο μιας δομής, δώσε το όνομα της δομής και χρησιμοποίησε άγκιστρα με ζεύγη κλειδιού-τιμής για τα πεδία.

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

Χρησιμοποίησε τη σημειογραφία με τελεία για να προσπελάσεις ή να τροποποιήσεις τα πεδία ενός μεταβλητού στιγμιότυπου δομής.

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 *, ο οποίος λειτουργεί ως το αντίθετο του τελεστή @. Μπορείς να κάνεις desnap μόνο σε τύπους που υλοποιούν το trait Copy.

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 
}

Οδηγίες

Ένα τοπικό σχολείο κοντά σου έχει έναν πολύ δραστήριο μαθητικό σύλλογο. Ο μαθητικός σύλλογος διοικείται από έναν πρόεδρο και κάθε 2 χρόνια γίνονται εκλογές για την ανάδειξη νέου προέδρου. Στις φετινές εκλογές, αποφασίστηκε ότι χρειαζόταν ένα νέο ψηφιακό σύστημα για την καταμέτρηση των ψήφων.

Το σχολείο χρειάζεται τη βοήθειά σου για να φτιάξεις αυτό το νέο σύστημα.

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 ασκήσεις και πραγματική καθοδήγηση από ανθρώπους, όλα δωρεάν.