1
0
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:
Dustin J. Mitchell 2023-09-22 07:36:57 -04:00 committed by GitHub
parent cc0a78ed9a
commit c4a821d43d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 187 additions and 2 deletions

View File

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

View 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?

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

View File

@ -10,4 +10,6 @@
## Pattern matching ## Pattern matching
TBD. ```rust
{{#include pattern-matching.rs:solution}}
```