 # piotr-jelinski's solution

## to Wordy in the TypeScript Track

Published at Sep 02 2019 · 0 comments
Instructions
Test suite
Solution

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 TypeScript to install the necessary dependencies:

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

## Requirements

Install assignment dependencies:

``````\$ yarn install
``````

## Making the test suite pass

Execute the tests with:

``````\$ yarn test
``````

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 changing `xit` to `it`.

## 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.

### wordy.test.ts

``````import { WordProblem, ArgumentError } from './wordy'

describe('Word Problem', () => {

const question = 'What is 1 plus 1?'
})

const question = 'What is 53 plus 2?'
})

xit('add negative numbers', () => {
const question = 'What is -1 plus -10?'
})

xit('add more digits', () => {
const question = 'What is 123 plus 45678?'
})

xit('subtract', () => {
const question = 'What is 4 minus -12?'
})

xit('multiply', () => {
const question = 'What is -3 multiplied by 25?'
})

xit('divide', () => {
const question = 'What is 33 divided by -3?'
})

const question = 'What is 1 plus 1 plus 1?'
})

xit('add then subtract', () => {
const question = 'What is 1 plus 5 minus -2?'
})

xit('subtract twice', () => {
const question = 'What is 20 minus 4 minus 13?'
})

xit('subtract then add', () => {
const question = 'What is 17 minus 6 plus 3?'
})

xit('multiply twice', () => {
const question = 'What is 2 multiplied by -2 multiplied by 3?'
})

xit('add then multiply', () => {
const question = 'What is -3 plus 7 multiplied by -2?'
})

xit('divide twice', () => {
const question = 'What is -12 divided by 2 divided by -3?'
})

const question = 'What is 53 cubed?'
const problem  = new WordProblem(question)

})

xit('irrelevant', () => {
const question = 'Who is the president of the United States?'
const problem  = new WordProblem(question)

})

})``````

### abstract-equation.ts

``````"use strict";

import EquationInterface from './equation-interface';

abstract class AbstractEquation implements EquationInterface {
/**
* @param {EquationInterface} valueLeft
* @param {EquationInterface} valueRight
*/
public constructor(protected valueLeft: EquationInterface, protected valueRight: EquationInterface) {}

/**
* @returns {number}
*/
public abstract getResult(): number;
}

export default AbstractEquation;``````

### divided.ts

``````"use strict";

import AbstractEquation from './abstract-equation';

class Divided extends AbstractEquation {
/**
* @returns {number}
*/
public getResult(): number {
return this.valueLeft.getResult() / this.valueRight.getResult();
}
}

export default Divided;``````

### equation-interface.ts

``````"use strict";

interface EquationInterface {
/**
* @return {number}
*/
getResult(): number;
}

export default EquationInterface;``````

### minus.ts

``````"use strict";

import AbstractEquation from './abstract-equation';

class Minus extends AbstractEquation {
/**
* @returns {number}
*/
public getResult(): number {
return this.valueLeft.getResult() - this.valueRight.getResult();
}
}

export default Minus;``````

### multiplied.ts

``````"use strict";

import AbstractEquation from './abstract-equation';

class Multiplied extends AbstractEquation {
/**
* @returns {number}
*/
public getResult(): number {
return this.valueLeft.getResult() * this.valueRight.getResult();
}
}

export default Multiplied;``````

### number.ts

``````"use strict";

import EquationInterface from './equation-interface';

class Number implements EquationInterface {
/**
* @param {number} value
*/
public constructor(private value: number) {}

/**
* @returns {number}
*/
public getResult(): number {
return this.value;
}
}

export default Number;``````

### operation-factory.ts

``````"use strict";

import EquationInterface from "./equation-interface";
import Number from './number';
import Divided from './divided';
import Multiplied from './multiplied';
import Minus from './minus';
import Plus from './plus';
import {ArgumentError} from "./wordy";

class OperationFactory {
/**
* @param {string} type
* @param {EquationInterface} equation
* @param {Number} value
* @returns {EquationInterface}
*/
public create(type: string, equation: EquationInterface, value: Number): EquationInterface {
let operation: EquationInterface;
switch (type) {
case 'divided':
operation = this.getDivided(equation, value);
break;
case 'multiplied':
operation = this.getMultiplied(equation, value);
break;
case 'minus':
operation = this.getMinus(equation, value);
break;
case 'plus':
operation = this.getPlus(equation, value);
break;
default:
throw new ArgumentError('Unknown operation');
}

return operation;
}

/**
* @param {EquationInterface} equation
* @param {Number} value
* @returns {Plus}
*/
public getDivided(equation: EquationInterface, value: Number): Divided {
return new Divided(equation, value);
}

/**
* @param {EquationInterface} equation
* @param {Number} value
* @returns {Multiplied}
*/
public getMultiplied(equation: EquationInterface, value: Number): Multiplied {
return new Multiplied(equation, value);
}

/**
* @param {EquationInterface} equation
* @param {Number} value
* @returns {Minus}
*/
public getMinus(equation: EquationInterface, value: Number): Minus {
return new Minus(equation, value);
}

/**
* @param {EquationInterface} equation
* @param {Number} value
* @returns {Plus}
*/
public getPlus(equation: EquationInterface, value: Number): Plus {
return new Plus(equation, value);
}
}

export default OperationFactory;``````

### plus.ts

``````"use strict";

import AbstractEquation from './abstract-equation';

class Plus extends AbstractEquation {
/**
* @returns {number}
*/
public getResult(): number {
return this.valueLeft.getResult() + this.valueRight.getResult();
}
}

export default Plus;``````

### word-equation-builder.ts

``````"use strict";

import { ArgumentError } from './wordy';
import EquationInterface from './equation-interface';
import Number from './number';
import OperationFactory from "./operation-factory";

class WordEquationBuilder {
/**
* @param {OperationFactory} factory
*/
public constructor(private factory: OperationFactory) {}

/**
* @param {Array<string>} elements
*/
private validate(elements: Array<string>): void {
if (elements.length < 3 || elements.length % 2 !== 1) {
throw new ArgumentError('Not enough entries to perform calculation of any recognizable kind');
}
}

/**
* @param {Array<string>} elements
* @returns {Number}
*/
private getNumber(elements: Array<string>): Number {
return new Number(parseFloat(elements.shift() || ''));
}

/**
* @param {Array<string>} elements
* @returns {EquationInterface}
*/
public build(elements: Array<string>): EquationInterface {
this.validate(elements);

let equation: EquationInterface = this.getNumber(elements);
while (elements.length) {
equation = this.factory.create(
elements.shift() || '',
equation,
this.getNumber(elements)
);
}

return equation;
}
}

export default WordEquationBuilder;``````

### word-equation-parser.ts

``````"use strict";

class WordEquationParser {
/**
* @type {RegExp}
*/
private matcher: RegExp = new RegExp('([0-9\-]+|multiplied|divided|plus|minus)', 'gm');

/**
* @param string \$message
* @return array
*/
public parse(message: string): Array<string> {
let matches = message.match(this.matcher);

return matches || [];
}
}

export default WordEquationParser;``````

### wordy.ts

``````"use strict";

import WordEquationParser from "./word-equation-parser";
import WordEquationBuilder from "./word-equation-builder";
import OperationFactory from "./operation-factory";

export class ArgumentError extends Error {

}

export class WordProblem {
/**
* @param {string} question
*/

/**
* @returns {number}
*/
let messageParser = new WordEquationParser();
let operationFactory = new OperationFactory();
let equationBuilder = new WordEquationBuilder(operationFactory);

return equationBuilder.build(messageParser.parse(this.question)).getResult();
}
}``````