vec-finish #9

Merged
lilymonade merged 5 commits from vec-finish into main 2025-03-14 22:13:53 +00:00
10 changed files with 341 additions and 30 deletions

View File

@ -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"

View File

@ -1 +1,2 @@
pub mod access;
pub mod compute;

View File

@ -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<f32> {
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<f32>) -> 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<i32> {
let mut tmp = v.to_vec();
tmp.sort();
match tmp.get(tmp.len() / 2) {
Some(&r) => Some(r),
None => None,
}
}

View File

@ -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<f32, ComputeError> {
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)
}

View File

@ -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);
}

View File

@ -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));
}

View File

@ -6,6 +6,7 @@
   │   └── result.rs
   ├── errors.rs
   ├── vec
   │   └── access.rs
   │   ├── access.rs
   │   └── compute.rs
   ├── vec.rs
   └── lib.rs

View File

@ -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<f32> {
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<f32>) -> 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<i32> {
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));
}
```

View File

@ -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<f32, ComputeError> {
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));
}
```

View File

@ -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<T>` 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<T>`.
```
```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<f32> {
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<T>` 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<T>`.