forked from wwwtyro/keyzen
-
Notifications
You must be signed in to change notification settings - Fork 5
/
js-weighted-list.js
executable file
·169 lines (155 loc) · 4.51 KB
/
js-weighted-list.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
/**
* js-weighted-list.js
*
* version 0.1
*
* This file is licensed under the MIT License, please see MIT-LICENSE.txt for details.
*
* https://github.com/timgilbert/js-weighted-list is its home.
*/
function WeightedList(initial) {
this.weights = {};
this.data = {};
this.length = 0;
this.hasData = false;
//console.debug(initial);
if (initial != null) {
for (var i = 0; i < initial.length; i++) {
var item = initial[i]
this.push(item[0], item[1], item[2]);
}
}
}
WeightedList.prototype = {
/**
* Add an item to the list
*/
push: function(key, weight, data) {
//console.debug('k:', key, 'w:', weight, 'd:', data);
this.weights[key] = weight;
if (data != null) {
this.hasData = true;
this.data[key] = data;
}
this.length++;
},
/**
* Add the given weight to the list item with the given key
*/
addWeight: function(key, weight) {
this.weights[key] += weight;
},
/**
* Select n random elements (without replacement), default 1.
* If andRemove is true (default false), remove the elements
* from the list. (This is what the pop() method does.)
*/
peek: function(n, andRemove) {
if (n == null) {
n = 1;
}
andRemove = !!andRemove;
heap = this._buildWeightedHeap();
//console.debug('heap:', heap);
result = [];
for (var i = 0; i < n; i++) {
key = heap.pop();
//console.debug('k:', key);
if (this.hasData) {
result.push({key: key, value: this.data[key]});
} else {
result.push(key);
}
if (andRemove) {
delete this.weights[key];
delete this.values[key];
this.length--;
}
}
return result;
},
/**
* Return the entire list in a random order (note that this does not mutate the list)
*/
shuffle: function() {
return this.peek(this.length);
},
/**
*
*/
pop: function(n) {
return peek(n, true);
},
/**
* Build a WeightedHeap instance based on the data we've got
*/
_buildWeightedHeap: function() {
var items = [];
for (var key in this.weights) {
// skip over Object.prototype monkey-patching per
// http://bonsaiden.github.com/JavaScript-Garden/#object.forinloop
if (this.weights.hasOwnProperty(key)) {
items.push([key, this.weights[key]]);
}
}
//console.log('items',items);
return new _WeightedHeap(items);
}
}
/**
* This is a javascript implementation of the algorithm described by
* Jason Orendorff here: http://stackoverflow.com/a/2149533/87990
*/
function _HeapNode(weight, value, total) {
this.weight = weight;
this.value = value;
this.total = total; // Total weight of this node and its children
}
/**
* Note, we're using a heap structure here for its tree properties, not as a
* classic binary heap. A node heap[i] has children at heap[i<<1] and at
* heap[(i<<1)+1]. Its parent is at h[i>>1]. Heap[0] is vacant.
*/
function _WeightedHeap(items) {
this.heap = [null]; // Math is easier to read if we index array from 1
// First put everything on the heap
for (var i = 0; i < items.length; i++) {
var weight = items[i][1];
var value = items[i][0];
this.heap.push(new _HeapNode(weight, value, weight));
}
// Now go through the heap and each node's weight to its parent
for (i = this.heap.length - 1; i > 1; i--) {
this.heap[i>>1].total += this.heap[i].total;
}
//console.debug('_Wh heap', this.heap);
}
_WeightedHeap.prototype = {
pop: function() {
// Start with a random amount of gas
var gas = this.heap[1].total * Math.random();
// Start driving at the root node
var i = 1;
// While we have enough gas to keep going past i:
while (gas > this.heap[i].weight) {
gas -= this.heap[i].weight; // Drive past i
i <<= 1; // Move to first child
if (gas > this.heap[i].total) {
gas -= this.heap[i].total // Drive past first child and its descendants
i++; // Move on to second child
}
}
// Out of gas - i is our selected node.
var value = this.heap[i].value;
var selectedWeight = this.heap[i].weight;
this.heap[i].weight = 0; // Make sure i isn't chosen again
while (i > 0) {
// Remove the weight from its parent's total
this.heap[i].total -= selectedWeight
i >>= 1; // Move to the next parent
}
return value;
}
};
// NB: another binary heap implementation is at
// http://eloquentjavascript.net/appendix2.html