Avatar of 23phy

23phy's solution

to Wordy in the TypeScript Track

Published at Aug 26 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.

Iteration 1 — Addition

Add two numbers together.

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', () => {

  it('add 1', () => {
    const question = 'What is 1 plus 1?'
    expect(new WordProblem(question).answer()).toEqual(2)
  })

  xit('add 2', () => {
    const question = 'What is 53 plus 2?'
    expect(new WordProblem(question).answer()).toEqual(55)
  })

  xit('add negative numbers', () => {
    const question = 'What is -1 plus -10?'
    expect(new WordProblem(question).answer()).toEqual(-11)
  })

  xit('add more digits', () => {
    const question = 'What is 123 plus 45678?'
    expect(new WordProblem(question).answer()).toEqual(45801)
  })

  xit('subtract', () => {
    const question = 'What is 4 minus -12?'
    expect(new WordProblem(question).answer()).toEqual(16)
  })

  xit('multiply', () => {
    const question = 'What is -3 multiplied by 25?'
    expect(new WordProblem(question).answer()).toEqual(-75)
  })

  xit('divide', () => {
    const question = 'What is 33 divided by -3?'
    expect(new WordProblem(question).answer()).toEqual(-11)
  })

  xit('add twice', () => {
    const question = 'What is 1 plus 1 plus 1?'
    expect(new WordProblem(question).answer()).toEqual(3)
  })

  xit('add then subtract', () => {
    const question = 'What is 1 plus 5 minus -2?'
    expect(new WordProblem(question).answer()).toEqual(8)
  })

  xit('subtract twice', () => {
    const question = 'What is 20 minus 4 minus 13?'
    expect(new WordProblem(question).answer()).toEqual(3)
  })

  xit('subtract then add', () => {
    const question = 'What is 17 minus 6 plus 3?'
    expect(new WordProblem(question).answer()).toEqual(14)
  })

  xit('multiply twice', () => {
    const question = 'What is 2 multiplied by -2 multiplied by 3?'
    expect(new WordProblem(question).answer()).toEqual(-12)
  })

  xit('add then multiply', () => {
    const question = 'What is -3 plus 7 multiplied by -2?'
    expect(new WordProblem(question).answer()).toEqual(-8)
  })

  xit('divide twice', () => {
    const question = 'What is -12 divided by 2 divided by -3?'
    expect(new WordProblem(question).answer()).toEqual(2)
  })

  xit('too advanced', () => {
    const question = 'What is 53 cubed?'
    const problem  = new WordProblem(question)

    expect(problem.answer.bind(problem)).toThrowError( ArgumentError )
  })

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

    expect(problem.answer.bind(problem)).toThrowError( ArgumentError )
  })

})

wordy.ts

import { Parser, Grammar } from "nearley";
// @ts-ignore - No declaration file for module './wordy-nec.js'
import { default as grammar } from "./wordy-nec.js";

export class WordProblem {
  private m_parser: Parser;

  constructor(private question: string) {
    this.m_parser = new Parser(Grammar.fromCompiled(grammar));
  }
  
  public answer(): number {
    try {
      this.m_parser.feed(this.question);
    } catch (error) {
      throw new ArgumentError(error);
    }

    return this.m_parser.results[0];
  }
}

export class ArgumentError extends Error {
  constructor(message: string) {
    super(message)
  }
}

wordy.ne

@{%
  const plus      = (a, b) => parseInt(a) + parseInt(b);
  const minus     = (a, b) => parseInt(a) - parseInt(b);
  const multiply  = (a, b) => parseInt(a) * parseInt(b);
  const divide    = (a, b) => parseInt(a) / parseInt(b);
  const cube      = (a) => Math.pow(parseInt(a), 3);
%}

start ->
    "What is" _ Expression "?" {% d => d[2] %}

Expression ->
    Integer                                 {% d => parseInt(d[0]) %}
  | Expression _ "plus"          _ Integer  {% d => plus(d[0], d[4]) %}
  | Expression _ "minus"         _ Integer  {% d => minus(d[0], d[4]) %}
  | Expression _ "multiplied by" _ Integer  {% d => multiply(d[0], d[4]) %}
  | Expression _ "divided by"    _ Integer  {% d => divide(d[0], d[4]) %}
  | Expression _ "cubed"                    {% d => cube(d[0]) %}

Integer ->
    null
  | "-" Integer   {% d => d.join('') %}
  | [0-9] Integer {% d => d.join('') %}

_ -> " "  {% d => d[0] %}

wordy-nec.js

// Generated automatically by nearley, version 2.18.0
// http://github.com/Hardmath123/nearley
(function () {
function id(x) { return x[0]; }

  const plus      = (a, b) => parseInt(a) + parseInt(b);
  const minus     = (a, b) => parseInt(a) - parseInt(b);
  const multiply  = (a, b) => parseInt(a) * parseInt(b);
  const divide    = (a, b) => parseInt(a) / parseInt(b);
  const cube      = (a) => Math.pow(parseInt(a), 3);
var grammar = {
    Lexer: undefined,
    ParserRules: [
    {"name": "start$string$1", "symbols": [{"literal":"W"}, {"literal":"h"}, {"literal":"a"}, {"literal":"t"}, {"literal":" "}, {"literal":"i"}, {"literal":"s"}], "postprocess": function joiner(d) {return d.join('');}},
    {"name": "start", "symbols": ["start$string$1", "_", "Expression", {"literal":"?"}], "postprocess": d => d[2]},
    {"name": "Expression", "symbols": ["Integer"], "postprocess": d => parseInt(d[0])},
    {"name": "Expression$string$1", "symbols": [{"literal":"p"}, {"literal":"l"}, {"literal":"u"}, {"literal":"s"}], "postprocess": function joiner(d) {return d.join('');}},
    {"name": "Expression", "symbols": ["Expression", "_", "Expression$string$1", "_", "Integer"], "postprocess": d => plus(d[0], d[4])},
    {"name": "Expression$string$2", "symbols": [{"literal":"m"}, {"literal":"i"}, {"literal":"n"}, {"literal":"u"}, {"literal":"s"}], "postprocess": function joiner(d) {return d.join('');}},
    {"name": "Expression", "symbols": ["Expression", "_", "Expression$string$2", "_", "Integer"], "postprocess": d => minus(d[0], d[4])},
    {"name": "Expression$string$3", "symbols": [{"literal":"m"}, {"literal":"u"}, {"literal":"l"}, {"literal":"t"}, {"literal":"i"}, {"literal":"p"}, {"literal":"l"}, {"literal":"i"}, {"literal":"e"}, {"literal":"d"}, {"literal":" "}, {"literal":"b"}, {"literal":"y"}], "postprocess": function joiner(d) {return d.join('');}},
    {"name": "Expression", "symbols": ["Expression", "_", "Expression$string$3", "_", "Integer"], "postprocess": d => multiply(d[0], d[4])},
    {"name": "Expression$string$4", "symbols": [{"literal":"d"}, {"literal":"i"}, {"literal":"v"}, {"literal":"i"}, {"literal":"d"}, {"literal":"e"}, {"literal":"d"}, {"literal":" "}, {"literal":"b"}, {"literal":"y"}], "postprocess": function joiner(d) {return d.join('');}},
    {"name": "Expression", "symbols": ["Expression", "_", "Expression$string$4", "_", "Integer"], "postprocess": d => divide(d[0], d[4])},
    {"name": "Expression$string$5", "symbols": [{"literal":"c"}, {"literal":"u"}, {"literal":"b"}, {"literal":"e"}, {"literal":"d"}], "postprocess": function joiner(d) {return d.join('');}},
    {"name": "Expression", "symbols": ["Expression", "_", "Expression$string$5"], "postprocess": d => cube(d[0])},
    {"name": "Integer", "symbols": []},
    {"name": "Integer", "symbols": [{"literal":"-"}, "Integer"], "postprocess": d => d.join('')},
    {"name": "Integer", "symbols": [/[0-9]/, "Integer"], "postprocess": d => d.join('')},
    {"name": "_", "symbols": [{"literal":" "}], "postprocess": d => d[0]}
]
  , ParserStart: "start"
}
if (typeof module !== 'undefined'&& typeof module.exports !== 'undefined') {
   module.exports = grammar;
} else {
   window.grammar = grammar;
}
})();

Community comments

Find this solution interesting? Ask the author a question to learn more.

23phy's Reflection

🤔 I like this solution. You can even say: "What is 2 cubed divided by 2 cubed?"