-
Notifications
You must be signed in to change notification settings - Fork 0
/
nd_binary_indexed_tree.js
141 lines (127 loc) · 3.46 KB
/
nd_binary_indexed_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
/*
* Ex: 0110 -> 0010
*/
function lsbMask(x) {
return x & -x;
}
function tailMask(x) {
return lsbMask(x) << 1;
}
function isRightChild(x) {
return (x & tailMask(x)) > 0;
}
/*
* Returns the integer encoding the input bit path
* without the last bit.
* Ex: path 0101 (lrlr) encodes as 010110
* one step back is encoded as 010100
* another step back is 011000
*/
function shortenBitPath(x) {
return (
x - lsbMask(x) // clear lsb
| tailMask(x) // move lsb up one
);
}
/**
* Encoding a binary tree in an array using bit paths
* is simplest with indicies starting at 1. We offset
* all path operations by -1 to save space and provide a
* familiar 0-indexed API.
*/
class BITree {
constructor (options, dimension=0) {
let {dimensions, initialValues} = options;
if(initialValues) {
dimensions = [];
for(let dim = initialValues; dim && dim.length; dim = dim[0]) {
dimensions.push(dim.length);
}
}
if(!dimensions || !dimensions.length) {
throw new Error('provide either dimensions or initial values');
}
// expected number of entries at this dimension
let size = dimensions[dimension];
// next larger power of 2 (we need a complete binary tree)
let k = Math.ceil(Math.log(size + 1) / Math.log(2));
// index into vectors
this.dimension = dimension;
this.isLastDimension = (dimension === dimensions.length - 1);
// exclusive
this.indexCap = (1 << k) - 1;
// index of the root of the tree
this.rootIndex = (1 << (k-1)) - 1;
// holds the next dimesion of sums, or
// the cumulative values for the last dimension.
this.cumulative = new Array(this.indexCap);
for(let i=0; i<this.indexCap; i++) {
this.cumulative[i] = (this.isLastDimension ? 0 :
new BITree({dimensions}, dimension+1)
);
}
if(initialValues) {
this._initialize(initialValues);
}
}
_initialize(values) {
let path = [];
for(let v of this._walk(values, path)) {
this.adjust(path, v);
}
}
/*
* Returns a generator that iterates the values of
* an N-dimensional nested array strucuture.
* If the provided, the argument path array will contain the
* path the to the current value after each iteration.
*/
*_walk(values, path=[]) {
for(let i=0; i<values.length; i++) {
path.push(i);
let v = values[i];
if(v.length) {
yield * this._walk(v, path);
} else {
yield v;
}
path.pop();
}
}
/**
* Returns the cumulative total of values through index i.
*/
sumPrefix(vector) {
let i = vector[this.dimension];
let total = 0;
while(i+1) {
let partial = (this.isLastDimension ?
this.cumulative[i] :
// recurse into the next dimension
this.cumulative[i].sumPrefix(vector)
);
total += partial;
// Simplified from: (i + 1) - lsbMask(i + 1) - 1
i -= lsbMask(i+1);
}
return total;
}
adjust(vector, diff) {
let i = vector[this.dimension];
let shouldAccumulate = true;
// Propagate up the tree, applying the diff
// whenever we came from a left child
while(i < this.indexCap) {
if(!shouldAccumulate){
// no op
} else if(this.isLastDimension) {
this.cumulative[i] += diff;
} else {
this.cumulative[i].adjust(vector, diff);
}
shouldAccumulate = !isRightChild(i+1);
i = shortenBitPath(i+1) - 1;
}
}
}
module.exports = BITree;