IntegerBufferSet.js.flow 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206
  1. /**
  2. * Copyright (c) 2013-present, Facebook, Inc.
  3. *
  4. * This source code is licensed under the MIT license found in the
  5. * LICENSE file in the root directory of this source tree.
  6. *
  7. * @providesModule IntegerBufferSet
  8. * @typechecks
  9. */
  10. 'use strict';
  11. const Heap = require("./Heap");
  12. const invariant = require("./invariant"); // Data structure that allows to store values and assign positions to them
  13. // in a way to minimize changing positions of stored values when new ones are
  14. // added or when some values are replaced. Stored elements are alwasy assigned
  15. // a consecutive set of positoins startin from 0 up to count of elements less 1
  16. // Following actions can be executed
  17. // * get position assigned to given value (null if value is not stored)
  18. // * create new entry for new value and get assigned position back
  19. // * replace value that is furthest from specified value range with new value
  20. // and get it's position back
  21. // All operations take amortized log(n) time where n is number of elements in
  22. // the set.
  23. class IntegerBufferSet {
  24. constructor() {
  25. this._valueToPositionMap = {};
  26. this._size = 0;
  27. this._smallValues = new Heap([], // Initial data in the heap
  28. this._smallerComparator);
  29. this._largeValues = new Heap([], // Initial data in the heap
  30. this._greaterComparator);
  31. this.getNewPositionForValue = this.getNewPositionForValue.bind(this);
  32. this.getValuePosition = this.getValuePosition.bind(this);
  33. this.getSize = this.getSize.bind(this);
  34. this.replaceFurthestValuePosition = this.replaceFurthestValuePosition.bind(this);
  35. }
  36. getSize()
  37. /*number*/
  38. {
  39. return this._size;
  40. }
  41. getValuePosition(
  42. /*number*/
  43. value)
  44. /*?number*/
  45. {
  46. if (this._valueToPositionMap[value] === undefined) {
  47. return null;
  48. }
  49. return this._valueToPositionMap[value];
  50. }
  51. getNewPositionForValue(
  52. /*number*/
  53. value)
  54. /*number*/
  55. {
  56. invariant(this._valueToPositionMap[value] === undefined, "Shouldn't try to find new position for value already stored in BufferSet");
  57. const newPosition = this._size;
  58. this._size++;
  59. this._pushToHeaps(newPosition, value);
  60. this._valueToPositionMap[value] = newPosition;
  61. return newPosition;
  62. }
  63. replaceFurthestValuePosition(
  64. /*number*/
  65. lowValue,
  66. /*number*/
  67. highValue,
  68. /*number*/
  69. newValue)
  70. /*?number*/
  71. {
  72. invariant(this._valueToPositionMap[newValue] === undefined, "Shouldn't try to replace values with value already stored value in " + "BufferSet");
  73. this._cleanHeaps();
  74. if (this._smallValues.empty() || this._largeValues.empty()) {
  75. // Threre are currently no values stored. We will have to create new
  76. // position for this value.
  77. return null;
  78. }
  79. const minValue = this._smallValues.peek().value;
  80. const maxValue = this._largeValues.peek().value;
  81. if (minValue >= lowValue && maxValue <= highValue) {
  82. // All values currently stored are necessary, we can't reuse any of them.
  83. return null;
  84. }
  85. let valueToReplace;
  86. if (lowValue - minValue > maxValue - highValue) {
  87. // minValue is further from provided range. We will reuse it's position.
  88. valueToReplace = minValue;
  89. this._smallValues.pop();
  90. } else {
  91. valueToReplace = maxValue;
  92. this._largeValues.pop();
  93. }
  94. const position = this._valueToPositionMap[valueToReplace];
  95. delete this._valueToPositionMap[valueToReplace];
  96. this._valueToPositionMap[newValue] = position;
  97. this._pushToHeaps(position, newValue);
  98. return position;
  99. }
  100. _pushToHeaps(
  101. /*number*/
  102. position,
  103. /*number*/
  104. value) {
  105. const element = {
  106. position,
  107. value
  108. }; // We can reuse the same object in both heaps, because we don't mutate them
  109. this._smallValues.push(element);
  110. this._largeValues.push(element);
  111. }
  112. _cleanHeaps() {
  113. // We not usually only remove object from one heap while moving value.
  114. // Here we make sure that there is no stale data on top of heaps.
  115. this._cleanHeap(this._smallValues);
  116. this._cleanHeap(this._largeValues);
  117. const minHeapSize = Math.min(this._smallValues.size(), this._largeValues.size());
  118. const maxHeapSize = Math.max(this._smallValues.size(), this._largeValues.size());
  119. if (maxHeapSize > 10 * minHeapSize) {
  120. // There are many old values in one of heaps. We nned to get rid of them
  121. // to not use too avoid memory leaks
  122. this._recreateHeaps();
  123. }
  124. }
  125. _recreateHeaps() {
  126. const sourceHeap = this._smallValues.size() < this._largeValues.size() ? this._smallValues : this._largeValues;
  127. const newSmallValues = new Heap([], // Initial data in the heap
  128. this._smallerComparator);
  129. const newLargeValues = new Heap([], // Initial datat in the heap
  130. this._greaterComparator);
  131. while (!sourceHeap.empty()) {
  132. const element = sourceHeap.pop(); // Push all stil valid elements to new heaps
  133. if (this._valueToPositionMap[element.value] !== undefined) {
  134. newSmallValues.push(element);
  135. newLargeValues.push(element);
  136. }
  137. }
  138. this._smallValues = newSmallValues;
  139. this._largeValues = newLargeValues;
  140. }
  141. _cleanHeap(
  142. /*object*/
  143. heap) {
  144. while (!heap.empty() && this._valueToPositionMap[heap.peek().value] === undefined) {
  145. heap.pop();
  146. }
  147. }
  148. _smallerComparator(
  149. /*object*/
  150. lhs,
  151. /*object*/
  152. rhs)
  153. /*boolean*/
  154. {
  155. return lhs.value < rhs.value;
  156. }
  157. _greaterComparator(
  158. /*object*/
  159. lhs,
  160. /*object*/
  161. rhs)
  162. /*boolean*/
  163. {
  164. return lhs.value > rhs.value;
  165. }
  166. }
  167. module.exports = IntegerBufferSet;