feat: add flat_tree allgorithm

This commit is contained in:
2024-04-10 21:57:03 +02:00
parent 2ed1501747
commit e2940183f1
9 changed files with 179 additions and 9 deletions

View File

@ -0,0 +1,61 @@
import { expect, test } from 'vitest'
import { decode, encode } from './flat_tree'
// Original test case
test('it correctly decodes/encodes complex nested arrays', () => {
const input = [5, [6, 1], 1, 5, [5], [7, 2, [5, 1]]];
const decoded = decode(encode(input));
expect(decoded).toEqual(input);
});
// Test with empty array
test('it correctly handles an empty array', () => {
const input: number[] = [];
const decoded = decode(encode(input));
expect(decoded).toEqual(input);
});
// Test with nested empty arrays
test('it correctly handles nested empty arrays', () => {
const input = [5, [], [6, []], []];
const decoded = decode(encode(input));
expect(decoded).toEqual(input);
});
// Test with single-element array
test('it correctly handles a single-element array', () => {
const input = [42];
const decoded = decode(encode(input));
expect(decoded).toEqual(input);
});
// Test with deeply nested array
test('it correctly handles deeply nested arrays', () => {
const input = [[[[[1]]]]];
const decoded = decode(encode(input));
expect(decoded).toEqual(input);
});
// Test with large numbers
test('it correctly handles large numbers', () => {
const input = [2147483647, [-2147483648, 1234567890]];
const decoded = decode(encode(input));
expect(decoded).toEqual(input);
});
// Test with sequential nesting
test('it correctly handles sequential nesting', () => {
const input = [1, [2, [3, [4, [5]]]]];
const decoded = decode(encode(input));
expect(decoded).toEqual(input);
});
// Test with mixed data types (if supported)
// Note: This test assumes your implementation supports mixed types.
// If not, you can ignore or remove this test.
test('it correctly handles arrays with mixed data types', () => {
const input = [1, 'text', [true, [null, ['another text']]]];
// @ts-ignore
const decoded = decode(encode(input));
expect(decoded).toEqual(input);
});

View File

@ -0,0 +1,60 @@
type SparseArray<T = number> = (T | T[] | SparseArray<T>)[];
// Encodes a nested array into a flat array with bracket and distance notation
export function encode(array: SparseArray): number[] {
const encoded = [0, 0]; // Initialize encoded array with root bracket notation
let missingBracketIndex = 1; // Track where to insert the distance to the next bracket
for (let index = 0; index < array.length; index++) {
const item = array[index];
if (Array.isArray(item)) {
// Update the distance to the next bracket for the last opened bracket
encoded[missingBracketIndex] = encoded.length - missingBracketIndex;
if (item.length === 0) {
// Handle empty arrays by directly adding bracket notation
encoded.push(0, 1, 1, 1);
} else {
// Recursively encode non-empty arrays
const child = encode(item);
encoded.push(...child, 1, 0); // Note: The trailing comma after 0 can be removed
}
// Update missingBracketIndex to the position of the newly added bracket
missingBracketIndex = encoded.length - 1;
} else {
// Handle non-array items
encoded.push(item);
// Update the distance for the last opened bracket
if (missingBracketIndex) encoded[missingBracketIndex] = index + 2;
}
}
return encoded;
};
function decode_recursive(dense: number[], index = 0) {
const decoded: (number | number[])[] = [];
let nextBracketIndex = dense[index + 1] + index + 1; // Calculate the index of the next bracket
index += 2; // Skip the initial bracket notation
while (index < dense.length) {
if (index === nextBracketIndex) {
if (dense[index] === 0) { // Opening bracket detected
const [p, nextIndex, _nextBracketIndex] = decode_recursive(dense, index);
decoded.push(p);
index = nextIndex + 1;
nextBracketIndex = _nextBracketIndex;
} else { // Closing bracket detected
nextBracketIndex = dense[index + 1] + index + 1;
return [decoded, index, nextBracketIndex] as const;
}
} else if (index < nextBracketIndex) {
decoded.push(dense[index]); // Add regular number to decoded array
}
index++;
}
return [decoded, index, nextBracketIndex] as const;
}
export function decode(dense: number[]) {
return decode_recursive(dense, 0)[0];
}

View File

@ -41,6 +41,8 @@ const nodeTypes: NodeType[] = [
const log = createLogger("node-registry");
export class RemoteNodeRegistry implements NodeRegistry {
status: "loading" | "ready" | "error" = "loading";
private nodes: Map<string, NodeType> = new Map();
constructor(private url: string) { }
@ -55,6 +57,7 @@ export class RemoteNodeRegistry implements NodeRegistry {
const wasmResponse = await fetch(`${nodeUrl}/wasm`);
const wrapperReponse = await fetch(`${nodeUrl}/wrapper`);
if (!wrapperReponse.ok) {
this.status = "error";
throw new Error(`Failed to load node ${id}`);
}
@ -68,6 +71,7 @@ wasm = val;`);
wasmWrapper.__wbg_set_wasm(instance.exports);
if (!response.ok) {
this.status = "error";
throw new Error(`Failed to load node ${id}`);
}
const node = await response.json();
@ -78,6 +82,7 @@ wasm = val;`);
const duration = performance.now() - a;
log.log("loaded nodes in", duration, "ms");
this.status = "ready";
}
getNode(id: string) {

View File

@ -6,6 +6,11 @@ export class MemoryRuntimeExecutor implements RuntimeExecutor {
constructor(private registry: NodeRegistry) { }
private getNodeTypes(graph: Graph) {
if (this.registry.status !== "ready") {
throw new Error("Node registry is not ready");
}
const typeMap = new Map<string, NodeType>();
for (const node of graph.nodes) {
if (!typeMap.has(node.type)) {