 # ksnortum's solution

## to Wordy in the Java Track

Published at Oct 19 2019 · 1 comment
Instructions
Test suite
Solution

#### Note:

This exercise has changed since this solution was written.

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.

What is 5 plus 13?

Evaluates to 18.

Handle large numbers and negative numbers.

## Iteration 2 — Subtraction, Multiplication and Division

Now, perform the other three operations.

What is 7 minus 5?

2

What is 6 multiplied by 4?

24

What is 25 divided by 5?

5

## Iteration 3 — Multiple Operations

Handle a set of operations, in sequence.

Since these are verbal word problems, evaluate the expression from left-to-right, ignoring the typical order of operations.

What is 5 plus 13 plus 6?

24

What is 3 plus 2 multiplied by 3?

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.

What is 2 raised to the 5th power?

32

## Setup

Go through the setup instructions for Java to install the necessary dependencies:

https://exercism.io/tracks/java/installation

# Running the tests

You can run all the tests for an exercise by entering the following in your terminal:

``````\$ gradle test
``````

Use `gradlew.bat` if you're on Windows

In the test suites all tests but the first have been skipped.

Once you get a test passing, you can enable the next one by removing the `@Ignore("Remove to run test")` annotation.

## Source

Inspired by one of the generated questions in the Extreme Startup game. https://github.com/rchatley/extreme_startup

## Submitting Incomplete Solutions

It's possible to submit an incomplete solution so you can see how others have completed the exercise.

### WordProblemSolverTest.java

``````import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
import org.junit.Before;
import org.junit.rules.ExpectedException;

import static org.junit.Assert.assertEquals;

public class WordProblemSolverTest {

@Rule
public ExpectedException expectedException = ExpectedException.none();

WordProblemSolver solver;

@Before
public void setup() {
solver = new WordProblemSolver();
}

@Test
public void testJustANumber() {
assertEquals(5, solver.solve("What is 5?"));
}

@Ignore("Remove to run test")
@Test
assertEquals(2, solver.solve("What is 1 plus 1?"));
}

@Ignore("Remove to run test")
@Test
assertEquals(55, solver.solve("What is 53 plus 2?"));
}

@Ignore("Remove to run test")
@Test
assertEquals(-11, solver.solve("What is -1 plus -10?"));
}

@Ignore("Remove to run test")
@Test
assertEquals(45801, solver.solve("What is 123 plus 45678?"));
}

@Ignore("Remove to run test")
@Test
public void testSingleSubtraction() {
assertEquals(16, solver.solve("What is 4 minus -12?"));
}

@Ignore("Remove to run test")
@Test
public void testSingleMultiplication() {
assertEquals(-75, solver.solve("What is -3 multiplied by 25?"));
}

@Ignore("Remove to run test")
@Test
public void testSingleDivision() {
assertEquals(-11, solver.solve("What is 33 divided by -3?"));
}

@Ignore("Remove to run test")
@Test
assertEquals(3, solver.solve("What is 1 plus 1 plus 1?"));
}

@Ignore("Remove to run test")
@Test
assertEquals(8, solver.solve("What is 1 plus 5 minus -2?"));
}

@Ignore("Remove to run test")
@Test
public void testMultipleSubtractions() {
assertEquals(3, solver.solve("What is 20 minus 4 minus 13?"));
}

@Ignore("Remove to run test")
@Test
assertEquals(14, solver.solve("What is 17 minus 6 plus 3?"));
}

@Ignore("Remove to run test")
@Test
public void testMultipleMultiplications() {
assertEquals(-12, solver.solve("What is 2 multiplied by -2 multiplied by 3?"));
}

@Ignore("Remove to run test")
@Test
assertEquals(-8, solver.solve("What is -3 plus 7 multiplied by -2?"));
}

@Ignore("Remove to run test")
@Test
public void testMultipleDivisions() {
assertEquals(2, solver.solve("What is -12 divided by 2 divided by -3?"));
}

@Ignore("Remove to run test")
@Test
public void testUnknownOperation() {
expectedException.expect(IllegalArgumentException.class);
expectedException.expectMessage("I'm sorry, I don't understand the question!");

solver.solve("What is 52 cubed?");
}

@Ignore("Remove to run test")
@Test
public void testNonMathQuestion() {
expectedException.expect(IllegalArgumentException.class);
expectedException.expectMessage("I'm sorry, I don't understand the question!");

// See https://en.wikipedia.org/wiki/President_of_the_United_States if you really need to know!
solver.solve("Who is the President of the United States?");
}

@Ignore("Remove to run test")
@Test
public void testMissingAnOperand() {
expectedException.expect(IllegalArgumentException.class);
expectedException.expectMessage("I'm sorry, I don't understand the question!");

solver.solve("What is 1 plus?");
}

@Ignore("Remove to run test")
@Test
public void testNoOperandsOrOperators() {
expectedException.expect(IllegalArgumentException.class);
expectedException.expectMessage("I'm sorry, I don't understand the question!");

solver.solve("What is?");
}

@Ignore("Remove to run test")
@Test
public void testTwoOperationsInARow() {
expectedException.expect(IllegalArgumentException.class);
expectedException.expectMessage("I'm sorry, I don't understand the question!");

solver.solve("What is 1 plus plus 2?");
}

@Ignore("Remove to run test")
@Test
public void testTwoNumbersAfterOperation() {
expectedException.expect(IllegalArgumentException.class);
expectedException.expectMessage("I'm sorry, I don't understand the question!");

solver.solve("What is 1 plus 2 1?");
}

@Ignore("Remove to run test")
@Test
public void testPostfixNotation() {
expectedException.expect(IllegalArgumentException.class);
expectedException.expectMessage("I'm sorry, I don't understand the question!");

solver.solve("What is 1 2 plus?");
}

@Ignore("Remove to run test")
@Test
public void testPrefixNotation() {
expectedException.expect(IllegalArgumentException.class);
expectedException.expectMessage("I'm sorry, I don't understand the question!");

solver.solve("What is plus 1 2?");
}
}``````

### src/main/java/WordProblemSolver.java

``````class WordProblemSolver {

enum State { BEGIN, SET, NUMBER, OPERATION, BY, TO, THE, POWER, TERMINATION }
enum Operation { NONE, PLUS, MINUS, MULTIPLY, DIVIDE, EXPONENT }

private static final String INVALID_MESSAGE = "I'm sorry, I don't understand the question!";
private static final String ONE_OR_MORE_WHITE_SPACES = "\\s+";
private static final String ENDS_WITH_ST = "st\$";
private static final String ENDS_WITH_ND = "nd\$";
private static final String ENDS_WITH_RD = "rd\$";
private static final String ENDS_WITH_TH = "th\$";

int solve(String question) {
Integer result = null;
State state = State.BEGIN;
Operation operation = Operation.NONE;

for (String token : question.split(ONE_OR_MORE_WHITE_SPACES)) {
switch (state) {
case BEGIN:
if (! "What".equalsIgnoreCase(token)) {
throw new IllegalArgumentException(INVALID_MESSAGE);
}

state = State.SET;
break;

case SET:
if (! "is".equalsIgnoreCase(token)) {
throw new IllegalArgumentException(INVALID_MESSAGE);
}

state = State.NUMBER;
break;

case NUMBER:
boolean terminate = false;

if (token.endsWith("?")) {
terminate = true;
token = token.substring(0, token.length() - 1);
}

String originalToken = token;
token = token.replaceFirst(ENDS_WITH_ST, "")
.replaceFirst(ENDS_WITH_ND, "")
.replaceFirst(ENDS_WITH_RD, "")
.replaceFirst(ENDS_WITH_TH, "");
boolean ordinal = ! token.equals(originalToken);

int number;
try {
number = Integer.parseInt(token);
} catch (NumberFormatException e) {
throw new IllegalArgumentException(INVALID_MESSAGE);
}

result = evaluate(number, operation, result);

if (terminate) {
state = State.TERMINATION;
} else if (ordinal) {
state = State.POWER;
} else {
state = State.OPERATION;
}

break;

case OPERATION:
token = token.toLowerCase();

switch (token) {
case "plus":
operation = Operation.PLUS;
state = State.NUMBER;
break;

case "minus":
operation = Operation.MINUS;
state = State.NUMBER;
break;

case "multiplied":
operation = Operation.MULTIPLY;
state = State.BY;
break;

case "divided":
operation = Operation.DIVIDE;
state = State.BY;
break;

case "raised":
operation = Operation.EXPONENT;
state = State.TO;
break;

default:
throw new IllegalArgumentException(INVALID_MESSAGE);
}

break;

case BY:
if (! "by".equalsIgnoreCase(token)) {
throw new IllegalArgumentException(INVALID_MESSAGE);
}

state = State.NUMBER;
break;

case TO:
if (! "to".equalsIgnoreCase(token)) {
throw new IllegalArgumentException(INVALID_MESSAGE);
}

state = State.THE;
break;

case THE:
if (! "the".equalsIgnoreCase(token)) {
throw new IllegalArgumentException(INVALID_MESSAGE);
}

state = State.NUMBER;
break;

case POWER:
if (! "power?".equalsIgnoreCase(token)) {
throw new IllegalArgumentException(INVALID_MESSAGE);
}

state = State.TERMINATION;
break;

case TERMINATION:
throw new IllegalArgumentException(INVALID_MESSAGE);
}
}

if (state != State.TERMINATION || result == null) {
throw new IllegalArgumentException(INVALID_MESSAGE);
}

return result;
}

private Integer evaluate(int number, Operation operation, Integer result) {
if (result == null && operation != Operation.NONE) {
throw new IllegalArgumentException(INVALID_MESSAGE);
}

switch (operation) {
case NONE:     result = number; break;
case PLUS:     result = result + number; break;
case MINUS:    result = result - number; break;
case MULTIPLY: result = result * number; break;
case DIVIDE:
if (number == 0) {
throw new IllegalArgumentException("Divide by zero error");
}
result = result / number;
break;
case EXPONENT: result = (int) Math.pow(result, number);
}

return result;
}

}``````

### src/test/java/WordProblemSolverTest.java

``````import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
import org.junit.Before;
import org.junit.rules.ExpectedException;

import static org.junit.Assert.assertEquals;

public class WordProblemSolverTest {

@Rule
public ExpectedException expectedException = ExpectedException.none();

WordProblemSolver solver;

@Before
public void setup() {
solver = new WordProblemSolver();
}

@Test
public void testJustANumber() {
assertEquals(5, solver.solve("What is 5?"));
}

@Test
assertEquals(2, solver.solve("What is 1 plus 1?"));
}

@Test
assertEquals(55, solver.solve("What is 53 plus 2?"));
}

@Test
assertEquals(-11, solver.solve("What is -1 plus -10?"));
}

@Test
assertEquals(45801, solver.solve("What is 123 plus 45678?"));
}

@Test
public void testSingleSubtraction() {
assertEquals(16, solver.solve("What is 4 minus -12?"));
}

@Test
public void testSingleMultiplication() {
assertEquals(-75, solver.solve("What is -3 multiplied by 25?"));
}

@Test
public void testSingleDivision() {
assertEquals(-11, solver.solve("What is 33 divided by -3?"));
}

@Test
assertEquals(3, solver.solve("What is 1 plus 1 plus 1?"));
}

@Test
assertEquals(8, solver.solve("What is 1 plus 5 minus -2?"));
}

@Test
public void testMultipleSubtractions() {
assertEquals(3, solver.solve("What is 20 minus 4 minus 13?"));
}

@Test
assertEquals(14, solver.solve("What is 17 minus 6 plus 3?"));
}

@Test
public void testMultipleMultiplications() {
assertEquals(-12, solver.solve("What is 2 multiplied by -2 multiplied by 3?"));
}

@Test
assertEquals(-8, solver.solve("What is -3 plus 7 multiplied by -2?"));
}

@Test
public void testMultipleDivisions() {
assertEquals(2, solver.solve("What is -12 divided by 2 divided by -3?"));
}

@Test
public void testExponentsFifth() {
assertEquals(32, solver.solve("What is 2 raised to the 5th power?"));
}

@Test
public void testExponentsFirst() {
assertEquals(2, solver.solve("What is 2 raised to the 1st power?"));
}

@Test
public void testExponentsSecond() {
assertEquals(4, solver.solve("What is 2 raised to the 2nd power?"));
}

@Test
public void testExponentsThird() {
assertEquals(8, solver.solve("What is 2 raised to the 3rd power?"));
}

@Test
public void testExponentsPowerOptional() {
assertEquals(32, solver.solve("What is 2 raised to the 5th?"));
}

@Test
public void testUnknownOperation() {
expectedException.expect(IllegalArgumentException.class);
expectedException.expectMessage("I'm sorry, I don't understand the question!");

solver.solve("What is 52 cubed?");
}

@Test
public void testNonMathQuestion() {
expectedException.expect(IllegalArgumentException.class);
expectedException.expectMessage("I'm sorry, I don't understand the question!");

// See https://en.wikipedia.org/wiki/President_of_the_United_States if you really need to know!
solver.solve("Who is the President of the United States?");
}

@Test
public void testMissingAnOperand() {
expectedException.expect(IllegalArgumentException.class);
expectedException.expectMessage("I'm sorry, I don't understand the question!");

solver.solve("What is 1 plus?");
}

@Test
public void testNoOperandsOrOperators() {
expectedException.expect(IllegalArgumentException.class);
expectedException.expectMessage("I'm sorry, I don't understand the question!");

solver.solve("What is?");
}

@Test
public void testTwoOperationsInARow() {
expectedException.expect(IllegalArgumentException.class);
expectedException.expectMessage("I'm sorry, I don't understand the question!");

solver.solve("What is 1 plus plus 2?");
}

@Test
public void testTwoNumbersAfterOperation() {
expectedException.expect(IllegalArgumentException.class);
expectedException.expectMessage("I'm sorry, I don't understand the question!");

solver.solve("What is 1 plus 2 1?");
}

@Test
public void testPostfixNotation() {
expectedException.expect(IllegalArgumentException.class);
expectedException.expectMessage("I'm sorry, I don't understand the question!");

solver.solve("What is 1 2 plus?");
}

@Test
public void testPrefixNotation() {
expectedException.expect(IllegalArgumentException.class);
expectedException.expectMessage("I'm sorry, I don't understand the question!");

solver.solve("What is plus 1 2?");
}

@Test
public void testWrongOrdinal() {
expectedException.expect(IllegalArgumentException.class);
expectedException.expectMessage("I'm sorry, I don't understand the question!");

solver.solve("What is 2 raised to the 5xx power?");
}
}`````` 