1 | var _ = require('lodash');
|
2 |
|
3 | function decorateTree(tree) {
|
4 | tree.lca = function lowestCommonAncestor(nodes) {
|
5 | var parentNodesInCommon = _.chain(nodes)
|
6 | .map(function (node) {
|
7 | if(!node || !_.isFunction(node.getPath)) {
|
8 | throw new Error('Cannot calculate lca with a null node');
|
9 | }
|
10 | return node.getPath();
|
11 | })
|
12 | .reduce(function (acc, nextPath) {
|
13 | return _.intersection(acc, nextPath)
|
14 | })
|
15 | .value();
|
16 | return parentNodesInCommon.pop();
|
17 | };
|
18 |
|
19 | tree.pathBetween = function pathBetweenNodes(from, to) {
|
20 |
|
21 | var lca, fromPath, toPath, fromLcaIdx, toLcaIdx, pathBetween;
|
22 |
|
23 |
|
24 | lca = tree.lca([from, to]);
|
25 |
|
26 |
|
27 | fromPath = _(from.getPath().reverse());
|
28 |
|
29 |
|
30 | toPath = _(to.getPath());
|
31 |
|
32 |
|
33 | fromLcaIdx = fromPath.findIndex(lca);
|
34 | toLcaIdx = toPath.findIndex(lca);
|
35 |
|
36 |
|
37 | pathBetween = fromPath.slice(0, fromLcaIdx).concat(toPath.slice(toLcaIdx).value());
|
38 |
|
39 | return pathBetween.value();
|
40 | };
|
41 | }
|
42 |
|
43 | function addPrototypeDecorations(tree) {
|
44 | var prototree = Object.getPrototypeOf(tree);
|
45 |
|
46 | if(!shouldDecorateTreePrototype(prototree)) {
|
47 | return;
|
48 | }
|
49 |
|
50 | prototree.depth = function calculateEffectiveNodeDepth(includeCompressedNodes) {
|
51 | var path = this.getPath()
|
52 | , depth = path.length - 1
|
53 | , compressedDepth;
|
54 |
|
55 | if(includeCompressedNodes) {
|
56 | compressedDepth = _.reduce(path, function (acc, n) {
|
57 | return acc + (n.compressedNodes ? n.compressedNodes.length : 0);
|
58 | }, 0);
|
59 | depth += compressedDepth;
|
60 | }
|
61 |
|
62 | return depth;
|
63 | };
|
64 |
|
65 | prototree.pathTo = function(to) {
|
66 | return tree.pathBetween(this, to);
|
67 | };
|
68 |
|
69 | prototree.leafNodes = function findAllLeafNodes() {
|
70 | return this.all(function (node) { return !node.hasChildren(); });
|
71 | };
|
72 |
|
73 | prototree.lcaWith = function(otherNodes) {
|
74 | var nodes = _.clone(otherNodes);
|
75 | nodes.push(this);
|
76 | return tree.lca(nodes);
|
77 | };
|
78 |
|
79 | prototree.filterWalk = function (callback) {
|
80 | function filterWalkRecursive(node) {
|
81 | var evaluateChildren = callback(node);
|
82 | if(evaluateChildren && _.isArray(node.children)) {
|
83 | _.forEach(node.children, filterWalkRecursive)
|
84 | }
|
85 | }
|
86 | return filterWalkRecursive(this);
|
87 | }
|
88 | }
|
89 |
|
90 | function shouldDecorateTreePrototype(prototype) {
|
91 | return !(
|
92 | _.isFunction(prototype.depth) &&
|
93 | _.isFunction(prototype.pathTo) &&
|
94 | _.isFunction(prototype.leafNodes) &&
|
95 | _.isFunction(prototype.lcaWith)
|
96 | )
|
97 | }
|
98 |
|
99 | function indexTree(tree, attrs) {
|
100 | tree.indices = _.chain(attrs)
|
101 | .map(function (attr) {
|
102 | var result = {_attr: attr};
|
103 | tree.walk(function (node) {
|
104 | var key = node.model[attr];
|
105 | if(! _.isUndefined(key) ) {
|
106 | result[key] = node;
|
107 | }
|
108 | });
|
109 | return result;
|
110 | })
|
111 | .indexBy('_attr')
|
112 | .value();
|
113 | }
|
114 |
|
115 | module.exports = {
|
116 | decorateTree: decorateTree,
|
117 | addPrototypeDecorations: addPrototypeDecorations,
|
118 | indexTree: indexTree
|
119 | }; |
\ | No newline at end of file |