recursion typescript codingame

CodinGame: Czas pochodnej - Część 1, Rekursja (Typescript)

Rozwiązanie ćwiczenia CodinGame. Prosty przykład rekurencji z typescriptem. Reprezentacja formuły inspirowana lispem.

Daniel Gustaw

Daniel Gustaw

17 min read

CodinGame: Czas pochodnej - Część 1, Rekursja (Typescript)

Teraz rozwiążemy zadanie CZAS PIERWŚCIEŃ:

Coding Games and Programming Challenges to Code Better

Naszym celem jest obliczenie pochodnej częściowej podanego wzoru.

Na przykład, mając funkcję “(5*(x*(y^2)))” oraz “y x”, zmienne, względem których musisz obliczyć pochodną

Więc tutaj f(x,y) = 5xy² i musisz obliczyć:

   d²f(x,y)
  ----------
    dxdy

daje ci wzór 10*y. Na końcu “x 2 y 6” oznacza x=2, y=6, daje ci wartości, dla których musisz ocenić uzyskaną pochodną. Tak więc odpowiedź powinna wynosić 60

Uwaga Aby uprościć zadanie, rozważaj tylko +, * i ^. Zakładaj, że +, * i ^ zawsze przyjmują dwa argumenty i że wyrażenia są w pełni zagnieżdżone.

Potęga ujemna nie ma nawiasów. np. (((18*(x^-1))+y)+z)

zmienne mogą być w innych formach niż x, y i z. Podobnie jak identyfikatory w wielu językach programowania, zmienna byłaby jakąś literą, po której następują litery, cyfry lub podkreślenie.

link do zasad rachunku:

Zasady różniczkowania - Wikipedia](https://en.wikipedia.org/wiki/Differentiation_rules)

Reguły potrzebne tutaj:

a'=0
(a*x)'=a
(x^a)'=a*x^(a-1) (when a is not 0)
(u+v)'=u'+v'
(u*v)'=u'*v+v'*u

Teoretyczne wprowadzenie do pochodnych

Aby obliczyć pochodną funkcji, musimy przedstawić funkcję jako obiekt możliwy do manipulacji. Jedną z możliwości jest przedstawienie jej jako tablicy, w której operacja znajduje się na początku, a argumenty zajmują pozostałe miejsca.

Koncepcja ta jest intensywnie używana w niektórych językach, takich jak Lisp czy Scheme. Jeśli nie znasz Lispa, możesz go łatwo nauczyć się w y minut na stronie:

Naucz się Common Lisp w Y minut

Zamiast pisać

a + b

w lispie napiszesz

(+ a b)

Nasze dalsze rozwiązanie będzie w dużej mierze oparte na tym koncepcie porządkowania. Jakie ma zalety w porównaniu do a + b? Przede wszystkim możemy dekomponować nasze wyrażenia przez

const [operation, ...arguments] = expression;

Po drugie, dla operacji stowarzyszonych możemy użyć funkcji reduce w następujący sposób

const res = arguments.reduce(fn, identity);

W tym przypadku identity to wartość, która nie zmieni wyniku operacji, a fn to funkcja przypisana do operation, np. (a,b) => a+b dla dodawania.

Mając na uwadze te podstawowe pojęcia, możemy uznać pochodną za funkcję, która przekształca tablicę reprezentującą formułę matematyczną w inną tablicę z inną formułą.

Wiemy, jakie przekształcenia należy zastosować do dodawania, mnożenia lub potęg. Możemy również uprościć wynik, znając pochodne funkcji stałej lub funkcji tożsamościowej.

Podzielmy więc nasze rozwiązanie na cztery kroki.

  1. Ustawienie projektu
  2. Parsowanie wejścia
  3. Ocena wartości
  4. Obliczanie pochodnej

Ustawienie projektu NodeJS z TypeScriptem i JEST

Zainicjujmy projekt

npm init -y
tsc --init

w wygenerowanym tsconfigl.json musimy ustawić

"target": "ESNext",

aby móc używać Map lub innych nowoczesnych struktur danych lub składni.

W package.json powinniśmy ustawić

"test": "jest"

w sekcji scripts i zainstalować wymagane pakiety.

npm i -D @types/jest @types/node esbuild-jest jest ts-node typescript

Teraz utwórzmy konfigurację jest jest.config.ts

module.exports = {
    roots: ['<rootDir>'],
    testMatch: ['**/__tests__/**/*.+(ts|tsx)', '**/?(*.)+(spec|test).+(ts|tsx)'],
    transform: {
        '^.+\\.(ts|tsx)$': 'esbuild-jest',
    },
    setupFilesAfterEnv: [],
    testEnvironment: 'node',
}

Ostatecznie możemy utworzyć trzy pliki: lib.ts z:

export function run(input: string): string {
    return ''
}

Przetestuj test/lib.test.ts za pomocą

import {run} from "../lib";

describe('e2e', () => {
    it('temporary', () => {
        expect(run('')).toEqual('')
    })
});

i index.ts, który jest odpowiedzialny za operacje wejścia/wyjścia

import {run} from "./lib";

process.stdin.on('data', (buff) => {
    const input = buff.toString();
    process.stdout.write(run(input));
})

Teraz powinniśmy być w stanie uruchomić program za pomocą

echo '' | ts-node index.ts

i testuj za pomocą polecenia

npm run test

Parsowanie wejścia i budowanie obiektów

Zobaczmy na wejście

Z treści tego ćwiczenia możemy odczytać, że wejście zawiera 3 linie:

Linia 1: wzór
Linia 2: lista zmiennych do pochodnej cząstkowej, oddzielona spacją, długość listy będzie wynosić 1, 2 lub 3.
Linia 3: wartości zmiennych, parowane i oddzielone spacją

Wynik to rezultat (zawsze liczba całkowita).

Więc na przykład wejście

(5*(x*y))
x
x 2 y 6

będziemy mieć pochodną według x, dającą (5*y), która w punkcie {x:2, y:6} wynosi 5*6 lub po prostu

30

Teraz wprowadzimy trzy obiekty, które będą odnosić się do tych linii:

Ogólnie rzecz biorąc, każdy z tych obiektów powinien być tworzony na podstawie swojej linii. Jedynym wyjątkiem jest Formuła, która wymaga zmiennych z współrzędnej, aby rozpoznać argumenty i traktować je w specjalny sposób.

Teraz funkcję run z lib.ts można przepisać jako

export function run(input: string): string {
    const [F, vs, dict] = input.split('\n');
    const point = new Coordinate(dict);
    const diff = new DiffOperator(vs);
    const formula = new Formula(F, point.variables);

    return diff.actOn(formula).evaluate(point).toString();
}

Co można wyjaśnić jako działanie operatora pochodnej na funkcji i jego ocenę w danym punkcie. To dokładnie to, co musimy zrobić w tym zadaniu.

Teraz zaimplementujemy te klasy i napiszemy kilka testów dla nich.

Implementacja współrzędnej

Zacznijmy od współrzędnej.

export class Coordinate {
    map: Map<string, number> = new Map();

    constructor(input: string) {
        const pairs = input.split(' ');
        for (let i = 0; i < pairs.length / 2; i++) {
            const [key, value] = [pairs[i * 2], Number.parseFloat(pairs[i * 2 + 1])];
            this.map.set(key, value);
        }
    }

    get variables(): string[] {
        return [...this.map.keys()]
    }

    get(name): number {
        return this.map.get(name) ?? 0;
    }
}

Po prostu dzieli jego ciąg x 2 y 6 według spacji i przesuwa parami, ustawiając je jako klucze i wartości wewnętrznej Map. Może zwrócić zarówno listę kluczy, aby pomóc w inicjalizacji Formula, jak i uzyskać pojedynczą wartość klucza, która jest użyteczna w ocenie.

testy dla współrzędnych są następujące

describe('coordinate', () => {
    it('parsing', () => {
        const point = new Coordinate('x 2 y 6');
        expect(point.variables).toEqual(['x', 'y']);
        expect(point.get('x')).toEqual(2);
        expect(point.get('y')).toEqual(6);
        expect(point.get('undefined')).toEqual(0);
    })
})

Inicjalizacja formuły

Następnym obiektem jest formuła. Potrzebujemy dwóch dodatkowych typów.

type Operator = '*' | '^' | '+' | '-';
type SubFormula = Array<string | number | SubFormula> | string | number

i możemy napisać naszą klasę formuły

export class Formula {
    f: SubFormula;
    v: string[] = [];
}

następujące funkcje będą umieszczone w tej klasie. Potrzebujemy konstruktora, który przyjmie ciąg formuły oraz listę zmiennych. Aby operować na ciągłej formie formuły, zdefiniujemy dwie funkcje:

Pierwsza będzie odpowiedzialna za analizę, druga sprawdzi, czy powinniśmy rozszerzyć wejście na elementy, czy też zostawić je jako atomową część formuły.

W naszym przypadku musimy dokonać dekompozycji, jeśli ciąg zaczyna się i kończy nawiasami oraz zawiera operator.

    isDecomposable(input: string): boolean {
        return input.startsWith('(') && input.endsWith(')') && /[*^+-]/.test(input)
    }

Dezagragacja zawiera część, która przygotowuje trzy elementy p jak poprzedni, n jak następny i operator.

decomposeInput(input: string): SubFormula {
        let p: SubFormula = '', operator: Operator | '' = '', n: SubFormula = '', level: number = 0;
        if (!this.isDecomposable(input)) return [0];
        for (let i = 1; i < input.length - 1; i++) {
            if (level === 0 && !operator && /[*^+-]/.test(input[i])) {
                operator = input[i] as Operator;
                continue;
            } else if (input[i] === '(') {
                level++;
            } else if (input[i] === ')') {
                level--;
            }
            if (operator) {
                n += input[i];
            } else {
                p += input[i];
            }
        }

używamy level, aby określić, na którym poziomie podzakresu się znajdujemy podczas pętli.

Po przejściu przez wszystkie znaki i zakończeniu p, n oraz operator musimy sprawdzić, czy powinniśmy rozłożyć lub analizować te elementy za pomocą kodu.

        if (this.isDecomposable(p)) {
            p = this.decomposeInput(p);
        } else {
            if (!this.v.includes(p)) {
                p = Number.parseFloat(p);
            }
        }
        if (this.isDecomposable(n)) {
            n = this.decomposeInput(n);
        } else {
            if (!this.v.includes(n)) {
                n = Number.parseFloat(n);
            }
        }
        return [operator, p, n];
    }

po zdefiniowaniu tych funkcji możemy napisać konstruktor

    constructor(input: string, variables: string[]) {
        this.v = variables;
        this.f = this.decomposeInput(input);
    }

i testować je

describe('formula', () => {
    const f = new Formula('(5*(x*y))', ['x', 'y']);

    it('parse', () => {
        expect(f.f).toEqual(['*', 5, ['*', 'x', 'y']]);
        expect(f.v).toEqual(['x', 'y']);
    });

    it('parse with many brackets', () => {
        const f = new Formula('((x^3)+(x^2))', ['x']);
        expect(f.f).toEqual(['+', ['^', 'x', 3], ['^', 'x', 2]])
    })
})

Inicjalizacja DiffOperator (pochodna)

Na szczęście pochodna jest niezwykle łatwa do skonstruowania.

export class DiffOperator {
    d: string[] = [];

    constructor(input: string) {
        this.d = input.split(' ');
    }
}

wewnątrz chcemy przechować tablicę nazw zmiennych, które będziemy używać sekwencyjnie, działając na danej funkcji.

Możemy pominąć testy dla tego i przejść do następnej części - Ocena wartości

Ocena wartości

Teraz nadal będziemy pisać kod w klasie Formula.

Nasza funkcja obliczeniowa będzie mogła wywoływać samą siebie wiele razy, więc aby uprościć ich interfejs, podzielimy obliczenie na:

static compute(f: SubFormula | string | number, point: Coordinate): number

i

evaluate(point: Coordinate): number

pierwszy będzie używany do bezpośredniego działania na SubFormula, ale drugi będzie warstwą, która została zastosowana w run w wyrażeniu

diff.actOn(formula).evaluate(point)

ocenić można zdefiniować jako

    evaluate(point: Coordinate): number {
        return Formula.compute(this.f, point);
    }

Ale compute powinno zaczynać się od klasyfikacji swojego argumentu. Może to być wyrażenie, wtedy Array.isArray(f) będzie prawdziwe, liczba, wtedy ta liczba powinna być zwrócona jako nazwa współrzędnej, następnie współrzędna zapisana w point może być zwrócona.

Nasz kod będzie następujący

    static compute(f: SubFormula | string | number, point: Coordinate): number {
        if (Array.isArray(f)) {
            // ... TO BE DEFINED IN NEXT STEP
        } else if (typeof f === 'number') {
            return f;
        } else {
            return point.get(f);
        }
    }

Najciekawszy jest przypadek, gdy f jest tablicą. Wtedy, korzystając z teorii z wprowadzenia, powinniśmy rozłożyć to na operator i argumenty;

const [op, ...rest] = f;

a w następnym kroku, w zależności od argumentów operator, można przetwarzać je za pomocą różnych funkcji:

switch (op) {
    case '+':
        return <number>rest.reduce((p: number, n: SubFormula): number => p + this.compute(n, point), 0);
    case '*':
        return <number>rest.reduce((p: number, n: SubFormula): number => p * this.compute(n, point), 1);
    case '^': {
        const [p, n] = rest;
        return Math.pow(this.compute(p, point), this.compute(n, point));
    }
    case '-': {
        const [p, n] = rest;
        return this.compute(p,point) - this.compute(n, point);
    }
    default:
        return 0;
}

Dla operacji asocjacyjnych, takich jak * i + z istniejącą wartością neutralną, użyliśmy reduce. Jednak w pozostałych przypadkach zakładamy, że istnieją dwa argumenty i używamy destrukturyzacji.

Aby udowodnić, że to działa, możemy dodać następujący test do sekcji formula

    it('evaluate', () => {
        expect(f.evaluate(new Coordinate('x 1 y 2'))).toStrictEqual(10);
    });

nasza formuła zdefiniowana wcześniej brzmiała (5*(x*y)), więc oczekujemy wyniku 10.

Jeśli śledzisz wszystkie te koncepcje do tego momentu, to następny krok będzie dla Ciebie łatwą logiczną kontynuacją.

Obliczanie pochodnej

Obliczanie pochodnej będzie wykonane w tym samym stylu co ocena wartości funkcji. Podzielimy to na dwie metody Dynamic actOn z interfejsem.

actOn(f: Formula): Formula

i statyczna funkcja wewnętrzna

static derivative(s: SubFormula, dir: string): SubFormula

Pierwsza z nich będzie iterować po wszystkich kierunkach zapisanych w właściwości d. Druga to pierwsza pochodna w kierunku przekazanym jako drugi argument. Te funkcje działają również na innych poziomach: actOn będzie używać abstrakcji Formula, ale derivative potrzebuje i zwraca SubFormula, aby operować na bardziej niskopoziomowej strukturze.

Wprowadzimy również jedną więcej funkcję statyczną.

static simplify(s: SubFormula): SubFormula

który nie będzie potrzebował wskazania i użyje prostych zasad algebry, aby uczynić częściowe wyniki obliczeń bardziej zrozumiałymi dla ludzi i łatwiejszymi do przetworzenia. Funkcja Simplify zwróci tę samą formułę matematyczną, ale zapisaną w najprostszy możliwy sposób, usuwając niepotrzebne dodawania 0 lub mnożenia przez 1.

Poniższe funkcje są zdefiniowane jako metody DiffOperator.

Uproszczenie formuły

Zacznijmy od simplify, ponieważ zacząłem od niej wyjaśniać. Aby uprościć formuły, zastosujemy następujące zasady:

Kod, który implementuje opisane zachowanie, to

    static simplify(s: SubFormula): SubFormula {
        if (Array.isArray(s)) {
            const [op, ...rest] = s;
            for (let i = 0; i < rest.length; i++) {
                rest[i] = this.simplify(rest[i]);
            }
            if (rest.every((r) => typeof r === 'number')) {
                return Formula.compute(s, new Coordinate(''));
            }
            if (rest[0] === 0) {
                if (op === '+') return rest[1]
                if (op === '*') return 0
                if (op === '^') return 0
            }
            if (rest[0] === 1) {
                if (op === '*') return rest[1]
                if (op === '^') return 1
            }
            if (rest[1] === 0) {
                if (op === '+') return rest[0]
                if (op === '*') return 0
                if (op === '^') return 1
            }
            if (rest[1] === 1) {
                if (op === '*') return rest[0]
                if (op === '^') return rest[0]
            }
            return [op, ...rest];
        } else {
            return s;
        }
    }

Pochodna wzoru

Aby zdefiniować pochodną pierwszego rzędu w danym kierunku:

static derivative(s: SubFormula, dir: string): SubFormula

musimy założyć, że możemy to obliczyć na

Jeśli jest to wykonywane na zmiennej lub stałej, wynik będzie 1 lub 0, ale w przypadku wyrażenia musimy sprawdzić operator i zdecydować, którą regułę powinniśmy zastosować. Ta część jest dokładnie tym samym konceptem co przy ewaluacji, ale zwracamy tablice z operatorami, które reprezentują funkcje zamiast liczb. Kod poniżej:

    static derivative(s: SubFormula, dir: string): SubFormula {
        if (Array.isArray(s)) {
            const [op, p, n] = s;
            switch (op) {
                case '*':
                    return ['+', ['*', p, this.derivative(n, dir)], ['*', this.derivative(p, dir), n]];
                case '+':
                    return ['+', this.derivative(p, dir), this.derivative(n, dir)];
                case '-':
                    return ['-', this.derivative(p, dir), this.derivative(n, dir)];
                case '^': {
                    if (p === dir) {
                        if (n === 0) return 0;
                        return typeof n === 'number' ? ['*', n, ['^', p, n - 1]] : ['*', n, ['^', p, ['+', n, -1]]]
                    } else {
                        return 0;
                    }
                }
            }

        } else if (typeof s === 'string' && s === dir) {
            return 1
        } else {
            return 0;
        }
    }

możemy zobaczyć, że tylko z potęgami jest trochę zamieszania, ponieważ postanowiłem ocenić odejmowanie 1 w potędze, jeśli jest liczbą. Postanowiłem pominąć obsługę a^x, ponieważ jest to funkcja wykładnicza, która wykracza poza zakres pierwszej części tego ćwiczenia.

Na koniec, aby zaimplementować actOn, będziemy potrzebować klonu formuły, więc na formule powinniśmy zaimplementować

    clone(): Formula {
        const o = new Formula('', []);
        o.f = JSON.parse(JSON.stringify(this.f));
        o.v = JSON.parse(JSON.stringify(this.f));
        return o;
    }

a następnie na DiffOperator

    actOn(f: Formula): Formula {
        const o = f.clone();
        return this.d.reduce((p, n) => {
            o.f = DiffOperator.simplify(DiffOperator.derivative(p.f, n));
            return o;
        }, o);
    }

Aby napisać stabilny, edytowalny i działający kod, powinniśmy dodać testy.

describe('diff operation', () => {
    it('simplify', () => {
        expect(DiffOperator.simplify(["+", ["*", 1, 0], ["*", 0, 1]])).toEqual(0);
        expect(DiffOperator.simplify(['^', 'x', 1])).toEqual('x');
        expect(DiffOperator.simplify(['*', 2, ['^', 'x', 1]])).toEqual(['*', 2, 'x']);
        expect(DiffOperator.simplify(["*", 5, ["+", ["*", "x", 0], ["*", 1, "y"]]])).toEqual(['*', 5, 'y'])
        expect(DiffOperator.simplify(["+", ["*", 5, ["+", ["*", "x", 0], ["*", 1, "y"]]], ["*", 0, ["*", "x", "y"]]])).toEqual(['*', 5, 'y'])
    });
    it('derivative', () => {
        expect(DiffOperator.simplify(DiffOperator.derivative(['*', 2, 'x'], 'x'))).toEqual(2);
        expect(DiffOperator.simplify(DiffOperator.derivative(['*', 5, ['*', 'x', 'y']], 'x'))).toEqual(['*', 5, 'y'])
    })
    it("a'=0", () => {
        const f = new Formula('(1*1)', ['x']);
        const d = new DiffOperator('x');
        expect(d.actOn(f).f).toEqual(0);
    });
    it("(a*x)'=a", () => {
        const f = new Formula('(4*x)', ['x']);
        const d = new DiffOperator('x');
        expect(d.actOn(f).f).toEqual(4);
    });
    it("(x^a)'=a*x^(a-1) (when a is not 0)", () => {
        const cases = [
            {in: '(x^5)', out: ['*', 5, ['^', 'x', 4]]},
            {in: '(x^3)', out: ['*', 3, ['^', 'x', 2]]},
            {in: '(x^2)', out: ['*', 2, 'x']},
        ];
        for (const c of cases) {
            const f = new Formula(c.in, ['x']);
            const d = new DiffOperator('x');
            expect(d.actOn(f).f).toEqual(c.out);
        }
    });
    it("(u+v)'=u'+v for (x+(x^2))'", () => {
        const f = new Formula('(x+(x^2))', ['x']);
        const d = new DiffOperator('x');
        expect(d.actOn(f).f).toEqual(['+', 1, ['*', 2, 'x']]);
    });

    it("(u+v)'=u'+v'", () => {
        const f = new Formula('((x^3)+(x^2))', ['x']);
        const d = new DiffOperator('x');
        console.log("f", f);
        expect(d.actOn(f).f).toEqual(['+', ['*', 3, ['^', 'x', 2]], ['*', 2, 'x']]);
    });
    it("(u*v)'=u'*v+v'*u", () => {
        const f = new Formula('(x*x)', ['x']);
        const d = new DiffOperator('x');
        console.log("f", f);
        expect(d.actOn(f).f).toEqual(['+', 'x', 'x']);
    });

    it('d(8*(y^x))/dy', () => {
        const f = new Formula('(8*(y^x))', ['x', 'y']);
        expect(f.f).toEqual(['*', 8, ['^', 'y', 'x']]);
        expect(DiffOperator.simplify(DiffOperator.derivative(['*', 8, ['^', 'y', 'x']], 'y'))).toEqual(['*', 8, ['*', 'x', ['^', 'y', ['+', 'x', -1]]]]);
    })

    it('(18*(x^-1))',() => {
        const f = new Formula('(18*(x^-1))', ['x']);
        expect(f.f).toEqual(['*', 18, ['^', 'x', -1]]);
        expect(DiffOperator.simplify(DiffOperator.derivative(f.f, 'x'))).toEqual( ['*', 18, ['*', -1, ['^', 'x', -2]]]);
    });

    it('d(((x^2)+(2*(z^5)))+(((18*(x^-1))+y)+z))/dx', () => {
        const f = new Formula('(((x^2)+(2*(z^5)))+(((18*(x^-1))+y)+z))', ['x', 'y', 'z']);
        expect(DiffOperator.simplify(DiffOperator.derivative(f.f, 'x'))).toEqual(['+', ['*', 2, 'x'], ['*', 18, ['*', -1, ['^', 'x', -2]]]]);
    })
})

W końcu wszystkie elementy programu działają, więc możemy również dodać testy e2e dla funkcji run.

describe('e2e', () => {
    it('easy multiply', () => {
        expect(run('(5*(x*y))\nx\nx 2 y 6')).toEqual('30')
    })
    it("second derivative", () => {
        expect(run('(5*((x^4)*(y^2)))\nx x\nx 2 y 6')).toEqual('8640')
    })
    it("second derivative mix", () => {
        expect(run('(5*(x*(y^2)))\ny x\nx 2 y 6')).toEqual('60')
    })
    it("power with number", () => {
        expect(run('((x^2)+(9*(x+y)))\nx\nx 1 y 2')).toEqual('11')
    })
    it("power with variable", () => {
        expect(run('(8*(y^x))\ny y\nx -1 y 2')).toEqual('2')
    })
    it("3 variables", () => {
        expect(run('(((x^2)+(2*(z^5)))+((x+y)+z))\nz\nx 2 y 3 z 4')).toEqual('2561')
    })
    it("fraction", () => {
        expect(run('(((x^2)+(2*(z^5)))+(((18*(x^-1))+y)+z))\nx\nx 3 y 4 z 1')).toEqual('4')
    })
    it("longer multiply", () => {
        expect(run('(((x^2)*(2*(z^5)))*((x+y)+z))\nz\nx 1 y 1 z 1')).toEqual('32')
    })
    it("3rd derivative", () => {
        expect(run('(((y^6)*(z^5))*(((3*(x^4))+y)+z))\ny y z\nx 1 y 1 z 2')).toEqual('16320')
    })
    it("some Greek ;)", () => {
        expect(run('(((Beta^6)*(Gamma^5))*(((3*(Alpha^4))+Beta)+Gamma))\nBeta Beta Gamma\nAlpha 1 Beta 1 Gamma 2')).toEqual('16320')
    })
    it("maybe not xyz ;)", () => {
        expect(run('(((x2^6)*(x3^5))*(((3*(x1^4))+x2)+x3))\nx2 x2 x3\nx1 1 x2 1 x3 2')).toEqual('16320')
    })
    it("some Vars ;)))", () => {
        expect(run('(((Var_2^6)*(Var_3^5))*(((3*(Var_1^4))+Var_2)+Var_3))\nVar_2 Var_2 Var_3\nVar_1 1 Var_2 1 Var_3 2')).toEqual('16320')
    })
    it("bigger constants", () => {
        expect(run('(50*((x^40)*(y^20)))\nx x\nx 1 y 1')).toEqual('78000')
    })
    it("bigger power", () => {
        expect(run('(x^(y^10))\nx x\nx 1 y 2')).toEqual('1047552')
    })
    it("cannot find", () => {
        expect(run('(5*(x*(y^2)))\nz\nx 2 y 6')).toEqual('0')
    })
})

i odpowiedni workflow githuba

name: Node.js CI

on:
  push:
    branches: [ "main" ]

jobs:
  build:

    runs-on: ubuntu-latest

    strategy:
      matrix:
        node-version: [14.x, 16.x, 18.x]

    steps:
    - uses: actions/checkout@v3
    - name: Use Node.js ${{ matrix.node-version }}
      uses: actions/setup-node@v3
      with:
        node-version: ${{ matrix.node-version }}
        cache: 'npm'
    - run: npm ci
    - run: npm run build --if-present
    - run: npm test

Dalsze kroki

W codingame jest także druga część tego ćwiczenia:

Coding Games and Programming Challenges to Code Better

która obejmie funkcje trygonometryczne, logarytm, eksponent i nawet regułę łańcuchową.

Możesz zaprosić mnie do swoich znajomych na tej platformie używając linku:

https://www.codingame.com/servlet/urlinvite?u=5287657

Wszystkie przedstawione kody znajdziesz na moim githubie:

https://github.com/gustawdaniel/codingame-derivative-time-part-1

Other articles

You can find interesting also.