sorted-array.ts 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275
  1. /**
  2. * Copyright (c) 2017 molio contributors, licensed under MIT, See LICENSE file for more info.
  3. *
  4. * @author David Sehnal <david.sehnal@gmail.com>
  5. */
  6. import { sortArray } from '../../sort'
  7. import { hash3, hash4 } from '../../hash-functions'
  8. import Interval from '../interval'
  9. type Nums = ArrayLike<number>
  10. export const Empty: Nums = []
  11. export function ofSortedArray(xs: Nums) {
  12. if (xs.length < 1) throw new Error('Sorted arrays must be non-empty.');
  13. return xs;
  14. }
  15. export function ofUnsortedArray(xs: Nums) { sortArray(xs); return xs; }
  16. export function is(xs: any): xs is Nums { return xs && (Array.isArray(xs) || !!xs.buffer); }
  17. export function start(xs: Nums) { return xs[0]; }
  18. export function end(xs: Nums) { return xs[xs.length - 1] + 1; }
  19. export function min(xs: Nums) { return xs[0]; }
  20. export function max(xs: Nums) { return xs[xs.length - 1]; }
  21. export function size(xs: Nums) { return xs.length; }
  22. export function hashCode(xs: Nums) {
  23. // hash of tuple (size, min, max, mid)
  24. const s = xs.length;
  25. if (!s) return 0;
  26. if (s > 2) return hash4(s, xs[0], xs[s - 1], xs[s << 1]);
  27. return hash3(s, xs[0], xs[s - 1]);
  28. }
  29. export function indexOf(xs: Nums, v: number) {
  30. const l = xs.length;
  31. return l === 0 ? -1 : xs[0] <= v && v <= xs[l - 1] ? binarySearchRange(xs, v, 0, l) : -1;
  32. }
  33. export function indexOfInterval(xs: Nums, v: number, bounds: Interval) {
  34. const l = xs.length;
  35. const s = Interval.start(bounds), e = Interval.end(bounds);
  36. return l === 0 || e <= s ? -1 : xs[s] <= v && v <= xs[e - 1] ? binarySearchRange(xs, v, s, e) : -1;
  37. }
  38. export function has(xs: Nums, v: number) { return indexOf(xs, v) >= 0; }
  39. export function getAt(xs: Nums, i: number) { return xs[i]; }
  40. export function areEqual(a: Nums, b: Nums) {
  41. if (a === b) return true;
  42. const aSize = a.length;
  43. if (aSize !== b.length || a[0] !== b[0] || a[aSize - 1] !== b[aSize - 1]) return false;
  44. for (let i = 0; i < aSize; i++) {
  45. if (a[i] !== b[i]) return false;
  46. }
  47. return true;
  48. }
  49. export function findPredecessorIndex(xs: Nums, v: number) {
  50. const len = xs.length;
  51. if (v <= xs[0]) return 0;
  52. if (v > xs[len - 1]) return len;
  53. return binarySearchPredIndexRange(xs, v, 0, len);
  54. }
  55. export function findPredecessorIndexInInterval(xs: Nums, v: number, bounds: Interval) {
  56. const s = Interval.start(bounds), e = Interval.end(bounds);
  57. if (v <= xs[s]) return s;
  58. if (e > s && v > xs[e - 1]) return e;
  59. return binarySearchPredIndexRange(xs, v, s, e);
  60. }
  61. export function findRange(xs: Nums, min: number, max: number) {
  62. return Interval.ofBounds(findPredecessorIndex(xs, min), findPredecessorIndex(xs, max + 1));
  63. }
  64. function binarySearchRange(xs: Nums, value: number, start: number, end: number) {
  65. let min = start, max = end - 1;
  66. while (min <= max) {
  67. if (min + 11 > max) {
  68. for (let i = min; i <= max; i++) {
  69. if (value === xs[i]) return i;
  70. }
  71. return -1;
  72. }
  73. const mid = (min + max) >> 1;
  74. const v = xs[mid];
  75. if (value < v) max = mid - 1;
  76. else if (value > v) min = mid + 1;
  77. else return mid;
  78. }
  79. return -1;
  80. }
  81. function binarySearchPredIndexRange(xs: Nums, value: number, start: number, end: number) {
  82. let min = start, max = end - 1;
  83. while (min < max) {
  84. const mid = (min + max) >> 1;
  85. const v = xs[mid];
  86. if (value < v) max = mid - 1;
  87. else if (value > v) min = mid + 1;
  88. else return mid;
  89. }
  90. if (min > max) return max + 1;
  91. return xs[min] >= value ? min : min + 1;
  92. }
  93. export function areIntersecting(a: Nums, b: Nums) {
  94. if (a === b) return true;
  95. let { startI: i, startJ: j, endI, endJ } = getSuitableIntersectionRange(a, b);
  96. while (i < endI && j < endJ) {
  97. const x = a[i], y = b[j];
  98. if (x < y) { i++; }
  99. else if (x > y) { j++; }
  100. else return true;
  101. }
  102. return false;
  103. }
  104. export function isSubset(a: Nums, b: Nums) {
  105. if (a === b) return true;
  106. const lenB = b.length;
  107. let { startI: i, startJ: j, endI, endJ } = getSuitableIntersectionRange(a, b);
  108. // must be able to advance by lenB elements
  109. if (endJ - j < lenB || endI - i < lenB) return false;
  110. let equal = 0;
  111. while (i < endI && j < endJ) {
  112. const x = a[i], y = b[j];
  113. if (x < y) { i++; }
  114. else if (x > y) { j++; }
  115. else { i++; j++; equal++; }
  116. }
  117. return equal === lenB;
  118. }
  119. export function union(a: Nums, b: Nums) {
  120. if (a === b) return a;
  121. const { startI: sI, startJ: sJ, endI, endJ } = getSuitableIntersectionRange(a, b);
  122. let i = sI, j = sJ;
  123. let commonCount = 0;
  124. while (i < endI && j < endJ) {
  125. const x = a[i], y = b[j];
  126. if (x < y) { i++; }
  127. else if (x > y) { j++; }
  128. else { i++; j++; commonCount++; }
  129. }
  130. const lenA = a.length, lenB = b.length;
  131. // A === B || B is subset of A ==> A
  132. if ((commonCount === lenA && commonCount === lenB) || commonCount === lenB) return a;
  133. // A is subset of B ===> B
  134. if (commonCount === lenA) return b;
  135. const indices = new Int32Array(lenA + lenB - commonCount);
  136. let offset = 0;
  137. // insert the "prefixes"
  138. for (let k = 0; k < sI; k++) indices[offset++] = a[k];
  139. for (let k = 0; k < sJ; k++) indices[offset++] = b[k];
  140. // insert the common part
  141. i = sI;
  142. j = sJ;
  143. while (i < endI && j < endJ) {
  144. const x = a[i], y = b[j];
  145. if (x < y) { indices[offset++] = x; i++; }
  146. else if (x > y) { indices[offset++] = y; j++; }
  147. else { indices[offset++] = x; i++; j++; }
  148. }
  149. // insert the "tail"
  150. for (; i < lenA; i++) indices[offset++] = a[i];
  151. for (; j < lenB; j++) indices[offset++] = b[j];
  152. return ofSortedArray(indices);
  153. }
  154. export function intersect(a: Nums, b: Nums) {
  155. if (a === b) return a;
  156. const { startI: sI, startJ: sJ, endI, endJ } = getSuitableIntersectionRange(a, b);
  157. let i = sI, j = sJ;
  158. let commonCount = 0;
  159. while (i < endI && j < endJ) {
  160. const x = a[i], y = b[j];
  161. if (x < y) { i++; }
  162. else if (x > y) { j++; }
  163. else { i++; j++; commonCount++; }
  164. }
  165. const lenA = a.length, lenB = b.length;
  166. // no common elements
  167. if (!commonCount) return Empty;
  168. // A === B || B is subset of A ==> B
  169. if ((commonCount === lenA && commonCount === lenB) || commonCount === lenB) return b;
  170. // A is subset of B ==> A
  171. if (commonCount === lenA) return a;
  172. const indices = new Int32Array(commonCount);
  173. let offset = 0;
  174. i = sI;
  175. j = sJ;
  176. while (i < endI && j < endJ) {
  177. const x = a[i], y = b[j];
  178. if (x < y) { i++; }
  179. else if (x > y) { j++; }
  180. else { indices[offset++] = x; i++; j++; }
  181. }
  182. return ofSortedArray(indices);
  183. }
  184. export function subtract(a: Nums, b: Nums) {
  185. if (a === b) return Empty;
  186. const lenA = a.length;
  187. const { startI: sI, startJ: sJ, endI, endJ } = getSuitableIntersectionRange(a, b);
  188. let i = sI, j = sJ;
  189. let commonCount = 0;
  190. while (i < endI && j < endJ) {
  191. const x = a[i], y = b[j];
  192. if (x < y) { i++; }
  193. else if (x > y) { j++; }
  194. else { i++; j++; commonCount++; }
  195. }
  196. // A isnt intersecting B ===> A
  197. if (!commonCount) return a;
  198. // A === B || A is subset of B ===> Empty
  199. if (commonCount >= lenA) return Empty;
  200. const indices = new Int32Array(lenA - commonCount);
  201. let offset = 0;
  202. // insert the "prefix"
  203. for (let k = 0; k < sI; k++) indices[offset++] = a[k];
  204. i = sI;
  205. j = sJ;
  206. while (i < endI && j < endJ) {
  207. const x = a[i], y = b[j];
  208. if (x < y) { indices[offset++] = x; i++; }
  209. else if (x > y) { j++; }
  210. else { i++; j++; }
  211. }
  212. // insert the "tail"
  213. for (; i < lenA; i++) indices[offset++] = a[i];
  214. return ofSortedArray(indices);
  215. }
  216. const _maxIntRangeRet = { startI: 0, startJ: 0, endI: 0, endJ: 0 };
  217. // for small sets, just gets the whole range, for large sets does a bunch of binary searches
  218. export function getSuitableIntersectionRange(a: Nums, b: Nums) {
  219. const la = a.length, lb = b.length;
  220. const ratio = la / lb;
  221. if (ratio <= 0.5 || ratio >= 2 || (la >= 128 && lb >= 128)) {
  222. _maxIntRangeRet.startI = findPredecessorIndex(a, start(b));
  223. _maxIntRangeRet.startJ = findPredecessorIndex(b, start(a));
  224. _maxIntRangeRet.endI = findPredecessorIndex(a, end(b));
  225. _maxIntRangeRet.endJ = findPredecessorIndex(b, end(a));
  226. } else {
  227. _maxIntRangeRet.startI = 0;
  228. _maxIntRangeRet.startJ = 0;
  229. _maxIntRangeRet.endI = la;
  230. _maxIntRangeRet.endJ = lb;
  231. }
  232. return _maxIntRangeRet;
  233. }