forked from google/lovefield
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathtree.js
392 lines (347 loc) · 12.2 KB
/
tree.js
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
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
/**
* @license
* Copyright 2014 The Lovefield Project Authors. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
goog.provide('lf.tree');
goog.require('goog.asserts');
/**
* Creates a new tree with the exact same structure, where every node in the
* tree has been replaced by a new node according to the mapping function. This
* is equivalent to Array#map, but for a tree data structure.
* Note: T1 and T2 are expected to be either lf.structs.TreeNode or subtypes
* but there is no way to currently express that in JS compiler annotations.
*
* @param {T1} original
* @param {!function(T1):T2} mapFn
* @return {T2}
* @template T1, T2
*/
lf.tree.map = function(original, mapFn) {
// A stack storing nodes that will be used as parents later in the traversal.
var copyParentStack = [];
/**
* Removes a node from the parent stack, if that node has already reached its
* target number of children.
* @param {?lf.structs.TreeNode} original The original node.
* @param {!lf.structs.TreeNode} clone The corresponding cloned node.
*/
var cleanUpParentStack = function(original, clone) {
if (goog.isNull(original)) {
return;
}
var cloneFull = original.getChildCount() == clone.getChildCount();
if (cloneFull) {
var cloneIndex = copyParentStack.indexOf(clone);
if (cloneIndex != -1) {
copyParentStack.splice(cloneIndex, 1);
}
}
};
// The node that should become the parent of the next traversed node.
var nextParent = null;
var copyRoot = null;
original.traverse(
/** @param {!lf.structs.TreeNode} node */
function(node) {
var newNode = mapFn(node);
if (node.getParent() == null) {
copyRoot = newNode;
} else {
nextParent.addChild(newNode);
}
cleanUpParentStack(node.getParent(), nextParent);
if (node.getChildCount() > 1) {
copyParentStack.push(newNode);
}
nextParent = node.isLeaf() ?
copyParentStack[copyParentStack.length - 1] : newNode;
});
return copyRoot;
};
/**
* Finds all leafs node existing in the subtree that starts at the given node.
* @param {!lf.structs.TreeNode} node
* @return {!Array<!lf.structs.TreeNode>}
*/
lf.tree.getLeafNodes = function(node) {
return lf.tree.find(node, function(node) { return node.isLeaf(); });
};
/**
* Removes a node from a tree. It takes care of re-parenting the children of the
* removed node with its parent (if any).
* @param {!lf.structs.TreeNode} node The node to be removed.
* @return {{
* parent: ?lf.structs.TreeNode,
* children: !Array<!lf.structs.TreeNode>
* }} An object holding the parent of the node prior to removal (if any), and
* the children of the node prior to removal.
*/
lf.tree.removeNode = function(node) {
var parentNode = node.getParent();
var originalIndex = 0;
if (!goog.isNull(parentNode)) {
originalIndex = parentNode.getChildren().indexOf(node);
parentNode.removeChild(node);
}
var children = node.getChildren().slice();
children.forEach(
function(child, index) {
node.removeChild(child);
if (!goog.isNull(parentNode)) {
parentNode.addChildAt(child, originalIndex + index);
}
});
return {
parent: parentNode,
children: children
};
};
/**
* Inserts a new node under an existing node. The new node inherits all children
* of the existing node, and the existing node ends up having only the new node
* as a child.
* Example: Calling iusertNodeAt(n2, n6) would result in the following
* transformation.
*
* n1 n1
* / \ / \
* n2 n5 n2 n5
* / \ => /
* n3 n4 n6
* / \
* n3 n4
*
* @param {!lf.structs.TreeNode} existingNode
* @param {!lf.structs.TreeNode} newNode
*/
lf.tree.insertNodeAt = function(existingNode, newNode) {
var children = existingNode.getChildren().slice();
children.forEach(
function(child) {
existingNode.removeChild(child);
newNode.addChild(child);
});
existingNode.addChild(newNode);
};
/**
* Swaps a node with its only child. The child also needs to have exactly one
* child.
* Example: Calling swapNodeWithChild(n2) would result in the following
* transformation.
*
* n1 n1
* / \ / \
* n2 n6 n3 n6
* / => /
* n3 n2
* / \ / \
* n4 n5 n4 n5
*
* @param {!lf.structs.TreeNode} node The node to be swapped.
* @return {!lf.structs.TreeNode} The new root of the subtree that used to
* start where "node" was before swapping.
*/
lf.tree.swapNodeWithChild = function(node) {
goog.asserts.assert(node.getChildCount() == 1);
var child = node.getChildAt(0);
goog.asserts.assert(child.getChildCount() == 1);
lf.tree.removeNode(node);
lf.tree.insertNodeAt(child, node);
return child;
};
/**
* Pushes a node below its only child. It takes care of replicating the node
* only for those branches where it makes sense.
* Example: Calling
* pushNodeBelowChild(
* n2,
* function(grandChild) {return true;},
* function(node) {return node.clone();})
* would result in the following transformation.
*
* n1 n1
* / \ / \
* n2 n6 n3 n6
* / => / \
* n3 n2' n2''
* / \ / \
* n4 n5 n4 n5
*
* where n2 has been pushed below n3, on both branches. n2'and n2'' denote that
* copies of the original node were made.
*
* @param {!lf.structs.TreeNode} node The node to be pushed down.
* @param {!function(!lf.structs.TreeNode):boolean} shouldPushDownFn
* A function that is called on every grandchild to determine whether the
* node can be pushed down on that branch.
* @param {function(!lf.structs.TreeNode):!lf.structs.TreeNode} cloneFn
* A function used to clone the node that is being pushed down.
* @return {!lf.structs.TreeNode} The new parent of the subtree that used to
* start at "node" or "node" itself if it could not be pushed down at all.
*/
lf.tree.pushNodeBelowChild = function(node, shouldPushDownFn, cloneFn) {
goog.asserts.assert(node.getChildCount() == 1);
var child = node.getChildAt(0);
goog.asserts.assert(child.getChildCount() > 1);
var grandChildren = child.getChildren().slice();
var canPushDown = grandChildren.some(
function(grandChild) {
return shouldPushDownFn(grandChild);
});
if (!canPushDown) {
return node;
}
lf.tree.removeNode(node);
grandChildren.forEach(
function(grandChild, index) {
if (shouldPushDownFn(grandChild)) {
var newNode = cloneFn(node);
child.removeChildAt(index);
newNode.addChild(grandChild);
child.addChildAt(newNode, index);
}
});
return child;
};
/**
* Replaces a chain of nodes with a new chain of nodes.
* Example: Calling replaceChainWithChain(n2, n3, n7, n8) would result in the
* following transformation.
*
* n1 n1
* / \ / \
* n2 n6 n7 n6
* / => /
* n3 n8
* / \ / \
* n4 n5 n4 n5
*
* @param {!lf.structs.TreeNode} oldHead The head of the chain to be removed.
* @param {!lf.structs.TreeNode} oldTail The tail of the chain to be removed.
* @param {!lf.structs.TreeNode} newHead The head of the chain to be added.
* @param {!lf.structs.TreeNode} newTail The tail of the chain to be added.
* @return {!lf.structs.TreeNode} The new root of the subtree that used to
* start at "oldhead". Effectively the new root is always equal to
* "newHead".
*/
lf.tree.replaceChainWithChain = function(oldHead, oldTail, newHead, newTail) {
var parentNode = oldHead.getParent();
if (!goog.isNull(parentNode)) {
var oldHeadIndex = parentNode.getChildren().indexOf(oldHead);
parentNode.removeChildAt(oldHeadIndex);
parentNode.addChildAt(newHead, oldHeadIndex);
}
oldTail.getChildren().slice().forEach(
function(child) {
oldTail.removeChild(child);
newTail.addChild(child);
});
return newHead;
};
/**
* Removes a node from the tree, and replaces it with a chain of nodes where
* each node in the chain (excluding the tail) has exactly one child.
* Example: Calling replaceNodeWithChain(n6, n10, n12), where the chain consists
* of n7->n8->n9, would result in the following transformation.
*
* n1 n1
* / \ / \
* n2 n6 n2 n10
* / / \ => / \
* n3 n7 n8 n3 n11
* / \ / \ \
* n4 n5 n4 n5 n12
* / \
* n7 n8
*
* @param {!lf.structs.TreeNode} node The node to be removed.
* @param {!lf.structs.TreeNode} head The start of the chain to be inserted.
* @param {!lf.structs.TreeNode} tail The tail of the chain to be inserted.
* @return {!lf.structs.TreeNode} The new root of the subtree that used to
* start at "node". Effectively the new root is always equal to "head".
*/
lf.tree.replaceNodeWithChain = function(node, head, tail) {
return lf.tree.replaceChainWithChain(node, node, head, tail);
};
/**
* Replaces a chain of nodes with a new node.
* Example: Calling replaceChainWithNode(n2, n3, n7) would result in the
* following transformation.
*
* n1 n1
* / \ / \
* n2 n6 n7 n6
* / => / \
* n3 n4 n5
* / \
* n4 n5
*
* @param {!lf.structs.TreeNode} head The head of the chain.
* @param {!lf.structs.TreeNode} tail The tail of the chain.
* @param {!lf.structs.TreeNode} node The node to be added.
* @return {!lf.structs.TreeNode} The new root of the subtree that used to
* start at "head". Effectively the new root is always equal to "node".
*/
lf.tree.replaceChainWithNode = function(head, tail, node) {
return lf.tree.replaceChainWithChain(head, tail, node, node);
};
/**
* Finds all nodes in the given tree that satisfy a given condition.
* @param {!lf.structs.TreeNode} root The root of the tree to search.
* @param {!function(!lf.structs.TreeNode):boolean} filterFn The filter
* function. It will be called on every node of the tree.
* @param {!function(!lf.structs.TreeNode):boolean=} opt_stopFn A function
* that indicates whether searching should be stopped. It will be called on
* every visited node on the tree. If false is returned searching will stop
* for nodes below that node.
* such a function is not provided the entire tree is searched.
* @return {!Array<!lf.structs.TreeNode>}
*/
lf.tree.find = function(root, filterFn, opt_stopFn) {
var results = [];
/** @param {!lf.structs.TreeNode} node */
var filterRec = function(node) {
if (filterFn(node)) {
results.push(node);
}
if (!goog.isDefAndNotNull(opt_stopFn) || !opt_stopFn(node)) {
node.getChildren().forEach(filterRec);
}
};
filterRec(root);
return results;
};
/**
* @param {!lf.structs.TreeNode} rootNode The root node of the tree.
* @param {(!function(!lf.structs.TreeNode):string)=} opt_stringFn The
* function to use for converting a single node to a string. If not provided
* a default function will be used.
* @return {string} A string representation of a tree. Useful for
* testing/debugging.
*/
lf.tree.toString = function(rootNode, opt_stringFn) {
var stringFn = opt_stringFn || function(node) {
return node.toString() + '\n';
};
var out = '';
rootNode.traverse(
function(node) {
for (var i = 0; i < node.getDepth(); i++) {
out += '-';
}
out += stringFn(node);
});
return out;
};