From 23d397cd32e717d5b7975b014921b4a76d76d6fb Mon Sep 17 00:00:00 2001 From: Alexander Pruss Date: Sun, 9 Jun 2019 18:03:31 +0200 Subject: [PATCH 01/12] Adding rational number parsing and CLI skeleton. --- src/cli/ClientEventHandler.ts | 0 src/cli/MatrixLoader.ts | 0 src/cli/MatrixPainter.ts | 0 src/cli/ReductionExecution.ts | 0 src/fields/rationals/RationalNumbers.spec.ts | 27 +------ src/fields/rationals/RationalNumbers.ts | 6 +- src/fields/rationals/RationalParser.spec.ts | 78 +++++++++++++++++++ src/fields/rationals/RationalParser.ts | 43 ++++++++++ .../test-helpers/RationalProvider.spec.ts | 10 +++ 9 files changed, 136 insertions(+), 28 deletions(-) create mode 100644 src/cli/ClientEventHandler.ts create mode 100644 src/cli/MatrixLoader.ts create mode 100644 src/cli/MatrixPainter.ts create mode 100644 src/cli/ReductionExecution.ts create mode 100644 src/fields/rationals/RationalParser.spec.ts create mode 100644 src/fields/rationals/RationalParser.ts create mode 100644 src/fields/rationals/test-helpers/RationalProvider.spec.ts diff --git a/src/cli/ClientEventHandler.ts b/src/cli/ClientEventHandler.ts new file mode 100644 index 0000000..e69de29 diff --git a/src/cli/MatrixLoader.ts b/src/cli/MatrixLoader.ts new file mode 100644 index 0000000..e69de29 diff --git a/src/cli/MatrixPainter.ts b/src/cli/MatrixPainter.ts new file mode 100644 index 0000000..e69de29 diff --git a/src/cli/ReductionExecution.ts b/src/cli/ReductionExecution.ts new file mode 100644 index 0000000..e69de29 diff --git a/src/fields/rationals/RationalNumbers.spec.ts b/src/fields/rationals/RationalNumbers.spec.ts index e9365f3..7347ea2 100644 --- a/src/fields/rationals/RationalNumbers.spec.ts +++ b/src/fields/rationals/RationalNumbers.spec.ts @@ -1,20 +1,13 @@ import {RationalNumber, Sign} from "./RationalNumber"; import {RationalNumbers} from "./RationalNumbers"; import {expect} from "chai"; +import {createRationalNumber} from "./test-helpers/RationalProvider.spec"; describe("RationalNumbers", () => { const rationalNumbers = new RationalNumbers(); const primeFactorService = rationalNumbers.primeFactorService; - function createRationalNumber(numerator: number, denominator: number, sign: Sign): RationalNumber { - return new RationalNumber( - primeFactorService.createFactoredNumber(numerator), - primeFactorService.createFactoredNumber(denominator), - sign - ); - } - describe("#add", () => { it('adds two numbers together, reducing the result', function () { @@ -168,23 +161,7 @@ describe("RationalNumbers", () => { expect(asString).to.eql("5/12"); }); - - it('adds a negative sign if the sign is negative ', function () { - const number = createRationalNumber(5, 12, Sign.NEGATIVE); - - const asString = rationalNumbers.elementToString(number); - - expect(asString).to.eql("-5/12"); - }); - - it('doesn\'t add the denominator if the denominator is 1', function () { - const number = createRationalNumber(5, 1, Sign.POSITIVE); - - const asString = rationalNumbers.elementToString(number); - - expect(asString).to.eql("5"); - }); - }) + }); describe("#hasNorm", () => { diff --git a/src/fields/rationals/RationalNumbers.ts b/src/fields/rationals/RationalNumbers.ts index 468e3d8..00d3981 100644 --- a/src/fields/rationals/RationalNumbers.ts +++ b/src/fields/rationals/RationalNumbers.ts @@ -2,6 +2,7 @@ import {RationalNumber, Sign} from "./RationalNumber"; import {Field} from "../Field"; import defaultPrimeFactorService, {PrimeFactorService} from "./PrimeFactorService"; import logger from "../../logging/Logger"; +import defaultRationalParser, {RationalParser} from "./RationalParser"; export class RationalNumbers implements Field { @@ -9,6 +10,7 @@ export class RationalNumbers implements Field { static ONE = new RationalNumber({value: 1, primeFactors: []}, {value: 1, primeFactors: []}); primeFactorService: PrimeFactorService = defaultPrimeFactorService; + rationalParser: RationalParser = defaultRationalParser; add(first: RationalNumber, second: RationalNumber): RationalNumber { const [lcd, firstMultiplier, secondMultiplier] = this.primeFactorService.combineDenominators( @@ -81,9 +83,7 @@ export class RationalNumbers implements Field { } elementToString(element: RationalNumber): string { - const sign = element.sign == Sign.NEGATIVE ? "-" : ""; - const denominator = element.denominator.value == 1 ? "" : `/${element.denominator.value}`; - return `${sign}${element.numerator.value}${denominator}`; + return this.rationalParser.elementToString(element); } hasNorm(): boolean { diff --git a/src/fields/rationals/RationalParser.spec.ts b/src/fields/rationals/RationalParser.spec.ts new file mode 100644 index 0000000..63755e6 --- /dev/null +++ b/src/fields/rationals/RationalParser.spec.ts @@ -0,0 +1,78 @@ +import {Sign} from "./RationalNumber"; +import {expect} from "chai"; +import {createRationalNumber} from "./test-helpers/RationalProvider.spec"; +import {RationalParser} from "./RationalParser"; + +describe("RationalParser", () => { + + const rationalParser = new RationalParser(); + + describe("#parse", () => { + + it('parses and reduces rational numbers without negative signs', function () { + const expected = createRationalNumber(3, 5, Sign.POSITIVE); + + const parsedNumber = rationalParser.parse("6/10"); + + expect(parsedNumber).to.eql(expected); + }); + + it('parses and reduces rational numbers with negative signs', function () { + const expected = createRationalNumber(3, 5, Sign.NEGATIVE); + + const parsedNumber = rationalParser.parse("-6/10"); + + expect(parsedNumber).to.eql(expected); + }); + + it('parses rational numbers without denominators', function () { + const expected = createRationalNumber(3, 1, Sign.POSITIVE); + + const parsedNumber = rationalParser.parse("3"); + + expect(parsedNumber).to.eql(expected); + }); + + it('throws an error if it can\'t parse a string part into a number', function () { + + expect(() =>rationalParser.parse("6/beans")).to.throw(Error); + + }); + + it('throws an error if there are too many denominators ("/" symbols)', function () { + + expect(() =>rationalParser.parse("6/4/2")).to.throw(Error); + + }); + + }); + + describe("#elementToString", () => { + + it('returns the numerator/denominator ', function () { + const number = createRationalNumber(5, 12, Sign.POSITIVE); + + const asString = rationalParser.elementToString(number); + + expect(asString).to.eql("5/12"); + + }); + + it('adds a negative sign if the sign is negative ', function () { + const number = createRationalNumber(5, 12, Sign.NEGATIVE); + + const asString = rationalParser.elementToString(number); + + expect(asString).to.eql("-5/12"); + }); + + it('doesn\'t add the denominator if the denominator is 1', function () { + const number = createRationalNumber(5, 1, Sign.POSITIVE); + + const asString = rationalParser.elementToString(number); + + expect(asString).to.eql("5"); + }); + }); +}); + diff --git a/src/fields/rationals/RationalParser.ts b/src/fields/rationals/RationalParser.ts new file mode 100644 index 0000000..8e46abc --- /dev/null +++ b/src/fields/rationals/RationalParser.ts @@ -0,0 +1,43 @@ +import {RationalNumber, Sign} from "./RationalNumber"; +import logger from "../../logging/Logger"; +import defaultPrimeFactorService, {PrimeFactorService} from "./PrimeFactorService"; + +export class RationalParser { + + primeFactorService: PrimeFactorService = defaultPrimeFactorService; + + /** + * Parses strings of the form "3/5", "-24/30", etc. + * @param elementAsString + */ + parse(elementAsString: string) : RationalNumber { + let sign = Sign.POSITIVE; + if(elementAsString.startsWith("-")) { + sign = Sign.NEGATIVE; + elementAsString = elementAsString.slice(1); + } + + const splitElement = elementAsString.split("/"); + if(splitElement.length > 2) { + const message = "Parsing error - a rational number had multiple '/' characters."; + logger.error(message); + throw new Error(message); + } + + const numeratorValue : number = Number(splitElement[0]); + const denominatorValue = splitElement.length == 1 ? 1 : Number(splitElement[1]); + const numerator = this.primeFactorService.createFactoredNumber(numeratorValue); + const denominator = this.primeFactorService.createFactoredNumber(denominatorValue); + + return this.primeFactorService.reduce(new RationalNumber(numerator, denominator, sign)); + } + + elementToString(element: RationalNumber): string { + const sign = element.sign == Sign.NEGATIVE ? "-" : ""; + const denominator = element.denominator.value == 1 ? "" : `/${element.denominator.value}`; + return `${sign}${element.numerator.value}${denominator}`; + } + +} + +export default new RationalParser(); \ No newline at end of file diff --git a/src/fields/rationals/test-helpers/RationalProvider.spec.ts b/src/fields/rationals/test-helpers/RationalProvider.spec.ts new file mode 100644 index 0000000..52dc798 --- /dev/null +++ b/src/fields/rationals/test-helpers/RationalProvider.spec.ts @@ -0,0 +1,10 @@ +import {RationalNumber, Sign} from "../RationalNumber"; +import primeFactorService from "../PrimeFactorService"; + +export function createRationalNumber(numerator: number, denominator: number, sign: Sign): RationalNumber { + return new RationalNumber( + primeFactorService.createFactoredNumber(numerator), + primeFactorService.createFactoredNumber(denominator), + sign + ); +} \ No newline at end of file From 5621d4cad6501ed345d3d7d5c6cb287414c304ed Mon Sep 17 00:00:00 2001 From: Alexander Pruss Date: Sun, 9 Jun 2019 18:30:15 +0200 Subject: [PATCH 02/12] Moving logic out of Cli.ts --- package.json | 6 +- src/Cli.ts | 40 ++++-------- src/cli/ClientEventHandler.ts | 85 ++++++++++++++++++++++++++ src/cli/ReductionExecution.ts | 3 + src/fields/rationals/RationalParser.ts | 4 ++ 5 files changed, 106 insertions(+), 32 deletions(-) diff --git a/package.json b/package.json index d1d4689..4c21839 100644 --- a/package.json +++ b/package.json @@ -7,12 +7,12 @@ "url": "git@github.com:AlexanderPruss/MatrixReduction.git" }, "bin": { - "matrix-stuff": "dist/cli.js" + "matrix-stuff": "dist/Cli.js" }, "scripts": { "build": "tsc", - "start": "node dist", - "devstart": "nodemon dist", + "start": "node dist/Cli.js", + "devstart": "nodemon dist/Cli.js", "test": "tsc && mocha 'dist/**/*.spec.js'", "test-clean": "rm -rf dist && npm run build && npm run test", "test-report": "touch report.xml && JUNIT_REPORT_PATH=report.xml mocha 'dist/**/*.spec.js' --colors --reporter mocha-jenkins-reporter --timeout 20000 --exit", diff --git a/src/Cli.ts b/src/Cli.ts index 1b6b0af..881084e 100644 --- a/src/Cli.ts +++ b/src/Cli.ts @@ -1,47 +1,29 @@ #!/usr/bin/env node import * as readline from "readline"; -import logger from "./logging/Logger"; -console.log("Hi"); -let prompt = 'OHAI>'; +import {ClientEventHandler} from "./cli/ClientEventHandler"; readline.emitKeypressEvents(process.stdin); const prompter = readline.createInterface({ input: process.stdin, output: process.stdout, - prompt: 'OHAI> ', + prompt: 'Matrix: ', historySize: 1 }); +const handler = new ClientEventHandler(prompter); + process.stdin.on('keypress', (str, key) => { - // console.log(`Pressed str: ${str} and key: ${key.name}`); + handler.handleKeypress(key.name); }); - -prompter.prompt(); - prompter.on('line', (line) => { - switch (line.trim()) { - case 'hello' : - console.log("Hi to you as well"); - logger.info("Wut"); - break; - case 'clear' : - console.log("Clearing"); - console.clear(); - break; - case 'ohai' : - prompt = `OHAI${prompt}`; - prompter.setPrompt(prompt); - break; - case 'stop' : - console.log("RUDE"); - process.exit(1); - break; - } - prompter.prompt(); + handler.handlePrompt(line); + prompter.prompt(); }).on('close', () => { - console.log("Farewell!"); + console.log("See you"); process.exit(0); -}); \ No newline at end of file +}); + +prompter.prompt(); diff --git a/src/cli/ClientEventHandler.ts b/src/cli/ClientEventHandler.ts index e69de29..f67c7c6 100644 --- a/src/cli/ClientEventHandler.ts +++ b/src/cli/ClientEventHandler.ts @@ -0,0 +1,85 @@ +import {RationalNumbers} from "../fields/rationals/RationalNumbers"; +import {ReductionExecution} from "./ReductionExecution"; +import {ReadLine} from "readline"; + +export class ClientEventHandler { + + currentExecution: ReductionExecution; + readLine: ReadLine; + + constructor(readLine: ReadLine) { + this.readLine = readLine; + } + + /** + * This currently doesn't do anything, it's a hook to allow different fields in the future. + */ + getCurrentField() { + return new RationalNumbers(); + } + + handlePrompt(prompt: string) { + prompt = prompt.trim(); + + if(prompt.includes("-h") || prompt.includes( " h ") || prompt.includes("help")){ + console.log(`The available commands are: + import {filename} - imports a matrix from the given file; + exit - exits the app. + + Matrix files should be formatted as follows: + * Each entry of the matrix is separated with an empty space + * Each row of the matrix is separated with a newline + + Currently, only rational numbers are supported. A sample valid row could look like this: + -4 12/15 0 4/6 + + When a matrix is imported, the follow commands become available: + + [Press LEFT] - See what the matrix looked like in the previous step of the execution. + [Press RIGHT] - See what the matrix looked like in the next step of the execution. + start - Jump back to the start of the execution. + result - Jump back to the result of the execution. + + A new matrix can be imported with the 'import {filename} command while a matrix execution is active.`) + } + + if(prompt.startsWith("import")) { + this.readLine.setPrompt("Matrix [<- go back; -> go forward]: ") + //import matrix + } + + if(prompt.startsWith("exit")) { + console.log("See you"); + process.exit(0); + } + + if(this.currentExecution == null) { + return; + } + + if(prompt.includes("start") || prompt == "S") { + //go to start of the execution + } + + if(prompt.includes("result") || prompt == "R") { + //go to result of execution + } + + } + + handleKeypress(key: string) { + if(this.currentExecution == null) { + return; + } + + switch(key) { + case "left": + //move back in execution + case "right": + //move forward in execution + default: + break + } + + } +} \ No newline at end of file diff --git a/src/cli/ReductionExecution.ts b/src/cli/ReductionExecution.ts index e69de29..ca68388 100644 --- a/src/cli/ReductionExecution.ts +++ b/src/cli/ReductionExecution.ts @@ -0,0 +1,3 @@ +export class ReductionExecution { + +} \ No newline at end of file diff --git a/src/fields/rationals/RationalParser.ts b/src/fields/rationals/RationalParser.ts index 8e46abc..bdba36d 100644 --- a/src/fields/rationals/RationalParser.ts +++ b/src/fields/rationals/RationalParser.ts @@ -29,6 +29,10 @@ export class RationalParser { const numerator = this.primeFactorService.createFactoredNumber(numeratorValue); const denominator = this.primeFactorService.createFactoredNumber(denominatorValue); + if(numeratorValue == 0) { + sign = Sign.POSITIVE; + } + return this.primeFactorService.reduce(new RationalNumber(numerator, denominator, sign)); } From 6c11bba0e48ea493e674c0563c8a0b9e39277f94 Mon Sep 17 00:00:00 2001 From: Alexander Pruss Date: Sun, 9 Jun 2019 19:31:38 +0200 Subject: [PATCH 03/12] Adding matrix printing, fixing up some more CLI hiccups --- examples/nicematrix.txt | 3 ++ src/Cli.ts | 1 - src/cli/ClientEventHandler.ts | 21 +++++++- src/cli/MatrixLoader.ts | 29 ++++++++++ src/cli/MatrixPainter.spec.ts | 39 ++++++++++++++ src/cli/MatrixPainter.ts | 73 ++++++++++++++++++++++++++ src/cli/Parser.ts | 7 +++ src/fields/rationals/RationalParser.ts | 3 +- test.js | 0 9 files changed, 172 insertions(+), 4 deletions(-) create mode 100644 examples/nicematrix.txt create mode 100644 src/cli/MatrixPainter.spec.ts create mode 100644 src/cli/Parser.ts create mode 100644 test.js diff --git a/examples/nicematrix.txt b/examples/nicematrix.txt new file mode 100644 index 0000000..86bb2a3 --- /dev/null +++ b/examples/nicematrix.txt @@ -0,0 +1,3 @@ +1 2 3 +4 5 6 +7 8 9 \ No newline at end of file diff --git a/src/Cli.ts b/src/Cli.ts index 881084e..452e66c 100644 --- a/src/Cli.ts +++ b/src/Cli.ts @@ -1,7 +1,6 @@ #!/usr/bin/env node import * as readline from "readline"; - import {ClientEventHandler} from "./cli/ClientEventHandler"; readline.emitKeypressEvents(process.stdin); diff --git a/src/cli/ClientEventHandler.ts b/src/cli/ClientEventHandler.ts index f67c7c6..d6ef6a7 100644 --- a/src/cli/ClientEventHandler.ts +++ b/src/cli/ClientEventHandler.ts @@ -1,12 +1,18 @@ -import {RationalNumbers} from "../fields/rationals/RationalNumbers"; +import defaultRationalNumbers, {RationalNumbers} from "../fields/rationals/RationalNumbers"; +import defaultRationalParser, {RationalParser} from "../fields/rationals/RationalParser"; import {ReductionExecution} from "./ReductionExecution"; import {ReadLine} from "readline"; +import defaultMatrixLoader, {MatrixLoader} from "./MatrixLoader"; export class ClientEventHandler { currentExecution: ReductionExecution; readLine: ReadLine; + matrixLoader: MatrixLoader = defaultMatrixLoader; + rationalNumbers: RationalNumbers = defaultRationalNumbers; + rationalParser: RationalParser = defaultRationalParser; + constructor(readLine: ReadLine) { this.readLine = readLine; } @@ -15,7 +21,10 @@ export class ClientEventHandler { * This currently doesn't do anything, it's a hook to allow different fields in the future. */ getCurrentField() { - return new RationalNumbers(); + return this.rationalNumbers; + } + getCurrentParser() { + return this.rationalParser; } handlePrompt(prompt: string) { @@ -44,6 +53,14 @@ export class ClientEventHandler { } if(prompt.startsWith("import")) { + try { + const filename = prompt.split(" ")[1]; + this.matrixLoader.importMatrix(this.rationalParser, filename); + } + catch(e) { + console.log(e.message); + return; + } this.readLine.setPrompt("Matrix [<- go back; -> go forward]: ") //import matrix } diff --git a/src/cli/MatrixLoader.ts b/src/cli/MatrixLoader.ts index e69de29..d3aa7fa 100644 --- a/src/cli/MatrixLoader.ts +++ b/src/cli/MatrixLoader.ts @@ -0,0 +1,29 @@ +import {Matrix} from "../matrix/Matrix"; +import {Parser} from "./Parser"; +import * as fileStream from "fs"; + +export class MatrixLoader { + + //Importing a file synchronously in node is probably a cardinal sin + importMatrix(parser: Parser, filename: string): Matrix { + + const buffer = fileStream.readFileSync(filename); + const bufferString = buffer.toString('utf8'); + + const rows = []; + for(let line of bufferString.split('\n')) { + const row = []; + rows.push(row); + + line = line.trim(); + for(const element of line.split(" ")) { + row.push(parser.parse(element)); + } + } + + return new Matrix(rows); + } + +} + +export default new MatrixLoader(); \ No newline at end of file diff --git a/src/cli/MatrixPainter.spec.ts b/src/cli/MatrixPainter.spec.ts new file mode 100644 index 0000000..27dd76b --- /dev/null +++ b/src/cli/MatrixPainter.spec.ts @@ -0,0 +1,39 @@ +import {MatrixPainter} from "./MatrixPainter"; +import {RationalParser} from "../fields/rationals/RationalParser"; +import {convertToRationalMatrix, defaultMatrix} from "../matrix/test-helpers/MatrixProvider.spec"; +import {expect} from "chai"; + +describe("MatrixPainter", () => { + + const matrixPainter = new MatrixPainter(); + const parser = new RationalParser(); + + describe("#printMatrix", () => { + + it('should print a matrix', function () { + const matrix = defaultMatrix(); + + const expected = `[ 1 2 3 ] +[ 4 5 6 ] +[ 7 8 9 ]` + "\n"; + + expect(matrixPainter.printMatrix(matrix, parser)).to.eql(expected); + }); + + it('should shorten long entries', function () { + const matrix = convertToRationalMatrix([ + [100, 2, 3], + [6, 90000, 123424343434343], + [7, 8, 9] + ]); + + const expected = `[ 100 2 3 ] +[ 6 90000 1234243... ] +[ 7 8 9 ]` +"\n"; + + expect(matrixPainter.printMatrix(matrix, parser)).to.eql(expected); + }); + + }) + +}); \ No newline at end of file diff --git a/src/cli/MatrixPainter.ts b/src/cli/MatrixPainter.ts index e69de29..2997119 100644 --- a/src/cli/MatrixPainter.ts +++ b/src/cli/MatrixPainter.ts @@ -0,0 +1,73 @@ +import {Matrix} from "../matrix/Matrix"; +import {Parser} from "./Parser"; + +export class MatrixPainter { + + minWidth = 4; + maxWidth = 10; + + printMatrix(matrix: Matrix, parser: Parser) : string{ + const stringRows : string[][] = []; + + const columnWidths = []; + for(let columnIndex = 0; columnIndex < matrix.numberOfColumns; columnIndex++) { + columnWidths.push(this.minWidth); + } + + //read in strings, measure the width-ish + for(const row of matrix.rows) { + const stringRow = []; + stringRows.push(stringRow); + + for(let columnIndex = 0; columnIndex < matrix.numberOfColumns; columnIndex++) { + const element = row[columnIndex]; + const elementString = parser.elementToString(element); + + const length = elementString.length > 10 ? 10 : elementString.length; + if(length > columnWidths[columnIndex]) { + columnWidths[columnIndex] = length; + } + + stringRow.push(parser.elementToString(element)); + } + } + + //resize strings to make the columns look nice + for(let rowIndex = 0; rowIndex < matrix.numberOfRows; rowIndex++) { + for(let columnIndex = 0; columnIndex < matrix.numberOfColumns; columnIndex++) { + const elementString = stringRows[rowIndex][columnIndex]; + const columnWidth = columnWidths[columnIndex]; + + const difference = columnWidth - elementString.length; + //The string is too short, so add empty spaces + if(difference > 0) { + let whitespace = ""; + for(let numSpaces = 0; numSpaces < difference; numSpaces++) { + whitespace += " "; + } + stringRows[rowIndex][columnIndex] = elementString + whitespace; + } + + //The string is too long, shorten it + if(difference < 0) { + stringRows[rowIndex][columnIndex] = elementString.substr(0, 7) + "..."; + } + } + } + + let matrixString = ""; + for(const stringRow of stringRows) { + matrixString += "[ "; + for(const stringElement of stringRow) { + matrixString += stringElement + " "; + } + matrixString += "]\n"; + } + + return matrixString; + } + + +} + +export default new MatrixPainter(); \ No newline at end of file diff --git a/src/cli/Parser.ts b/src/cli/Parser.ts new file mode 100644 index 0000000..f1bd53f --- /dev/null +++ b/src/cli/Parser.ts @@ -0,0 +1,7 @@ +export interface Parser { + + parse(elementAsString: string): E; + + elementToString(element: E): string; + +} \ No newline at end of file diff --git a/src/fields/rationals/RationalParser.ts b/src/fields/rationals/RationalParser.ts index bdba36d..f487285 100644 --- a/src/fields/rationals/RationalParser.ts +++ b/src/fields/rationals/RationalParser.ts @@ -1,8 +1,9 @@ import {RationalNumber, Sign} from "./RationalNumber"; import logger from "../../logging/Logger"; import defaultPrimeFactorService, {PrimeFactorService} from "./PrimeFactorService"; +import {Parser} from "../../cli/Parser"; -export class RationalParser { +export class RationalParser implements Parser{ primeFactorService: PrimeFactorService = defaultPrimeFactorService; diff --git a/test.js b/test.js new file mode 100644 index 0000000..e69de29 From 537080bddd60723ae4be785ba464e42f0d8e8d8a Mon Sep 17 00:00:00 2001 From: Alexander Pruss Date: Sun, 9 Jun 2019 19:44:27 +0200 Subject: [PATCH 04/12] Teaching events to draw. --- src/fields/Field.ts | 4 ++++ src/fields/rationals/RationalNumbers.ts | 5 +++++ src/matrix/events/AddRowsReductionEvent.ts | 18 ++++++++++++++++-- src/matrix/events/EndReductionEvent.ts | 5 ++--- .../events/MultiplyRowsReductionEvent.ts | 12 +++++++++--- src/matrix/events/ReductionEvent.ts | 2 +- src/matrix/events/StartReductionEvent.ts | 5 ++--- src/matrix/events/SwapReductionEvent.ts | 15 +++++++++++++-- 8 files changed, 52 insertions(+), 14 deletions(-) diff --git a/src/fields/Field.ts b/src/fields/Field.ts index f0fad45..ab2219a 100644 --- a/src/fields/Field.ts +++ b/src/fields/Field.ts @@ -1,3 +1,5 @@ +import {Parser} from "../cli/Parser"; + export interface Field { add(first: Element, second: Element): Element; @@ -44,4 +46,6 @@ export interface Field { norm(element: Element) : number; elementsEqual(first: Element, second: Element) : boolean; + + getParser() : Parser; } \ No newline at end of file diff --git a/src/fields/rationals/RationalNumbers.ts b/src/fields/rationals/RationalNumbers.ts index 00d3981..8cb541b 100644 --- a/src/fields/rationals/RationalNumbers.ts +++ b/src/fields/rationals/RationalNumbers.ts @@ -3,6 +3,7 @@ import {Field} from "../Field"; import defaultPrimeFactorService, {PrimeFactorService} from "./PrimeFactorService"; import logger from "../../logging/Logger"; import defaultRationalParser, {RationalParser} from "./RationalParser"; +import {Parser} from "../../cli/Parser"; export class RationalNumbers implements Field { @@ -98,6 +99,10 @@ export class RationalNumbers implements Field { return first.sign == second.sign && this.norm(first) == this.norm(second); } + getParser(): Parser { + return this.rationalParser; + } + } export default new RationalNumbers(); \ No newline at end of file diff --git a/src/matrix/events/AddRowsReductionEvent.ts b/src/matrix/events/AddRowsReductionEvent.ts index d2c14b9..6c8c5f3 100644 --- a/src/matrix/events/AddRowsReductionEvent.ts +++ b/src/matrix/events/AddRowsReductionEvent.ts @@ -2,6 +2,7 @@ import {ReductionEvent} from "./ReductionEvent"; import {Field} from "../../fields/Field"; import {Matrix} from "../Matrix"; import {ReductionService} from "../ReductionService"; +import {Parser} from "../../cli/Parser"; export class AddRowsReductionEvent implements ReductionEvent { field: Field; @@ -10,6 +11,7 @@ export class AddRowsReductionEvent implements ReductionEvent { multiplier: E; reductionService: ReductionService; + parsingService: Parser; constructor(field: Field, rowIndexToAdd: number, rowIndexToAddTo: number, multiplier: E, reductionService: ReductionService) { this.field = field; @@ -17,14 +19,26 @@ export class AddRowsReductionEvent implements ReductionEvent { this.rowIndexToAddTo = rowIndexToAddTo; this.multiplier = multiplier; this.reductionService = reductionService; + this.parsingService = field.getParser(); } apply(matrix: Matrix): Matrix { return this.reductionService.addRows(matrix, this.field, this.rowIndexToAdd, this.rowIndexToAddTo, this.multiplier)[0]; } - drawMatrix(matrix: Matrix): string { - return ""; //TODO: not implemented yet + drawMatrix(matrixAsString: string): string { + const rows = matrixAsString.split("\n"); + + const startIndex = this.rowIndexToAdd < this.rowIndexToAddTo ? this.rowIndexToAdd : this.rowIndexToAddTo; + const endIndex = this.rowIndexToAdd < this.rowIndexToAddTo ? this.rowIndexToAddTo : this.rowIndexToAdd; + + for(let rowIndex = startIndex + 1; rowIndex < endIndex - 1; rowIndex++) { + rows[rowIndex] += " |" + } + rows[this.rowIndexToAdd] += `---- +(${this.parsingService.elementToString(this.multiplier)})`; + rows[this.rowIndexToAddTo] += "<---"; + + return rows.join("\n"); } reverse(matrix: Matrix): Matrix { diff --git a/src/matrix/events/EndReductionEvent.ts b/src/matrix/events/EndReductionEvent.ts index a5c02a6..d3dd90e 100644 --- a/src/matrix/events/EndReductionEvent.ts +++ b/src/matrix/events/EndReductionEvent.ts @@ -21,9 +21,8 @@ export class EndReductionEvent implements ReductionEvent { return matrix; } - drawMatrix(matrix: Matrix): string { - //TODO: Not implemented yet - return ""; + drawMatrix(matrixAsString: string): string { + return this.lastReductionEvent.drawMatrix(matrixAsString); } /** diff --git a/src/matrix/events/MultiplyRowsReductionEvent.ts b/src/matrix/events/MultiplyRowsReductionEvent.ts index 663fe83..251254a 100644 --- a/src/matrix/events/MultiplyRowsReductionEvent.ts +++ b/src/matrix/events/MultiplyRowsReductionEvent.ts @@ -2,27 +2,33 @@ import {ReductionEvent} from "./ReductionEvent"; import {Field} from "../../fields/Field"; import {ReductionService} from "../ReductionService"; import {Matrix} from "../Matrix"; +import {Parser} from "../../cli/Parser"; export class MultiplyRowsReductionEvent implements ReductionEvent { field: Field; rowIndex: number; multiplier: E; reductionService: ReductionService; - + parsingService: Parser; constructor(field: Field, rowIndex: number, multiplier: E, reductionService: ReductionService) { this.field = field; this.rowIndex = rowIndex; this.multiplier = multiplier; this.reductionService = reductionService; + this.parsingService = field.getParser(); } apply(matrix: Matrix): Matrix { return this.reductionService.multiplyRow(matrix, this.field, this.rowIndex, this.multiplier)[0]; } - drawMatrix(matrix: Matrix): string { - return ""; //TODO: not implemented yet + drawMatrix(matrixAsString: string): string { + const rows = matrixAsString.split("\n"); + + rows[this.rowIndex] += ` * (${this.parsingService.elementToString(this.multiplier)})`; + + return rows.join("\n"); } reverse(matrix: Matrix): Matrix { diff --git a/src/matrix/events/ReductionEvent.ts b/src/matrix/events/ReductionEvent.ts index 96af10f..2194640 100644 --- a/src/matrix/events/ReductionEvent.ts +++ b/src/matrix/events/ReductionEvent.ts @@ -21,5 +21,5 @@ export interface ReductionEvent { */ reverse(matrix: Matrix): Matrix; - drawMatrix(matrix: Matrix): string; + drawMatrix(matrixAsString: string): string; } \ No newline at end of file diff --git a/src/matrix/events/StartReductionEvent.ts b/src/matrix/events/StartReductionEvent.ts index 10ec622..10e35f4 100644 --- a/src/matrix/events/StartReductionEvent.ts +++ b/src/matrix/events/StartReductionEvent.ts @@ -16,9 +16,8 @@ export class StartReductionEvent implements ReductionEvent { return this.firstReductionEvent.apply(matrix); } - drawMatrix(matrix: Matrix): string { - //TODO: Not implemented yet - return ""; + drawMatrix(matrixAsString: string): string { + return this.firstReductionEvent.drawMatrix(matrixAsString); } /** diff --git a/src/matrix/events/SwapReductionEvent.ts b/src/matrix/events/SwapReductionEvent.ts index 15a71e3..eee40b7 100644 --- a/src/matrix/events/SwapReductionEvent.ts +++ b/src/matrix/events/SwapReductionEvent.ts @@ -21,8 +21,19 @@ export class SwapReductionEvent implements ReductionEvent { return this.reductionService.swapRows(matrix, this.field, this.firstRowIndex, this.secondRowIndex)[0]; } - drawMatrix(matrix: Matrix): string { - return ""; //TODO: not implemented yet + drawMatrix(matrixAsString: string): string { + const rows = matrixAsString.split("\n"); + + const startIndex = this.firstRowIndex < this.secondRowIndex ? this.firstRowIndex : this.secondRowIndex; + const endIndex = this.firstRowIndex < this.secondRowIndex ? this.secondRowIndex : this.firstRowIndex; + + for(let rowIndex = startIndex + 1; rowIndex < endIndex - 1; rowIndex++) { + rows[rowIndex] += " |" + } + rows[this.firstRowIndex] += "<---"; + rows[this.secondRowIndex] += "<---"; + + return rows.join("\n"); } reverse(matrix: Matrix): Matrix { From 266cfdb4ef0031e0bfdfbd14fe1105e8d3c4cf5d Mon Sep 17 00:00:00 2001 From: Alexander Pruss Date: Sun, 9 Jun 2019 20:01:16 +0200 Subject: [PATCH 05/12] Draft implementation of the rest of the CLI --- src/cli/ClientEventHandler.ts | 57 ++++++++++++++++++-------------- src/cli/MatrixLoader.ts | 4 +-- src/cli/MatrixPainter.spec.ts | 8 ++--- src/cli/MatrixPainter.ts | 26 +++++++-------- src/cli/ReductionExecution.ts | 62 ++++++++++++++++++++++++++++++++++- 5 files changed, 113 insertions(+), 44 deletions(-) diff --git a/src/cli/ClientEventHandler.ts b/src/cli/ClientEventHandler.ts index d6ef6a7..ce8c467 100644 --- a/src/cli/ClientEventHandler.ts +++ b/src/cli/ClientEventHandler.ts @@ -1,17 +1,20 @@ import defaultRationalNumbers, {RationalNumbers} from "../fields/rationals/RationalNumbers"; -import defaultRationalParser, {RationalParser} from "../fields/rationals/RationalParser"; import {ReductionExecution} from "./ReductionExecution"; import {ReadLine} from "readline"; import defaultMatrixLoader, {MatrixLoader} from "./MatrixLoader"; +import defaultReductionService, {ReductionService} from "../matrix/ReductionService"; +import defaultMatrixPainter, {MatrixPainter} from "./MatrixPainter"; export class ClientEventHandler { - currentExecution: ReductionExecution; + currentExecution: ReductionExecution; readLine: ReadLine; matrixLoader: MatrixLoader = defaultMatrixLoader; + matrixPainter: MatrixPainter = defaultMatrixPainter; + rationalNumbers: RationalNumbers = defaultRationalNumbers; - rationalParser: RationalParser = defaultRationalParser; + reductionService: ReductionService = defaultReductionService; constructor(readLine: ReadLine) { this.readLine = readLine; @@ -23,15 +26,12 @@ export class ClientEventHandler { getCurrentField() { return this.rationalNumbers; } - getCurrentParser() { - return this.rationalParser; - } handlePrompt(prompt: string) { prompt = prompt.trim(); - if(prompt.includes("-h") || prompt.includes( " h ") || prompt.includes("help")){ - console.log(`The available commands are: + if (prompt.includes("-h") || prompt.includes(" h ") || prompt.includes("help")) { + console.log(`The available commands are: import {filename} - imports a matrix from the given file; exit - exits the app. @@ -52,48 +52,57 @@ export class ClientEventHandler { A new matrix can be imported with the 'import {filename} command while a matrix execution is active.`) } - if(prompt.startsWith("import")) { + if (prompt.startsWith("import")) { try { const filename = prompt.split(" ")[1]; - this.matrixLoader.importMatrix(this.rationalParser, filename); - } - catch(e) { + const matrix = this.matrixLoader.importMatrix(this.getCurrentField().getParser(), filename); + this.currentExecution = new ReductionExecution(matrix, this.getCurrentField(), this.matrixPainter, this.reductionService); + } catch (e) { console.log(e.message); return; } - this.readLine.setPrompt("Matrix [<- go back; -> go forward]: ") - //import matrix + + this.readLine.setPrompt("Matrix [<- go back; -> go forward]: "); } - if(prompt.startsWith("exit")) { + if (prompt.startsWith("exit")) { console.log("See you"); process.exit(0); + return; } - if(this.currentExecution == null) { + if (this.currentExecution == null) { return; } - if(prompt.includes("start") || prompt == "S") { - //go to start of the execution + if (prompt.includes("start") || prompt == "S" || prompt == "s") { + this.currentExecution.goToInitialMatrix(); + this.readLine.prompt(); + return; } - if(prompt.includes("result") || prompt == "R") { - //go to result of execution + if (prompt.includes("result") || prompt == "R" || prompt == "r") { + this.currentExecution.goToFinalMatrix(); + this.readLine.prompt(); + return; } + console.log("Sorry, I didn't understand"); + this.readLine.prompt(); } handleKeypress(key: string) { - if(this.currentExecution == null) { + if (this.currentExecution == null) { return; } - switch(key) { + switch (key) { case "left": - //move back in execution + this.currentExecution.goToPreviousMatrix(); + this.readLine.prompt(); case "right": - //move forward in execution + this.currentExecution.goToNextMatrix(); + this.readLine.prompt(); default: break } diff --git a/src/cli/MatrixLoader.ts b/src/cli/MatrixLoader.ts index d3aa7fa..a18769a 100644 --- a/src/cli/MatrixLoader.ts +++ b/src/cli/MatrixLoader.ts @@ -11,12 +11,12 @@ export class MatrixLoader { const bufferString = buffer.toString('utf8'); const rows = []; - for(let line of bufferString.split('\n')) { + for (let line of bufferString.split('\n')) { const row = []; rows.push(row); line = line.trim(); - for(const element of line.split(" ")) { + for (const element of line.split(" ")) { row.push(parser.parse(element)); } } diff --git a/src/cli/MatrixPainter.spec.ts b/src/cli/MatrixPainter.spec.ts index 27dd76b..6ea9f8c 100644 --- a/src/cli/MatrixPainter.spec.ts +++ b/src/cli/MatrixPainter.spec.ts @@ -22,14 +22,14 @@ describe("MatrixPainter", () => { it('should shorten long entries', function () { const matrix = convertToRationalMatrix([ - [100, 2, 3], - [6, 90000, 123424343434343], - [7, 8, 9] + [100, 2, 3], + [6, 90000, 123424343434343], + [7, 8, 9] ]); const expected = `[ 100 2 3 ] [ 6 90000 1234243... ] -[ 7 8 9 ]` +"\n"; +[ 7 8 9 ]` + "\n"; expect(matrixPainter.printMatrix(matrix, parser)).to.eql(expected); }); diff --git a/src/cli/MatrixPainter.ts b/src/cli/MatrixPainter.ts index 2997119..219a75e 100644 --- a/src/cli/MatrixPainter.ts +++ b/src/cli/MatrixPainter.ts @@ -6,25 +6,25 @@ export class MatrixPainter { minWidth = 4; maxWidth = 10; - printMatrix(matrix: Matrix, parser: Parser) : string{ - const stringRows : string[][] = []; + printMatrix(matrix: Matrix, parser: Parser): string { + const stringRows: string[][] = []; const columnWidths = []; - for(let columnIndex = 0; columnIndex < matrix.numberOfColumns; columnIndex++) { + for (let columnIndex = 0; columnIndex < matrix.numberOfColumns; columnIndex++) { columnWidths.push(this.minWidth); } //read in strings, measure the width-ish - for(const row of matrix.rows) { + for (const row of matrix.rows) { const stringRow = []; stringRows.push(stringRow); - for(let columnIndex = 0; columnIndex < matrix.numberOfColumns; columnIndex++) { + for (let columnIndex = 0; columnIndex < matrix.numberOfColumns; columnIndex++) { const element = row[columnIndex]; const elementString = parser.elementToString(element); const length = elementString.length > 10 ? 10 : elementString.length; - if(length > columnWidths[columnIndex]) { + if (length > columnWidths[columnIndex]) { columnWidths[columnIndex] = length; } @@ -33,32 +33,32 @@ export class MatrixPainter { } //resize strings to make the columns look nice - for(let rowIndex = 0; rowIndex < matrix.numberOfRows; rowIndex++) { - for(let columnIndex = 0; columnIndex < matrix.numberOfColumns; columnIndex++) { + for (let rowIndex = 0; rowIndex < matrix.numberOfRows; rowIndex++) { + for (let columnIndex = 0; columnIndex < matrix.numberOfColumns; columnIndex++) { const elementString = stringRows[rowIndex][columnIndex]; const columnWidth = columnWidths[columnIndex]; const difference = columnWidth - elementString.length; //The string is too short, so add empty spaces - if(difference > 0) { + if (difference > 0) { let whitespace = ""; - for(let numSpaces = 0; numSpaces < difference; numSpaces++) { + for (let numSpaces = 0; numSpaces < difference; numSpaces++) { whitespace += " "; } stringRows[rowIndex][columnIndex] = elementString + whitespace; } //The string is too long, shorten it - if(difference < 0) { + if (difference < 0) { stringRows[rowIndex][columnIndex] = elementString.substr(0, 7) + "..."; } } } let matrixString = ""; - for(const stringRow of stringRows) { + for (const stringRow of stringRows) { matrixString += "[ "; - for(const stringElement of stringRow) { + for (const stringElement of stringRow) { matrixString += stringElement + " "; } matrixString += "]\n"; diff --git a/src/cli/ReductionExecution.ts b/src/cli/ReductionExecution.ts index ca68388..987e017 100644 --- a/src/cli/ReductionExecution.ts +++ b/src/cli/ReductionExecution.ts @@ -1,3 +1,63 @@ -export class ReductionExecution { +import {Matrix} from "../matrix/Matrix"; +import {ReductionEvent} from "../matrix/events/ReductionEvent"; +import {MatrixPainter} from "./MatrixPainter"; +import {Field} from "../fields/Field"; +import {ReductionService} from "../matrix/ReductionService"; + +export class ReductionExecution { + + initialMatrix: Matrix; + finalMatrix: Matrix; + currentMatrix: Matrix; + + events: ReductionEvent[]; + currentIndex: number = 0; + + field: Field; + painter: MatrixPainter; + + constructor(initialMatrix: Matrix, field: Field, painter: MatrixPainter, reductionService: ReductionService) { + this.initialMatrix = initialMatrix; + this.currentMatrix = initialMatrix; + this.field = field; + this.painter = painter; + + [this.finalMatrix, this.events] = reductionService.reduce(initialMatrix, field); + } + + redrawMatrix(): string { + const matrixString = this.painter.printMatrix(this.currentMatrix, this.field.getParser()); + const enhancedMatrix = this.events[this.currentIndex].drawMatrix(matrixString); + console.log(enhancedMatrix); + return enhancedMatrix; + } + + goToNextMatrix(): string { + this.currentMatrix = this.events[this.currentIndex].apply(this.currentMatrix); + if (this.currentIndex + 1 < this.events.length) { + this.currentIndex++; + } + return this.redrawMatrix(); + } + + goToPreviousMatrix(): string { + this.currentMatrix = this.events[this.currentIndex].apply(this.currentMatrix); + if (this.currentIndex - 1 >= 0) { + this.currentIndex--; + } + return this.redrawMatrix(); + } + + goToInitialMatrix(): string { + this.currentMatrix = this.initialMatrix; + this.currentIndex = 0; + return this.redrawMatrix(); + } + + goToFinalMatrix(): string { + this.currentMatrix = this.finalMatrix; + this.currentIndex = this.events.length - 1; + return this.redrawMatrix(); + } } \ No newline at end of file From b6e7ff9fee85e293c7924ae994e49e2791c4c17e Mon Sep 17 00:00:00 2001 From: Alexander Pruss Date: Sun, 9 Jun 2019 20:30:14 +0200 Subject: [PATCH 06/12] Working out some kinks --- src/cli/ClientEventHandler.ts | 18 +++++++++++++----- src/cli/ReductionExecution.ts | 18 ++++++++++++------ src/matrix/ReductionService.ts | 8 ++++++++ src/matrix/events/AddRowsReductionEvent.ts | 2 +- src/matrix/events/EndReductionEvent.ts | 2 +- src/matrix/events/SwapReductionEvent.ts | 2 +- 6 files changed, 36 insertions(+), 14 deletions(-) diff --git a/src/cli/ClientEventHandler.ts b/src/cli/ClientEventHandler.ts index ce8c467..2168678 100644 --- a/src/cli/ClientEventHandler.ts +++ b/src/cli/ClientEventHandler.ts @@ -63,6 +63,7 @@ export class ClientEventHandler { } this.readLine.setPrompt("Matrix [<- go back; -> go forward]: "); + return; } if (prompt.startsWith("exit")) { @@ -96,15 +97,22 @@ export class ClientEventHandler { return; } + let result: string = ""; switch (key) { case "left": - this.currentExecution.goToPreviousMatrix(); - this.readLine.prompt(); + result = this.currentExecution.goToPreviousMatrix(); + if(result != ""){ + this.readLine.prompt(); + } + break; case "right": - this.currentExecution.goToNextMatrix(); - this.readLine.prompt(); + result = this.currentExecution.goToNextMatrix(); + if(result != ""){ + this.readLine.prompt(); + } + break; default: - break + break; } } diff --git a/src/cli/ReductionExecution.ts b/src/cli/ReductionExecution.ts index 987e017..799cc5b 100644 --- a/src/cli/ReductionExecution.ts +++ b/src/cli/ReductionExecution.ts @@ -16,6 +16,8 @@ export class ReductionExecution { field: Field; painter: MatrixPainter; + appliedLastEvent = false; + constructor(initialMatrix: Matrix, field: Field, painter: MatrixPainter, reductionService: ReductionService) { this.initialMatrix = initialMatrix; this.currentMatrix = initialMatrix; @@ -23,28 +25,32 @@ export class ReductionExecution { this.painter = painter; [this.finalMatrix, this.events] = reductionService.reduce(initialMatrix, field); + this.redrawMatrix(); } redrawMatrix(): string { const matrixString = this.painter.printMatrix(this.currentMatrix, this.field.getParser()); - const enhancedMatrix = this.events[this.currentIndex].drawMatrix(matrixString); + const enhancedMatrix = "\n" + this.events[this.currentIndex].drawMatrix(matrixString); console.log(enhancedMatrix); return enhancedMatrix; } goToNextMatrix(): string { this.currentMatrix = this.events[this.currentIndex].apply(this.currentMatrix); - if (this.currentIndex + 1 < this.events.length) { - this.currentIndex++; + if (this.currentIndex + 1 == this.events.length) { + return ""; } + this.currentIndex++; return this.redrawMatrix(); } goToPreviousMatrix(): string { - this.currentMatrix = this.events[this.currentIndex].apply(this.currentMatrix); - if (this.currentIndex - 1 >= 0) { - this.currentIndex--; + if(this.currentIndex == 0) { + return ""; } + this.currentIndex--; + this.currentMatrix = this.events[this.currentIndex].reverse(this.currentMatrix); + return this.redrawMatrix(); } diff --git a/src/matrix/ReductionService.ts b/src/matrix/ReductionService.ts index 5ba399b..96fbf0c 100644 --- a/src/matrix/ReductionService.ts +++ b/src/matrix/ReductionService.ts @@ -5,6 +5,8 @@ import {SwapReductionEvent} from "./events/SwapReductionEvent"; import logger from "../logging/Logger"; import {AddRowsReductionEvent} from "./events/AddRowsReductionEvent"; import {MultiplyRowsReductionEvent} from "./events/MultiplyRowsReductionEvent"; +import {StartReductionEvent} from "./events/StartReductionEvent"; +import {EndReductionEvent} from "./events/EndReductionEvent"; //I'd much rather just have the field as part of the Matrix, but this isn't doable yet due to typescript generic limitations. export class ReductionService { @@ -70,6 +72,12 @@ export class ReductionService { destinationForNextPivot++; } + + if (events.length > 0) { + events[0] = new StartReductionEvent(field, events[0]); + events.push(new EndReductionEvent(field, events[events.length - 1])); + //events[events.length - 1] = new EndReductionEvent(field, events[events.length - 1]); + } return [matrix, events]; } diff --git a/src/matrix/events/AddRowsReductionEvent.ts b/src/matrix/events/AddRowsReductionEvent.ts index 6c8c5f3..9878250 100644 --- a/src/matrix/events/AddRowsReductionEvent.ts +++ b/src/matrix/events/AddRowsReductionEvent.ts @@ -32,7 +32,7 @@ export class AddRowsReductionEvent implements ReductionEvent { const startIndex = this.rowIndexToAdd < this.rowIndexToAddTo ? this.rowIndexToAdd : this.rowIndexToAddTo; const endIndex = this.rowIndexToAdd < this.rowIndexToAddTo ? this.rowIndexToAddTo : this.rowIndexToAdd; - for(let rowIndex = startIndex + 1; rowIndex < endIndex - 1; rowIndex++) { + for(let rowIndex = startIndex + 1; rowIndex < endIndex; rowIndex++) { rows[rowIndex] += " |" } rows[this.rowIndexToAdd] += `---- +(${this.parsingService.elementToString(this.multiplier)})`; diff --git a/src/matrix/events/EndReductionEvent.ts b/src/matrix/events/EndReductionEvent.ts index d3dd90e..6ec76d7 100644 --- a/src/matrix/events/EndReductionEvent.ts +++ b/src/matrix/events/EndReductionEvent.ts @@ -22,7 +22,7 @@ export class EndReductionEvent implements ReductionEvent { } drawMatrix(matrixAsString: string): string { - return this.lastReductionEvent.drawMatrix(matrixAsString); + return matrixAsString + " Finished!"; } /** diff --git a/src/matrix/events/SwapReductionEvent.ts b/src/matrix/events/SwapReductionEvent.ts index eee40b7..c66950d 100644 --- a/src/matrix/events/SwapReductionEvent.ts +++ b/src/matrix/events/SwapReductionEvent.ts @@ -27,7 +27,7 @@ export class SwapReductionEvent implements ReductionEvent { const startIndex = this.firstRowIndex < this.secondRowIndex ? this.firstRowIndex : this.secondRowIndex; const endIndex = this.firstRowIndex < this.secondRowIndex ? this.secondRowIndex : this.firstRowIndex; - for(let rowIndex = startIndex + 1; rowIndex < endIndex - 1; rowIndex++) { + for(let rowIndex = startIndex + 1; rowIndex < endIndex; rowIndex++) { rows[rowIndex] += " |" } rows[this.firstRowIndex] += "<---"; From 78a36a11361d350cad622d4d054d1d70b180a7e8 Mon Sep 17 00:00:00 2001 From: Alexander Pruss Date: Sun, 9 Jun 2019 20:43:58 +0200 Subject: [PATCH 07/12] Last tweaks --- src/cli/ClientEventHandler.ts | 4 ++-- src/cli/ReductionExecution.ts | 2 +- src/fields/Field.ts | 10 +++++----- src/fields/rationals/RationalNumbers.spec.ts | 6 +++--- src/fields/rationals/RationalNumbers.ts | 2 +- src/fields/rationals/RationalParser.spec.ts | 4 ++-- src/fields/rationals/RationalParser.ts | 12 ++++++------ src/matrix/events/AddRowsReductionEvent.ts | 2 +- src/matrix/events/StartReductionEvent.ts | 2 +- src/matrix/events/SwapReductionEvent.ts | 4 ++-- 10 files changed, 24 insertions(+), 24 deletions(-) diff --git a/src/cli/ClientEventHandler.ts b/src/cli/ClientEventHandler.ts index 2168678..cb89b1c 100644 --- a/src/cli/ClientEventHandler.ts +++ b/src/cli/ClientEventHandler.ts @@ -101,13 +101,13 @@ export class ClientEventHandler { switch (key) { case "left": result = this.currentExecution.goToPreviousMatrix(); - if(result != ""){ + if (result != "") { this.readLine.prompt(); } break; case "right": result = this.currentExecution.goToNextMatrix(); - if(result != ""){ + if (result != "") { this.readLine.prompt(); } break; diff --git a/src/cli/ReductionExecution.ts b/src/cli/ReductionExecution.ts index 799cc5b..12cc2d2 100644 --- a/src/cli/ReductionExecution.ts +++ b/src/cli/ReductionExecution.ts @@ -45,7 +45,7 @@ export class ReductionExecution { } goToPreviousMatrix(): string { - if(this.currentIndex == 0) { + if (this.currentIndex == 0) { return ""; } this.currentIndex--; diff --git a/src/fields/Field.ts b/src/fields/Field.ts index ab2219a..c189575 100644 --- a/src/fields/Field.ts +++ b/src/fields/Field.ts @@ -36,16 +36,16 @@ export interface Field { multiplicativeIdentity(): Element; //TODO: This will need to move to a dedicated drawing class - elementToString(element: Element) : string; + elementToString(element: Element): string; /** * Whether the field has a norm defined on it. */ - hasNorm() : boolean; + hasNorm(): boolean; - norm(element: Element) : number; + norm(element: Element): number; - elementsEqual(first: Element, second: Element) : boolean; + elementsEqual(first: Element, second: Element): boolean; - getParser() : Parser; + getParser(): Parser; } \ No newline at end of file diff --git a/src/fields/rationals/RationalNumbers.spec.ts b/src/fields/rationals/RationalNumbers.spec.ts index 7347ea2..90aa8cd 100644 --- a/src/fields/rationals/RationalNumbers.spec.ts +++ b/src/fields/rationals/RationalNumbers.spec.ts @@ -1,4 +1,4 @@ -import {RationalNumber, Sign} from "./RationalNumber"; +import {Sign} from "./RationalNumber"; import {RationalNumbers} from "./RationalNumbers"; import {expect} from "chai"; import {createRationalNumber} from "./test-helpers/RationalProvider.spec"; @@ -176,8 +176,8 @@ describe("RationalNumbers", () => { const positiveNumber = createRationalNumber(5, 12, Sign.POSITIVE); const negativeNumber = createRationalNumber(5, 12, Sign.NEGATIVE); - expect(rationalNumbers.norm(positiveNumber)).to.eql(5/12); - expect(rationalNumbers.norm(negativeNumber)).to.eql(5/12); + expect(rationalNumbers.norm(positiveNumber)).to.eql(5 / 12); + expect(rationalNumbers.norm(negativeNumber)).to.eql(5 / 12); }); }); diff --git a/src/fields/rationals/RationalNumbers.ts b/src/fields/rationals/RationalNumbers.ts index 8cb541b..25f8354 100644 --- a/src/fields/rationals/RationalNumbers.ts +++ b/src/fields/rationals/RationalNumbers.ts @@ -92,7 +92,7 @@ export class RationalNumbers implements Field { } norm(element: RationalNumber): number { - return element.numerator.value/element.denominator.value; + return element.numerator.value / element.denominator.value; } elementsEqual(first: RationalNumber, second: RationalNumber): boolean { diff --git a/src/fields/rationals/RationalParser.spec.ts b/src/fields/rationals/RationalParser.spec.ts index 63755e6..d174190 100644 --- a/src/fields/rationals/RationalParser.spec.ts +++ b/src/fields/rationals/RationalParser.spec.ts @@ -35,13 +35,13 @@ describe("RationalParser", () => { it('throws an error if it can\'t parse a string part into a number', function () { - expect(() =>rationalParser.parse("6/beans")).to.throw(Error); + expect(() => rationalParser.parse("6/beans")).to.throw(Error); }); it('throws an error if there are too many denominators ("/" symbols)', function () { - expect(() =>rationalParser.parse("6/4/2")).to.throw(Error); + expect(() => rationalParser.parse("6/4/2")).to.throw(Error); }); diff --git a/src/fields/rationals/RationalParser.ts b/src/fields/rationals/RationalParser.ts index f487285..83b91ff 100644 --- a/src/fields/rationals/RationalParser.ts +++ b/src/fields/rationals/RationalParser.ts @@ -3,7 +3,7 @@ import logger from "../../logging/Logger"; import defaultPrimeFactorService, {PrimeFactorService} from "./PrimeFactorService"; import {Parser} from "../../cli/Parser"; -export class RationalParser implements Parser{ +export class RationalParser implements Parser { primeFactorService: PrimeFactorService = defaultPrimeFactorService; @@ -11,26 +11,26 @@ export class RationalParser implements Parser{ * Parses strings of the form "3/5", "-24/30", etc. * @param elementAsString */ - parse(elementAsString: string) : RationalNumber { + parse(elementAsString: string): RationalNumber { let sign = Sign.POSITIVE; - if(elementAsString.startsWith("-")) { + if (elementAsString.startsWith("-")) { sign = Sign.NEGATIVE; elementAsString = elementAsString.slice(1); } const splitElement = elementAsString.split("/"); - if(splitElement.length > 2) { + if (splitElement.length > 2) { const message = "Parsing error - a rational number had multiple '/' characters."; logger.error(message); throw new Error(message); } - const numeratorValue : number = Number(splitElement[0]); + const numeratorValue: number = Number(splitElement[0]); const denominatorValue = splitElement.length == 1 ? 1 : Number(splitElement[1]); const numerator = this.primeFactorService.createFactoredNumber(numeratorValue); const denominator = this.primeFactorService.createFactoredNumber(denominatorValue); - if(numeratorValue == 0) { + if (numeratorValue == 0) { sign = Sign.POSITIVE; } diff --git a/src/matrix/events/AddRowsReductionEvent.ts b/src/matrix/events/AddRowsReductionEvent.ts index 9878250..fec85b6 100644 --- a/src/matrix/events/AddRowsReductionEvent.ts +++ b/src/matrix/events/AddRowsReductionEvent.ts @@ -32,7 +32,7 @@ export class AddRowsReductionEvent implements ReductionEvent { const startIndex = this.rowIndexToAdd < this.rowIndexToAddTo ? this.rowIndexToAdd : this.rowIndexToAddTo; const endIndex = this.rowIndexToAdd < this.rowIndexToAddTo ? this.rowIndexToAddTo : this.rowIndexToAdd; - for(let rowIndex = startIndex + 1; rowIndex < endIndex; rowIndex++) { + for (let rowIndex = startIndex + 1; rowIndex < endIndex; rowIndex++) { rows[rowIndex] += " |" } rows[this.rowIndexToAdd] += `---- +(${this.parsingService.elementToString(this.multiplier)})`; diff --git a/src/matrix/events/StartReductionEvent.ts b/src/matrix/events/StartReductionEvent.ts index 10e35f4..1b10a6b 100644 --- a/src/matrix/events/StartReductionEvent.ts +++ b/src/matrix/events/StartReductionEvent.ts @@ -25,7 +25,7 @@ export class StartReductionEvent implements ReductionEvent { * @param matrix */ reverse(matrix: Matrix): Matrix { - return matrix; + return this.firstReductionEvent.reverse(matrix); } } \ No newline at end of file diff --git a/src/matrix/events/SwapReductionEvent.ts b/src/matrix/events/SwapReductionEvent.ts index c66950d..c6a49d6 100644 --- a/src/matrix/events/SwapReductionEvent.ts +++ b/src/matrix/events/SwapReductionEvent.ts @@ -27,10 +27,10 @@ export class SwapReductionEvent implements ReductionEvent { const startIndex = this.firstRowIndex < this.secondRowIndex ? this.firstRowIndex : this.secondRowIndex; const endIndex = this.firstRowIndex < this.secondRowIndex ? this.secondRowIndex : this.firstRowIndex; - for(let rowIndex = startIndex + 1; rowIndex < endIndex; rowIndex++) { + for (let rowIndex = startIndex + 1; rowIndex < endIndex; rowIndex++) { rows[rowIndex] += " |" } - rows[this.firstRowIndex] += "<---"; + rows[this.firstRowIndex] += "<---"; rows[this.secondRowIndex] += "<---"; return rows.join("\n"); From 822c1d2e4cb2ee46cda83a9b07191ea3ccdd3693 Mon Sep 17 00:00:00 2001 From: Alexander Pruss Date: Sun, 9 Jun 2019 20:47:22 +0200 Subject: [PATCH 08/12] Final test adjustments. --- src/matrix/ReductionService.spec.ts | 4 ++-- src/matrix/events/StartReductionEvent.spec.ts | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/matrix/ReductionService.spec.ts b/src/matrix/ReductionService.spec.ts index 5cd2050..b691e18 100644 --- a/src/matrix/ReductionService.spec.ts +++ b/src/matrix/ReductionService.spec.ts @@ -40,7 +40,7 @@ describe("ReductionService", () => { const [reducedMatrix, events] = reductionService.reduce(matrix, rationalNumbers); expect(reducedMatrix).to.eql(expectedMatrix); - expect(events).to.have.lengthOf(8); + expect(events).to.have.lengthOf(9); }); it('reduces a matrix to reduced row echelon form, handling rows of all zeroes correctly', function () { @@ -58,7 +58,7 @@ describe("ReductionService", () => { const [reducedMatrix, events] = reductionService.reduce(matrix, rationalNumbers); expect(reducedMatrix).to.eql(expectedMatrix); - expect(events).to.have.lengthOf(8); + expect(events).to.have.lengthOf(9); }); }); diff --git a/src/matrix/events/StartReductionEvent.spec.ts b/src/matrix/events/StartReductionEvent.spec.ts index 2ef438e..2faee57 100644 --- a/src/matrix/events/StartReductionEvent.spec.ts +++ b/src/matrix/events/StartReductionEvent.spec.ts @@ -22,7 +22,7 @@ describe("StartReductionEvent", () => { describe("#apply", () => { - it('does nothing, as this is the last event', function () { + it('passes the event into its inner event', function () { const resultingMatrix = startEvent.apply(matrixBefore); expect(resultingMatrix).to.eql(matrixAfter); @@ -32,8 +32,8 @@ describe("StartReductionEvent", () => { describe("#reverse", () => { - it('does nothing, as this is the first event', function () { - const resultingMatrix = startEvent.reverse(matrixAfter); + it('passes the event into its inner event', function () { + const resultingMatrix = startEvent.reverse(matrixBefore); expect(resultingMatrix).to.eql(matrixAfter); }); From da1de3c7e6b92a4ec4cc73e8f4ae05ba7f4a67e0 Mon Sep 17 00:00:00 2001 From: Alexander Pruss Date: Sun, 9 Jun 2019 20:56:23 +0200 Subject: [PATCH 09/12] Updating Readme --- Readme.MD | 50 +++++++++++++++++++++++++++++++++- examples/complicatedmatrix.txt | 5 ++++ package-lock.json | 2 +- 3 files changed, 55 insertions(+), 2 deletions(-) create mode 100644 examples/complicatedmatrix.txt diff --git a/Readme.MD b/Readme.MD index a91ccd3..72eb044 100644 --- a/Readme.MD +++ b/Readme.MD @@ -3,8 +3,55 @@ This is a simple POC app that I'm using to poke at doing numerics in node. I'm also playing with node-based CLIs while I'm at it. +# Installation + +You'll need NPM installed to build the project. Load dependencies and compile the app with + +`npm install && npm run build` + +You can then start the application from source code directly with + +`npm run start` + +Alternatively, you can save the app to your terminal as a CLI app. To do this, run + +`npm link` + +This will install the app as `matrix-stuff` to your path. + # Usage +Starting the app opens a CLI session in your terminal. You can ask for commands by typing "help", which will give the +following response: + + The available commands are: + import {filename} - imports a matrix from the given file; + exit - exits the app. + + Matrix files should be formatted as follows: + * Each entry of the matrix is separated with an empty space + * Each row of the matrix is separated with a newline + + Currently, only rational numbers are supported. A sample valid row could look like this: + -4 12/15 0 4/6 + + When a matrix is imported, the follow commands become available: + + [Press LEFT] - See what the matrix looked like in the previous step of the execution. + [Press RIGHT] - See what the matrix looked like in the next step of the execution. + start - Jump back to the start of the execution. + result - Jump back to the result of the execution. + + A new matrix can be imported with the 'import {filename} command while a matrix execution is active.` + + +# Examples + +Two example matrices can be found in the `examples` folder. You could try them out from inside the app with + +`import examples/nicematrix.txt` +`import examples/complicatedMatrix.txt` + # Continuous Integration ATM this POC isn't packaged, so the CI just runs tests. @@ -16,4 +63,5 @@ https://circleci.com/gh/AlexanderPruss/Matrix-Reduction * Add a more intelligent way to factor a number into prime factors * Having better immutable types would be helpful * Some improvements could be made to RationalNumbers to help prevent overflow -* Need to look into whether having non-blocking functions (more async) is relevant when the program is a CLI \ No newline at end of file +* Need to look into whether having non-blocking functions (more async) is relevant when the program is a CLI +* While the math is very well covered, some of the CLI stuff isn't tested much \ No newline at end of file diff --git a/examples/complicatedmatrix.txt b/examples/complicatedmatrix.txt new file mode 100644 index 0000000..63cdaa9 --- /dev/null +++ b/examples/complicatedmatrix.txt @@ -0,0 +1,5 @@ +1 2 0 4 5 +4 5 0 -2 -3 +7 8 0 1/2 3/4 +-1/5 1 0 3 3 +2 4 0 8 10 \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index e8cf7b6..8e0ab99 100644 --- a/package-lock.json +++ b/package-lock.json @@ -452,7 +452,7 @@ }, "commander": { "version": "2.15.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.15.1.tgz", + "resolved": "http://registry.npmjs.org/commander/-/commander-2.15.1.tgz", "integrity": "sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag==", "dev": true }, From a94e021be21086a56ecfc9497932aa6d17283b10 Mon Sep 17 00:00:00 2001 From: Alexander Pruss Date: Sun, 9 Jun 2019 21:11:58 +0200 Subject: [PATCH 10/12] Final final final tweaks, maybe --- src/matrix/ReductionService.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/matrix/ReductionService.ts b/src/matrix/ReductionService.ts index 96fbf0c..d2c0e54 100644 --- a/src/matrix/ReductionService.ts +++ b/src/matrix/ReductionService.ts @@ -34,7 +34,7 @@ export class ReductionService { let column = matrix.getColumn(columnIndex); //Find the pivot, if one exists, and move it into the diagonal - const pivotIndex = this.findPivot(column, field, columnIndex); + const pivotIndex = this.findPivot(column, field, destinationForNextPivot); if (pivotIndex == -1) { continue; } @@ -89,10 +89,10 @@ export class ReductionService { * Returns -1 if the eligible part of the column is filled with zeroes. * @param column * @param field - * @param columnIndex + * @param initialIndex */ - findPivot(column: E[], field: Field, columnIndex: number): number { - logger.info(`Finding the pivot for column ${columnIndex}`); + findPivot(column: E[], field: Field, initialIndex: number): number { + logger.info(`Finding the pivot for column ${initialIndex}`); //If we haven't defined a norm, just return a column with a nonzero element. if (!field.hasNorm()) { @@ -100,8 +100,8 @@ export class ReductionService { return column.findIndex(element => !field.elementsEqual(zero, element)); } - let largestNorm = field.norm(column[columnIndex]); - let pivotIndex = columnIndex; + let largestNorm = field.norm(column[initialIndex]); + let pivotIndex = initialIndex; for (let rowIndex = pivotIndex + 1; rowIndex < column.length; rowIndex++) { const norm = field.norm(column[rowIndex]); if (norm > largestNorm) { From bf8f0a1b947af171e32de0e9b817cadfd37606cc Mon Sep 17 00:00:00 2001 From: Alexander Pruss Date: Sun, 9 Jun 2019 21:12:01 +0200 Subject: [PATCH 11/12] 1.0.0 --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 8e0ab99..2ace6a6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "matrix-reduction", - "version": "0.0.1", + "version": "1.0.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 4c21839..94eb913 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "matrix-reduction", - "version": "0.0.1", + "version": "1.0.0", "description": "POC matrix reduction program, trying out some numerics in node", "repository": { "type": "git", From f3e6082eb28174ff213540689c792198a91ede03 Mon Sep 17 00:00:00 2001 From: Alexander Pruss Date: Sun, 9 Jun 2019 21:13:10 +0200 Subject: [PATCH 12/12] Typos --- Readme.MD | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Readme.MD b/Readme.MD index 72eb044..8382c29 100644 --- a/Readme.MD +++ b/Readme.MD @@ -50,8 +50,11 @@ following response: Two example matrices can be found in the `examples` folder. You could try them out from inside the app with `import examples/nicematrix.txt` + `import examples/complicatedMatrix.txt` +Feel free to write your own matrices to be imported and reduced. + # Continuous Integration ATM this POC isn't packaged, so the CI just runs tests.