rs-03-rpn
1.000
Challenge ยท difficulty 3/5
# Evaluate RPN
Implement the library file **`src/lib.rs`** exposing:
```rust
pub fn eval_rpn(tokens: &[&str]) -> Result<f64, String>
```
Evaluate a [Reverse Polish Notation](https://en.wikipedia.org/wiki/Reverse_Polish_notation)
expression given as a slice of tokens. Each token is either:
- a number that parses as `f64`, or
- one of the four binary operators `+`, `-`, `*`, `/`.
Evaluation uses a stack: push numbers; for an operator, pop the top two values
`b` (top) then `a` (next) and push the result of `a OP b`. After consuming all
tokens, exactly one value must remain โ that value is the result.
Return `Ok(value)` on success, or `Err(message)` (any non-empty message) on
malformed input:
- **too few operands** โ an operator with fewer than two values on the stack.
- **leftover operands** โ more than one value remains after all tokens consumed
(or zero tokens / empty input, which leaves no value).
- **unknown token** โ a token that is neither a valid `f64` nor a known operator.
- **division by zero** โ a `/` whose right operand `b` is `0.0`.
Note: operator order matters for non-commutative operators. For `["3", "4", "-"]`
the result is `3 - 4 = -1.0`, and for `["8", "2", "/"]` it is `8 / 2 = 4.0`.
Examples:
- `eval_rpn(&["2", "3", "+"])` โ `Ok(5.0)`
- `eval_rpn(&["5", "1", "2", "+", "4", "*", "+", "3", "-"])` โ `Ok(14.0)` (precedence-via-RPN)
- `eval_rpn(&["3", "4", "-"])` โ `Ok(-1.0)`
- `eval_rpn(&["1", "+"])` โ `Err(..)` (too few operands)
- `eval_rpn(&["1", "2"])` โ `Err(..)` (leftover operands)
- `eval_rpn(&["1", "foo", "+"])` โ `Err(..)` (unknown token)
- `eval_rpn(&["1", "0", "/"])` โ `Err(..)` (division by zero)
Use only the standard library. Tests live in `tests/` and call it as
`challenge::eval_rpn`.
tests/rpn.rs
use challenge::eval_rpn;
#[test]
fn basic_eval() {
assert_eq!(eval_rpn(&["2", "3", "+"]), Ok(5.0));
assert_eq!(eval_rpn(&["8", "2", "/"]), Ok(4.0));
assert_eq!(eval_rpn(&["42"]), Ok(42.0));
}
#[test]
fn operator_order_matters() {
assert_eq!(eval_rpn(&["3", "4", "-"]), Ok(-1.0));
assert_eq!(eval_rpn(&["10", "4", "-"]), Ok(6.0));
}
#[test]
fn precedence_via_rpn() {
// (1 + 2) * 4 + 5 - 3 = 14
assert_eq!(
eval_rpn(&["5", "1", "2", "+", "4", "*", "+", "3", "-"]),
Ok(14.0)
);
}
#[test]
fn err_too_few_operands() {
assert!(eval_rpn(&["1", "+"]).is_err());
assert!(eval_rpn(&["+"]).is_err());
}
#[test]
fn err_leftover_operands() {
assert!(eval_rpn(&["1", "2"]).is_err());
assert!(eval_rpn(&["1", "2", "3", "+"]).is_err());
}
#[test]
fn err_empty_input() {
assert!(eval_rpn(&[]).is_err());
}
#[test]
fn err_unknown_token() {
assert!(eval_rpn(&["1", "foo", "+"]).is_err());
assert!(eval_rpn(&["1", "2", "%"]).is_err());
}
#[test]
fn err_division_by_zero() {
assert!(eval_rpn(&["1", "0", "/"]).is_err());
}
System prompt
You are an expert programmer. Solve the task exactly as specified. Output your solution as fenced code blocks using the required file name(s) and the exact function/type signatures requested. Prefer correctness; do not include prose outside code unless asked.Proposed solution
```rust
pub fn eval_rpn(tokens: &[&str]) -> Result<f64, String> {
let mut stack: Vec<f64> = Vec::new();
for token in tokens {
match *token {
"+" => {
if stack.len() < 2 {
return Err("too few operands".to_string());
}
let b = stack.pop().unwrap();
let a = stack.pop().unwrap();
stack.push(a + b);
}
"-" => {
if stack.len() < 2 {
return Err("too few operands".to_string());
}
let b = stack.pop().unwrap();
let a = stack.pop().unwrap();
stack.push(a - b);
}
"*" => {
if stack.len() < 2 {
return Err("too few operands".to_string());
}
let b = stack.pop().unwrap();
let a = stack.pop().unwrap();
stack.push(a * b);
}
"/" => {
if stack.len() < 2 {
return Err("too few operands".to_string());
}
let b = stack.pop().unwrap();
let a = stack.pop().unwrap();
if b == 0.0 {
return Err("division by zero".to_string());
}
stack.push(a / b);
}
_ => {
match token.parse::<f64>() {
Ok(num) => stack.push(num),
Err(_) => return Err(format!("unknown token: {}", token)),
}
}
}
}
if stack.len() != 1 {
return Err("leftover operands".to_string());
}
Ok(stack.pop().unwrap())
}
```Test output (stdout)
running 0 tests
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
running 8 tests
........
test result: ok. 8 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
running 0 tests
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
Reasoning (chain-of-thought)
The user wants me to implement a Reverse Polish Notation (RPN) evaluator in Rust.
Requirements:
- Function signature: `pub fn eval_rpn(tokens: &[&str]) -> Result<f64, String>`
- Uses a stack-based approach
- Tokens are either numbers (parseable as f64) or operators (+, -, *, /)
- For operators: pop b (top), then a (next), push a OP b
- After all tokens, exactly one value must remain
- Error cases:
- Too few operands (operator with < 2 values on stack)
- Leftover operands (> 1 value remaining, or 0 values)
- Unknown token (not a valid f64 and not a known operator)
- Division by zero
Let me implement this:
```rust
pub fn eval_rpn(tokens: &[&str]) -> Result<f64, String> {
let mut stack: Vec<f64> = Vec::new();
for token in tokens {
match *token {
"+" => {
if stack.len() < 2 {
return Err("too few operands".to_string());
}
let b = stack.pop().unwrap();
let a = stack.pop().unwrap();
stack.push(a + b);
}
"-" => {
if stack.len() < 2 {
return Err("too few operands".to_string());
}
let b = stack.pop().unwrap();
let a = stack.pop().unwrap();
stack.push(a - b);
}
"*" => {
if stack.len() < 2 {
return Err("too few operands".to_string());
}
let b = stack.pop().unwrap();
let a = stack.pop().unwrap();
stack.push(a * b);
}
"/" => {
if stack.len() < 2 {
return Err("too few operands".to_string());
}
let b = stack.pop().unwrap();
let a = stack.pop().unwrap();
if b == 0.0 {
return Err("division by zero".to_string());
}
stack.push(a / b);
}
_ => {
match token.parse::<f64>() {
Ok(num) => stack.push(num),
Err(_) => return Err(format!("unknown token: {}", token)),
}
}
}
}
if stack.len() != 1 {
return Err("leftover operands".to_string());
}
Ok(stack.pop().unwrap())
}
```
This looks correct. Let me verify with the examples:
- `["2", "3", "+"]`: push 2, push 3, pop 3 and 2, push 2+3=5. Stack has [5]. Return Ok(5.0). โ
- `["5", "1", "2", "+", "4", "*", "+", "3", "-"]`:
- push 5, push 1, push 2
- +: pop 2, 1, push 3. Stack: [5, 3]
- push 4. Stack: [5, 3, 4]
- *: pop 4, 3, push 12. Stack: [5, 12]
- +: pop 12, 5, push 17. Stack: [17]
- push 3. Stack: [17, 3]
- -: pop 3, 17, push 14. Stack: [14]
- Return Ok(14.0). โ
- `["3", "4", "-"]`: push 3, push 4, pop 4 and 3, push 3-4=-1. Return Ok(-1.0). โ
- `["1", "+"]`: push 1, + needs 2 operands but only 1. Error. โ
- `["1", "2"]`: push 1, push 2. Stack has 2 values. Error. โ
- `["1", "foo", "+"]`: push 1, "foo" can't parse. Error. โ
- `["1", "0", "/"]`: push 1, push 0, / checks b==0. Error. โ
Looks good. I'll output the solution.