qheap.js 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261
  1. /**
  2. * nodejs heap, classic array implementation
  3. *
  4. * Items are stored in a balanced binary tree packed into an array where
  5. * node is at [i], left child is at [2*i], right at [2*i+1]. Root is at [1].
  6. *
  7. * Copyright (C) 2014-2020 Andras Radics
  8. * Licensed under the Apache License, Version 2.0
  9. */
  10. 'use strict';
  11. //module.exports = Heap;
  12. function isBeforeDefault( a, b ) { return a < b; }
  13. function Heap( opts ) {
  14. if (!(this instanceof Heap)) return new Heap(opts);
  15. if (typeof opts === 'function') opts = {compar: opts};
  16. // copy out known options to not bind to caller object
  17. this.options = !opts ? {} : {
  18. compar: opts.compar,
  19. comparBefore: opts.comparBefore,
  20. freeSpace: opts.freeSpace,
  21. size: opts.size,
  22. };
  23. opts = this.options;
  24. var self = this;
  25. this._isBefore = opts.compar ? function(a, b) { return opts.compar(a, b) < 0 } : opts.comparBefore || isBeforeDefault;
  26. this._sortBefore = opts.compar || function(a, b) { return self._isBefore(a, b) ? -1 : 1 };
  27. this._freeSpace = opts.freeSpace ? this._trimArraySize : false;
  28. this._list = new Array(opts.size || 20);
  29. // 14% slower to mix ints and pointers in an array, even if deleted
  30. // this._list[0] = undefined;
  31. this.length = 0;
  32. }
  33. Heap.prototype._list = null;
  34. Heap.prototype._compar = null;
  35. Heap.prototype._isBefore = null;
  36. Heap.prototype._freeSpace = null;
  37. Heap.prototype._sortBefore = null;
  38. Heap.prototype.length = 0;
  39. /*
  40. * insert new item at end, and bubble up
  41. */
  42. Heap.prototype.insert = function Heap_insert( item ) {
  43. var idx = ++this.length;
  44. return this._bubbleup(idx, item);
  45. };
  46. Heap.prototype._bubbleup = function _bubbleup( idx, item ) {
  47. var list = this._list;
  48. list[idx] = item;
  49. while (idx > 1) {
  50. if (!(this._isBefore(item, list[idx >> 1]))) break;
  51. list[idx] = list[idx >> 1];
  52. idx = idx >> 1;
  53. }
  54. list[idx] = item;
  55. };
  56. Heap.prototype.append = Heap.prototype.insert;
  57. Heap.prototype.push = Heap.prototype.insert;
  58. Heap.prototype.unshift = Heap.prototype.insert;
  59. Heap.prototype.enqueue = Heap.prototype.insert;
  60. Heap.prototype.peek = function Heap_peek( ) {
  61. return this.length > 0 ? this._list[1] : undefined;
  62. };
  63. Heap.prototype.size = function Heap_size( ) {
  64. return this.length;
  65. };
  66. /*
  67. * return the root, and bubble down last item from top root position
  68. * when bubbling down, r: root idx, c: child sub-tree root idx, cv: child root value
  69. * Note that the child at (c == this.length) does not have to be tested in the loop,
  70. * since its value is the one being bubbled down, so can loop `while (c < len)`.
  71. */
  72. Heap.prototype.remove = function Heap_remove( ) {
  73. var len = this.length;
  74. if (len < 1) return undefined;
  75. return this._bubbledown(1, len);
  76. /**
  77. // experiment: ripple down hole from root, bubble up last from hole
  78. var list = this._list;
  79. var ret = list[1];
  80. var holeIdx = this._rippleup(1, len);
  81. this._bubbleup(holeIdx, list[this.length--]);
  82. if (this._freeSpace) this._freeSpace(list, len);
  83. return ret;
  84. /**/
  85. };
  86. Heap.prototype._bubbledown = function _bubbledown( r, len ) {
  87. var list = this._list, ret = list[r], itm = list[len];
  88. var c, _isBefore = this._isBefore;
  89. while ((c = r << 1) < len) {
  90. var cv = list[c], cv1 = list[c+1];
  91. if (_isBefore(cv1, cv)) { c++; cv = cv1; }
  92. if (!(_isBefore(cv, itm))) break;
  93. list[r] = cv;
  94. r = c;
  95. }
  96. list[r] = itm;
  97. list[len] = 0;
  98. this.length = --len;
  99. if (this._freeSpace) this._freeSpace(this._list, this.length);
  100. return ret;
  101. };
  102. /**
  103. Heap.prototype._rippleup = function _rippleup( r, len ) {
  104. var list = this._list;
  105. var c, _isBefore = this._isBefore;
  106. while ((c = r << 1) < len) {
  107. var cv = list[c];
  108. var cv1 = list[c+1];
  109. if (_isBefore(cv1, cv)) { cv = cv1; c = c+1 }
  110. list[r] = cv;
  111. r = c;
  112. }
  113. if (c === len) {
  114. list[r] = list[c];
  115. r = c;
  116. }
  117. return r;
  118. };
  119. /**/
  120. Heap.prototype.shift = Heap.prototype.remove;
  121. Heap.prototype.pop = Heap.prototype.remove;
  122. Heap.prototype.dequeue = Heap.prototype.remove;
  123. // builder, not initializer: appends items, not replaces
  124. Heap.prototype.fromArray = function fromArray( array, base, bound ) {
  125. var base = base || 0;
  126. var bound = bound || array.length;
  127. for (var i=base; i<bound; i++) this.insert(array[i]);
  128. }
  129. Heap.prototype.toArray = function toArray( limit ) {
  130. limit = typeof limit === 'number' ? limit + 1 : this.length + 1;
  131. return this._list.slice(1, limit);
  132. }
  133. Heap.prototype.copy = function copy( ) {
  134. var ret = new Heap(this.options);
  135. for (var i = 1; i <= this.length; i++) ret._list[i] = this._list[i];
  136. ret.length = this.length;
  137. return ret;
  138. }
  139. Heap.prototype.peekHead = function peekHead( n ) {
  140. // todo: think about a more efficient approach than cloning the list
  141. // todo: would be more efficient to sort the whole list if peeking at say over 50% of the items
  142. var copy = this.copy();
  143. var item, head = new Array();
  144. while (head.length < n && (item = copy.shift()) !== undefined) {
  145. head.push(item);
  146. }
  147. return head;
  148. }
  149. // sort the contents of the storage array
  150. // Trim the array for the sort, ensure first item is smallest, sort.
  151. // Note that sorted order is valid heap format.
  152. // Truncating the list is faster than copy-out/copy-in.
  153. // node-v11 and up are 5x faster to sort 1k numbers than v10.
  154. Heap.prototype.sort = function sort( ) {
  155. if (this.length < 3) return;
  156. this._list.splice(this.length + 1);
  157. this._list[0] = this._list[1];
  158. this._list.sort(this._sortBefore);
  159. this._list[0] = 0;
  160. }
  161. // generate a uniformly distributed subsample of k items (Reservoir Algorithm)
  162. // First select the first k items, then once have k samples
  163. // randomly replace a selection with the i-th item i>k, with k/i probability.
  164. // Eg: pick 2 [1,2,3]: get [1,2], replace 3 with 2/3 probability into slot [0] or [1].
  165. // Note that this._list is indexed 1..N, but samples are indexed 0..k-1
  166. // Note: if k is much smaller than .length, would be faster to
  167. // generate k unique random array indexes than N random values.
  168. Heap.prototype.subsample = function subsample( k, options ) {
  169. options = options || {};
  170. var samples = new Array();
  171. if (k > this.length) k = this.length;
  172. for (var i = 1; i <= k; i++) samples[i - 1] = this._list[i];
  173. for (var i = k + 1; i <= this.length; i++) {
  174. var j = Math.floor(Math.random() * i);
  175. if (j < k) samples[j] = this._list[i];
  176. }
  177. if (options.sort) samples.sort(this._sortBefore);
  178. return samples;
  179. }
  180. /*
  181. * Free unused storage slots in the _list.
  182. * The default is to unconditionally gc, use the options to omit when not useful.
  183. */
  184. Heap.prototype.gc = function Heap_gc( options ) {
  185. if (!options) options = {};
  186. var minListLength = options.minLength; // smallest list that will be gc-d
  187. if (minListLength === undefined) minListLength = 0;
  188. var minListFull = options.minFull; // list utilization below which to gc
  189. if (minListFull === undefined) minListFull = 1.00;
  190. if (this._list.length >= minListLength && (this.length < this._list.length * minListFull)) {
  191. // gc reallocates the array to free the unused storage slots at the end
  192. // use splice to actually free memory; 7% slower than setting .length
  193. // note: list.slice makes the array slower to insert to?? splice is better
  194. this._list.splice(this.length+1, this._list.length);
  195. }
  196. }
  197. Heap.prototype._trimArraySize = function Heap__trimArraySize( list, len ) {
  198. if (len > 10000 && list.length > 4 * len) {
  199. // use slice to actually free memory; 7% slower than setting .length
  200. // note: list.slice makes the array slower to insert to?? splice is better
  201. list.splice(len+1, list.length);
  202. }
  203. }
  204. Heap.prototype._check = function Heap__check( ) {
  205. var isBefore = this._isBefore;
  206. var _compar = this._sortBefore;
  207. var i, p, fail = 0;
  208. for (i=this.length; i>1; i--) {
  209. // error if parent should go after child, but not if don`t care
  210. p = i >>> 1;
  211. // swapping the values must change their ordering, otherwise the
  212. // comparison is a tie. (Ie, consider the ordering func (a <= b)
  213. // that for some values reports both that a < b and b < a.)
  214. if (_compar(this._list[p], this._list[i]) > 0 &&
  215. _compar(this._list[i], this._list[p]) < 0)
  216. {
  217. fail = i;
  218. }
  219. }
  220. if (fail) console.log("failed at", (fail >>> 1), fail);
  221. return !fail;
  222. }
  223. // optimize access
  224. Heap.prototype = toStruct(Heap.prototype);
  225. function toStruct(o) { return toStruct.prototype = o }