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/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); };