From 85812474014b6436ab45a9cce0804c3e1f4e0d55 Mon Sep 17 00:00:00 2001 From: Nikoilay Petrov Date: Tue, 13 Dec 2022 14:25:12 +0300 Subject: [PATCH 1/3] initial commit --- .gitignore | 3 ++ src/lesson_2_my/index.ts | 32 ++++++++++++ src/lesson_2_my/mathOperators.test.ts | 27 ++++++++++ src/lesson_2_my/mathOperators.ts | 10 ++++ src/lesson_2_my/parser.test.ts | 15 ++++++ src/lesson_2_my/parser.ts | 73 +++++++++++++++++++++++++++ src/lesson_2_my/runner.test.ts | 39 ++++++++++++++ src/lesson_2_my/runner.ts | 5 ++ 8 files changed, 204 insertions(+) create mode 100644 src/lesson_2_my/index.ts create mode 100644 src/lesson_2_my/mathOperators.test.ts create mode 100644 src/lesson_2_my/mathOperators.ts create mode 100644 src/lesson_2_my/parser.test.ts create mode 100644 src/lesson_2_my/parser.ts create mode 100644 src/lesson_2_my/runner.test.ts create mode 100644 src/lesson_2_my/runner.ts diff --git a/.gitignore b/.gitignore index e87c7623..9a9f3d57 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,6 @@ .vscode node_modules dist +.idea/ +venv/ +src/lesson2/my_version.js diff --git a/src/lesson_2_my/index.ts b/src/lesson_2_my/index.ts new file mode 100644 index 00000000..cdd3f888 --- /dev/null +++ b/src/lesson_2_my/index.ts @@ -0,0 +1,32 @@ +import { createInterface } from "readline"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-ignore +// @ts-ignore +import { runner } from "./runner"; + +const rl = createInterface({ + input: process.stdin, + output: process.stdout, +}); + +const question = (): Promise => + new Promise((resolve) => { + rl.question("> ", (answer: string) => { + const result = runner(answer); + + if (result) { + console.log(`Result: ${result}`); + } + + resolve(); + }); + }); + +async function app(): Promise { + console.log("Starting app..."); + while (true) { + await question(); + } +} + +app(); diff --git a/src/lesson_2_my/mathOperators.test.ts b/src/lesson_2_my/mathOperators.test.ts new file mode 100644 index 00000000..c2ff9a96 --- /dev/null +++ b/src/lesson_2_my/mathOperators.test.ts @@ -0,0 +1,27 @@ +import mathOperations from "./mathOperators"; + +describe("mathOperators test cases", () => { + it("mul 1 * 2 to equal 2", () => { + expect(mathOperations["*"](1, 2)).toBe(2); + }); + + it("mul 2 * 2 to equal 4", () => { + expect(mathOperations["*"](2, 2)).toBe(4); + }); + + it("div 2 / 2 to equal 1", () => { + expect(mathOperations["/"](2, 2)).toBe(1); + }); + + it("div 4 / 2 to equal 2", () => { + expect(mathOperations["/"](4, 2)).toBe(2); + }); + + it("add 4 + 2 to equal 6", () => { + expect(mathOperations["+"](4, 2)).toBe(6); + }); + + it("minus 4 - 2 to equal 2", () => { + expect(mathOperations["-"](4, 2)).toBe(2); + }); +}); diff --git a/src/lesson_2_my/mathOperators.ts b/src/lesson_2_my/mathOperators.ts new file mode 100644 index 00000000..94877bb9 --- /dev/null +++ b/src/lesson_2_my/mathOperators.ts @@ -0,0 +1,10 @@ +export type ScalarOperationType = (first: number, second: number) => number; + +const mathOperations: { [key: string]: ScalarOperationType } = { + "+": (a: number, b: number): number => a + b, + "-": (a: number, b: number): number => a - b, + "*": (a: number, b: number): number => a * b, + "/": (a: number, b: number): number => a / b, +}; + +export default mathOperations; diff --git a/src/lesson_2_my/parser.test.ts b/src/lesson_2_my/parser.test.ts new file mode 100644 index 00000000..635d59d5 --- /dev/null +++ b/src/lesson_2_my/parser.test.ts @@ -0,0 +1,15 @@ +import calculate from "./parser"; + +describe("Parser correct cases", () => { + it("1 + 32", () => { + expect(calculate("1 + 32")).toEqual(33); + }); + + it("11 + 3 * 22", () => { + expect(calculate("11 + 3 * 22")).toEqual(77); + }); + + it("1 + 32 - 2 + 2", () => { + expect(calculate("1 + 32 - 2 + 2")).toEqual(29); + }); +}); diff --git a/src/lesson_2_my/parser.ts b/src/lesson_2_my/parser.ts new file mode 100644 index 00000000..fcca17aa --- /dev/null +++ b/src/lesson_2_my/parser.ts @@ -0,0 +1,73 @@ +// eslint-disable-next-line @typescript-eslint/no-var-requires +const _ = require("lodash"); +import mathOperations from "./mathOperators"; + +const GROUP_PATTERN = /\([-+/*0-9.]{3,}\)/g; +const DIGIT_PATTERN = /[0-9.]+/g; +const OPERATOR_PATTERN = /[-+/*]/g; +const LOW_PRIORITY_PATTERN = /[0-9.]+[+-][0-9.]+/g; +const HIGH_PRIORITY_PATTERN = /[0-9.]+[*/][0-9.]+/g; + +const customEval = (subStr: string): number => { + const res: RegExpMatchArray | null = subStr.match(DIGIT_PATTERN); + if (res) { + const operator: string = _.trimEnd(_.trimStart(subStr, res[0]), res[1]); + return mathOperations[operator](Number(res[0]), Number(res[1])); + } + throw new TypeError("Invalid expression"); +}; + +const calculateProcess = (pattern: RegExp, subExp: string): string => { + while (true) { + const res = subExp.match(pattern); + if (!res) break; + for (const s of res) { + const mathResult = customEval(s); + const startI = subExp.indexOf(s); + const endI = s.length; + subExp = + subExp.slice(0, startI) + + String(mathResult) + + subExp.slice(startI + endI); + } + } + return subExp; +}; + +const calculateHighPriority = (subExp: string): string => { + return calculateProcess(HIGH_PRIORITY_PATTERN, subExp); +}; + +const calculateLowPriority = (subExp: string): string => { + return calculateProcess(LOW_PRIORITY_PATTERN, subExp); +}; + +const calculateSubExp = (subExp: string): string => { + return calculateLowPriority(calculateHighPriority(subExp)); +}; + +const calculate = (expression: string): number => { + // очистка выражения от пробелов + expression = expression.replace(/\s+/g, ""); + // будем находить группы до тех пор пока они есть + // вычислять значение, и заменять в оригинальном выражении + while (true) { + // ищем все выражения в скобках + const groups = expression.match(GROUP_PATTERN); + // если ничего не нашли прекращаем + if (!groups) break; + for (const group of groups) { + const subExp = _.trim(group, "()"); + const mathResult = calculateSubExp(subExp); + const startI = expression.indexOf(group); + const endI = group.length; + expression = + expression.slice(0, startI) + + String(mathResult) + + expression.slice(startI + endI); + } + } + return Number(calculateSubExp(expression)); +}; + +export default calculate; diff --git a/src/lesson_2_my/runner.test.ts b/src/lesson_2_my/runner.test.ts new file mode 100644 index 00000000..1318dce3 --- /dev/null +++ b/src/lesson_2_my/runner.test.ts @@ -0,0 +1,39 @@ +import { runner } from "./runner"; + +describe("Runner simple cases", () => { + it("1 * 32", () => { + expect(runner("1 * 32")).toEqual(32); + }); + + it("2 * 32", () => { + expect(runner("2 * 32")).toEqual(64); + }); + + it("2 + 32", () => { + expect(runner("2 + 32")).toEqual(34); + }); +}); + +describe("Runner tripled/mixed cases", () => { + it("2 * 2 * 3", () => { + expect(runner("2 * 2 * 3")).toEqual(12); + }); + + it("2 * 2 + 3", () => { + expect(runner("2 * 2 + 3")).toEqual(7); + }); + + it("2 + 2 * 3", () => { + expect(runner("2 + 2 * 3")).toEqual(8); + }); +}); + +describe("Runner long cases", () => { + it("20 + 1 * 10 - 5 * 3", () => { + expect(runner("20 + 1 * 10 - 5 * 3")).toEqual(15); + }); + + it("20 - 10 * 10 / 5 - 3", () => { + expect(runner("20 - 10 * 10 / 5 - 3")).toEqual(-3); + }); +}); diff --git a/src/lesson_2_my/runner.ts b/src/lesson_2_my/runner.ts new file mode 100644 index 00000000..746f2876 --- /dev/null +++ b/src/lesson_2_my/runner.ts @@ -0,0 +1,5 @@ +import calculate from "./parser"; + +export const runner = (line: string): number => { + return calculate(line); +}; From 10fdd786cd73fa6917d3ca0d3855e7fbd94e4ebc Mon Sep 17 00:00:00 2001 From: Nikoilay Petrov Date: Sun, 18 Dec 2022 18:47:46 +0300 Subject: [PATCH 2/3] add ! and ** --- src/lesson_2_my/mathOperators.test.ts | 26 +++++++++++---- src/lesson_2_my/mathOperators.ts | 32 +++++++++++++++++-- src/lesson_2_my/parser.test.ts | 15 --------- src/lesson_2_my/parser.ts | 31 +++++++++++++----- src/lesson_2_my/runner.test.ts | 46 +++++++++++++++++++++++++-- 5 files changed, 116 insertions(+), 34 deletions(-) delete mode 100644 src/lesson_2_my/parser.test.ts diff --git a/src/lesson_2_my/mathOperators.test.ts b/src/lesson_2_my/mathOperators.test.ts index c2ff9a96..d7f6cc75 100644 --- a/src/lesson_2_my/mathOperators.test.ts +++ b/src/lesson_2_my/mathOperators.test.ts @@ -1,27 +1,39 @@ -import mathOperations from "./mathOperators"; +import mathOperation from "./mathOperators"; describe("mathOperators test cases", () => { it("mul 1 * 2 to equal 2", () => { - expect(mathOperations["*"](1, 2)).toBe(2); + expect(mathOperation("*", 1, 2)).toBe(2); }); it("mul 2 * 2 to equal 4", () => { - expect(mathOperations["*"](2, 2)).toBe(4); + expect(mathOperation("*", 2, 2)).toBe(4); }); it("div 2 / 2 to equal 1", () => { - expect(mathOperations["/"](2, 2)).toBe(1); + expect(mathOperation("/", 2, 2)).toBe(1); }); it("div 4 / 2 to equal 2", () => { - expect(mathOperations["/"](4, 2)).toBe(2); + expect(mathOperation("/", 4, 2)).toBe(2); }); it("add 4 + 2 to equal 6", () => { - expect(mathOperations["+"](4, 2)).toBe(6); + expect(mathOperation("+", 4, 2)).toBe(6); }); it("minus 4 - 2 to equal 2", () => { - expect(mathOperations["-"](4, 2)).toBe(2); + expect(mathOperation("-", 4, 2)).toBe(2); + }); + + it("factorial 5! to equal 120", () => { + expect(mathOperation("!", 5)).toBe(120); + }); + + it("exponentiation 2 ^ 5 to equal 32", () => { + expect(mathOperation("^", 2, 5)).toBe(32); + }); + + it("squaring 5** to equal 525", () => { + expect(mathOperation("**", 5)).toBe(25); }); }); diff --git a/src/lesson_2_my/mathOperators.ts b/src/lesson_2_my/mathOperators.ts index 94877bb9..01cc6356 100644 --- a/src/lesson_2_my/mathOperators.ts +++ b/src/lesson_2_my/mathOperators.ts @@ -1,10 +1,38 @@ export type ScalarOperationType = (first: number, second: number) => number; +export type UnaryOperationType = (number: number) => number; -const mathOperations: { [key: string]: ScalarOperationType } = { +const binaryOperations: { + [key: string]: ScalarOperationType; +} = { "+": (a: number, b: number): number => a + b, "-": (a: number, b: number): number => a - b, "*": (a: number, b: number): number => a * b, "/": (a: number, b: number): number => a / b, + "^": (a: number, b: number): number => Math.pow(a, b), }; -export default mathOperations; +const unaryOperations: { + [key: string]: UnaryOperationType; +} = { + "**": (a: number): number => binaryOperations["*"](a, a), + "!": (a: number): number => { + let f = 1; + for (let i = 1; i <= a; i++) { + f *= i; + } + return f; + }, +}; + +const mathOperation = (operator: string, a: number, b?: number): number => { + if (b) { + return binaryOperations[operator](a, b); + } else { + console.log(operator); + console.log(a); + console.log(unaryOperations[operator]); + return unaryOperations[operator](a); + } +}; + +export default mathOperation; diff --git a/src/lesson_2_my/parser.test.ts b/src/lesson_2_my/parser.test.ts deleted file mode 100644 index 635d59d5..00000000 --- a/src/lesson_2_my/parser.test.ts +++ /dev/null @@ -1,15 +0,0 @@ -import calculate from "./parser"; - -describe("Parser correct cases", () => { - it("1 + 32", () => { - expect(calculate("1 + 32")).toEqual(33); - }); - - it("11 + 3 * 22", () => { - expect(calculate("11 + 3 * 22")).toEqual(77); - }); - - it("1 + 32 - 2 + 2", () => { - expect(calculate("1 + 32 - 2 + 2")).toEqual(29); - }); -}); diff --git a/src/lesson_2_my/parser.ts b/src/lesson_2_my/parser.ts index fcca17aa..e7957d38 100644 --- a/src/lesson_2_my/parser.ts +++ b/src/lesson_2_my/parser.ts @@ -1,18 +1,29 @@ // eslint-disable-next-line @typescript-eslint/no-var-requires const _ = require("lodash"); -import mathOperations from "./mathOperators"; +import mathOperation from "./mathOperators"; -const GROUP_PATTERN = /\([-+/*0-9.]{3,}\)/g; -const DIGIT_PATTERN = /[0-9.]+/g; -const OPERATOR_PATTERN = /[-+/*]/g; -const LOW_PRIORITY_PATTERN = /[0-9.]+[+-][0-9.]+/g; -const HIGH_PRIORITY_PATTERN = /[0-9.]+[*/][0-9.]+/g; +const GROUP_PATTERN = /\([-+/*0-9\.]{3,}\)/g; +const DIGIT_PATTERN = /[0-9\.]+/g; +// const OPERATOR_PATTERN = /[-+/*]/g; +const ZERO_PRIORITY_PATTERN = /[0-9\.]+[\^][0-9\.]+/g; +const LOW_PRIORITY_PATTERN = /[0-9\.]+[+-][0-9\.]+/g; +const HIGH_PRIORITY_PATTERN = /[0-9\.]+[*/][0-9\.]+/g; +const UNARY_OPERATION = /[\d\.]+(\*{2,}|!)/g; + +const priorityOperation: { + [key: number]: RegExp; +} = { + 0: UNARY_OPERATION, + 1: ZERO_PRIORITY_PATTERN, + 2: HIGH_PRIORITY_PATTERN, + 3: LOW_PRIORITY_PATTERN, +}; const customEval = (subStr: string): number => { const res: RegExpMatchArray | null = subStr.match(DIGIT_PATTERN); if (res) { const operator: string = _.trimEnd(_.trimStart(subStr, res[0]), res[1]); - return mathOperations[operator](Number(res[0]), Number(res[1])); + return mathOperation(operator, Number(res[0]), Number(res[1])); } throw new TypeError("Invalid expression"); }; @@ -43,7 +54,11 @@ const calculateLowPriority = (subExp: string): string => { }; const calculateSubExp = (subExp: string): string => { - return calculateLowPriority(calculateHighPriority(subExp)); + //return calculateLowPriority(calculateHighPriority(subExp)); + for (const priority in priorityOperation) { + subExp = calculateProcess(priorityOperation[priority], subExp); + } + return subExp; }; const calculate = (expression: string): number => { diff --git a/src/lesson_2_my/runner.test.ts b/src/lesson_2_my/runner.test.ts index 1318dce3..f9bd56f1 100644 --- a/src/lesson_2_my/runner.test.ts +++ b/src/lesson_2_my/runner.test.ts @@ -5,13 +5,29 @@ describe("Runner simple cases", () => { expect(runner("1 * 32")).toEqual(32); }); - it("2 * 32", () => { - expect(runner("2 * 32")).toEqual(64); + it("20 / 2", () => { + expect(runner("20 / 2")).toEqual(10); + }); + + it("3 ^ 3", () => { + expect(runner("3 ^ 3")).toEqual(27); }); it("2 + 32", () => { expect(runner("2 + 32")).toEqual(34); }); + + it("100 - 20", () => { + expect(runner("100 - 20")).toEqual(80); + }); + + it("5!", () => { + expect(runner("5!")).toEqual(120); + }); + + it("2**", () => { + expect(runner("2**")).toEqual(4); + }); }); describe("Runner tripled/mixed cases", () => { @@ -26,6 +42,14 @@ describe("Runner tripled/mixed cases", () => { it("2 + 2 * 3", () => { expect(runner("2 + 2 * 3")).toEqual(8); }); + + it("2 ^ 3 + 2**", () => { + expect(runner("2 ^ 3 + 2**")).toEqual(12); + }); + + it("5! + 4**", () => { + expect(runner("5! + 4**")).toEqual(136); + }); }); describe("Runner long cases", () => { @@ -36,4 +60,22 @@ describe("Runner long cases", () => { it("20 - 10 * 10 / 5 - 3", () => { expect(runner("20 - 10 * 10 / 5 - 3")).toEqual(-3); }); + + it("(((2+2) + 3*3 + 2) + 10 / 2) ^ 3 + 3** - 5!", () => { + expect(runner("(((2+2) + 3*3 + 2) + 10 / 2) ^ 3 + 3** - 5!")).toEqual(7889); + }); +}); + +describe("Runner group cases", () => { + it("(2 + 2) * 3", () => { + expect(runner("(2 + 2) * 3")).toEqual(12); + }); + + it("(10 + 10) / (4 - 2)", () => { + expect(runner("(10 + 10) / (4 - 2)")).toEqual(10); + }); + + it("((2 + 2) / 4 ) ^ 2", () => { + expect(runner("((2 + 2) / 4 ) ^ 2")).toEqual(1); + }); }); From ec92d82e70c965f5e15fac22df084b38684d7f57 Mon Sep 17 00:00:00 2001 From: Nikoilay Petrov Date: Sun, 18 Dec 2022 19:23:17 +0300 Subject: [PATCH 3/3] mv --- src/lesson2/engine.test.ts | 47 ------------ src/lesson2/engine.ts | 42 ----------- src/lesson2/helpers.ts | 1 - src/lesson2/index.ts | 3 + src/lesson2/mathOperators.test.ts | 26 +++++-- src/lesson2/mathOperators.ts | 67 +++++++++-------- src/lesson2/parser.test.ts | 27 ------- src/lesson2/parser.ts | 101 +++++++++++++++++++++----- src/lesson2/runner.test.ts | 46 +++++++++++- src/lesson2/runner.ts | 18 +---- src/lesson_2_my/index.ts | 32 -------- src/lesson_2_my/mathOperators.test.ts | 39 ---------- src/lesson_2_my/mathOperators.ts | 38 ---------- src/lesson_2_my/parser.ts | 88 ---------------------- src/lesson_2_my/runner.test.ts | 81 --------------------- src/lesson_2_my/runner.ts | 5 -- 16 files changed, 182 insertions(+), 479 deletions(-) delete mode 100644 src/lesson2/engine.test.ts delete mode 100644 src/lesson2/engine.ts delete mode 100644 src/lesson2/helpers.ts delete mode 100644 src/lesson2/parser.test.ts delete mode 100644 src/lesson_2_my/index.ts delete mode 100644 src/lesson_2_my/mathOperators.test.ts delete mode 100644 src/lesson_2_my/mathOperators.ts delete mode 100644 src/lesson_2_my/parser.ts delete mode 100644 src/lesson_2_my/runner.test.ts delete mode 100644 src/lesson_2_my/runner.ts diff --git a/src/lesson2/engine.test.ts b/src/lesson2/engine.test.ts deleted file mode 100644 index eae1cc00..00000000 --- a/src/lesson2/engine.test.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { firstPrioritiesCalc, secondPrioritiesCalc } from "./engine"; - -describe("firstPrioritiesCalc simple cases", () => { - it("[1, * 32]", () => { - expect(firstPrioritiesCalc([1, "*", 32])).toEqual([32]); - }); - - it("[32, /, 32]", () => { - expect(firstPrioritiesCalc([32, "/", 32])).toEqual([1]); - }); - - it("[32, + 32]", () => { - expect(firstPrioritiesCalc([32, "+", 32])).toEqual([32, "+", 32]); - }); -}); - -describe("firstPrioritiesCalc mixed with second priorities cases", () => { - it("[32, /, 32, +, 10, *, 10]", () => { - expect(firstPrioritiesCalc([32, "/", 32, "+", 10, "*", 10])).toEqual([ - 1, - "+", - 100, - ]); - }); -}); - -describe("secondPrioritiesCalc invalid cases", () => { - it("[32, / 32]", () => { - expect(() => secondPrioritiesCalc([32, "/", 32])).toThrow( - TypeError("Unexpected stack!") - ); - }); -}); - -describe("secondPrioritiesCalc simple cases", () => { - it("[32, + 32]", () => { - expect(secondPrioritiesCalc([32, "+", 32])).toEqual(64); - }); - - it("[32, - 32]", () => { - expect(secondPrioritiesCalc([32, "-", 32])).toEqual(0); - }); - - it("[32, - 32, +, 10]", () => { - expect(secondPrioritiesCalc([32, "-", 32, "+", 10])).toEqual(10); - }); -}); diff --git a/src/lesson2/engine.ts b/src/lesson2/engine.ts deleted file mode 100644 index 78aee1d7..00000000 --- a/src/lesson2/engine.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { ParsedLineType } from "./parser"; -import { isNumber } from "./helpers"; -import { - mathOperators, - mathPriorities, - mathOperatorsPriorities, -} from "./mathOperators"; - -const [FIRST, SECOND] = mathPriorities; - -export const firstPrioritiesCalc = (stack: ParsedLineType): ParsedLineType => - stack.reduce((result, nextItem) => { - const prevItem = result[result.length - 2]; - const item = result[result.length - 1]; - - if (!isNumber(String(item)) && mathOperatorsPriorities[item] === FIRST) { - if (!mathOperators[item]) { - throw new TypeError("Unexpected stack!"); - } - result = [ - ...result.slice(0, -2), - mathOperators[item](Number(prevItem), Number(nextItem)), - ]; - } else { - result.push(nextItem); - } - return result; - }, []); - -export const secondPrioritiesCalc = (stack: ParsedLineType): number => - stack.reduce((result, nextItem, key) => { - const item = stack[key - 1]; - - if (mathOperatorsPriorities[item] === FIRST) { - throw new TypeError("Unexpected stack!"); - } - - if (!isNumber(String(item)) && mathOperatorsPriorities[item] === SECOND) { - result = mathOperators[item](Number(result), Number(nextItem)); - } - return result; - }, Number(stack[0])); diff --git a/src/lesson2/helpers.ts b/src/lesson2/helpers.ts deleted file mode 100644 index b5a4b6ae..00000000 --- a/src/lesson2/helpers.ts +++ /dev/null @@ -1 +0,0 @@ -export const isNumber = (item: string): boolean => !isNaN(Number(item)); diff --git a/src/lesson2/index.ts b/src/lesson2/index.ts index 1766cf85..cdd3f888 100644 --- a/src/lesson2/index.ts +++ b/src/lesson2/index.ts @@ -1,5 +1,7 @@ import { createInterface } from "readline"; +// eslint-disable-next-line @typescript-eslint/ban-ts-ignore +// @ts-ignore import { runner } from "./runner"; const rl = createInterface({ @@ -21,6 +23,7 @@ const question = (): Promise => }); async function app(): Promise { + console.log("Starting app..."); while (true) { await question(); } diff --git a/src/lesson2/mathOperators.test.ts b/src/lesson2/mathOperators.test.ts index aad07e8d..d7f6cc75 100644 --- a/src/lesson2/mathOperators.test.ts +++ b/src/lesson2/mathOperators.test.ts @@ -1,27 +1,39 @@ -import { mul, div, add, minus } from "./mathOperators"; +import mathOperation from "./mathOperators"; describe("mathOperators test cases", () => { it("mul 1 * 2 to equal 2", () => { - expect(mul(1, 2)).toBe(2); + expect(mathOperation("*", 1, 2)).toBe(2); }); it("mul 2 * 2 to equal 4", () => { - expect(mul(2, 2)).toBe(4); + expect(mathOperation("*", 2, 2)).toBe(4); }); it("div 2 / 2 to equal 1", () => { - expect(div(2, 2)).toBe(1); + expect(mathOperation("/", 2, 2)).toBe(1); }); it("div 4 / 2 to equal 2", () => { - expect(div(4, 2)).toBe(2); + expect(mathOperation("/", 4, 2)).toBe(2); }); it("add 4 + 2 to equal 6", () => { - expect(add(4, 2)).toBe(6); + expect(mathOperation("+", 4, 2)).toBe(6); }); it("minus 4 - 2 to equal 2", () => { - expect(minus(4, 2)).toBe(2); + expect(mathOperation("-", 4, 2)).toBe(2); + }); + + it("factorial 5! to equal 120", () => { + expect(mathOperation("!", 5)).toBe(120); + }); + + it("exponentiation 2 ^ 5 to equal 32", () => { + expect(mathOperation("^", 2, 5)).toBe(32); + }); + + it("squaring 5** to equal 525", () => { + expect(mathOperation("**", 5)).toBe(25); }); }); diff --git a/src/lesson2/mathOperators.ts b/src/lesson2/mathOperators.ts index af8eb770..01cc6356 100644 --- a/src/lesson2/mathOperators.ts +++ b/src/lesson2/mathOperators.ts @@ -1,39 +1,38 @@ export type ScalarOperationType = (first: number, second: number) => number; - -export const mul: ScalarOperationType = ( - first: number, - second: number -): number => first * second; - -export const div: ScalarOperationType = ( - first: number, - second: number -): number => first / second; - -export const add: ScalarOperationType = ( - first: number, - second: number -): number => first + second; - -export const minus: ScalarOperationType = ( - first: number, - second: number -): number => first - second; - -export const mathOperators: { [key: string]: ScalarOperationType } = { - "*": mul, - "/": div, - "+": add, - "-": minus, +export type UnaryOperationType = (number: number) => number; + +const binaryOperations: { + [key: string]: ScalarOperationType; +} = { + "+": (a: number, b: number): number => a + b, + "-": (a: number, b: number): number => a - b, + "*": (a: number, b: number): number => a * b, + "/": (a: number, b: number): number => a / b, + "^": (a: number, b: number): number => Math.pow(a, b), }; -export const mathPriorities: number[] = [1, 2]; - -const [FIRST, SECOND] = mathPriorities; +const unaryOperations: { + [key: string]: UnaryOperationType; +} = { + "**": (a: number): number => binaryOperations["*"](a, a), + "!": (a: number): number => { + let f = 1; + for (let i = 1; i <= a; i++) { + f *= i; + } + return f; + }, +}; -export const mathOperatorsPriorities: { [key: string]: number } = { - "*": FIRST, - "/": FIRST, - "+": SECOND, - "-": SECOND, +const mathOperation = (operator: string, a: number, b?: number): number => { + if (b) { + return binaryOperations[operator](a, b); + } else { + console.log(operator); + console.log(a); + console.log(unaryOperations[operator]); + return unaryOperations[operator](a); + } }; + +export default mathOperation; diff --git a/src/lesson2/parser.test.ts b/src/lesson2/parser.test.ts deleted file mode 100644 index 5593e312..00000000 --- a/src/lesson2/parser.test.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { parser } from "./parser"; - -describe("Parser correct cases", () => { - it("1 + 32", () => { - expect(parser("1 + 32")).toEqual([1, "+", 32]); - }); - - it("11 + 3 * 22", () => { - expect(parser("11 + 3 * 22")).toEqual([11, "+", 3, "*", 22]); - }); - - it("1 + 32 - 2 + 2", () => { - expect(parser("1 + 32 - 2 + 2")).toEqual([1, "+", 32, "-", 2, "+", 2]); - }); -}); - -describe("Parser invalid cases", () => { - it("1 + + 33 - 2", () => { - expect(() => parser("1 + + 33 - 2")).toThrow( - TypeError("Unexpected string") - ); - }); - - it("1 ! 33 - 2", () => { - expect(() => parser("1 ! 33 - 2")).toThrow(TypeError("Unexpected string")); - }); -}); diff --git a/src/lesson2/parser.ts b/src/lesson2/parser.ts index 118ff662..e7957d38 100644 --- a/src/lesson2/parser.ts +++ b/src/lesson2/parser.ts @@ -1,27 +1,88 @@ -import { isNumber } from "./helpers"; -import { mathOperators } from "./mathOperators"; +// eslint-disable-next-line @typescript-eslint/no-var-requires +const _ = require("lodash"); +import mathOperation from "./mathOperators"; -export type ParsedLineType = (number | string)[]; +const GROUP_PATTERN = /\([-+/*0-9\.]{3,}\)/g; +const DIGIT_PATTERN = /[0-9\.]+/g; +// const OPERATOR_PATTERN = /[-+/*]/g; +const ZERO_PRIORITY_PATTERN = /[0-9\.]+[\^][0-9\.]+/g; +const LOW_PRIORITY_PATTERN = /[0-9\.]+[+-][0-9\.]+/g; +const HIGH_PRIORITY_PATTERN = /[0-9\.]+[*/][0-9\.]+/g; +const UNARY_OPERATION = /[\d\.]+(\*{2,}|!)/g; -export const parser = (line: string): ParsedLineType | null => { - const stack = line.split(" "); +const priorityOperation: { + [key: number]: RegExp; +} = { + 0: UNARY_OPERATION, + 1: ZERO_PRIORITY_PATTERN, + 2: HIGH_PRIORITY_PATTERN, + 3: LOW_PRIORITY_PATTERN, +}; + +const customEval = (subStr: string): number => { + const res: RegExpMatchArray | null = subStr.match(DIGIT_PATTERN); + if (res) { + const operator: string = _.trimEnd(_.trimStart(subStr, res[0]), res[1]); + return mathOperation(operator, Number(res[0]), Number(res[1])); + } + throw new TypeError("Invalid expression"); +}; - return stack.reduce((result, item, key) => { - const prevItem = stack[key - 1]; +const calculateProcess = (pattern: RegExp, subExp: string): string => { + while (true) { + const res = subExp.match(pattern); + if (!res) break; + for (const s of res) { + const mathResult = customEval(s); + const startI = subExp.indexOf(s); + const endI = s.length; + subExp = + subExp.slice(0, startI) + + String(mathResult) + + subExp.slice(startI + endI); + } + } + return subExp; +}; - const isValidNumberPush = !isNumber(prevItem) && isNumber(item); - const isValidOperatorPush = - isNumber(prevItem) && - !isNumber(item) && - mathOperators.hasOwnProperty(item); +const calculateHighPriority = (subExp: string): string => { + return calculateProcess(HIGH_PRIORITY_PATTERN, subExp); +}; - if (isValidNumberPush) { - result.push(Number(item)); - } else if (isValidOperatorPush) { - result.push(item); - } else { - throw new TypeError("Unexpected string"); +const calculateLowPriority = (subExp: string): string => { + return calculateProcess(LOW_PRIORITY_PATTERN, subExp); +}; + +const calculateSubExp = (subExp: string): string => { + //return calculateLowPriority(calculateHighPriority(subExp)); + for (const priority in priorityOperation) { + subExp = calculateProcess(priorityOperation[priority], subExp); + } + return subExp; +}; + +const calculate = (expression: string): number => { + // очистка выражения от пробелов + expression = expression.replace(/\s+/g, ""); + // будем находить группы до тех пор пока они есть + // вычислять значение, и заменять в оригинальном выражении + while (true) { + // ищем все выражения в скобках + const groups = expression.match(GROUP_PATTERN); + // если ничего не нашли прекращаем + if (!groups) break; + for (const group of groups) { + const subExp = _.trim(group, "()"); + const mathResult = calculateSubExp(subExp); + const startI = expression.indexOf(group); + const endI = group.length; + expression = + expression.slice(0, startI) + + String(mathResult) + + expression.slice(startI + endI); } - return result; - }, []); + } + return Number(calculateSubExp(expression)); }; + +export default calculate; diff --git a/src/lesson2/runner.test.ts b/src/lesson2/runner.test.ts index 1318dce3..f9bd56f1 100644 --- a/src/lesson2/runner.test.ts +++ b/src/lesson2/runner.test.ts @@ -5,13 +5,29 @@ describe("Runner simple cases", () => { expect(runner("1 * 32")).toEqual(32); }); - it("2 * 32", () => { - expect(runner("2 * 32")).toEqual(64); + it("20 / 2", () => { + expect(runner("20 / 2")).toEqual(10); + }); + + it("3 ^ 3", () => { + expect(runner("3 ^ 3")).toEqual(27); }); it("2 + 32", () => { expect(runner("2 + 32")).toEqual(34); }); + + it("100 - 20", () => { + expect(runner("100 - 20")).toEqual(80); + }); + + it("5!", () => { + expect(runner("5!")).toEqual(120); + }); + + it("2**", () => { + expect(runner("2**")).toEqual(4); + }); }); describe("Runner tripled/mixed cases", () => { @@ -26,6 +42,14 @@ describe("Runner tripled/mixed cases", () => { it("2 + 2 * 3", () => { expect(runner("2 + 2 * 3")).toEqual(8); }); + + it("2 ^ 3 + 2**", () => { + expect(runner("2 ^ 3 + 2**")).toEqual(12); + }); + + it("5! + 4**", () => { + expect(runner("5! + 4**")).toEqual(136); + }); }); describe("Runner long cases", () => { @@ -36,4 +60,22 @@ describe("Runner long cases", () => { it("20 - 10 * 10 / 5 - 3", () => { expect(runner("20 - 10 * 10 / 5 - 3")).toEqual(-3); }); + + it("(((2+2) + 3*3 + 2) + 10 / 2) ^ 3 + 3** - 5!", () => { + expect(runner("(((2+2) + 3*3 + 2) + 10 / 2) ^ 3 + 3** - 5!")).toEqual(7889); + }); +}); + +describe("Runner group cases", () => { + it("(2 + 2) * 3", () => { + expect(runner("(2 + 2) * 3")).toEqual(12); + }); + + it("(10 + 10) / (4 - 2)", () => { + expect(runner("(10 + 10) / (4 - 2)")).toEqual(10); + }); + + it("((2 + 2) / 4 ) ^ 2", () => { + expect(runner("((2 + 2) / 4 ) ^ 2")).toEqual(1); + }); }); diff --git a/src/lesson2/runner.ts b/src/lesson2/runner.ts index 920249fd..746f2876 100644 --- a/src/lesson2/runner.ts +++ b/src/lesson2/runner.ts @@ -1,19 +1,5 @@ -import { parser } from "./parser"; - -import { firstPrioritiesCalc, secondPrioritiesCalc } from "./engine"; +import calculate from "./parser"; export const runner = (line: string): number => { - const stack = parser(line); - - if (stack === null) { - throw new TypeError("Unexpected string"); - } - - const firstPrioritiesRes = firstPrioritiesCalc(stack); - - if (firstPrioritiesRes.length === 1) { - return Number(firstPrioritiesRes[0]); - } - - return secondPrioritiesCalc(firstPrioritiesRes); + return calculate(line); }; diff --git a/src/lesson_2_my/index.ts b/src/lesson_2_my/index.ts deleted file mode 100644 index cdd3f888..00000000 --- a/src/lesson_2_my/index.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { createInterface } from "readline"; - -// eslint-disable-next-line @typescript-eslint/ban-ts-ignore -// @ts-ignore -import { runner } from "./runner"; - -const rl = createInterface({ - input: process.stdin, - output: process.stdout, -}); - -const question = (): Promise => - new Promise((resolve) => { - rl.question("> ", (answer: string) => { - const result = runner(answer); - - if (result) { - console.log(`Result: ${result}`); - } - - resolve(); - }); - }); - -async function app(): Promise { - console.log("Starting app..."); - while (true) { - await question(); - } -} - -app(); diff --git a/src/lesson_2_my/mathOperators.test.ts b/src/lesson_2_my/mathOperators.test.ts deleted file mode 100644 index d7f6cc75..00000000 --- a/src/lesson_2_my/mathOperators.test.ts +++ /dev/null @@ -1,39 +0,0 @@ -import mathOperation from "./mathOperators"; - -describe("mathOperators test cases", () => { - it("mul 1 * 2 to equal 2", () => { - expect(mathOperation("*", 1, 2)).toBe(2); - }); - - it("mul 2 * 2 to equal 4", () => { - expect(mathOperation("*", 2, 2)).toBe(4); - }); - - it("div 2 / 2 to equal 1", () => { - expect(mathOperation("/", 2, 2)).toBe(1); - }); - - it("div 4 / 2 to equal 2", () => { - expect(mathOperation("/", 4, 2)).toBe(2); - }); - - it("add 4 + 2 to equal 6", () => { - expect(mathOperation("+", 4, 2)).toBe(6); - }); - - it("minus 4 - 2 to equal 2", () => { - expect(mathOperation("-", 4, 2)).toBe(2); - }); - - it("factorial 5! to equal 120", () => { - expect(mathOperation("!", 5)).toBe(120); - }); - - it("exponentiation 2 ^ 5 to equal 32", () => { - expect(mathOperation("^", 2, 5)).toBe(32); - }); - - it("squaring 5** to equal 525", () => { - expect(mathOperation("**", 5)).toBe(25); - }); -}); diff --git a/src/lesson_2_my/mathOperators.ts b/src/lesson_2_my/mathOperators.ts deleted file mode 100644 index 01cc6356..00000000 --- a/src/lesson_2_my/mathOperators.ts +++ /dev/null @@ -1,38 +0,0 @@ -export type ScalarOperationType = (first: number, second: number) => number; -export type UnaryOperationType = (number: number) => number; - -const binaryOperations: { - [key: string]: ScalarOperationType; -} = { - "+": (a: number, b: number): number => a + b, - "-": (a: number, b: number): number => a - b, - "*": (a: number, b: number): number => a * b, - "/": (a: number, b: number): number => a / b, - "^": (a: number, b: number): number => Math.pow(a, b), -}; - -const unaryOperations: { - [key: string]: UnaryOperationType; -} = { - "**": (a: number): number => binaryOperations["*"](a, a), - "!": (a: number): number => { - let f = 1; - for (let i = 1; i <= a; i++) { - f *= i; - } - return f; - }, -}; - -const mathOperation = (operator: string, a: number, b?: number): number => { - if (b) { - return binaryOperations[operator](a, b); - } else { - console.log(operator); - console.log(a); - console.log(unaryOperations[operator]); - return unaryOperations[operator](a); - } -}; - -export default mathOperation; diff --git a/src/lesson_2_my/parser.ts b/src/lesson_2_my/parser.ts deleted file mode 100644 index e7957d38..00000000 --- a/src/lesson_2_my/parser.ts +++ /dev/null @@ -1,88 +0,0 @@ -// eslint-disable-next-line @typescript-eslint/no-var-requires -const _ = require("lodash"); -import mathOperation from "./mathOperators"; - -const GROUP_PATTERN = /\([-+/*0-9\.]{3,}\)/g; -const DIGIT_PATTERN = /[0-9\.]+/g; -// const OPERATOR_PATTERN = /[-+/*]/g; -const ZERO_PRIORITY_PATTERN = /[0-9\.]+[\^][0-9\.]+/g; -const LOW_PRIORITY_PATTERN = /[0-9\.]+[+-][0-9\.]+/g; -const HIGH_PRIORITY_PATTERN = /[0-9\.]+[*/][0-9\.]+/g; -const UNARY_OPERATION = /[\d\.]+(\*{2,}|!)/g; - -const priorityOperation: { - [key: number]: RegExp; -} = { - 0: UNARY_OPERATION, - 1: ZERO_PRIORITY_PATTERN, - 2: HIGH_PRIORITY_PATTERN, - 3: LOW_PRIORITY_PATTERN, -}; - -const customEval = (subStr: string): number => { - const res: RegExpMatchArray | null = subStr.match(DIGIT_PATTERN); - if (res) { - const operator: string = _.trimEnd(_.trimStart(subStr, res[0]), res[1]); - return mathOperation(operator, Number(res[0]), Number(res[1])); - } - throw new TypeError("Invalid expression"); -}; - -const calculateProcess = (pattern: RegExp, subExp: string): string => { - while (true) { - const res = subExp.match(pattern); - if (!res) break; - for (const s of res) { - const mathResult = customEval(s); - const startI = subExp.indexOf(s); - const endI = s.length; - subExp = - subExp.slice(0, startI) + - String(mathResult) + - subExp.slice(startI + endI); - } - } - return subExp; -}; - -const calculateHighPriority = (subExp: string): string => { - return calculateProcess(HIGH_PRIORITY_PATTERN, subExp); -}; - -const calculateLowPriority = (subExp: string): string => { - return calculateProcess(LOW_PRIORITY_PATTERN, subExp); -}; - -const calculateSubExp = (subExp: string): string => { - //return calculateLowPriority(calculateHighPriority(subExp)); - for (const priority in priorityOperation) { - subExp = calculateProcess(priorityOperation[priority], subExp); - } - return subExp; -}; - -const calculate = (expression: string): number => { - // очистка выражения от пробелов - expression = expression.replace(/\s+/g, ""); - // будем находить группы до тех пор пока они есть - // вычислять значение, и заменять в оригинальном выражении - while (true) { - // ищем все выражения в скобках - const groups = expression.match(GROUP_PATTERN); - // если ничего не нашли прекращаем - if (!groups) break; - for (const group of groups) { - const subExp = _.trim(group, "()"); - const mathResult = calculateSubExp(subExp); - const startI = expression.indexOf(group); - const endI = group.length; - expression = - expression.slice(0, startI) + - String(mathResult) + - expression.slice(startI + endI); - } - } - return Number(calculateSubExp(expression)); -}; - -export default calculate; diff --git a/src/lesson_2_my/runner.test.ts b/src/lesson_2_my/runner.test.ts deleted file mode 100644 index f9bd56f1..00000000 --- a/src/lesson_2_my/runner.test.ts +++ /dev/null @@ -1,81 +0,0 @@ -import { runner } from "./runner"; - -describe("Runner simple cases", () => { - it("1 * 32", () => { - expect(runner("1 * 32")).toEqual(32); - }); - - it("20 / 2", () => { - expect(runner("20 / 2")).toEqual(10); - }); - - it("3 ^ 3", () => { - expect(runner("3 ^ 3")).toEqual(27); - }); - - it("2 + 32", () => { - expect(runner("2 + 32")).toEqual(34); - }); - - it("100 - 20", () => { - expect(runner("100 - 20")).toEqual(80); - }); - - it("5!", () => { - expect(runner("5!")).toEqual(120); - }); - - it("2**", () => { - expect(runner("2**")).toEqual(4); - }); -}); - -describe("Runner tripled/mixed cases", () => { - it("2 * 2 * 3", () => { - expect(runner("2 * 2 * 3")).toEqual(12); - }); - - it("2 * 2 + 3", () => { - expect(runner("2 * 2 + 3")).toEqual(7); - }); - - it("2 + 2 * 3", () => { - expect(runner("2 + 2 * 3")).toEqual(8); - }); - - it("2 ^ 3 + 2**", () => { - expect(runner("2 ^ 3 + 2**")).toEqual(12); - }); - - it("5! + 4**", () => { - expect(runner("5! + 4**")).toEqual(136); - }); -}); - -describe("Runner long cases", () => { - it("20 + 1 * 10 - 5 * 3", () => { - expect(runner("20 + 1 * 10 - 5 * 3")).toEqual(15); - }); - - it("20 - 10 * 10 / 5 - 3", () => { - expect(runner("20 - 10 * 10 / 5 - 3")).toEqual(-3); - }); - - it("(((2+2) + 3*3 + 2) + 10 / 2) ^ 3 + 3** - 5!", () => { - expect(runner("(((2+2) + 3*3 + 2) + 10 / 2) ^ 3 + 3** - 5!")).toEqual(7889); - }); -}); - -describe("Runner group cases", () => { - it("(2 + 2) * 3", () => { - expect(runner("(2 + 2) * 3")).toEqual(12); - }); - - it("(10 + 10) / (4 - 2)", () => { - expect(runner("(10 + 10) / (4 - 2)")).toEqual(10); - }); - - it("((2 + 2) / 4 ) ^ 2", () => { - expect(runner("((2 + 2) / 4 ) ^ 2")).toEqual(1); - }); -}); diff --git a/src/lesson_2_my/runner.ts b/src/lesson_2_my/runner.ts deleted file mode 100644 index 746f2876..00000000 --- a/src/lesson_2_my/runner.ts +++ /dev/null @@ -1,5 +0,0 @@ -import calculate from "./parser"; - -export const runner = (line: string): number => { - return calculate(line); -};