Maxim wrote a recursive algorithm and ran into a Stack Overflow exception.
Why did Maxim do it?
Because he loves solutions that are short and elegant in his opinion.
He doesn't enjoy it when he writes like this:
function factorial(n) {
let res = 1;
for (let i = 2; i <= n; i++) {
res *= i;
}
return res;
}
He wants to write like this:
const factorial = (n) => (n > 1 ? n * factorial(n - 1) : 1);
But when he runs recursive algorithms like this, it happens that he sees this:
Why didn't the "elegant" algorithm work?
Javascript . factorial
. — . — 10700+ .
?
, .
, . , . .
, . , . . .
, .
, , , .
. , .
«TODO-»
, . , .
:
/**
* factorial(n) returns n! for non-negative integer n
* if n <= 18 it will be the exact value of the n!
* if n <= 170 it will be approximate float value
* if n >= 170 it will return Infinity
*/
function factorial(n) {
if (n <= 1) return 1
// TODO: Implement for non-trivial cases
.
«» , n <= 1
.
«» .
.
function factorial(n) {
if (n <= 1) return 1;
let result = 0;
const todoStack = [];
// TODO: Plan some tasks
while (todoStack.length > 0) {
const task = todoStack.pop();
// TODO: process the task
}
return result;
}
while
. result
.
, .
N - 1
.
N - 1
N
.
todoStack
. , .
function factorial(n) {
if (n <= 1) return 1;
let result = 0;
const todoStack = [];
todoStack.push({ type: "multiplyBy", multiplier: n });
todoStack.push({ type: "calculteFactorial", value: n - 1 });
while (todoStack.length > 0) {
const task = todoStack.pop();
// TODO: process the task
}
return result;
}
, .
function factorial(n) {
if (n <= 1) return 1;
let result = 0;
const todoStack = [];
todoStack.push({ type: "multiplyBy", multiplier: n });
todoStack.push({ type: "calculateFactorial", value: n - 1 });
while (todoStack.length > 0) {
const task = todoStack.pop();
if (task.type === "multiplyBy") {
const { multiplier } = task;
result *= multiplier;
continue;
}
if (task.type === "calculateFactorial") {
const { value } = task;
// «»
if (value <= 1) {
result = 1;
continue;
}
// «», .
todoStack.push({ type: "multiplyBy", multiplier: value });
todoStack.push({ type: "calculateFactorial", value: value - 1 });
continue;
}
}
return result;
}
, for
. , «», , .
. , , . . , .
type Parser<T> = (
input: string
) => Iterator<[parsedValue: T, restString: string]>;
many1
:
function many1<T>(parser: Parser<T>): Parser<T[]> {
// TODO: Write this function
}
.
const helloParser = function* (text) {
if (text.startsWith("hello")) {
yield ["hello", text.slice("hello".length)];
}
};
const many1HelloParser = many1(helloParser);
const parsed = [...many1HelloParser("hellohellohelloabc")];
/*
parsed = [
[['hello', 'hello', 'hello'], 'abc'],
[['hello', 'hello'], 'helloabc'],
[['hello'], 'hellohelloabc'],
]
*/
, .
function many1(parser) {
return function* (text) {
//
yield* recMany1(parser, text, []);
};
}
//
// -
// - ,
// -
function* recMany1(parser, text, parsedValues) {
//
for (const [parsed, restString] of parser(text)) {
const newParsedValues = [...parsedValues, parsed];
yield* recMany1(parser, restString, newParsedValues);
}
// -
if (parsedValues.length > 0) {
yield [parsedValues, text];
}
}
. .
:
function many1(parser) {
return function* (text) {
const tasksStack = [];
tasksStack.push({
type: "iterate",
iterator: parser(text),
parsedValues: [],
});
while (tasksStack.length > 0) {
const task = tasksStack.pop();
if (task.type === "yield") {
// -
yield [task.parsedValues, task.restString];
continue;
}
if (task.type === "iterate") {
//
//
const step = task.iterator.next();
// .
if (step.done) continue;
//
const [parsedValue, restString] = step.value;
// :
// 1. restString
// 2.
// 3.
// 3
tasksStack.push({
type: "yield",
parsedValues: [...task.parsedValues, parsedValue],
restString,
});
// 2
tasksStack.push(task);
// 1
tasksStack.push({
type: "iterate",
iterator: parser(restString),
parsedValues: [...task.parsedValues, parsedValue],
});
}
}
};
}
: , , .
:
, «» «» .
, . ? ? , ? ? , Stack Overflow Error?
. , ?