-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathtree.ts
125 lines (116 loc) · 3.72 KB
/
tree.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
type IChildrenExtractor<T> = (node: T) => T[] | undefined;
const createArrayChildrenExtractor = <T>(childrenKey: string): IChildrenExtractor<T> => {
return (node) => node[childrenKey];
};
type ITreeFlattener<T> = (node: T) => T[];
/**
* create a flattener method that can flatten tree into node list
* @param childrenExtractor
*/
export const createTreeFlattener = <T>(
childrenExtractor: IChildrenExtractor<T>,
): ITreeFlattener<T> => {
const flattener: ITreeFlattener<T> = (node) => {
const children = childrenExtractor(node);
if (!children || !Array.isArray(children)) {
return [node];
}
return children.map(flattener).reduce(
(list, childList) => {
return list.concat(childList);
},
[node],
);
};
return flattener;
};
/**
* create a flattener method that can flatten tree into node list, specialized version of childrenExtractor
* @param childrenKey
*/
export function createArrayChildrenTreeFlattener<T>(childrenKey: string): ITreeFlattener<T> {
return createTreeFlattener(createArrayChildrenExtractor(childrenKey));
}
type INodeFilter<T> = (node: T) => boolean;
type INodeCloner<T> = (node: T, childrenReplace: T[]) => T;
type ITreeFilter<T> = (node: T) => T | undefined;
/**
* create a filter method that can generate a new tree with nodes filtered
* @param filter
* @param childrenExtractor
* @param nodeCloner
*/
export function createTreeFilter<T>(
filter: INodeFilter<T>,
childrenExtractor: IChildrenExtractor<T>,
nodeCloner: INodeCloner<T>,
): ITreeFilter<T> {
const treeFilter: ITreeFilter<T> = (node) => {
if (!filter(node)) {
return undefined;
}
const children = childrenExtractor(node);
if (!children || !Array.isArray(children)) {
// unchanged, just return node
return node;
}
const childrenReplace = children
.map((child) => treeFilter(child))
.filter((child) => !!child);
return nodeCloner(node, childrenReplace);
};
return treeFilter;
}
/**
* create a filter method that can generate a new tree with nodes filtered, specialized version of createTreeFilter
* @param filter
* @param childrenKey
*/
export function createArrayChildrenTreeFilter<T>(
filter: INodeFilter<T>,
childrenKey: string,
): ITreeFilter<T> {
return createTreeFilter(
filter,
createArrayChildrenExtractor(childrenKey),
(node, childrenReplace) => {
return { ...node, [childrenKey]: childrenReplace };
},
);
}
type ITreeFinder<T> = (node: T) => T | undefined;
/**
* create a finder method that can find a node matches filter in breadth first order
* @param filter
* @param childrenExtractor
*/
export function createBreadthFirstTreeFinder<T>(
filter: INodeFilter<T>,
childrenExtractor: IChildrenExtractor<T>,
): ITreeFinder<T> {
return (root) => {
const queue = [root];
while (queue.length > 0) {
const node = queue.shift();
if (filter(node)) {
return node;
}
const children = childrenExtractor(node);
if (children && Array.isArray(children)) {
queue.push(...children);
}
}
return undefined;
};
}
/**
* create a finder method that can find a node matches filter in breadth first order, specialized version of createBreadthFirstTreeFinder
* @param filter
* @param childrenKey
*/
export function createBreadthFirstArrayChildrenTreeFinder<T>(
filter: INodeFilter<T>,
childrenKey: string,
): ITreeFinder<T> {
return createBreadthFirstTreeFinder(filter, createArrayChildrenExtractor(childrenKey));
}