From 98caeeba408d39f6e91fda7117bee1746fd53a81 Mon Sep 17 00:00:00 2001 From: Nenad Date: Tue, 25 Feb 2025 11:36:14 +0100 Subject: [PATCH] Add magician-in-training (#343) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * implement code * update impl and add .meta files * update arrays links * add hints * add instructions * add introduction + append * lint * update design * fix exercise * Apply suggestions from code review Co-authored-by: András B Nagy <20251272+BNAndras@users.noreply.github.com> --------- Co-authored-by: András B Nagy <20251272+BNAndras@users.noreply.github.com> --- concepts/arrays/links.json | 8 +++ .../magician-in-training/.docs/hints.md | 27 ++++++++ .../.docs/instructions.append.md | 7 ++ .../.docs/instructions.md | 63 +++++++++++++++++ .../.docs/introduction.md | 57 ++++++++++++++++ .../magician-in-training/.meta/config.json | 4 +- .../magician-in-training/.meta/design.md | 23 +++++++ .../magician-in-training/.meta/exemplar.cairo | 27 ++++++++ .../concept/magician-in-training/Scarb.toml | 7 ++ .../magician-in-training/src/lib.cairo | 19 ++++++ .../tests/magician_in_training.cairo | 68 +++++++++++++++++++ 11 files changed, 308 insertions(+), 2 deletions(-) create mode 100644 exercises/concept/magician-in-training/.docs/instructions.append.md diff --git a/concepts/arrays/links.json b/concepts/arrays/links.json index 915920c0..7e9041d5 100644 --- a/concepts/arrays/links.json +++ b/concepts/arrays/links.json @@ -10,5 +10,13 @@ { "url": "https://book.cairo-lang.org/ch03-02-dictionaries.html#using-arrays-inside-dictionaries", "description": "Using Arrays inside Dictionaries" + }, + { + "url": "https://docs.swmansion.com/scarb/corelib/core-array-ArrayTrait.html", + "description": "Array trait docs" + }, + { + "url": "https://docs.swmansion.com/scarb/corelib/core-array-SpanTrait.html", + "description": "Span trait docs" } ] diff --git a/exercises/concept/magician-in-training/.docs/hints.md b/exercises/concept/magician-in-training/.docs/hints.md index e69de29b..d4fcc5f4 100644 --- a/exercises/concept/magician-in-training/.docs/hints.md +++ b/exercises/concept/magician-in-training/.docs/hints.md @@ -0,0 +1,27 @@ +# Hints + +## 1. Insert a card at the of top the stack + +- The [`ArrayTrait::append` function][append] can be used to add to the back of an array. + +## 2. Remove the top card from the stack + +- You can always convert an array into a span, and then use the [`SpanTrait::pop_back` function][pop-back] to remove from the back of an array. + +## 3. Insert a card at the bottom of the stack + +- There's no way to append an item to the front of the array, but one way around this is to create a new one-item array and append the old one as a span with [`ArrayTrait::append_span`][append-span]. + +## 4. Remove a card from the bottom of the stack + +- The [`ArrayTrait::pop_front` function][pop-front] can be used to remove from the front of an array. + +## 5. Check size of the stack + +- The [`ArrayTrait::len` function][len] can be used to get the length of an array. + +[append]: https://docs.swmansion.com/scarb/corelib/core-array-ArrayTrait.html#append +[pop-back]: https://docs.swmansion.com/scarb/corelib/core-array-SpanTrait.html#pop_back +[append-span]: https://docs.swmansion.com/scarb/corelib/core-array-ArrayTrait.html#append_span +[pop-front]: https://docs.swmansion.com/scarb/corelib/core-array-ArrayTrait.html#pop_front +[len]: https://docs.swmansion.com/scarb/corelib/core-array-ArrayTrait.html#len diff --git a/exercises/concept/magician-in-training/.docs/instructions.append.md b/exercises/concept/magician-in-training/.docs/instructions.append.md new file mode 100644 index 00000000..9c26b0e2 --- /dev/null +++ b/exercises/concept/magician-in-training/.docs/instructions.append.md @@ -0,0 +1,7 @@ +# Instructions append + +To understand why working with arrays in Cairo is not as straightforward as in other programming languages, it is helpful to bear in mind that Cairo uses an immutable memory model, meaning that once a memory cell is written to, it can't ever be overwritten, only read from. + +The language abstracts away this feature somewhat, allowing developers to declare and use their variables as if they were mutable. + +Although this makes writing code a bit more challenging, especially code that makes use of the `Array` type (including `ByteArray`, which underneath is an "array of bytes"), it enables writing _provable_ programs which can be confidently executed even on untrusted machines, making the trade-off more than worth it. diff --git a/exercises/concept/magician-in-training/.docs/instructions.md b/exercises/concept/magician-in-training/.docs/instructions.md index e69de29b..3beec36d 100644 --- a/exercises/concept/magician-in-training/.docs/instructions.md +++ b/exercises/concept/magician-in-training/.docs/instructions.md @@ -0,0 +1,63 @@ +# Instructions + +As a magician-to-be, Elyse needs to practice some basics. + +She has a stack of cards that she wants to manipulate. + +To make things a bit easier she only uses the cards 1 to 10. + +The stack of cards is represented by an `Array`, with the bottom of the stack at the front of the array, and the top of the stack at the back. + +## 1. Insert a card at the of top the stack + +Implement the function `insert_top` that returns a copy of the stack with the new card provided added to the top of the stack. + +```rust +let stack = array![5, 9, 7, 1]; +insert_top(stack, 8); +// -> [5, 9, 7, 1, 8] +``` + +## 2. Remove the top card from the stack + +Implement the function `remove_top_card` that returns a copy of the stack having had its top card removed. + +If the given stack is empty, the original stack should be returned, unchanged. + +```rust +let stack = array![3, 2, 6, 4, 8]; +remove_top_card(stack); +// -> [3, 2, 6, 4] +``` + +## 3. Insert a card at the bottom of the stack + +Implement the function `insert_bottom` that returns a copy of the stack with the new card provided added to the bottom of the stack. + +```rust +let stack = array![5, 9, 7, 1]; +insert_bottom(stack, 8); +// -> [8, 5, 9, 7, 1] +``` + +## 4. Remove a card from the bottom of the stack + +Implement the function `remove_bottom_card` that returns a copy of the stack having had its bottom card removed. + +If the given stack is empty, the original stack should be returned, unchanged. + +```rust +let stack = array![8, 5, 9, 7, 1]; +remove_bottom_card(stack); +// -> [5, 9, 7, 1] +``` + +## 5. Check size of the stack + +Implement the function `check_size_of_stack` that checks whether the size of the stack is equal to a given `stack_size` or not. + +```rust +let stack = array![3, 2, 6, 4, 8]; +check_size_of_stack(stack, 4); +// -> false +``` diff --git a/exercises/concept/magician-in-training/.docs/introduction.md b/exercises/concept/magician-in-training/.docs/introduction.md index e69de29b..db28e49f 100644 --- a/exercises/concept/magician-in-training/.docs/introduction.md +++ b/exercises/concept/magician-in-training/.docs/introduction.md @@ -0,0 +1,57 @@ +# Introduction + +## Arrays + +The Cairo core library implements an `Array` type. + +It is similar to the usual array types found in other programming languages, but with a few key differences: + +- Elements can be efficiently added only to the back of the `Array` type, and they can only be efficiently removed from the front. +- It is impossible to manually set an item using the array index. + +Arrays can be created using the `ArrayTrait::new` function and also the `array!` macro: + +```rust +let empty: Array = ArrayTrait::new(); +let one_to_four = array![1, 2, 3, 4]; +``` + +Elements can be added to the array using the `ArrayTrait::append` function: + +```rust +let mut num_array = array![1, 2, 3, 4]; +num_array.append(5); +// -> [1, 2, 3, 4, 5] +``` + +Elements can be removed from the array using the `ArrayTrait::pop_front` function: + +```rust +let mut one_to_four = array![1, 2, 3, 4]; + +one_to_four.pop_front(); +// -> [2, 3, 4] +``` + +Cairo also introduces a `Span` type that acts like a "snapshot" of the `Array`. + +Every `Array` can be converted into a `Span` with the `ToSpanTrait::span` function: + +```rust +let one_to_four = array![1, 2, 3, 4]; +let one_to_four_span = one_to_four.span(); +// -> [1, 2, 3, 4] +``` + +Converting an array into a `Span` is very efficient, as `Span` is just a data structure that allows Cairo to "view" a section of the array, it doesn't modify it in any way. + +By converting an array into a `Span`, Cairo allows more operations to be performed than would be permitted on `Array` directly, like removing an item from the front of the span. + +```rust +let one_to_four = array![1, 2, 3, 4]; +let mut one_to_four_span = one_to_four.span(); +one_to_four_span.pop_back(); +// -> [2, 3, 4] +``` + +Note that we had to make `one_to_four_span` mutable with the `mut` keyword in order to invoke `pop_back`, which mutates the span by shortening the section of the array the span is "viewing". diff --git a/exercises/concept/magician-in-training/.meta/config.json b/exercises/concept/magician-in-training/.meta/config.json index 6c45a885..7f39fab5 100644 --- a/exercises/concept/magician-in-training/.meta/config.json +++ b/exercises/concept/magician-in-training/.meta/config.json @@ -1,6 +1,6 @@ { "authors": [ - "" + "0xNeshi" ], "files": { "solution": [ @@ -19,5 +19,5 @@ "forked_from": [ "gleam/magician-in-training" ], - "blurb": "" + "blurb": "Help Elyse with her Enchantments and learn about arrays in the process." } diff --git a/exercises/concept/magician-in-training/.meta/design.md b/exercises/concept/magician-in-training/.meta/design.md index e69de29b..c0c886a4 100644 --- a/exercises/concept/magician-in-training/.meta/design.md +++ b/exercises/concept/magician-in-training/.meta/design.md @@ -0,0 +1,23 @@ +# Design + +## Learning objectives + +- Know of the `Array` and `Span` types. +- Know it's cheap to append to the end of an array, but costly to push to the front. +- Know it's cheap to remove from the front of an array, but costly to remove from the back. +- Know that it can be converted to and from a `Span`. +- Know how to utilize `Span`s to manipulate `Array`s. + +## Out of scope + +- Handling popped (removed) values. +- Fixed-size array type. +- Traits. + +## Concepts + +- `arrays` + +## Prerequisites + +- `integers` diff --git a/exercises/concept/magician-in-training/.meta/exemplar.cairo b/exercises/concept/magician-in-training/.meta/exemplar.cairo index e69de29b..0cd77c1a 100644 --- a/exercises/concept/magician-in-training/.meta/exemplar.cairo +++ b/exercises/concept/magician-in-training/.meta/exemplar.cairo @@ -0,0 +1,27 @@ +pub fn insert_top(stack: Array, card: u32) -> Array { + let mut stack = stack; + stack.append(card); + stack +} + +pub fn remove_top_card(stack: Array) -> Array { + let mut span = stack.span(); + let _ = span.pop_back(); + span.into() +} + +pub fn insert_bottom(stack: Array, card: u32) -> Array { + let mut new_stack = array![card]; + new_stack.append_span(stack.span()); + new_stack +} + +pub fn remove_bottom_card(stack: Array) -> Array { + let mut stack = stack; + let _ = stack.pop_front(); + stack +} + +pub fn check_size_of_stack(stack: Array, target: u32) -> bool { + stack.len() == target +} diff --git a/exercises/concept/magician-in-training/Scarb.toml b/exercises/concept/magician-in-training/Scarb.toml index e69de29b..98d6d38d 100644 --- a/exercises/concept/magician-in-training/Scarb.toml +++ b/exercises/concept/magician-in-training/Scarb.toml @@ -0,0 +1,7 @@ +[package] +name = "magician_in_training" +version = "0.1.0" +edition = "2024_07" + +[dev-dependencies] +cairo_test = "2.9.2" diff --git a/exercises/concept/magician-in-training/src/lib.cairo b/exercises/concept/magician-in-training/src/lib.cairo index e69de29b..71246cb1 100644 --- a/exercises/concept/magician-in-training/src/lib.cairo +++ b/exercises/concept/magician-in-training/src/lib.cairo @@ -0,0 +1,19 @@ +pub fn insert_top(stack: Array, card: u32) -> Array { + panic!("implement `insert_top`") +} + +pub fn remove_top_card(stack: Array) -> Array { + panic!("implement `remove_top_card`") +} + +pub fn insert_bottom(stack: Array, card: u32) -> Array { + panic!("implement `insert_bottom`") +} + +pub fn remove_bottom_card(stack: Array) -> Array { + panic!("implement `remove_bottom_card`") +} + +pub fn check_size_of_stack(stack: Array, target: u32) -> bool { + panic!("implement `check_size_of_stack`") +} diff --git a/exercises/concept/magician-in-training/tests/magician_in_training.cairo b/exercises/concept/magician-in-training/tests/magician_in_training.cairo index e69de29b..f653ef00 100644 --- a/exercises/concept/magician-in-training/tests/magician_in_training.cairo +++ b/exercises/concept/magician-in-training/tests/magician_in_training.cairo @@ -0,0 +1,68 @@ +use magician_in_training::*; + +#[test] +fn insert_top_test() { + let stack = array![5, 9, 7, 1]; + let actual = insert_top(stack, 8); + let expected = array![5, 9, 7, 1, 8]; + assert_eq!(expected, actual); +} + +#[test] +#[ignore] +fn remove_top_card_with_empty_stack_test() { + let stack = array![]; + let actual = remove_top_card(stack); + let expected = array![]; + assert_eq!(expected, actual); +} + +#[test] +#[ignore] +fn remove_top_card_test() { + let stack = array![5, 9, 7, 1]; + let actual = remove_top_card(stack); + let expected = array![5, 9, 7]; + assert_eq!(expected, actual); +} + +#[test] +#[ignore] +fn insert_bottom_test() { + let stack = array![5, 9, 7, 1]; + let actual = insert_bottom(stack, 8); + let expected = array![8, 5, 9, 7, 1]; + assert_eq!(expected, actual); +} + +#[test] +#[ignore] +fn remove_bottom_card_with_empty_stack_test() { + let stack = array![]; + let actual = remove_bottom_card(stack); + let expected = array![]; + assert_eq!(expected, actual); +} + +#[test] +#[ignore] +fn remove_bottom_card_test() { + let stack = array![5, 9, 7, 1]; + let actual = remove_bottom_card(stack); + let expected = array![9, 7, 1]; + assert_eq!(expected, actual); +} + +#[test] +#[ignore] +fn check_size_of_stack_not_matching_test() { + let stack = array![5, 9, 7, 1]; + assert!(!check_size_of_stack(stack, 5)); +} + +#[test] +#[ignore] +fn check_size_of_stack_matching_test() { + let stack = array![5, 9, 7, 1]; + assert!(check_size_of_stack(stack, 4)); +}