forked from iyegoroff/un-flatten-tree
-
Notifications
You must be signed in to change notification settings - Fork 0
/
un-flatten-tree.ts
134 lines (115 loc) · 3.6 KB
/
un-flatten-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
126
127
128
129
130
131
132
133
134
const map = Array.prototype.map;
const reduce = Array.prototype.reduce;
const find = <T>(list: ArrayLike<T>, predicate: (item: T) => boolean) => {
const len = list.length;
for (let i = 0; i < len; i++) {
if (predicate(list[i])) {
return list[i];
}
}
return undefined;
};
/**
* Converts tree to list.
*
* @param tree Array-like object representing tree.
* @param getChildNodes Function to return child nodes.
* @param convertNode Function to modify each item of result list.
* @param generateId Function to generate unique ids for each item of result list.
* @return Returns list of out nodes.
*/
export function flatten<Node, OutNode, Id>(
tree: ArrayLike<Node>,
getChildNodes: (node: Node) => ArrayLike<Node>,
convertNode: (
node: Node,
parentNode?: Node,
nodeId?: Id,
parentNodeId?: Id
) => OutNode = (node: Node & OutNode) => node,
generateId: (node: Node) => Id = () => undefined
): OutNode[] {
const stack = tree && tree.length ? [{ pointer: tree, offset: 0 }] : [];
const flat: OutNode[] = [];
let current: { pointer: ArrayLike<Node>; offset: number; node?: Node; nodeId?: Id };
while (stack.length) {
current = stack.pop();
while (current.offset < current.pointer.length) {
const node = current.pointer[current.offset];
const nodeId = generateId(node);
const children = getChildNodes(node);
flat.push(convertNode(node, current.node, nodeId, current.nodeId));
current.offset += 1;
if (children) {
stack.push(current);
current = {
pointer: children,
offset: 0,
node,
nodeId
};
}
}
}
return flat;
}
/**
* Converts list to tree.
*
* @param list Array-like object representing list.
* @param isChildNode Function to check for child-parent relation.
* @param addChildNode Function to add out node to its parent node.
* @param convertNode Function to modify each node of resulting tree.
* @return Returns tree of out nodes.
*/
export function unflatten<Node>(
list: ArrayLike<Node>,
isChildNode: (node: Node, parentNode: Node) => boolean,
addChildNode: (node: Node, parentNode: Node) => void
): Node[];
export function unflatten<Node, OutNode>(
list: ArrayLike<Node>,
isChildNode: (node: Node, parentNode: Node) => boolean,
addChildNode: (node: OutNode, parentNode: OutNode) => void,
convertNode: (node: Node) => OutNode
): OutNode[];
export function unflatten<Node, OutNode>(
list: ArrayLike<Node>,
isChildNode: (node: Node, parentNode: Node) => boolean,
addChildNode: (node: Node | OutNode, parentNode: Node | OutNode) => void,
convertNode?: (node: Node) => OutNode
): Array<Node | OutNode> {
if (convertNode === undefined) {
return reduce.call(
list,
(tree: Node[], node: Node) => {
const parentNode = find(list, parent => isChildNode(node, parent));
if (parentNode === undefined) {
tree.push(node);
} else {
addChildNode(node, parentNode);
}
return tree;
},
[]
);
} else {
const mappedList: { in: Node; out: OutNode }[] = map.call(list, (node: Node) => ({
in: node,
out: convertNode(node)
}));
return reduce.call(
mappedList,
(tree: OutNode[], node: { in: Node; out: OutNode }) => {
const parentNode = find(mappedList, parent => isChildNode(node.in, parent.in));
if (parentNode === undefined) {
tree.push(node.out);
} else {
addChildNode(node.out, find(mappedList, treeNode => treeNode.in === parentNode.in).out);
}
return tree;
},
[]
);
}
}