CodinGame: Czas pochodnej - Część 1, Rekursja (Typescript)
Rozwiązanie ćwiczenia CodinGame. Prosty przykład rekurencji z typescriptem. Reprezentacja formuły inspirowana lispem.

Daniel Gustaw
• 17 min read

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.
- Ustawienie projektu
- Parsowanie wejścia
- Ocena wartości
- 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:
- Formuła
- DiffOperator
- Współrzędna
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:
decomposeInput(input: string): SubFormula
isDecomposable(input: string): boolean
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:
- wszystkie podargumenty powinny być uproszczone (rekurencyjnie)
- jeśli wszystkie podargumenty są liczbami, powinny zostać obliczone
- jeśli jeden z argumentów to
0
lub1
, specjalne traktowanie drugiego argumentu powinno być stosowane w zależności odoperatora
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
- wyrażeniu
- naszej zmiennej
- stałej
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.

Jeszcze jedna instrukcja instalacji Arch Linux (i3)
Instalacja Arch Linux za każdym razem uczy mnie czegoś nowego na temat dysków, sieci, systemów operacyjnych. Polecam Ci ją jeśli chcesz mieć system skrojony pod Twoje wymagania.

Daniel Gustaw
• 15 min read

Komponent logowania w Nuxt (Rest Strapi)
Prosty przykład strony logowania w nuxt3, napisany jako baza do kopiowania i wklejania w wielu podobnych projektach.

Daniel Gustaw
• 5 min read

Wyciskamy dane z PDF jak sok z cytryny
W tym wpisie pokarzemy jak pisząc naprawdę znikome ilości kodu można wygodnie wydobyć dane z plików PDF.

Daniel Gustaw
• 7 min read