diff --git a/package.json b/package.json index 45909e2..20b9f8d 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,8 @@ "scripts": { "start": "npx webpack-dev-server --mode development --open --hot", "test": "npx jest", - "build": "npx webpack --mode production" + "build": "npx webpack --mode production", + "calc": "npx ts-node src/lesson2" }, "keywords": [ "calculation", @@ -30,6 +31,7 @@ "html-webpack-plugin": "^4.3.0", "jest": "^25.5.4", "prettier": "^2.0.5", + "ts-node": "^8.10.2", "typescript": "^3.9.5", "webpack": "^4.43.0", "webpack-cli": "^3.3.12", diff --git a/src/lesson2/calc.test.ts b/src/lesson2/calc.test.ts new file mode 100644 index 0000000..cbaecf0 --- /dev/null +++ b/src/lesson2/calc.test.ts @@ -0,0 +1,11 @@ +import { calc } from './calc'; + +test.each` + input | expected + ${'2 + 2 + 2'} | ${6} + ${'2** + 2 + 2'} | ${8} + ${'4! + 2 + 2'} | ${28} + ${'4^2 + 2 + 2'} | ${20} +`('receive $expected submit functions calc($input)', ({ input, expected }) => { + expect(calc(input)).toEqual(expected); +}); diff --git a/src/lesson2/calc.ts b/src/lesson2/calc.ts new file mode 100644 index 0000000..413086a --- /dev/null +++ b/src/lesson2/calc.ts @@ -0,0 +1,18 @@ +import { parser } from './parser'; + +import { firstPrioritiesCalc, secondPrioritiesCalc, degreeСalc } from './logic'; + +export const calc = (extention: string): number => { + const parserExtention = parser(extention); + + const firstPrioritiesRes = + parserExtention.includes('**') || parserExtention.includes('!') + ? degreeСalc(parserExtention) + : firstPrioritiesCalc(parserExtention); + + if (firstPrioritiesRes.length === 1) { + return Number(firstPrioritiesRes[0]); + } + + return secondPrioritiesCalc(firstPrioritiesRes); +}; diff --git a/src/lesson2/common.ts b/src/lesson2/common.ts new file mode 100644 index 0000000..e7d73b8 --- /dev/null +++ b/src/lesson2/common.ts @@ -0,0 +1,9 @@ +export type parserType = (string | number)[]; + +export function isNumeric(params: string | number): boolean { + if (!isNaN(Number(params))) { + return true; + } else { + return false; + } +} diff --git a/src/lesson2/index.ts b/src/lesson2/index.ts new file mode 100644 index 0000000..b162399 --- /dev/null +++ b/src/lesson2/index.ts @@ -0,0 +1,30 @@ +import { createInterface } from 'readline'; + +import { calc } from './calc'; + +const rl = createInterface({ + input: process.stdin, + output: process.stdout, +}); + +const question = (): Promise => + new Promise((resolve) => { + rl.question('> ', (answer: string) => { + const result = calc(answer); + + if (result) { + // eslint-disable-next-line no-console + console.log(`Result: ${result}`); + } + + resolve(); + }); + }); + +async function app(): Promise { + while (true) { + await question(); + } +} + +app(); diff --git a/src/lesson2/logic.test.ts b/src/lesson2/logic.test.ts new file mode 100644 index 0000000..ddb4559 --- /dev/null +++ b/src/lesson2/logic.test.ts @@ -0,0 +1,35 @@ +import { degreeСalc, firstPrioritiesCalc, secondPrioritiesCalc } from './logic'; + +test.each` + input | expected + ${[11, '**', '+', 3]} | ${[121, '+', 3]} + ${[4, '!', '+', 3]} | ${[24, '+', 3]} +`( + 'receive $expected submit functions degreeСalc($input)', + ({ input, expected }) => { + expect(degreeСalc(input)).toEqual(expected); + }, +); + +test.each` + input | expected + ${[4, '^', 2, '+', 6]} | ${[16, '+', 6]} + ${[4, '*', 5, '+', 6]} | ${[20, '+', 6]} +`( + 'receive $expected submit functions firstPrioritiesCalc($input)', + ({ input, expected }) => { + expect(firstPrioritiesCalc(input)).toEqual(expected); + }, +); + +test.each` + input | expected + ${[4, '+', 5, '+', 6]} | ${15} + ${[9, '+', 1, '+', 10]} | ${20} + ${[9, '+', 1, '-', 5]} | ${5} +`( + 'receive $expected submit functions secondPrioritiesCalc($input)', + ({ input, expected }) => { + expect(secondPrioritiesCalc(input)).toBe(expected); + }, +); diff --git a/src/lesson2/logic.ts b/src/lesson2/logic.ts new file mode 100644 index 0000000..cbe0a7f --- /dev/null +++ b/src/lesson2/logic.ts @@ -0,0 +1,45 @@ +import { operators, operatorsPriorities, priorites } from './operators'; +import { parserType } from './common'; + +const [FIRST, SECOND] = priorites; + +export const degreeСalc = (extention: parserType): parserType => + extention.reduce((result, item, key, arr) => { + const operandLeft = result[result.length - 1]; + + if (item === '**' || item === '!') { + result = [ + ...result.slice(0, -1), + operators[item](Number(operandLeft), Number(operandLeft)), + ]; + } else { + result.push(item); + } + return result; + }, []); + +export const firstPrioritiesCalc = (extention: parserType): parserType => + extention.reduce((arr, operandRight) => { + const operandLeft = arr[arr.length - 2]; + const operand = arr[arr.length - 1]; + + if (operatorsPriorities[operand] === FIRST) { + arr = [ + ...arr.slice(0, -2), + operators[operand](Number(operandLeft), Number(operandRight)), + ]; + } else { + arr.push(operandRight); + } + return arr; + }, []); + +export const secondPrioritiesCalc = (extention: parserType): number => + extention.reduce((result, nextItem, key) => { + const operand = extention[key - 1]; + + if (operatorsPriorities[operand] === SECOND) { + result = operators[operand](Number(result), Number(nextItem)); + } + return result; + }, Number(extention[0])); diff --git a/src/lesson2/operators.test.ts b/src/lesson2/operators.test.ts new file mode 100644 index 0000000..b5982b8 --- /dev/null +++ b/src/lesson2/operators.test.ts @@ -0,0 +1,40 @@ +import { + division, + multiplication, + addition, + subtraction, + power, + powerto, + factorial, +} from './operators'; + +test.each` + input | expected | textinput + ${multiplication(2, 2)} | ${4} | ${'multiplication(2, 2)'} + ${division(8, 2)} | ${4} | ${'division(8, 2)'} + ${addition(5, 2)} | ${7} | ${'addition(5, 2)'} + ${subtraction(5, 2)} | ${3} | ${'subtraction(5, 2)'} + ${power(5)} | ${25} | ${'power(5)'} + ${powerto(5, 3)} | ${125} | ${'powerto(5, 3)'} + ${factorial(5)} | ${120} | ${'factorial(5)'} + ${factorial(1)} | ${1} | ${'factorial(1)'} +`('receive $expected submit functions $textinput', ({ input, expected }) => { + expect(input).toBe(expected); +}); + +test.each` + input | expected | textinput + ${multiplication(2, 2)} | ${6} | ${'multiplication(2, 2)'} + ${division(8, 2)} | ${5} | ${'division(8, 2)'} + ${addition(5, 2)} | ${8} | ${'addition(5, 2)'} + ${subtraction(5, 2)} | ${4} | ${'subtraction(5, 2)'} + ${power(5)} | ${24} | ${'power(5)'} + ${powerto(5, 3)} | ${123} | ${'powerto(5, 3)'} + ${factorial(5)} | ${121} | ${'factorial(5)'} + ${factorial(1)} | ${2} | ${'factorial(1)'} +`( + 'receive shouldn`t be $expected submit functions $textinput', + ({ input, expected }) => { + expect(input).not.toBe(expected); + }, +); diff --git a/src/lesson2/operators.ts b/src/lesson2/operators.ts new file mode 100644 index 0000000..cb66412 --- /dev/null +++ b/src/lesson2/operators.ts @@ -0,0 +1,69 @@ +type typeOperators = (first: number, second: number) => number; +type oneParametrsType = (value: number) => number; + +export const addition: typeOperators = ( + first: number, + second: number, +): number => first + second; + +export const subtraction: typeOperators = ( + first: number, + second: number, +): number => first - second; + +export const multiplication: typeOperators = ( + first: number, + second: number, +): number => first * second; + +export const division: typeOperators = ( + first: number, + second: number, +): number => first / second; + +export const power: oneParametrsType = (first: number): number => + (first *= first); + +export const powerto: typeOperators = ( + first: number, + second: number, +): number => { + let result = first; + for (let pow = 1; pow < second; pow++) { + result *= first; + } + return result; +}; + +export const factorial: oneParametrsType = (value: number): number => { + if (value === 1) { + return 1; + } + return value * factorial(value - 1); +}; + +export const operators: { + [key: string]: typeOperators; +} = { + '/': division, + '*': multiplication, + '+': addition, + '-': subtraction, + '**': power, + '^': powerto, + '!': factorial, +}; + +export const priorites = [1, 2]; + +const [FIRST, SECOND] = priorites; + +export const operatorsPriorities: { [key: string]: number } = { + '!': FIRST, + '*': FIRST, + '/': FIRST, + '**': FIRST, + '^': FIRST, + '+': SECOND, + '-': SECOND, +}; diff --git a/src/lesson2/parser.test.ts b/src/lesson2/parser.test.ts new file mode 100644 index 0000000..cb04fa9 --- /dev/null +++ b/src/lesson2/parser.test.ts @@ -0,0 +1,27 @@ +import { parser } from './parser'; + +test.each` + input | expected + ${'1 + + 33 - 2'} | ${'Unexpected string'} + ${'1 ! 33 - 2'} | ${'Unexpected string'} +`( + 'receive $expected after parsing the string $input', + ({ input, expected }) => { + expect(() => parser(input)).toThrow(TypeError(expected)); + }, +); + +test.each` + input | expected + ${'2 + 2'} | ${[2, '+', 2]} + ${'2 + 2 + 5 + 7'} | ${[2, '+', 2, '+', 5, '+', 7]} + ${'11 + 3 * 22'} | ${[11, '+', 3, '*', 22]} + ${'11** + 3 * 22'} | ${[11, '**', '+', 3, '*', 22]} + ${'2** + 2 + 2'} | ${[2, '**', '+', 2, '+', 2]} + ${'2** + 2 + 2 + 2!'} | ${[2, '**', '+', 2, '+', 2, '+', 2, '!']} +`( + 'receive $expected after parsing the string $input', + ({ input, expected }) => { + expect(parser(input)).toEqual(expected); + }, +); diff --git a/src/lesson2/parser.ts b/src/lesson2/parser.ts new file mode 100644 index 0000000..574cfa1 --- /dev/null +++ b/src/lesson2/parser.ts @@ -0,0 +1,36 @@ +import { parserType, isNumeric } from './common'; +export const parser = (extention: string): parserType => { + const parserExtention = extention.split(' '); + + return parserExtention.reduce((arr, item) => { + if ( + item.match(/((\d+)(\*\*))?((\d+)(\!))?((\d+)(\^)(\d*))?$/)![0] + .length > 1 + ) { + item.match(/((\d+)(\*\*))?((\d+)(\!))?((\d+)(\^)(\d+))?$/)! + .filter((value) => { + return ( + value != undefined && + !value.match(/\d+(\!|\^|\*\*)(\d*)?$/) + ); + }) + .forEach((value) => { + const typeValue = isNumeric(value) + ? Number(value) + : String(value); + arr.push(typeValue); + }); + } else if (item.match(/^-{0,1}\d+$/)) { + arr.push(Number(item)); + } else if ( + ['-', '+', '/', '*'].includes(item) && + arr.indexOf(item, arr.length - 1) == -1 + ) { + arr.push(item); + } else { + throw new TypeError('Unexpected string'); + } + + return arr; + }, []); +}; diff --git a/tsconfig.json b/tsconfig.json index 67700ae..cbfdacb 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -7,8 +7,8 @@ "target": "ES2017" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */, "module": "commonjs" /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */, // "lib": [], /* Specify library files to be included in the compilation. */ - "allowJs": true, /* Allow javascript files to be compiled. */ - "checkJs": true, /* Report errors in .js files. */ + "allowJs": true /* Allow javascript files to be compiled. */, + "checkJs": true /* Report errors in .js files. */, // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */ // "declaration": true, /* Generates corresponding '.d.ts' file. */ // "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */