|
1 |
| -export type TreeBlueprintNamespace = Map<string, TreeBlueprintNode>; |
2 |
| -export type TreeBlueprintNode = |
3 |
| - | { type: "leaf"; key: string } |
4 |
| - | { type: "ns"; ns: TreeBlueprintNamespace }; |
5 |
| -export const digTreeBlueprintNamespace = ( |
6 |
| - ns: TreeBlueprintNamespace, |
7 |
| - path: string[], |
8 |
| -): TreeBlueprintNamespace => { |
9 |
| - if (path.length === 0) { |
10 |
| - return ns; |
11 |
| - } |
12 |
| - const [pathHead, ...pathTail] = path; |
13 |
| - const childNs = (() => { |
14 |
| - if (ns.has(pathHead)) { |
15 |
| - const node = ns.get(pathHead)!; |
16 |
| - switch (node.type) { |
17 |
| - case "ns": |
18 |
| - return node.ns; |
19 |
| - case "leaf": { |
20 |
| - const childNs: TreeBlueprintNamespace = new Map(); |
21 |
| - childNs.set("", node); |
22 |
| - ns.set(pathHead, { type: "ns", ns: childNs }); |
23 |
| - return childNs; |
24 |
| - } |
25 |
| - } |
26 |
| - } else { |
27 |
| - const childNs: TreeBlueprintNamespace = new Map(); |
28 |
| - ns.set(pathHead, { type: "ns", ns: childNs }); |
29 |
| - return childNs; |
30 |
| - } |
31 |
| - })(); |
32 |
| - return digTreeBlueprintNamespace(childNs, pathTail); |
33 |
| -}; |
34 |
| - |
35 | 1 | export type TreeNamespace<T> = Map<string, TreeNode<T>>;
|
36 | 2 | export type TreeNode<T> =
|
37 | 3 | | { type: "leaf"; value: T }
|
38 | 4 | | { type: "ns"; ns: TreeNamespace<T> };
|
39 | 5 |
|
40 |
| -export const buildTree = <T>( |
41 |
| - blueprint: TreeBlueprintNamespace, |
42 |
| - getValue: (key: string) => T, |
43 |
| -): TreeNamespace<T> => { |
44 |
| - const map = new Map(); |
45 |
| - for (const [key, value] of blueprint) { |
46 |
| - switch (value.type) { |
47 |
| - case "leaf": |
48 |
| - map.set(key, { type: "leaf", value: getValue(value.key) }); |
49 |
| - break; |
50 |
| - case "ns": |
51 |
| - map.set(key, { type: "ns", ns: buildTree(value.ns, getValue) }); |
52 |
| - break; |
| 6 | +const makeLeaf = <T>(value: T): TreeNode<T> => ({ type: "leaf", value }); |
| 7 | +const makeNs = <T>(ns: TreeNamespace<T>): TreeNode<T> => ({ type: "ns", ns }); |
| 8 | + |
| 9 | +const mapMapWithKey = <K, T, U>( |
| 10 | + map: Map<K, T>, |
| 11 | + f: (key: K, value: T) => U, |
| 12 | +): Map<K, U> => { |
| 13 | + const result = new Map(); |
| 14 | + for (const [key, value] of map) { |
| 15 | + result.set(key, f(key, value)); |
| 16 | + } |
| 17 | + return result; |
| 18 | +}; |
| 19 | + |
| 20 | +const mapTreeRec = <T, U>( |
| 21 | + tree: TreeNode<T>, |
| 22 | + path: string[], |
| 23 | + f: (path: string[], node: T) => U, |
| 24 | +): TreeNode<U> => { |
| 25 | + switch (tree.type) { |
| 26 | + case "leaf": |
| 27 | + return makeLeaf(f(path, tree.value)); |
| 28 | + case "ns": |
| 29 | + return makeNs( |
| 30 | + mapMapWithKey(tree.ns, (key, child) => |
| 31 | + mapTreeRec(child, [...path, key], f), |
| 32 | + ), |
| 33 | + ); |
| 34 | + } |
| 35 | +}; |
| 36 | + |
| 37 | +export const mapTree = <T, U>( |
| 38 | + tree: TreeNode<T>, |
| 39 | + f: (path: string[], node: T) => U, |
| 40 | +): TreeNode<U> => { |
| 41 | + return mapTreeRec(tree, [], f); |
| 42 | +}; |
| 43 | + |
| 44 | +export const mapNamespace = <T, U>( |
| 45 | + ns: TreeNamespace<T>, |
| 46 | + f: (path: string[], node: T) => U, |
| 47 | +): TreeNamespace<U> => { |
| 48 | + const tree = makeNs(ns); |
| 49 | + const mappedTree = mapTreeRec(tree, [], f); |
| 50 | + if (mappedTree.type === "ns") { |
| 51 | + return mappedTree.ns; |
| 52 | + } else { |
| 53 | + throw new Error("Impossible"); |
| 54 | + } |
| 55 | +}; |
| 56 | + |
| 57 | +export const singleton = <T>(path: string[], value: T): TreeNode<T> => { |
| 58 | + let tree: TreeNode<T> = { type: "leaf", value }; |
| 59 | + for (const key of path.toReversed()) { |
| 60 | + const ns = new Map(); |
| 61 | + ns.set(key, tree); |
| 62 | + tree = { type: "ns", ns }; |
| 63 | + } |
| 64 | + return tree; |
| 65 | +}; |
| 66 | + |
| 67 | +export const add = <T>( |
| 68 | + tree: TreeNode<T>, |
| 69 | + path: string[], |
| 70 | + value: T, |
| 71 | +): TreeNode<T> => { |
| 72 | + switch (tree.type) { |
| 73 | + case "leaf": |
| 74 | + if (path.length === 0) { |
| 75 | + tree.value = value; |
| 76 | + return tree; |
| 77 | + } else { |
| 78 | + const [pathHead, ...pathTail] = path; |
| 79 | + const ns = new Map(); |
| 80 | + ns.set("", tree); |
| 81 | + ns.set(pathHead, singleton(pathTail, value)); |
| 82 | + return { |
| 83 | + type: "ns", |
| 84 | + ns, |
| 85 | + }; |
| 86 | + } |
| 87 | + case "ns": |
| 88 | + addToNamespace(tree.ns, path, value); |
| 89 | + return tree; |
| 90 | + } |
| 91 | +}; |
| 92 | + |
| 93 | +export const addToNamespace = <T>( |
| 94 | + ns: TreeNamespace<T>, |
| 95 | + path: string[], |
| 96 | + value: T, |
| 97 | +): void => { |
| 98 | + if (path.length === 0) { |
| 99 | + ns.set("", { type: "leaf", value }); |
| 100 | + } else { |
| 101 | + const [pathHead, ...pathTail] = path; |
| 102 | + const child = ns.get(pathHead); |
| 103 | + if (child === undefined) { |
| 104 | + ns.set(pathHead, singleton(pathTail, value)); |
| 105 | + } else { |
| 106 | + ns.set(pathHead, add(child, pathTail, value)); |
53 | 107 | }
|
54 | 108 | }
|
55 |
| - return map; |
56 | 109 | };
|
0 commit comments