mirror of
https://github.com/google/comprehensive-rust.git
synced 2025-05-16 07:36:05 +02:00
Add a pattern-matching exercise (#1231)
This adds a second day-1 afternoon exercise, drawn from the v2 work. It illustrates general use of an enum and also previews `Result`. Fixes #1228.
This commit is contained in:
parent
cc0a78ed9a
commit
c4a821d43d
@ -70,7 +70,7 @@
|
|||||||
|
|
||||||
- [Exercises](exercises/day-1/afternoon.md)
|
- [Exercises](exercises/day-1/afternoon.md)
|
||||||
- [Luhn Algorithm](exercises/day-1/luhn.md)
|
- [Luhn Algorithm](exercises/day-1/luhn.md)
|
||||||
- [Pattern Matching (TBD)]()
|
- [Pattern Matching](exercises/day-1/pattern-matching.md)
|
||||||
|
|
||||||
# Day 2: Morning
|
# Day 2: Morning
|
||||||
|
|
||||||
|
33
src/exercises/day-1/pattern-matching.md
Normal file
33
src/exercises/day-1/pattern-matching.md
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
# Exercise: Expression Evaluation
|
||||||
|
|
||||||
|
Let's write a simple recursive evaluator for arithmetic expressions.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
{{#include pattern-matching.rs:Operation}}
|
||||||
|
|
||||||
|
{{#include pattern-matching.rs:Expression}}
|
||||||
|
|
||||||
|
{{#include pattern-matching.rs:Res}}
|
||||||
|
|
||||||
|
{{#include pattern-matching.rs:eval}}
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
{{#include pattern-matching.rs:tests}}
|
||||||
|
```
|
||||||
|
|
||||||
|
The `Box` type here is a smart pointer, and will be covered in detail later in
|
||||||
|
the course. An expression can be "boxed" with `Box::new` as seen in the tests.
|
||||||
|
To evaluate a boxed expression, use the deref operator to "unbox" it:
|
||||||
|
`eval(*boxed_expr)`.
|
||||||
|
|
||||||
|
Some expressions cannot be evaluated and will return an error. The `Res`
|
||||||
|
type represents either a successful value or an error with a message. This is
|
||||||
|
very similar to the standard-library `Result` which we will see later.
|
||||||
|
|
||||||
|
Copy and paste the code into the Rust playground, and begin implementing
|
||||||
|
`eval`. The final product should pass the tests. It may be helpful to use
|
||||||
|
`todo!()` and get the tests to pass one-by-one.
|
||||||
|
|
||||||
|
If you finish early, try writing a test that results in an integer overflow.
|
||||||
|
How could you handle this with `Res::Err` instead of a panic?
|
150
src/exercises/day-1/pattern-matching.rs
Normal file
150
src/exercises/day-1/pattern-matching.rs
Normal file
@ -0,0 +1,150 @@
|
|||||||
|
// Copyright 2023 Google LLC
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
// ANCHOR: solution
|
||||||
|
// ANCHOR: Operation
|
||||||
|
/// An operation to perform on two subexpressions.
|
||||||
|
#[derive(Debug)]
|
||||||
|
enum Operation {
|
||||||
|
Add,
|
||||||
|
Sub,
|
||||||
|
Mul,
|
||||||
|
Div,
|
||||||
|
}
|
||||||
|
// ANCHOR_END: Operation
|
||||||
|
|
||||||
|
// ANCHOR: Expression
|
||||||
|
/// An expression, in tree form.
|
||||||
|
#[derive(Debug)]
|
||||||
|
enum Expression {
|
||||||
|
/// An operation on two subexpressions.
|
||||||
|
Op {
|
||||||
|
op: Operation,
|
||||||
|
left: Box<Expression>,
|
||||||
|
right: Box<Expression>,
|
||||||
|
},
|
||||||
|
|
||||||
|
/// A literal value
|
||||||
|
Value(i64),
|
||||||
|
}
|
||||||
|
// ANCHOR_END: Expression
|
||||||
|
|
||||||
|
// ANCHOR: Res
|
||||||
|
/// The result of evaluating an expression.
|
||||||
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
|
enum Res {
|
||||||
|
/// Evaluation was successful, with the given result.
|
||||||
|
Ok(i64),
|
||||||
|
/// Evaluation failed, with the given error message.
|
||||||
|
Err(String),
|
||||||
|
}
|
||||||
|
// Allow `Ok` and `Err` as shorthands for `Res::Ok` and `Res::Err`.
|
||||||
|
use Res::{Err, Ok};
|
||||||
|
// ANCHOR_END: Res
|
||||||
|
|
||||||
|
// ANCHOR: eval
|
||||||
|
fn eval(e: Expression) -> Res {
|
||||||
|
// ANCHOR_END: eval
|
||||||
|
match e {
|
||||||
|
Expression::Op { op, left, right } => {
|
||||||
|
let left = match eval(*left) {
|
||||||
|
Ok(v) => v,
|
||||||
|
Err(msg) => return Err(msg),
|
||||||
|
};
|
||||||
|
let right = match eval(*right) {
|
||||||
|
Ok(v) => v,
|
||||||
|
Err(msg) => return Err(msg),
|
||||||
|
};
|
||||||
|
Ok(match op {
|
||||||
|
Operation::Add => left + right,
|
||||||
|
Operation::Sub => left - right,
|
||||||
|
Operation::Mul => left * right,
|
||||||
|
Operation::Div => {
|
||||||
|
if right == 0 {
|
||||||
|
return Err(String::from("division by zero"));
|
||||||
|
} else {
|
||||||
|
left / right
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
Expression::Value(v) => Ok(v),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ANCHOR: tests
|
||||||
|
#[test]
|
||||||
|
fn test_value() {
|
||||||
|
assert_eq!(eval(Expression::Value(19)), Ok(19));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_sum() {
|
||||||
|
assert_eq!(
|
||||||
|
eval(Expression::Op {
|
||||||
|
op: Operation::Add,
|
||||||
|
left: Box::new(Expression::Value(10)),
|
||||||
|
right: Box::new(Expression::Value(20)),
|
||||||
|
}),
|
||||||
|
Ok(30)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_recursion() {
|
||||||
|
let term1 = Expression::Op {
|
||||||
|
op: Operation::Mul,
|
||||||
|
left: Box::new(Expression::Value(10)),
|
||||||
|
right: Box::new(Expression::Value(9)),
|
||||||
|
};
|
||||||
|
let term2 = Expression::Op {
|
||||||
|
op: Operation::Mul,
|
||||||
|
left: Box::new(Expression::Op {
|
||||||
|
op: Operation::Sub,
|
||||||
|
left: Box::new(Expression::Value(3)),
|
||||||
|
right: Box::new(Expression::Value(4)),
|
||||||
|
}),
|
||||||
|
right: Box::new(Expression::Value(5)),
|
||||||
|
};
|
||||||
|
assert_eq!(
|
||||||
|
eval(Expression::Op {
|
||||||
|
op: Operation::Add,
|
||||||
|
left: Box::new(term1),
|
||||||
|
right: Box::new(term2),
|
||||||
|
}),
|
||||||
|
Ok(85)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_error() {
|
||||||
|
assert_eq!(
|
||||||
|
eval(Expression::Op {
|
||||||
|
op: Operation::Div,
|
||||||
|
left: Box::new(Expression::Value(99)),
|
||||||
|
right: Box::new(Expression::Value(0)),
|
||||||
|
}),
|
||||||
|
Err(String::from("division by zero"))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
// ANCHOR_END: tests
|
||||||
|
fn main() {
|
||||||
|
let expr = Expression::Op {
|
||||||
|
op: Operation::Sub,
|
||||||
|
left: Box::new(Expression::Value(20)),
|
||||||
|
right: Box::new(Expression::Value(10)),
|
||||||
|
};
|
||||||
|
println!("expr: {:?}", expr);
|
||||||
|
println!("result: {:?}", eval(expr));
|
||||||
|
}
|
@ -10,4 +10,6 @@
|
|||||||
|
|
||||||
## Pattern matching
|
## Pattern matching
|
||||||
|
|
||||||
TBD.
|
```rust
|
||||||
|
{{#include pattern-matching.rs:solution}}
|
||||||
|
```
|
||||||
|
Loading…
x
Reference in New Issue
Block a user