diff --git a/exercises/wordy/Cargo.toml b/exercises/wordy/Cargo.toml index 14a10da9e..2e7d157b7 100644 --- a/exercises/wordy/Cargo.toml +++ b/exercises/wordy/Cargo.toml @@ -1,3 +1,3 @@ [package] name = "wordy" -version = "1.1.0" +version = "1.4.0" diff --git a/exercises/wordy/README.md b/exercises/wordy/README.md index 6938d2201..24453b15d 100644 --- a/exercises/wordy/README.md +++ b/exercises/wordy/README.md @@ -2,6 +2,14 @@ Parse and evaluate simple math word problems returning the answer as an integer. +## Iteration 0 — Numbers + +Problems with no operations simply evaluate to the number given. + +> What is 5? + +Evaluates to 5. + ## Iteration 1 — Addition Add two numbers together. @@ -43,6 +51,14 @@ left-to-right, _ignoring the typical order of operations._ 15 (i.e. not 9) +## Iteration 4 — Errors + +The parser should reject: + +* Unsupported operations ("What is 52 cubed?") +* Non-math questions ("Who is the President of the United States") +* Word problems with invalid syntax ("What is 1 plus plus 2?") + ## Bonus — Exponentials If you'd like, handle exponentials. diff --git a/exercises/wordy/example.rs b/exercises/wordy/example.rs index 73e28de12..e4a089fb9 100644 --- a/exercises/wordy/example.rs +++ b/exercises/wordy/example.rs @@ -1,65 +1,53 @@ -struct Token<'a> { - value: &'a str, +#[derive(PartialEq)] +enum Token<'a> { + Number(i32), + NonNumber(&'a str), } -impl <'a> Token<'a> { - fn is_valid(&self) -> bool { - !self.value.is_empty() && (self.is_operand() || self.is_operator()) - } - - fn is_operand(&self) -> bool { - self.value.chars().all(|c| c.is_numeric() || c == '-') - } - - fn is_operator(&self) -> bool { - self.value == "plus" - || self.value == "minus" - || self.value == "multiplied" - || self.value == "divided" - } +fn apply_op<'a, 'b>(num1: i32, words: &'a [Token<'b>]) -> Option<(i32, &'a [Token<'b>])> { + let number_pos = words.iter().position(|w| match w { + Token::Number(_) => true, + Token::NonNumber(_) => false, + })?; + let (op_and_num, remainder) = words.split_at(number_pos + 1); + let (op, num2) = op_and_num.split_at(number_pos); + let num2 = match num2 { + [Token::Number(i)] => i, + _ => unreachable!("We split at a Number above, so num2 is surely a single-element slice w/ a number"), + }; + match op { + [Token::NonNumber("plus")] => Some(num1 + num2), + [Token::NonNumber("minus")] => Some(num1 - num2), + [Token::NonNumber("multiplied"), Token::NonNumber("by")] => Some(num1 * num2), + [Token::NonNumber("divided"), Token::NonNumber("by")] => Some(num1 / num2), + _ => None, + }.map(|n| (n, remainder)) } pub fn answer(c: &str) -> Option { - let mut t = tokens(c); - let mut result: i32 = 0; - let mut opr = "plus"; - - if t.len() <= 1 { - None - } else { - while t.len() > 1 { - result = evaluate(result, opr, operand(&t.remove(0))); - opr = operator(&t.remove(0)); + let words = c.trim_end_matches('?').split_whitespace().map(|word| { + if let Ok(i) = word.parse::() { + Token::Number(i) + } else { + Token::NonNumber(word) } - result = evaluate(result, opr, operand(&t.remove(0))); - Some(result) + }).collect::>(); + if words.len() < 3 { + return None; } -} - -fn evaluate(r: i32, operator: &str, operand: i32) -> i32 { - match operator { - "plus" => r + operand, - "minus" => r - operand, - "multiplied" => r * operand, - "divided" => r / operand, - _ => r, + let mut result: i32 = match words[0..3] { + [ + Token::NonNumber("What"), + Token::NonNumber("is"), + Token::Number(i), + ] => i, + _ => return None, + }; + let mut words = words.split_at(3).1; + while !words.is_empty() { + let tmp = apply_op(result, words)?; + result = tmp.0; + words = tmp.1; } -} - -fn operand(t: &Token) -> i32 { - t.value.parse().unwrap() -} - -fn operator<'a>(t: &Token<'a>) -> &'a str { - t.value -} - -fn tokens<'a>(command: &'a str) -> Vec> { - command - .split(|c: char| c.is_whitespace() || c == '?') - .map(|w| Token { - value: w, - }) - .filter(|t| t.is_valid()) - .collect() + Some(result) } diff --git a/exercises/wordy/tests/wordy.rs b/exercises/wordy/tests/wordy.rs index 5587eb039..595b6d45b 100644 --- a/exercises/wordy/tests/wordy.rs +++ b/exercises/wordy/tests/wordy.rs @@ -3,6 +3,13 @@ extern crate wordy; use wordy::answer; #[test] +fn just_a_number() { + let command = "What is 5?"; + assert_eq!(Some(5), answer(command)); +} + +#[test] +#[ignore] fn addition() { let command = "What is 1 plus 1?"; assert_eq!(Some(2), answer(command)); @@ -112,3 +119,38 @@ fn non_math_question() { let command = "Who is the President of the United States?"; assert!(answer(command).is_none()); } + +#[test] +#[ignore] +fn reject_incomplete_problem() { + let command = "What is 1 plus?"; + assert!(answer(command).is_none()); +} + +#[test] +#[ignore] +fn reject_two_operations_in_a_row() { + let command = "What is 1 plus plus 2?"; + assert!(answer(command).is_none()); +} + +#[test] +#[ignore] +fn reject_two_numbers_in_a_row() { + let command = "What is 1 plus 2 1?"; + assert!(answer(command).is_none()); +} + +#[test] +#[ignore] +fn reject_postfix_notation() { + let command = "What is 1 2 plus?"; + assert!(answer(command).is_none()); +} + +#[test] +#[ignore] +fn reject_prefix_notation() { + let command = "What is plus 1 2?"; + assert!(answer(command).is_none()); +}