bst-typed 2.0.4 → 2.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/data-structures/binary-tree/binary-tree.js +14 -10
- package/dist/types/utils/utils.d.ts +5 -6
- package/dist/utils/utils.d.ts +110 -49
- package/dist/utils/utils.js +147 -73
- package/package.json +2 -2
- package/src/data-structures/binary-tree/binary-tree.ts +18 -13
- package/src/types/utils/utils.ts +7 -5
- package/src/utils/utils.ts +157 -86
|
@@ -1092,18 +1092,20 @@ class BinaryTree extends base_1.IterableEntryBase {
|
|
|
1092
1092
|
return callback(startNode);
|
|
1093
1093
|
if (iterationType === 'RECURSIVE') {
|
|
1094
1094
|
const dfs = (cur) => {
|
|
1095
|
-
|
|
1095
|
+
const { left } = cur;
|
|
1096
|
+
if (!this.isRealNode(left))
|
|
1096
1097
|
return cur;
|
|
1097
|
-
return dfs(
|
|
1098
|
+
return dfs(left);
|
|
1098
1099
|
};
|
|
1099
1100
|
return callback(dfs(startNode));
|
|
1100
1101
|
}
|
|
1101
1102
|
else {
|
|
1102
1103
|
// Indirect implementation of iteration using tail recursion optimization
|
|
1103
|
-
const dfs = (0, utils_1.
|
|
1104
|
-
|
|
1104
|
+
const dfs = (0, utils_1.makeTrampoline)((cur) => {
|
|
1105
|
+
const { left } = cur;
|
|
1106
|
+
if (!this.isRealNode(left))
|
|
1105
1107
|
return cur;
|
|
1106
|
-
return
|
|
1108
|
+
return (0, utils_1.makeTrampolineThunk)(() => dfs(left));
|
|
1107
1109
|
});
|
|
1108
1110
|
return callback(dfs(startNode));
|
|
1109
1111
|
}
|
|
@@ -1138,18 +1140,20 @@ class BinaryTree extends base_1.IterableEntryBase {
|
|
|
1138
1140
|
return callback(startNode);
|
|
1139
1141
|
if (iterationType === 'RECURSIVE') {
|
|
1140
1142
|
const dfs = (cur) => {
|
|
1141
|
-
|
|
1143
|
+
const { right } = cur;
|
|
1144
|
+
if (!this.isRealNode(right))
|
|
1142
1145
|
return cur;
|
|
1143
|
-
return dfs(
|
|
1146
|
+
return dfs(right);
|
|
1144
1147
|
};
|
|
1145
1148
|
return callback(dfs(startNode));
|
|
1146
1149
|
}
|
|
1147
1150
|
else {
|
|
1148
1151
|
// Indirect implementation of iteration using tail recursion optimization
|
|
1149
|
-
const dfs = (0, utils_1.
|
|
1150
|
-
|
|
1152
|
+
const dfs = (0, utils_1.makeTrampoline)((cur) => {
|
|
1153
|
+
const { right } = cur;
|
|
1154
|
+
if (!this.isRealNode(right))
|
|
1151
1155
|
return cur;
|
|
1152
|
-
return
|
|
1156
|
+
return (0, utils_1.makeTrampolineThunk)(() => dfs(right));
|
|
1153
1157
|
});
|
|
1154
1158
|
return callback(dfs(startNode));
|
|
1155
1159
|
}
|
|
@@ -1,9 +1,3 @@
|
|
|
1
|
-
export type ToThunkFn<R = any> = () => R;
|
|
2
|
-
export type Thunk<R = any> = ToThunkFn<R> & {
|
|
3
|
-
__THUNK__?: symbol;
|
|
4
|
-
};
|
|
5
|
-
export type TrlFn<A extends any[] = any[], R = any> = (...args: A) => R;
|
|
6
|
-
export type TrlAsyncFn = (...args: any[]) => any;
|
|
7
1
|
export type SpecifyOptional<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>;
|
|
8
2
|
export type Any = string | number | bigint | boolean | symbol | undefined | object;
|
|
9
3
|
export type Arithmetic = number | bigint;
|
|
@@ -20,3 +14,8 @@ export interface StringComparableObject extends BaseComparableObject {
|
|
|
20
14
|
}
|
|
21
15
|
export type ComparableObject = ValueComparableObject | StringComparableObject;
|
|
22
16
|
export type Comparable = ComparablePrimitive | Date | ComparableObject;
|
|
17
|
+
export type TrampolineThunk<T> = {
|
|
18
|
+
readonly isThunk: true;
|
|
19
|
+
readonly fn: () => Trampoline<T>;
|
|
20
|
+
};
|
|
21
|
+
export type Trampoline<T> = T | TrampolineThunk<T>;
|
package/dist/utils/utils.d.ts
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* @copyright Copyright (c) 2022 Pablo Zeng <zrwusa@gmail.com>
|
|
6
6
|
* @license MIT License
|
|
7
7
|
*/
|
|
8
|
-
import type { Comparable,
|
|
8
|
+
import type { Comparable, TrampolineThunk, Trampoline } from '../types';
|
|
9
9
|
/**
|
|
10
10
|
* The function generates a random UUID (Universally Unique Identifier) in TypeScript.
|
|
11
11
|
* @returns A randomly generated UUID (Universally Unique Identifier) in the format
|
|
@@ -23,54 +23,6 @@ export declare const uuidV4: () => string;
|
|
|
23
23
|
* `predicate` function.
|
|
24
24
|
*/
|
|
25
25
|
export declare const arrayRemove: <T>(array: T[], predicate: (item: T, index: number, array: T[]) => boolean) => T[];
|
|
26
|
-
export declare const THUNK_SYMBOL: unique symbol;
|
|
27
|
-
/**
|
|
28
|
-
* The function `isThunk` checks if a given value is a function with a specific symbol property.
|
|
29
|
-
* @param {any} fnOrValue - The `fnOrValue` parameter in the `isThunk` function can be either a
|
|
30
|
-
* function or a value that you want to check if it is a thunk. Thunks are functions that are wrapped
|
|
31
|
-
* around a value or computation for lazy evaluation. The function checks if the `fnOrValue` is
|
|
32
|
-
* @returns The function `isThunk` is checking if the input `fnOrValue` is a function and if it has a
|
|
33
|
-
* property `__THUNK__` equal to `THUNK_SYMBOL`. The return value will be `true` if both conditions are
|
|
34
|
-
* met, otherwise it will be `false`.
|
|
35
|
-
*/
|
|
36
|
-
export declare const isThunk: (fnOrValue: any) => boolean;
|
|
37
|
-
/**
|
|
38
|
-
* The `toThunk` function in TypeScript converts a function into a thunk by wrapping it in a closure.
|
|
39
|
-
* @param {ToThunkFn} fn - `fn` is a function that will be converted into a thunk.
|
|
40
|
-
* @returns A thunk function is being returned. Thunk functions are functions that delay the evaluation
|
|
41
|
-
* of an expression or operation until it is explicitly called or invoked. In this case, the `toThunk`
|
|
42
|
-
* function takes a function `fn` as an argument and returns a thunk function that, when called, will
|
|
43
|
-
* execute the `fn` function provided as an argument.
|
|
44
|
-
*/
|
|
45
|
-
export declare const toThunk: (fn: ToThunkFn) => Thunk;
|
|
46
|
-
/**
|
|
47
|
-
* The `trampoline` function in TypeScript enables tail call optimization by using thunks to avoid
|
|
48
|
-
* stack overflow.
|
|
49
|
-
* @param {TrlFn} fn - The `fn` parameter in the `trampoline` function is a function that takes any
|
|
50
|
-
* number of arguments and returns a value.
|
|
51
|
-
* @returns The `trampoline` function returns an object with two properties:
|
|
52
|
-
* 1. A function that executes the provided function `fn` and continues to execute any thunks returned
|
|
53
|
-
* by `fn` until a non-thunk value is returned.
|
|
54
|
-
* 2. A `cont` property that is a function which creates a thunk for the provided function `fn`.
|
|
55
|
-
*/
|
|
56
|
-
export declare const trampoline: (fn: TrlFn) => ((...args: [...Parameters<TrlFn>]) => any) & {
|
|
57
|
-
cont: (...args: [...Parameters<TrlFn>]) => ReturnType<TrlFn>;
|
|
58
|
-
};
|
|
59
|
-
/**
|
|
60
|
-
* The `trampolineAsync` function in TypeScript allows for asynchronous trampolining of a given
|
|
61
|
-
* function.
|
|
62
|
-
* @param {TrlAsyncFn} fn - The `fn` parameter in the `trampolineAsync` function is expected to be a
|
|
63
|
-
* function that returns a Promise. This function will be called recursively until a non-thunk value is
|
|
64
|
-
* returned.
|
|
65
|
-
* @returns The `trampolineAsync` function returns an object with two properties:
|
|
66
|
-
* 1. An async function that executes the provided `TrlAsyncFn` function and continues to execute any
|
|
67
|
-
* thunks returned by the function until a non-thunk value is returned.
|
|
68
|
-
* 2. A `cont` property that is a function which wraps the provided `TrlAsyncFn` function in a thunk
|
|
69
|
-
* and returns it.
|
|
70
|
-
*/
|
|
71
|
-
export declare const trampolineAsync: (fn: TrlAsyncFn) => ((...args: [...Parameters<TrlAsyncFn>]) => Promise<any>) & {
|
|
72
|
-
cont: (...args: [...Parameters<TrlAsyncFn>]) => ReturnType<TrlAsyncFn>;
|
|
73
|
-
};
|
|
74
26
|
/**
|
|
75
27
|
* The function `getMSB` returns the most significant bit of a given number.
|
|
76
28
|
* @param {number} value - The `value` parameter is a number for which we want to find the position of
|
|
@@ -146,3 +98,112 @@ export declare const roundFixed: (num: number, digit?: number) => number;
|
|
|
146
98
|
* considered comparable or not.
|
|
147
99
|
*/
|
|
148
100
|
export declare function isComparable(value: unknown, isForceObjectComparable?: boolean): value is Comparable;
|
|
101
|
+
/**
|
|
102
|
+
* Creates a trampoline thunk object.
|
|
103
|
+
*
|
|
104
|
+
* A "thunk" is a deferred computation — instead of performing a recursive call immediately,
|
|
105
|
+
* it wraps the next step of the computation in a function. This allows recursive processes
|
|
106
|
+
* to be executed iteratively, preventing stack overflows.
|
|
107
|
+
*
|
|
108
|
+
* @template T - The type of the final computation result.
|
|
109
|
+
* @param computation - A function that, when executed, returns the next trampoline step.
|
|
110
|
+
* @returns A TrampolineThunk object containing the deferred computation.
|
|
111
|
+
*/
|
|
112
|
+
export declare const makeTrampolineThunk: <T>(computation: () => Trampoline<T>) => TrampolineThunk<T>;
|
|
113
|
+
/**
|
|
114
|
+
* Type guard to check whether a given value is a TrampolineThunk.
|
|
115
|
+
*
|
|
116
|
+
* This function is used to distinguish between a final computation result (value)
|
|
117
|
+
* and a deferred computation (thunk).
|
|
118
|
+
*
|
|
119
|
+
* @template T - The type of the value being checked.
|
|
120
|
+
* @param value - The value to test.
|
|
121
|
+
* @returns True if the value is a valid TrampolineThunk, false otherwise.
|
|
122
|
+
*/
|
|
123
|
+
export declare const isTrampolineThunk: <T>(value: Trampoline<T>) => value is TrampolineThunk<T>;
|
|
124
|
+
/**
|
|
125
|
+
* Executes a trampoline computation until a final (non-thunk) result is obtained.
|
|
126
|
+
*
|
|
127
|
+
* The trampoline function repeatedly invokes the deferred computations (thunks)
|
|
128
|
+
* in an iterative loop. This avoids deep recursive calls and prevents stack overflow,
|
|
129
|
+
* which is particularly useful for implementing recursion in a stack-safe manner.
|
|
130
|
+
*
|
|
131
|
+
* @template T - The type of the final result.
|
|
132
|
+
* @param initial - The initial Trampoline value or thunk to start execution from.
|
|
133
|
+
* @returns The final result of the computation (a non-thunk value).
|
|
134
|
+
*/
|
|
135
|
+
export declare function trampoline<T>(initial: Trampoline<T>): T;
|
|
136
|
+
/**
|
|
137
|
+
* Wraps a recursive function inside a trampoline executor.
|
|
138
|
+
*
|
|
139
|
+
* This function transforms a potentially recursive function (that returns a Trampoline<Result>)
|
|
140
|
+
* into a *stack-safe* function that executes iteratively using the `trampoline` runner.
|
|
141
|
+
*
|
|
142
|
+
* In other words, it allows you to write functions that look recursive,
|
|
143
|
+
* but actually run in constant stack space.
|
|
144
|
+
*
|
|
145
|
+
* @template Args - The tuple type representing the argument list of the original function.
|
|
146
|
+
* @template Result - The final return type after all trampoline steps are resolved.
|
|
147
|
+
*
|
|
148
|
+
* @param fn - A function that performs a single step of computation
|
|
149
|
+
* and returns a Trampoline (either a final value or a deferred thunk).
|
|
150
|
+
*
|
|
151
|
+
* @returns A new function with the same arguments, but which automatically
|
|
152
|
+
* runs the trampoline process and returns the *final result* instead
|
|
153
|
+
* of a Trampoline.
|
|
154
|
+
*
|
|
155
|
+
* @example
|
|
156
|
+
* // Example: Computing factorial in a stack-safe way
|
|
157
|
+
* const factorial = makeTrampoline(function fact(n: number, acc: number = 1): Trampoline<number> {
|
|
158
|
+
* return n === 0
|
|
159
|
+
* ? acc
|
|
160
|
+
* : makeTrampolineThunk(() => fact(n - 1, acc * n));
|
|
161
|
+
* });
|
|
162
|
+
*
|
|
163
|
+
* console.log(factorial(100000)); // Works without stack overflow
|
|
164
|
+
*/
|
|
165
|
+
export declare function makeTrampoline<Args extends any[], Result>(fn: (...args: Args) => Trampoline<Result>): (...args: Args) => Result;
|
|
166
|
+
/**
|
|
167
|
+
* Executes an asynchronous trampoline computation until a final (non-thunk) result is obtained.
|
|
168
|
+
*
|
|
169
|
+
* This function repeatedly invokes asynchronous deferred computations (thunks)
|
|
170
|
+
* in an iterative loop. Each thunk may return either a Trampoline<T> or a Promise<Trampoline<T>>.
|
|
171
|
+
*
|
|
172
|
+
* It ensures that asynchronous recursive functions can run without growing the call stack,
|
|
173
|
+
* making it suitable for stack-safe async recursion.
|
|
174
|
+
*
|
|
175
|
+
* @template T - The type of the final result.
|
|
176
|
+
* @param initial - The initial Trampoline or Promise of Trampoline to start execution from.
|
|
177
|
+
* @returns A Promise that resolves to the final result (a non-thunk value).
|
|
178
|
+
*/
|
|
179
|
+
export declare function asyncTrampoline<T>(initial: Trampoline<T> | Promise<Trampoline<T>>): Promise<T>;
|
|
180
|
+
/**
|
|
181
|
+
* Wraps an asynchronous recursive function inside an async trampoline executor.
|
|
182
|
+
*
|
|
183
|
+
* This helper transforms a recursive async function that returns a Trampoline<Result>
|
|
184
|
+
* (or Promise<Trampoline<Result>>) into a *stack-safe* async function that executes
|
|
185
|
+
* iteratively via the `asyncTrampoline` runner.
|
|
186
|
+
*
|
|
187
|
+
* @template Args - The tuple type representing the argument list of the original function.
|
|
188
|
+
* @template Result - The final return type after all async trampoline steps are resolved.
|
|
189
|
+
*
|
|
190
|
+
* @param fn - An async or sync function that performs a single step of computation
|
|
191
|
+
* and returns a Trampoline (either a final value or a deferred thunk).
|
|
192
|
+
*
|
|
193
|
+
* @returns An async function with the same arguments, but which automatically
|
|
194
|
+
* runs the trampoline process and resolves to the *final result*.
|
|
195
|
+
*
|
|
196
|
+
* @example
|
|
197
|
+
* // Example: Async factorial using trampoline
|
|
198
|
+
* const asyncFactorial = makeAsyncTrampoline(async function fact(
|
|
199
|
+
* n: number,
|
|
200
|
+
* acc: number = 1
|
|
201
|
+
* ): Promise<Trampoline<number>> {
|
|
202
|
+
* return n === 0
|
|
203
|
+
* ? acc
|
|
204
|
+
* : makeTrampolineThunk(() => fact(n - 1, acc * n));
|
|
205
|
+
* });
|
|
206
|
+
*
|
|
207
|
+
* asyncFactorial(100000).then(console.log); // Works without stack overflow
|
|
208
|
+
*/
|
|
209
|
+
export declare function makeAsyncTrampoline<Args extends any[], Result>(fn: (...args: Args) => Trampoline<Result> | Promise<Trampoline<Result>>): (...args: Args) => Promise<Result>;
|
package/dist/utils/utils.js
CHANGED
|
@@ -9,7 +9,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
9
9
|
});
|
|
10
10
|
};
|
|
11
11
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
-
exports.
|
|
12
|
+
exports.makeAsyncTrampoline = exports.asyncTrampoline = exports.makeTrampoline = exports.trampoline = exports.isTrampolineThunk = exports.makeTrampolineThunk = exports.isComparable = exports.roundFixed = exports.calcMinUnitsRequired = exports.isWeakKey = exports.throwRangeError = exports.rangeCheck = exports.getMSB = exports.arrayRemove = exports.uuidV4 = void 0;
|
|
13
13
|
/**
|
|
14
14
|
* The function generates a random UUID (Universally Unique Identifier) in TypeScript.
|
|
15
15
|
* @returns A randomly generated UUID (Universally Unique Identifier) in the format
|
|
@@ -46,78 +46,6 @@ const arrayRemove = function (array, predicate) {
|
|
|
46
46
|
return result;
|
|
47
47
|
};
|
|
48
48
|
exports.arrayRemove = arrayRemove;
|
|
49
|
-
exports.THUNK_SYMBOL = Symbol('thunk');
|
|
50
|
-
/**
|
|
51
|
-
* The function `isThunk` checks if a given value is a function with a specific symbol property.
|
|
52
|
-
* @param {any} fnOrValue - The `fnOrValue` parameter in the `isThunk` function can be either a
|
|
53
|
-
* function or a value that you want to check if it is a thunk. Thunks are functions that are wrapped
|
|
54
|
-
* around a value or computation for lazy evaluation. The function checks if the `fnOrValue` is
|
|
55
|
-
* @returns The function `isThunk` is checking if the input `fnOrValue` is a function and if it has a
|
|
56
|
-
* property `__THUNK__` equal to `THUNK_SYMBOL`. The return value will be `true` if both conditions are
|
|
57
|
-
* met, otherwise it will be `false`.
|
|
58
|
-
*/
|
|
59
|
-
const isThunk = (fnOrValue) => {
|
|
60
|
-
return typeof fnOrValue === 'function' && fnOrValue.__THUNK__ === exports.THUNK_SYMBOL;
|
|
61
|
-
};
|
|
62
|
-
exports.isThunk = isThunk;
|
|
63
|
-
/**
|
|
64
|
-
* The `toThunk` function in TypeScript converts a function into a thunk by wrapping it in a closure.
|
|
65
|
-
* @param {ToThunkFn} fn - `fn` is a function that will be converted into a thunk.
|
|
66
|
-
* @returns A thunk function is being returned. Thunk functions are functions that delay the evaluation
|
|
67
|
-
* of an expression or operation until it is explicitly called or invoked. In this case, the `toThunk`
|
|
68
|
-
* function takes a function `fn` as an argument and returns a thunk function that, when called, will
|
|
69
|
-
* execute the `fn` function provided as an argument.
|
|
70
|
-
*/
|
|
71
|
-
const toThunk = (fn) => {
|
|
72
|
-
const thunk = () => fn();
|
|
73
|
-
thunk.__THUNK__ = exports.THUNK_SYMBOL;
|
|
74
|
-
return thunk;
|
|
75
|
-
};
|
|
76
|
-
exports.toThunk = toThunk;
|
|
77
|
-
/**
|
|
78
|
-
* The `trampoline` function in TypeScript enables tail call optimization by using thunks to avoid
|
|
79
|
-
* stack overflow.
|
|
80
|
-
* @param {TrlFn} fn - The `fn` parameter in the `trampoline` function is a function that takes any
|
|
81
|
-
* number of arguments and returns a value.
|
|
82
|
-
* @returns The `trampoline` function returns an object with two properties:
|
|
83
|
-
* 1. A function that executes the provided function `fn` and continues to execute any thunks returned
|
|
84
|
-
* by `fn` until a non-thunk value is returned.
|
|
85
|
-
* 2. A `cont` property that is a function which creates a thunk for the provided function `fn`.
|
|
86
|
-
*/
|
|
87
|
-
const trampoline = (fn) => {
|
|
88
|
-
const cont = (...args) => (0, exports.toThunk)(() => fn(...args));
|
|
89
|
-
return Object.assign((...args) => {
|
|
90
|
-
let result = fn(...args);
|
|
91
|
-
while ((0, exports.isThunk)(result) && typeof result === 'function') {
|
|
92
|
-
result = result();
|
|
93
|
-
}
|
|
94
|
-
return result;
|
|
95
|
-
}, { cont });
|
|
96
|
-
};
|
|
97
|
-
exports.trampoline = trampoline;
|
|
98
|
-
/**
|
|
99
|
-
* The `trampolineAsync` function in TypeScript allows for asynchronous trampolining of a given
|
|
100
|
-
* function.
|
|
101
|
-
* @param {TrlAsyncFn} fn - The `fn` parameter in the `trampolineAsync` function is expected to be a
|
|
102
|
-
* function that returns a Promise. This function will be called recursively until a non-thunk value is
|
|
103
|
-
* returned.
|
|
104
|
-
* @returns The `trampolineAsync` function returns an object with two properties:
|
|
105
|
-
* 1. An async function that executes the provided `TrlAsyncFn` function and continues to execute any
|
|
106
|
-
* thunks returned by the function until a non-thunk value is returned.
|
|
107
|
-
* 2. A `cont` property that is a function which wraps the provided `TrlAsyncFn` function in a thunk
|
|
108
|
-
* and returns it.
|
|
109
|
-
*/
|
|
110
|
-
const trampolineAsync = (fn) => {
|
|
111
|
-
const cont = (...args) => (0, exports.toThunk)(() => fn(...args));
|
|
112
|
-
return Object.assign((...args) => __awaiter(void 0, void 0, void 0, function* () {
|
|
113
|
-
let result = yield fn(...args);
|
|
114
|
-
while ((0, exports.isThunk)(result) && typeof result === 'function') {
|
|
115
|
-
result = yield result();
|
|
116
|
-
}
|
|
117
|
-
return result;
|
|
118
|
-
}), { cont });
|
|
119
|
-
};
|
|
120
|
-
exports.trampolineAsync = trampolineAsync;
|
|
121
49
|
/**
|
|
122
50
|
* The function `getMSB` returns the most significant bit of a given number.
|
|
123
51
|
* @param {number} value - The `value` parameter is a number for which we want to find the position of
|
|
@@ -276,3 +204,149 @@ function isComparable(value, isForceObjectComparable = false) {
|
|
|
276
204
|
return isPrimitiveComparable(comparableValue);
|
|
277
205
|
}
|
|
278
206
|
exports.isComparable = isComparable;
|
|
207
|
+
/**
|
|
208
|
+
* Creates a trampoline thunk object.
|
|
209
|
+
*
|
|
210
|
+
* A "thunk" is a deferred computation — instead of performing a recursive call immediately,
|
|
211
|
+
* it wraps the next step of the computation in a function. This allows recursive processes
|
|
212
|
+
* to be executed iteratively, preventing stack overflows.
|
|
213
|
+
*
|
|
214
|
+
* @template T - The type of the final computation result.
|
|
215
|
+
* @param computation - A function that, when executed, returns the next trampoline step.
|
|
216
|
+
* @returns A TrampolineThunk object containing the deferred computation.
|
|
217
|
+
*/
|
|
218
|
+
const makeTrampolineThunk = (computation) => ({
|
|
219
|
+
isThunk: true,
|
|
220
|
+
fn: computation // The deferred computation function
|
|
221
|
+
});
|
|
222
|
+
exports.makeTrampolineThunk = makeTrampolineThunk;
|
|
223
|
+
/**
|
|
224
|
+
* Type guard to check whether a given value is a TrampolineThunk.
|
|
225
|
+
*
|
|
226
|
+
* This function is used to distinguish between a final computation result (value)
|
|
227
|
+
* and a deferred computation (thunk).
|
|
228
|
+
*
|
|
229
|
+
* @template T - The type of the value being checked.
|
|
230
|
+
* @param value - The value to test.
|
|
231
|
+
* @returns True if the value is a valid TrampolineThunk, false otherwise.
|
|
232
|
+
*/
|
|
233
|
+
const isTrampolineThunk = (value) => typeof value === 'object' && // Must be an object
|
|
234
|
+
value !== null && // Must not be null
|
|
235
|
+
'isThunk' in value && // Must have the 'isThunk' property
|
|
236
|
+
value.isThunk; // The flag must be true
|
|
237
|
+
exports.isTrampolineThunk = isTrampolineThunk;
|
|
238
|
+
/**
|
|
239
|
+
* Executes a trampoline computation until a final (non-thunk) result is obtained.
|
|
240
|
+
*
|
|
241
|
+
* The trampoline function repeatedly invokes the deferred computations (thunks)
|
|
242
|
+
* in an iterative loop. This avoids deep recursive calls and prevents stack overflow,
|
|
243
|
+
* which is particularly useful for implementing recursion in a stack-safe manner.
|
|
244
|
+
*
|
|
245
|
+
* @template T - The type of the final result.
|
|
246
|
+
* @param initial - The initial Trampoline value or thunk to start execution from.
|
|
247
|
+
* @returns The final result of the computation (a non-thunk value).
|
|
248
|
+
*/
|
|
249
|
+
function trampoline(initial) {
|
|
250
|
+
let current = initial; // Start with the initial trampoline value
|
|
251
|
+
while ((0, exports.isTrampolineThunk)(current)) { // Keep unwrapping while we have thunks
|
|
252
|
+
current = current.fn(); // Execute the deferred function to get the next step
|
|
253
|
+
}
|
|
254
|
+
return current; // Once no thunks remain, return the final result
|
|
255
|
+
}
|
|
256
|
+
exports.trampoline = trampoline;
|
|
257
|
+
/**
|
|
258
|
+
* Wraps a recursive function inside a trampoline executor.
|
|
259
|
+
*
|
|
260
|
+
* This function transforms a potentially recursive function (that returns a Trampoline<Result>)
|
|
261
|
+
* into a *stack-safe* function that executes iteratively using the `trampoline` runner.
|
|
262
|
+
*
|
|
263
|
+
* In other words, it allows you to write functions that look recursive,
|
|
264
|
+
* but actually run in constant stack space.
|
|
265
|
+
*
|
|
266
|
+
* @template Args - The tuple type representing the argument list of the original function.
|
|
267
|
+
* @template Result - The final return type after all trampoline steps are resolved.
|
|
268
|
+
*
|
|
269
|
+
* @param fn - A function that performs a single step of computation
|
|
270
|
+
* and returns a Trampoline (either a final value or a deferred thunk).
|
|
271
|
+
*
|
|
272
|
+
* @returns A new function with the same arguments, but which automatically
|
|
273
|
+
* runs the trampoline process and returns the *final result* instead
|
|
274
|
+
* of a Trampoline.
|
|
275
|
+
*
|
|
276
|
+
* @example
|
|
277
|
+
* // Example: Computing factorial in a stack-safe way
|
|
278
|
+
* const factorial = makeTrampoline(function fact(n: number, acc: number = 1): Trampoline<number> {
|
|
279
|
+
* return n === 0
|
|
280
|
+
* ? acc
|
|
281
|
+
* : makeTrampolineThunk(() => fact(n - 1, acc * n));
|
|
282
|
+
* });
|
|
283
|
+
*
|
|
284
|
+
* console.log(factorial(100000)); // Works without stack overflow
|
|
285
|
+
*/
|
|
286
|
+
function makeTrampoline(fn // A function that returns a trampoline step
|
|
287
|
+
) {
|
|
288
|
+
// Return a wrapped function that automatically runs the trampoline execution loop
|
|
289
|
+
return (...args) => trampoline(fn(...args));
|
|
290
|
+
}
|
|
291
|
+
exports.makeTrampoline = makeTrampoline;
|
|
292
|
+
/**
|
|
293
|
+
* Executes an asynchronous trampoline computation until a final (non-thunk) result is obtained.
|
|
294
|
+
*
|
|
295
|
+
* This function repeatedly invokes asynchronous deferred computations (thunks)
|
|
296
|
+
* in an iterative loop. Each thunk may return either a Trampoline<T> or a Promise<Trampoline<T>>.
|
|
297
|
+
*
|
|
298
|
+
* It ensures that asynchronous recursive functions can run without growing the call stack,
|
|
299
|
+
* making it suitable for stack-safe async recursion.
|
|
300
|
+
*
|
|
301
|
+
* @template T - The type of the final result.
|
|
302
|
+
* @param initial - The initial Trampoline or Promise of Trampoline to start execution from.
|
|
303
|
+
* @returns A Promise that resolves to the final result (a non-thunk value).
|
|
304
|
+
*/
|
|
305
|
+
function asyncTrampoline(initial) {
|
|
306
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
307
|
+
let current = yield initial; // Wait for the initial step to resolve if it's a Promise
|
|
308
|
+
// Keep executing thunks until we reach a non-thunk (final) value
|
|
309
|
+
while ((0, exports.isTrampolineThunk)(current)) {
|
|
310
|
+
current = yield current.fn(); // Execute the thunk function (may be async)
|
|
311
|
+
}
|
|
312
|
+
// Once the final value is reached, return it
|
|
313
|
+
return current;
|
|
314
|
+
});
|
|
315
|
+
}
|
|
316
|
+
exports.asyncTrampoline = asyncTrampoline;
|
|
317
|
+
/**
|
|
318
|
+
* Wraps an asynchronous recursive function inside an async trampoline executor.
|
|
319
|
+
*
|
|
320
|
+
* This helper transforms a recursive async function that returns a Trampoline<Result>
|
|
321
|
+
* (or Promise<Trampoline<Result>>) into a *stack-safe* async function that executes
|
|
322
|
+
* iteratively via the `asyncTrampoline` runner.
|
|
323
|
+
*
|
|
324
|
+
* @template Args - The tuple type representing the argument list of the original function.
|
|
325
|
+
* @template Result - The final return type after all async trampoline steps are resolved.
|
|
326
|
+
*
|
|
327
|
+
* @param fn - An async or sync function that performs a single step of computation
|
|
328
|
+
* and returns a Trampoline (either a final value or a deferred thunk).
|
|
329
|
+
*
|
|
330
|
+
* @returns An async function with the same arguments, but which automatically
|
|
331
|
+
* runs the trampoline process and resolves to the *final result*.
|
|
332
|
+
*
|
|
333
|
+
* @example
|
|
334
|
+
* // Example: Async factorial using trampoline
|
|
335
|
+
* const asyncFactorial = makeAsyncTrampoline(async function fact(
|
|
336
|
+
* n: number,
|
|
337
|
+
* acc: number = 1
|
|
338
|
+
* ): Promise<Trampoline<number>> {
|
|
339
|
+
* return n === 0
|
|
340
|
+
* ? acc
|
|
341
|
+
* : makeTrampolineThunk(() => fact(n - 1, acc * n));
|
|
342
|
+
* });
|
|
343
|
+
*
|
|
344
|
+
* asyncFactorial(100000).then(console.log); // Works without stack overflow
|
|
345
|
+
*/
|
|
346
|
+
function makeAsyncTrampoline(fn) {
|
|
347
|
+
// Return a wrapped async function that runs through the async trampoline loop
|
|
348
|
+
return (...args) => __awaiter(this, void 0, void 0, function* () {
|
|
349
|
+
return asyncTrampoline(fn(...args));
|
|
350
|
+
});
|
|
351
|
+
}
|
|
352
|
+
exports.makeAsyncTrampoline = makeAsyncTrampoline;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "bst-typed",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.5",
|
|
4
4
|
"description": "Binary Search Tree",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -144,6 +144,6 @@
|
|
|
144
144
|
"typescript": "^4.9.5"
|
|
145
145
|
},
|
|
146
146
|
"dependencies": {
|
|
147
|
-
"data-structure-typed": "^2.0.
|
|
147
|
+
"data-structure-typed": "^2.0.5"
|
|
148
148
|
}
|
|
149
149
|
}
|
|
@@ -21,10 +21,11 @@ import type {
|
|
|
21
21
|
NodePredicate,
|
|
22
22
|
OptNodeOrNull,
|
|
23
23
|
RBTNColor,
|
|
24
|
-
ToEntryFn
|
|
24
|
+
ToEntryFn,
|
|
25
|
+
Trampoline
|
|
25
26
|
} from '../../types';
|
|
26
27
|
import { IBinaryTree } from '../../interfaces';
|
|
27
|
-
import { isComparable,
|
|
28
|
+
import { isComparable, makeTrampoline, makeTrampolineThunk } from '../../utils';
|
|
28
29
|
import { Queue } from '../queue';
|
|
29
30
|
import { IterableEntryBase } from '../base';
|
|
30
31
|
import { DFSOperation, Range } from '../../common';
|
|
@@ -1294,19 +1295,20 @@ export class BinaryTree<K = any, V = any, R = object, MK = any, MV = any, MR = o
|
|
|
1294
1295
|
startNode = this.ensureNode(startNode);
|
|
1295
1296
|
|
|
1296
1297
|
if (!this.isRealNode(startNode)) return callback(startNode);
|
|
1297
|
-
|
|
1298
1298
|
if (iterationType === 'RECURSIVE') {
|
|
1299
1299
|
const dfs = (cur: BinaryTreeNode<K, V>): BinaryTreeNode<K, V> => {
|
|
1300
|
-
|
|
1301
|
-
|
|
1300
|
+
const { left } = cur;
|
|
1301
|
+
if (!this.isRealNode(left)) return cur;
|
|
1302
|
+
return dfs(left);
|
|
1302
1303
|
};
|
|
1303
1304
|
|
|
1304
1305
|
return callback(dfs(startNode));
|
|
1305
1306
|
} else {
|
|
1306
1307
|
// Indirect implementation of iteration using tail recursion optimization
|
|
1307
|
-
const dfs =
|
|
1308
|
-
|
|
1309
|
-
|
|
1308
|
+
const dfs = makeTrampoline((cur: BinaryTreeNode<K, V>): Trampoline<BinaryTreeNode<K, V>> => {
|
|
1309
|
+
const { left } = cur;
|
|
1310
|
+
if (!this.isRealNode(left)) return cur;
|
|
1311
|
+
return makeTrampolineThunk(() => dfs(left));
|
|
1310
1312
|
});
|
|
1311
1313
|
|
|
1312
1314
|
return callback(dfs(startNode));
|
|
@@ -1346,16 +1348,19 @@ export class BinaryTree<K = any, V = any, R = object, MK = any, MV = any, MR = o
|
|
|
1346
1348
|
|
|
1347
1349
|
if (iterationType === 'RECURSIVE') {
|
|
1348
1350
|
const dfs = (cur: BinaryTreeNode<K, V>): BinaryTreeNode<K, V> => {
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
+
const { right } = cur;
|
|
1352
|
+
if (!this.isRealNode(right)) return cur;
|
|
1353
|
+
return dfs(right);
|
|
1351
1354
|
};
|
|
1352
1355
|
|
|
1353
1356
|
return callback(dfs(startNode));
|
|
1354
1357
|
} else {
|
|
1355
1358
|
// Indirect implementation of iteration using tail recursion optimization
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
+
|
|
1360
|
+
const dfs = makeTrampoline((cur: BinaryTreeNode<K, V>): Trampoline<BinaryTreeNode<K, V>> => {
|
|
1361
|
+
const { right } = cur;
|
|
1362
|
+
if (!this.isRealNode(right)) return cur;
|
|
1363
|
+
return makeTrampolineThunk(() => dfs(right));
|
|
1359
1364
|
});
|
|
1360
1365
|
|
|
1361
1366
|
return callback(dfs(startNode));
|
package/src/types/utils/utils.ts
CHANGED
|
@@ -1,8 +1,3 @@
|
|
|
1
|
-
export type ToThunkFn<R = any> = () => R;
|
|
2
|
-
export type Thunk<R = any> = ToThunkFn<R> & { __THUNK__?: symbol };
|
|
3
|
-
export type TrlFn<A extends any[] = any[], R = any> = (...args: A) => R;
|
|
4
|
-
export type TrlAsyncFn = (...args: any[]) => any;
|
|
5
|
-
|
|
6
1
|
export type SpecifyOptional<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>;
|
|
7
2
|
|
|
8
3
|
export type Any = string | number | bigint | boolean | symbol | undefined | object;
|
|
@@ -27,3 +22,10 @@ export interface StringComparableObject extends BaseComparableObject {
|
|
|
27
22
|
export type ComparableObject = ValueComparableObject | StringComparableObject;
|
|
28
23
|
|
|
29
24
|
export type Comparable = ComparablePrimitive | Date | ComparableObject;
|
|
25
|
+
|
|
26
|
+
export type TrampolineThunk<T> = {
|
|
27
|
+
readonly isThunk: true;
|
|
28
|
+
readonly fn: () => Trampoline<T>;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export type Trampoline<T> = T | TrampolineThunk<T>;
|
package/src/utils/utils.ts
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* @copyright Copyright (c) 2022 Pablo Zeng <zrwusa@gmail.com>
|
|
6
6
|
* @license MIT License
|
|
7
7
|
*/
|
|
8
|
-
import type { Comparable, ComparablePrimitive,
|
|
8
|
+
import type { Comparable, ComparablePrimitive, TrampolineThunk, Trampoline } from '../types';
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
11
|
* The function generates a random UUID (Universally Unique Identifier) in TypeScript.
|
|
@@ -47,91 +47,6 @@ export const arrayRemove = function <T>(array: T[], predicate: (item: T, index:
|
|
|
47
47
|
return result;
|
|
48
48
|
};
|
|
49
49
|
|
|
50
|
-
export const THUNK_SYMBOL = Symbol('thunk');
|
|
51
|
-
|
|
52
|
-
/**
|
|
53
|
-
* The function `isThunk` checks if a given value is a function with a specific symbol property.
|
|
54
|
-
* @param {any} fnOrValue - The `fnOrValue` parameter in the `isThunk` function can be either a
|
|
55
|
-
* function or a value that you want to check if it is a thunk. Thunks are functions that are wrapped
|
|
56
|
-
* around a value or computation for lazy evaluation. The function checks if the `fnOrValue` is
|
|
57
|
-
* @returns The function `isThunk` is checking if the input `fnOrValue` is a function and if it has a
|
|
58
|
-
* property `__THUNK__` equal to `THUNK_SYMBOL`. The return value will be `true` if both conditions are
|
|
59
|
-
* met, otherwise it will be `false`.
|
|
60
|
-
*/
|
|
61
|
-
export const isThunk = (fnOrValue: any) => {
|
|
62
|
-
return typeof fnOrValue === 'function' && fnOrValue.__THUNK__ === THUNK_SYMBOL;
|
|
63
|
-
};
|
|
64
|
-
|
|
65
|
-
/**
|
|
66
|
-
* The `toThunk` function in TypeScript converts a function into a thunk by wrapping it in a closure.
|
|
67
|
-
* @param {ToThunkFn} fn - `fn` is a function that will be converted into a thunk.
|
|
68
|
-
* @returns A thunk function is being returned. Thunk functions are functions that delay the evaluation
|
|
69
|
-
* of an expression or operation until it is explicitly called or invoked. In this case, the `toThunk`
|
|
70
|
-
* function takes a function `fn` as an argument and returns a thunk function that, when called, will
|
|
71
|
-
* execute the `fn` function provided as an argument.
|
|
72
|
-
*/
|
|
73
|
-
export const toThunk = (fn: ToThunkFn): Thunk => {
|
|
74
|
-
const thunk = () => fn();
|
|
75
|
-
thunk.__THUNK__ = THUNK_SYMBOL;
|
|
76
|
-
return thunk;
|
|
77
|
-
};
|
|
78
|
-
|
|
79
|
-
/**
|
|
80
|
-
* The `trampoline` function in TypeScript enables tail call optimization by using thunks to avoid
|
|
81
|
-
* stack overflow.
|
|
82
|
-
* @param {TrlFn} fn - The `fn` parameter in the `trampoline` function is a function that takes any
|
|
83
|
-
* number of arguments and returns a value.
|
|
84
|
-
* @returns The `trampoline` function returns an object with two properties:
|
|
85
|
-
* 1. A function that executes the provided function `fn` and continues to execute any thunks returned
|
|
86
|
-
* by `fn` until a non-thunk value is returned.
|
|
87
|
-
* 2. A `cont` property that is a function which creates a thunk for the provided function `fn`.
|
|
88
|
-
*/
|
|
89
|
-
export const trampoline = (fn: TrlFn) => {
|
|
90
|
-
const cont = (...args: [...Parameters<TrlFn>]): ReturnType<TrlFn> => toThunk(() => fn(...args));
|
|
91
|
-
|
|
92
|
-
return Object.assign(
|
|
93
|
-
(...args: [...Parameters<TrlFn>]) => {
|
|
94
|
-
let result = fn(...args);
|
|
95
|
-
|
|
96
|
-
while (isThunk(result) && typeof result === 'function') {
|
|
97
|
-
result = result();
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
return result;
|
|
101
|
-
},
|
|
102
|
-
{ cont }
|
|
103
|
-
);
|
|
104
|
-
};
|
|
105
|
-
|
|
106
|
-
/**
|
|
107
|
-
* The `trampolineAsync` function in TypeScript allows for asynchronous trampolining of a given
|
|
108
|
-
* function.
|
|
109
|
-
* @param {TrlAsyncFn} fn - The `fn` parameter in the `trampolineAsync` function is expected to be a
|
|
110
|
-
* function that returns a Promise. This function will be called recursively until a non-thunk value is
|
|
111
|
-
* returned.
|
|
112
|
-
* @returns The `trampolineAsync` function returns an object with two properties:
|
|
113
|
-
* 1. An async function that executes the provided `TrlAsyncFn` function and continues to execute any
|
|
114
|
-
* thunks returned by the function until a non-thunk value is returned.
|
|
115
|
-
* 2. A `cont` property that is a function which wraps the provided `TrlAsyncFn` function in a thunk
|
|
116
|
-
* and returns it.
|
|
117
|
-
*/
|
|
118
|
-
export const trampolineAsync = (fn: TrlAsyncFn) => {
|
|
119
|
-
const cont = (...args: [...Parameters<TrlAsyncFn>]): ReturnType<TrlAsyncFn> => toThunk(() => fn(...args));
|
|
120
|
-
|
|
121
|
-
return Object.assign(
|
|
122
|
-
async (...args: [...Parameters<TrlAsyncFn>]) => {
|
|
123
|
-
let result = await fn(...args);
|
|
124
|
-
|
|
125
|
-
while (isThunk(result) && typeof result === 'function') {
|
|
126
|
-
result = await result();
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
return result;
|
|
130
|
-
},
|
|
131
|
-
{ cont }
|
|
132
|
-
);
|
|
133
|
-
};
|
|
134
|
-
|
|
135
50
|
/**
|
|
136
51
|
* The function `getMSB` returns the most significant bit of a given number.
|
|
137
52
|
* @param {number} value - The `value` parameter is a number for which we want to find the position of
|
|
@@ -282,3 +197,159 @@ export function isComparable(value: unknown, isForceObjectComparable = false): v
|
|
|
282
197
|
if (comparableValue === null || comparableValue === undefined) return false;
|
|
283
198
|
return isPrimitiveComparable(comparableValue);
|
|
284
199
|
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Creates a trampoline thunk object.
|
|
203
|
+
*
|
|
204
|
+
* A "thunk" is a deferred computation — instead of performing a recursive call immediately,
|
|
205
|
+
* it wraps the next step of the computation in a function. This allows recursive processes
|
|
206
|
+
* to be executed iteratively, preventing stack overflows.
|
|
207
|
+
*
|
|
208
|
+
* @template T - The type of the final computation result.
|
|
209
|
+
* @param computation - A function that, when executed, returns the next trampoline step.
|
|
210
|
+
* @returns A TrampolineThunk object containing the deferred computation.
|
|
211
|
+
*/
|
|
212
|
+
export const makeTrampolineThunk = <T>(
|
|
213
|
+
computation: () => Trampoline<T>
|
|
214
|
+
): TrampolineThunk<T> => ({
|
|
215
|
+
isThunk: true, // Marker indicating this is a thunk
|
|
216
|
+
fn: computation // The deferred computation function
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Type guard to check whether a given value is a TrampolineThunk.
|
|
221
|
+
*
|
|
222
|
+
* This function is used to distinguish between a final computation result (value)
|
|
223
|
+
* and a deferred computation (thunk).
|
|
224
|
+
*
|
|
225
|
+
* @template T - The type of the value being checked.
|
|
226
|
+
* @param value - The value to test.
|
|
227
|
+
* @returns True if the value is a valid TrampolineThunk, false otherwise.
|
|
228
|
+
*/
|
|
229
|
+
export const isTrampolineThunk = <T>(
|
|
230
|
+
value: Trampoline<T>
|
|
231
|
+
): value is TrampolineThunk<T> =>
|
|
232
|
+
typeof value === 'object' && // Must be an object
|
|
233
|
+
value !== null && // Must not be null
|
|
234
|
+
'isThunk' in value && // Must have the 'isThunk' property
|
|
235
|
+
value.isThunk; // The flag must be true
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Executes a trampoline computation until a final (non-thunk) result is obtained.
|
|
239
|
+
*
|
|
240
|
+
* The trampoline function repeatedly invokes the deferred computations (thunks)
|
|
241
|
+
* in an iterative loop. This avoids deep recursive calls and prevents stack overflow,
|
|
242
|
+
* which is particularly useful for implementing recursion in a stack-safe manner.
|
|
243
|
+
*
|
|
244
|
+
* @template T - The type of the final result.
|
|
245
|
+
* @param initial - The initial Trampoline value or thunk to start execution from.
|
|
246
|
+
* @returns The final result of the computation (a non-thunk value).
|
|
247
|
+
*/
|
|
248
|
+
export function trampoline<T>(initial: Trampoline<T>): T {
|
|
249
|
+
let current = initial; // Start with the initial trampoline value
|
|
250
|
+
while (isTrampolineThunk(current)) { // Keep unwrapping while we have thunks
|
|
251
|
+
current = current.fn(); // Execute the deferred function to get the next step
|
|
252
|
+
}
|
|
253
|
+
return current; // Once no thunks remain, return the final result
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* Wraps a recursive function inside a trampoline executor.
|
|
258
|
+
*
|
|
259
|
+
* This function transforms a potentially recursive function (that returns a Trampoline<Result>)
|
|
260
|
+
* into a *stack-safe* function that executes iteratively using the `trampoline` runner.
|
|
261
|
+
*
|
|
262
|
+
* In other words, it allows you to write functions that look recursive,
|
|
263
|
+
* but actually run in constant stack space.
|
|
264
|
+
*
|
|
265
|
+
* @template Args - The tuple type representing the argument list of the original function.
|
|
266
|
+
* @template Result - The final return type after all trampoline steps are resolved.
|
|
267
|
+
*
|
|
268
|
+
* @param fn - A function that performs a single step of computation
|
|
269
|
+
* and returns a Trampoline (either a final value or a deferred thunk).
|
|
270
|
+
*
|
|
271
|
+
* @returns A new function with the same arguments, but which automatically
|
|
272
|
+
* runs the trampoline process and returns the *final result* instead
|
|
273
|
+
* of a Trampoline.
|
|
274
|
+
*
|
|
275
|
+
* @example
|
|
276
|
+
* // Example: Computing factorial in a stack-safe way
|
|
277
|
+
* const factorial = makeTrampoline(function fact(n: number, acc: number = 1): Trampoline<number> {
|
|
278
|
+
* return n === 0
|
|
279
|
+
* ? acc
|
|
280
|
+
* : makeTrampolineThunk(() => fact(n - 1, acc * n));
|
|
281
|
+
* });
|
|
282
|
+
*
|
|
283
|
+
* console.log(factorial(100000)); // Works without stack overflow
|
|
284
|
+
*/
|
|
285
|
+
export function makeTrampoline<Args extends any[], Result>(
|
|
286
|
+
fn: (...args: Args) => Trampoline<Result> // A function that returns a trampoline step
|
|
287
|
+
): (...args: Args) => Result {
|
|
288
|
+
// Return a wrapped function that automatically runs the trampoline execution loop
|
|
289
|
+
return (...args: Args) => trampoline(fn(...args));
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* Executes an asynchronous trampoline computation until a final (non-thunk) result is obtained.
|
|
294
|
+
*
|
|
295
|
+
* This function repeatedly invokes asynchronous deferred computations (thunks)
|
|
296
|
+
* in an iterative loop. Each thunk may return either a Trampoline<T> or a Promise<Trampoline<T>>.
|
|
297
|
+
*
|
|
298
|
+
* It ensures that asynchronous recursive functions can run without growing the call stack,
|
|
299
|
+
* making it suitable for stack-safe async recursion.
|
|
300
|
+
*
|
|
301
|
+
* @template T - The type of the final result.
|
|
302
|
+
* @param initial - The initial Trampoline or Promise of Trampoline to start execution from.
|
|
303
|
+
* @returns A Promise that resolves to the final result (a non-thunk value).
|
|
304
|
+
*/
|
|
305
|
+
export async function asyncTrampoline<T>(
|
|
306
|
+
initial: Trampoline<T> | Promise<Trampoline<T>>
|
|
307
|
+
): Promise<T> {
|
|
308
|
+
let current = await initial; // Wait for the initial step to resolve if it's a Promise
|
|
309
|
+
|
|
310
|
+
// Keep executing thunks until we reach a non-thunk (final) value
|
|
311
|
+
while (isTrampolineThunk(current)) {
|
|
312
|
+
current = await current.fn(); // Execute the thunk function (may be async)
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
// Once the final value is reached, return it
|
|
316
|
+
return current;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
/**
|
|
320
|
+
* Wraps an asynchronous recursive function inside an async trampoline executor.
|
|
321
|
+
*
|
|
322
|
+
* This helper transforms a recursive async function that returns a Trampoline<Result>
|
|
323
|
+
* (or Promise<Trampoline<Result>>) into a *stack-safe* async function that executes
|
|
324
|
+
* iteratively via the `asyncTrampoline` runner.
|
|
325
|
+
*
|
|
326
|
+
* @template Args - The tuple type representing the argument list of the original function.
|
|
327
|
+
* @template Result - The final return type after all async trampoline steps are resolved.
|
|
328
|
+
*
|
|
329
|
+
* @param fn - An async or sync function that performs a single step of computation
|
|
330
|
+
* and returns a Trampoline (either a final value or a deferred thunk).
|
|
331
|
+
*
|
|
332
|
+
* @returns An async function with the same arguments, but which automatically
|
|
333
|
+
* runs the trampoline process and resolves to the *final result*.
|
|
334
|
+
*
|
|
335
|
+
* @example
|
|
336
|
+
* // Example: Async factorial using trampoline
|
|
337
|
+
* const asyncFactorial = makeAsyncTrampoline(async function fact(
|
|
338
|
+
* n: number,
|
|
339
|
+
* acc: number = 1
|
|
340
|
+
* ): Promise<Trampoline<number>> {
|
|
341
|
+
* return n === 0
|
|
342
|
+
* ? acc
|
|
343
|
+
* : makeTrampolineThunk(() => fact(n - 1, acc * n));
|
|
344
|
+
* });
|
|
345
|
+
*
|
|
346
|
+
* asyncFactorial(100000).then(console.log); // Works without stack overflow
|
|
347
|
+
*/
|
|
348
|
+
export function makeAsyncTrampoline<Args extends any[], Result>(
|
|
349
|
+
fn: (...args: Args) => Trampoline<Result> | Promise<Trampoline<Result>>
|
|
350
|
+
): (...args: Args) => Promise<Result> {
|
|
351
|
+
// Return a wrapped async function that runs through the async trampoline loop
|
|
352
|
+
return async (...args: Args): Promise<Result> => {
|
|
353
|
+
return asyncTrampoline(fn(...args));
|
|
354
|
+
};
|
|
355
|
+
}
|