diff --git a/practical.yml b/practical.yml index 2988374..10cf0d5 100644 --- a/practical.yml +++ b/practical.yml @@ -44,3 +44,22 @@ exercises: tests: - "add_last_two_not_enough" - "add_last_two_enough" + - "dup_top_empty" + - "dup_top_has_value" + - "median_already_sorted" + - "median_shuffled" + - "median_empty" + compute: + required_files: + - "src/vec.rs" + - "src/vec/compute.rs" + tests: + - "compute_empty" + - "compute_too_many_ops" + - "compute_division_by_zero_push" + - "compute_division_by_zero_operation" + - "compute_all_ops" + - "compute_add" + - "compute_sub" + - "compute_mul" + - "compute_div" diff --git a/subject_source/src/vec.rs b/subject_source/src/vec.rs index ce652d9..32cd690 100644 --- a/subject_source/src/vec.rs +++ b/subject_source/src/vec.rs @@ -1 +1,2 @@ pub mod access; +pub mod compute; diff --git a/subject_source/src/vec/access.rs b/subject_source/src/vec/access.rs index 16ab63a..2b12258 100644 --- a/subject_source/src/vec/access.rs +++ b/subject_source/src/vec/access.rs @@ -1,10 +1,37 @@ /// Add the last two numbers of the input slice. /// -/// If the slice is not large enough, return `None` -/// If it is, return the computed value in a `Some` +/// # Return value +/// `None` if the slice is not large enough +/// `Some(result)` if the slice has at least 2 elements pub fn add_last_two(v: &[f32]) -> Option { match v.last_chunk() { Some([a, b]) => Some(a + b), None => None, } } + +/// Duplicate the top element from the stack if it exist +/// (the stack is represented as a Vec with top == last) +/// +/// # Return value +/// `Some(())` if the operation succeeded +/// `None` if not +pub fn dup_top(v: &mut Vec) -> Option<()> { + match v.last() { + Some(last) => { + v.push(*last); + return Some(()); + } + _ => return None, + } +} + +/// Compute the median of a slice in place (if the slice was sorted, it would be the middle element) +pub fn median(v: &[i32]) -> Option { + let mut tmp = v.to_vec(); + tmp.sort(); + match tmp.get(tmp.len() / 2) { + Some(&r) => Some(r), + None => None, + } +} diff --git a/subject_source/src/vec/compute.rs b/subject_source/src/vec/compute.rs new file mode 100644 index 0000000..01cd4e3 --- /dev/null +++ b/subject_source/src/vec/compute.rs @@ -0,0 +1,48 @@ +pub enum Operation { + Push(f32), + Binary(Binary), +} + +pub enum Binary { + Add, + Sub, + Mul, + Div, +} + +#[derive(PartialEq, Debug)] +pub enum ComputeError { + NotEnoughData, + DivisionByZero, +} + +pub fn compute(operations: &[Operation]) -> Result { + let mut stack = vec![]; + for operation in operations { + match operation { + Operation::Push(n) => stack.push(*n), + Operation::Binary(op) => { + let b = stack.pop(); + let a = stack.pop(); + + let r = match (a, b) { + (Some(a), Some(b)) => match op { + Binary::Add => a + b, + Binary::Mul => a * b, + Binary::Div => { + if b == 0.0 { + return Err(ComputeError::DivisionByZero); + } + a / b + } + Binary::Sub => a - b, + }, + _ => return Err(ComputeError::NotEnoughData), + }; + stack.push(r); + } + } + } + + stack.pop().ok_or(ComputeError::NotEnoughData) +} diff --git a/subject_source/tests/vec_access.rs b/subject_source/tests/vec_access.rs index 376a2ee..8096fdb 100644 --- a/subject_source/tests/vec_access.rs +++ b/subject_source/tests/vec_access.rs @@ -9,3 +9,31 @@ pub fn add_last_two_not_enough() { pub fn add_last_two_enough() { assert_eq!(access::add_last_two(&[1.0, 2.0, 3.0]), Some(5.0)); } + +#[test] +pub fn dup_top_empty() { + let mut empty = vec![]; + assert!(access::dup_top(&mut empty).is_none()); +} + +#[test] +pub fn dup_top_has_values() { + let mut empty = vec![1.0, 2.0]; + assert!(access::dup_top(&mut empty).is_some()); + assert_eq!(empty, &[1.0, 2.0, 2.0]); +} + +#[test] +pub fn median_already_sorted() { + assert_eq!(access::median(&[1, 2, 3]), Some(2)); +} + +#[test] +pub fn median_shuffled() { + assert_eq!(access::median(&[420, 69, 128]), Some(128)); +} + +#[test] +pub fn median_empty() { + assert_eq!(access::median(&[]), None); +} diff --git a/subject_source/tests/vec_compute.rs b/subject_source/tests/vec_compute.rs new file mode 100644 index 0000000..4d613dc --- /dev/null +++ b/subject_source/tests/vec_compute.rs @@ -0,0 +1,78 @@ +use subject_source::vec::compute::*; + +#[test] +pub fn compute_empty() { + assert_eq!(compute(&[]), Err(ComputeError::NotEnoughData)); +} + +#[test] +pub fn compute_too_many_ops() { + assert_eq!( + compute(&[Operation::Push(1.0), Operation::Binary(Binary::Add)]), + Err(ComputeError::NotEnoughData) + ); +} + +#[test] +pub fn compute_division_by_zero_push() { + assert_eq!( + compute(&[ + Operation::Push(1.0), + Operation::Push(0.0), + Operation::Binary(Binary::Div) + ]), + Err(ComputeError::DivisionByZero) + ); +} + +#[test] +pub fn compute_division_by_zero_operation() { + assert_eq!( + compute(&[ + Operation::Push(1.0), + Operation::Push(1.0), + Operation::Push(1.0), + Operation::Binary(Binary::Sub), + Operation::Binary(Binary::Div) + ]), + Err(ComputeError::DivisionByZero) + ); +} + +#[test] +pub fn compute_all_ops() { + assert_eq!( + compute(&[ + Operation::Push(1.0), + Operation::Push(3.0), + Operation::Push(2.0), + Operation::Binary(Binary::Sub), + Operation::Push(5.0), + Operation::Binary(Binary::Mul), + Operation::Binary(Binary::Add), + Operation::Push(2.0), + Operation::Binary(Binary::Div), + ]), + Ok(3.0), + ); +} + +#[test] +pub fn compute_add() { + assert_eq!(compute(&[Operation::Push(1.0), Operation::Push(2.0), Operation::Binary(Binary::Add)]), Ok(3.0)); +} + +#[test] +pub fn compute_sub() { + assert_eq!(compute(&[Operation::Push(1.0), Operation::Push(2.0), Operation::Binary(Binary::Sub)]), Ok(-1.0)); +} + +#[test] +pub fn compute_mul() { + assert_eq!(compute(&[Operation::Push(3.0), Operation::Push(2.0), Operation::Binary(Binary::Mul)]), Ok(6.0)); +} + +#[test] +pub fn compute_div() { + assert_eq!(compute(&[Operation::Push(42.0), Operation::Push(7.0), Operation::Binary(Binary::Div)]), Ok(6.0)); +} diff --git a/subject_text/tree b/subject_text/tree index 3f6193c..94956a0 100644 --- a/subject_text/tree +++ b/subject_text/tree @@ -6,6 +6,7 @@    │   └── result.rs    ├── errors.rs    ├── vec -    │   └── access.rs +    │   ├── access.rs +    │   └── compute.rs    ├── vec.rs    └── lib.rs diff --git a/subject_text/vec/access.md b/subject_text/vec/access.md new file mode 100644 index 0000000..532cc07 --- /dev/null +++ b/subject_text/vec/access.md @@ -0,0 +1,66 @@ +--- +name = "Accessing values" +file = "src/vec/access.rs" +--- + +Instead of using the good old C-style bound checking: + +```rust +if vec.len() < 1 { + return None; +} else { + // compiler still thinks this line can panic + return vec[0]; +} +``` + +Try to implement these functions using non-panicking methods like [`last`](https://doc.rust-lang.org/std/primitive.slice.html#method.last), [`last_chunk`](https://doc.rust-lang.org/std/primitive.slice.html#method.last_chunk), or [`get`](https://doc.rust-lang.org/std/primitive.slice.html#method.get). + +> ## note +> Don't be afraid of the `get` function prototype, look at the examples, they are fairly simple, it's just that `get` can work on multiple types, allowing for slice indexing as well as single element indexing. + +> ## note +> You may want to look at the [`sort`](https://doc.rust-lang.org/std/primitive.slice.html#method.sort) and [`to_vec`](https://doc.rust-lang.org/std/primitive.slice.html#method.to_vec) functions for the median. + +```prototype +/// Add the last two numbers of the input slice. +/// +/// # Return value +/// `None` if the slice is not large enough +/// `Some(result)` if the slice has at least 2 elements +pub fn add_last_two(v: &[f32]) -> Option { + unimplemented!() +} + +/// Duplicate the top element from the stack if it exist +/// (the stack is represented as a Vec with top == last) +/// +/// # Return value +/// `Some(())` if the operation succeeded +/// `None` if not +pub fn dup_top(v: &mut Vec) -> Option<()> { + unimplemented!() +} + +/// Compute the median of a slice in place (if the slice was sorted, it would be the middle element) +pub fn median(v: &[i32]) -> Option { + unimplemented!() +} +``` + +```example +fn main() { + assert_eq!(add_last_two(&[]), None); + assert_eq!(add_last_two(&[10.0]), None); + assert_eq!(add_last_two(&[1.0, 2.0, 3.0]), Some(5.0)); + + let mut stack = vec![1.0]; + assert!(dup_top(&mut stack).is_some()); + assert_eq!(&stack, &[1.0, 1.0]); + + stack.clear(); + assert!(dup_top(&mut stack).is_none()); + + assert_eq!(median(&[2, 1, 3]), Some(2)); +} +``` diff --git a/subject_text/vec/compute.md b/subject_text/vec/compute.md new file mode 100644 index 0000000..2cd0460 --- /dev/null +++ b/subject_text/vec/compute.md @@ -0,0 +1,64 @@ +--- +name = "Compute simple Forth expressions" +file = "src/vec/compute.rs" +--- + +[Forth](https://en.wikipedia.org/wiki/Forth_(programming_language)) is a stack based programming language based on the [Reverse Polish Notation](https://en.wikipedia.org/wiki/Reverse_Polish_notation). In this language, a program is expressed as a stream of words, each word representing an instruction. For example, this expression: + +``` +1.0 2.0 + +``` + +represent a program made of 3 words: `1.0` meaning "push the number 1.0" on the stack, `2.0` meaning push 2.0 on the stack, and `+` meaning "pop the two highest numbers off the stack, and push their sum". We will first define some types to represent such program, and then implement the `compute` function, taking a `slice` of operations and giving the result of computing these operations. + +```rust +/// Possible operations +pub enum Operation { + Push(f32), + Binary(Binary), +} + +/// Different binary operators +pub enum Binary { + /// + + Add, + /// - + Sub, + /// * + Mul, + /// / + Div, +} + +/// What could go wrong when computing a Forth expression +#[derive(PartialEq, Debug)] +pub enum ComputeError { + NotEnoughData, + DivisionByZero, +} +``` + +```prototype +pub fn compute(operations: &[Operation]) -> Result { + unimplemented!() +} +``` + +```example +fn main() { + // 1 2 3 * + + // gives the result 7 because it's computed as 1 + 2 * 3 + assert_eq!( + compute(&[ + Operation::Push(1.0), + Operation::Push(2.0), + Operation::Push(3.0), + Operation::Binary(Binary::Mul), + Operation::Binary(Binary::Add), + ]), + Ok(7.0) + ); + + assert_eq!(compute(&[Operation::Binary(Binary::Add)), Err(ComputeError::NotEnoughData)); +} +``` diff --git a/subject_text/vec/index.md b/subject_text/vec/index.md index af20326..4aa491d 100644 --- a/subject_text/vec/index.md +++ b/subject_text/vec/index.md @@ -1,34 +1,13 @@ --- name = "Vecs and slices" -difficulty = 2 -exercises = ["access.md"] +difficulty = 5 +exercises = ["access.md", "compute.md"] --- Let's now look at some functions on [`slice`](https://doc.rust-lang.org/std/primitive.slice.html)s and [`Vec`](https://doc.rust-lang.org/std/vec/struct.Vec.html)s. Instead of manualy checking things we will follow the type system using `Option`s and `Result`s we saw earlier. -```note -Slices (`[T]`) represent some memory space containing an arbitrary number of elements of type `T`. Since they don't have a size known at compilation time, we can only access them through pointers, commonly `&[T]` (references to slices). -``` - -```deepening -`Vec` can be seen as [owned](https://doc.rust-lang.org/book/ch04-00-understanding-ownership.html) `[T]`, it means that every function working on a `&[T]` can work on a `&Vec`. -``` - -```prototype -/// Add the last two numbers of the input slice. -/// -/// If the slice is not large enough, return `None` -/// If it is, return the computed value in a `Some` -pub fn add_last_two(v: &[f32]) -> Option { - unimplemented!() -} -``` - -```example -fn main() { - assert_eq!(add_last_two(&[]), None); - assert_eq!(add_last_two(&[10.0]), None); - assert_eq!(add_last_two(&[1.0, 2.0, 3.0]), Some(5.0)); -} -``` +> ## note +> Slices (`[T]`) represent some memory space containing an arbitrary number of elements of type `T`. Since they don't have a size known at compilation time, we can only access them through pointers, commonly `&[T]` (references to slices). +> ## deepening +> `Vec` can be seen as [owned](https://doc.rust-lang.org/book/ch04-00-understanding-ownership.html) `[T]`, it means that every function working on a `&[T]` can work on a `&Vec`.