Skip to content

Commit d1d2dad

Browse files
authored
Merge pull request #736 from petertseng/wordy
wordy 1.4.0
2 parents da676c3 + f7ebfd3 commit d1d2dad

File tree

4 files changed

+103
-57
lines changed

4 files changed

+103
-57
lines changed

exercises/wordy/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
[package]
22
name = "wordy"
3-
version = "1.1.0"
3+
version = "1.4.0"

exercises/wordy/README.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,14 @@
22

33
Parse and evaluate simple math word problems returning the answer as an integer.
44

5+
## Iteration 0 — Numbers
6+
7+
Problems with no operations simply evaluate to the number given.
8+
9+
> What is 5?
10+
11+
Evaluates to 5.
12+
513
## Iteration 1 — Addition
614

715
Add two numbers together.
@@ -43,6 +51,14 @@ left-to-right, _ignoring the typical order of operations._
4351
4452
15 (i.e. not 9)
4553

54+
## Iteration 4 — Errors
55+
56+
The parser should reject:
57+
58+
* Unsupported operations ("What is 52 cubed?")
59+
* Non-math questions ("Who is the President of the United States")
60+
* Word problems with invalid syntax ("What is 1 plus plus 2?")
61+
4662
## Bonus — Exponentials
4763

4864
If you'd like, handle exponentials.

exercises/wordy/example.rs

Lines changed: 44 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -1,65 +1,53 @@
1-
struct Token<'a> {
2-
value: &'a str,
1+
#[derive(PartialEq)]
2+
enum Token<'a> {
3+
Number(i32),
4+
NonNumber(&'a str),
35
}
46

5-
impl <'a> Token<'a> {
6-
fn is_valid(&self) -> bool {
7-
!self.value.is_empty() && (self.is_operand() || self.is_operator())
8-
}
9-
10-
fn is_operand(&self) -> bool {
11-
self.value.chars().all(|c| c.is_numeric() || c == '-')
12-
}
13-
14-
fn is_operator(&self) -> bool {
15-
self.value == "plus"
16-
|| self.value == "minus"
17-
|| self.value == "multiplied"
18-
|| self.value == "divided"
19-
}
7+
fn apply_op<'a, 'b>(num1: i32, words: &'a [Token<'b>]) -> Option<(i32, &'a [Token<'b>])> {
8+
let number_pos = words.iter().position(|w| match w {
9+
Token::Number(_) => true,
10+
Token::NonNumber(_) => false,
11+
})?;
12+
let (op_and_num, remainder) = words.split_at(number_pos + 1);
13+
let (op, num2) = op_and_num.split_at(number_pos);
14+
let num2 = match num2 {
15+
[Token::Number(i)] => i,
16+
_ => unreachable!("We split at a Number above, so num2 is surely a single-element slice w/ a number"),
17+
};
18+
match op {
19+
[Token::NonNumber("plus")] => Some(num1 + num2),
20+
[Token::NonNumber("minus")] => Some(num1 - num2),
21+
[Token::NonNumber("multiplied"), Token::NonNumber("by")] => Some(num1 * num2),
22+
[Token::NonNumber("divided"), Token::NonNumber("by")] => Some(num1 / num2),
23+
_ => None,
24+
}.map(|n| (n, remainder))
2025
}
2126

2227
pub fn answer(c: &str) -> Option<i32> {
23-
let mut t = tokens(c);
24-
let mut result: i32 = 0;
25-
let mut opr = "plus";
26-
27-
if t.len() <= 1 {
28-
None
29-
} else {
30-
while t.len() > 1 {
31-
result = evaluate(result, opr, operand(&t.remove(0)));
32-
opr = operator(&t.remove(0));
28+
let words = c.trim_end_matches('?').split_whitespace().map(|word| {
29+
if let Ok(i) = word.parse::<i32>() {
30+
Token::Number(i)
31+
} else {
32+
Token::NonNumber(word)
3333
}
34-
result = evaluate(result, opr, operand(&t.remove(0)));
35-
Some(result)
34+
}).collect::<Vec<_>>();
35+
if words.len() < 3 {
36+
return None;
3637
}
37-
}
38-
39-
fn evaluate(r: i32, operator: &str, operand: i32) -> i32 {
40-
match operator {
41-
"plus" => r + operand,
42-
"minus" => r - operand,
43-
"multiplied" => r * operand,
44-
"divided" => r / operand,
45-
_ => r,
38+
let mut result: i32 = match words[0..3] {
39+
[
40+
Token::NonNumber("What"),
41+
Token::NonNumber("is"),
42+
Token::Number(i),
43+
] => i,
44+
_ => return None,
45+
};
46+
let mut words = words.split_at(3).1;
47+
while !words.is_empty() {
48+
let tmp = apply_op(result, words)?;
49+
result = tmp.0;
50+
words = tmp.1;
4651
}
47-
}
48-
49-
fn operand(t: &Token) -> i32 {
50-
t.value.parse().unwrap()
51-
}
52-
53-
fn operator<'a>(t: &Token<'a>) -> &'a str {
54-
t.value
55-
}
56-
57-
fn tokens<'a>(command: &'a str) -> Vec<Token<'a>> {
58-
command
59-
.split(|c: char| c.is_whitespace() || c == '?')
60-
.map(|w| Token {
61-
value: w,
62-
})
63-
.filter(|t| t.is_valid())
64-
.collect()
52+
Some(result)
6553
}

exercises/wordy/tests/wordy.rs

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,13 @@ extern crate wordy;
33
use wordy::answer;
44

55
#[test]
6+
fn just_a_number() {
7+
let command = "What is 5?";
8+
assert_eq!(Some(5), answer(command));
9+
}
10+
11+
#[test]
12+
#[ignore]
613
fn addition() {
714
let command = "What is 1 plus 1?";
815
assert_eq!(Some(2), answer(command));
@@ -112,3 +119,38 @@ fn non_math_question() {
112119
let command = "Who is the President of the United States?";
113120
assert!(answer(command).is_none());
114121
}
122+
123+
#[test]
124+
#[ignore]
125+
fn reject_incomplete_problem() {
126+
let command = "What is 1 plus?";
127+
assert!(answer(command).is_none());
128+
}
129+
130+
#[test]
131+
#[ignore]
132+
fn reject_two_operations_in_a_row() {
133+
let command = "What is 1 plus plus 2?";
134+
assert!(answer(command).is_none());
135+
}
136+
137+
#[test]
138+
#[ignore]
139+
fn reject_two_numbers_in_a_row() {
140+
let command = "What is 1 plus 2 1?";
141+
assert!(answer(command).is_none());
142+
}
143+
144+
#[test]
145+
#[ignore]
146+
fn reject_postfix_notation() {
147+
let command = "What is 1 2 plus?";
148+
assert!(answer(command).is_none());
149+
}
150+
151+
#[test]
152+
#[ignore]
153+
fn reject_prefix_notation() {
154+
let command = "What is plus 1 2?";
155+
assert!(answer(command).is_none());
156+
}

0 commit comments

Comments
 (0)