sorted-array.ts 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400
  1. /**
  2. * Copyright (c) 2017-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
  3. *
  4. * @author David Sehnal <david.sehnal@gmail.com>
  5. */
  6. import { sortArray, hash3, hash4, createRangeArray } from '../../util';
  7. import { Interval } from '../interval';
  8. type Nums = ArrayLike<number>
  9. export const Empty: Nums = [];
  10. export function ofSingleton(v: number) { return [v]; }
  11. export function ofSortedArray(xs: Nums) { return xs; }
  12. export function ofUnsortedArray(xs: Nums) { sortArray(xs); return xs; }
  13. export function ofRange(min: number, max: number) {
  14. if (max < min) return [];
  15. const ret = new Int32Array(max - min + 1);
  16. for (let i = min; i <= max; i++) ret[i - min] = i;
  17. return ret;
  18. }
  19. export function is(xs: any): xs is Nums { return xs && (Array.isArray(xs) || !!xs.buffer); }
  20. export function start(xs: Nums) { return xs[0]; }
  21. export function end(xs: Nums) { return xs[xs.length - 1] + 1; }
  22. export function min(xs: Nums) { return xs[0]; }
  23. export function max(xs: Nums) { return xs[xs.length - 1]; }
  24. export function size(xs: Nums) { return xs.length; }
  25. export function hashCode(xs: Nums) {
  26. // hash of tuple (size, min, max, mid)
  27. const s = xs.length;
  28. if (!s) return 0;
  29. if (s > 2) return hash4(s, xs[0], xs[s - 1], xs[s >> 1]);
  30. return hash3(s, xs[0], xs[s - 1]);
  31. }
  32. export function toString(xs: Nums) {
  33. const s = xs.length;
  34. if (s > 5) return `[${xs[0]}, ${xs[1]}, ..., ${xs[s - 1]}], length ${s}`;
  35. return `[${(xs as number[]).join(', ')}]`;
  36. }
  37. /** Returns the index of `x` in `set` or -1 if not found. */
  38. export function indexOf(xs: Nums, v: number) {
  39. const l = xs.length;
  40. return l === 0 ? -1 : xs[0] <= v && v <= xs[l - 1] ? binarySearchRange(xs, v, 0, l) : -1;
  41. }
  42. export function indexOfInInterval(xs: Nums, v: number, bounds: Interval) {
  43. return indexOfInRange(xs, v, Interval.start(bounds), Interval.end(bounds));
  44. }
  45. export function indexOfInRange(xs: Nums, v: number, s: number, e: number) {
  46. const l = xs.length;
  47. return l === 0 || e <= s ? -1 : xs[s] <= v && v <= xs[e - 1] ? binarySearchRange(xs, v, s, e) : -1;
  48. }
  49. export function has(xs: Nums, v: number) { return indexOf(xs, v) >= 0; }
  50. export function getAt(xs: Nums, i: number) { return xs[i]; }
  51. export function areEqual(a: Nums, b: Nums) {
  52. if (a === b) return true;
  53. const aSize = a.length;
  54. if (aSize !== b.length || a[0] !== b[0] || a[aSize - 1] !== b[aSize - 1]) return false;
  55. for (let i = 0; i < aSize; i++) {
  56. if (a[i] !== b[i]) return false;
  57. }
  58. return true;
  59. }
  60. /**
  61. * Returns 0 if `v` is smaller or equal the first element of `xs`
  62. * Returns length of `xs` if `v` is bigger than the last element of `xs`
  63. * Otherwise returns the first index where the value of `xs` is equal or bigger than `v`
  64. */
  65. export function findPredecessorIndex(xs: Nums, v: number) {
  66. const len = xs.length;
  67. if (v <= xs[0]) return 0;
  68. if (v > xs[len - 1]) return len;
  69. return binarySearchPredIndexRange(xs, v, 0, len);
  70. }
  71. export function findPredecessorIndexInInterval(xs: Nums, v: number, bounds: Interval) {
  72. const s = Interval.start(bounds), e = Interval.end(bounds);
  73. const sv = xs[s];
  74. if (v <= sv) return s;
  75. if (e > s && v > xs[e - 1]) return e;
  76. // do a linear search if there are only 10 or less items remaining
  77. if (v - sv <= 11) return linearSearchPredInRange(xs, v, s + 1, e);
  78. return binarySearchPredIndexRange(xs, v, s, e);
  79. }
  80. export function findRange(xs: Nums, min: number, max: number) {
  81. return Interval.ofBounds(findPredecessorIndex(xs, min), findPredecessorIndex(xs, max + 1));
  82. }
  83. function binarySearchRange(xs: Nums, value: number, start: number, end: number) {
  84. let min = start, max = end - 1;
  85. while (min <= max) {
  86. // do a linear search if there are only 10 or less items remaining
  87. if (min + 11 > max) {
  88. for (let i = min; i <= max; i++) {
  89. if (value === xs[i]) return i;
  90. }
  91. return -1;
  92. }
  93. const mid = (min + max) >> 1;
  94. const v = xs[mid];
  95. if (value < v) max = mid - 1;
  96. else if (value > v) min = mid + 1;
  97. else return mid;
  98. }
  99. return -1;
  100. }
  101. function binarySearchPredIndexRange(xs: Nums, value: number, start: number, end: number) {
  102. let min = start, max = end - 1;
  103. while (min < max) {
  104. // do a linear search if there are only 10 or less items remaining
  105. if (min + 11 > max) {
  106. for (let i = min; i <= max; i++) {
  107. if (value <= xs[i]) return i;
  108. }
  109. return max + 1;
  110. }
  111. const mid = (min + max) >> 1;
  112. const v = xs[mid];
  113. if (value < v) max = mid - 1;
  114. else if (value > v) min = mid + 1;
  115. else return mid;
  116. }
  117. if (min > max) return max + 1;
  118. return xs[min] >= value ? min : min + 1;
  119. }
  120. function linearSearchPredInRange(xs: Nums, value: number, start: number, end: number) {
  121. for (let i = start; i < end; i++) {
  122. if (value <= xs[i]) return i;
  123. }
  124. return end;
  125. }
  126. export function areIntersecting(a: Nums, b: Nums) {
  127. if (a === b) return true;
  128. let { startI: i, startJ: j, endI, endJ } = getSuitableIntersectionRange(a, b);
  129. while (i < endI && j < endJ) {
  130. const x = a[i], y = b[j];
  131. if (x < y) i++;
  132. else if (x > y) j++;
  133. else return true;
  134. }
  135. return false;
  136. }
  137. export function isSubset(a: Nums, b: Nums) {
  138. if (a === b) return true;
  139. const lenB = b.length;
  140. let { startI: i, startJ: j, endI, endJ } = getSuitableIntersectionRange(a, b);
  141. // must be able to advance by lenB elements
  142. if (endJ - j < lenB || endI - i < lenB) return false;
  143. let equal = 0;
  144. while (i < endI && j < endJ) {
  145. const x = a[i], y = b[j];
  146. if (x < y) {
  147. i++;
  148. } else if (x > y) {
  149. j++;
  150. } else {
  151. i++; j++; equal++;
  152. }
  153. }
  154. return equal === lenB;
  155. }
  156. export function union(a: Nums, b: Nums): Nums {
  157. if (a === b) return a;
  158. const lenA = a.length, lenB = b.length;
  159. if (lenA === 0) return b;
  160. if (lenB === 0) return a;
  161. if (a[0] > b[0]) return union(b, a);
  162. const { startI, startJ, endI, endJ } = getSuitableIntersectionRange(a, b);
  163. const commonCount = getCommonCount(a, b, startI, startJ, endI, endJ);
  164. // A === B || B is subset of A ==> A
  165. if ((commonCount === lenA && commonCount === lenB) || commonCount === lenB) return a;
  166. // A is subset of B ===> B
  167. if (commonCount === lenA) return b;
  168. const indices = new Int32Array(lenA + lenB - commonCount);
  169. let i = 0, j = 0, offset = 0;
  170. // insert the "prefixes"
  171. for (i = 0; i < startI; i++) indices[offset++] = a[i];
  172. while (j < endJ && a[startI] > b[j]) indices[offset++] = b[j++];
  173. // insert the common part
  174. while (i < endI && j < endJ) {
  175. const x = a[i], y = b[j];
  176. if (x < y) {
  177. indices[offset++] = x; i++;
  178. } else if (x > y) {
  179. indices[offset++] = y; j++;
  180. } else {
  181. indices[offset++] = x; i++; j++;
  182. }
  183. }
  184. // insert the remaining common part
  185. for (; i < endI; i++) indices[offset++] = a[i];
  186. for (; j < endJ; j++) indices[offset++] = b[j];
  187. // insert the "tail"
  188. for (; i < lenA; i++) indices[offset++] = a[i];
  189. for (; j < lenB; j++) indices[offset++] = b[j];
  190. return ofSortedArray(indices);
  191. }
  192. export function intersectionSize(a: Nums, b: Nums) {
  193. if (a === b) return size(a);
  194. const { startI, startJ, endI, endJ } = getSuitableIntersectionRange(a, b);
  195. return getCommonCount(a, b, startI, startJ, endI, endJ);
  196. }
  197. function getCommonCount(a: Nums, b: Nums, startI: number, startJ: number, endI: number, endJ: number) {
  198. let i = startI, j = startJ;
  199. let commonCount = 0;
  200. while (i < endI && j < endJ) {
  201. const x = a[i], y = b[j];
  202. if (x < y) {
  203. i++;
  204. } else if (x > y) {
  205. j++;
  206. } else {
  207. i++; j++; commonCount++;
  208. }
  209. }
  210. return commonCount;
  211. }
  212. export function intersect(a: Nums, b: Nums) {
  213. if (a === b) return a;
  214. const { startI, startJ, endI, endJ } = getSuitableIntersectionRange(a, b);
  215. const commonCount = getCommonCount(a, b, startI, startJ, endI, endJ);
  216. const lenA = a.length, lenB = b.length;
  217. // no common elements
  218. if (!commonCount) return Empty;
  219. // A === B || B is subset of A ==> B
  220. if ((commonCount === lenA && commonCount === lenB) || commonCount === lenB) return b;
  221. // A is subset of B ==> A
  222. if (commonCount === lenA) return a;
  223. const indices = new Int32Array(commonCount);
  224. let offset = 0;
  225. let i = startI;
  226. let j = startJ;
  227. while (i < endI && j < endJ) {
  228. const x = a[i], y = b[j];
  229. if (x < y) {
  230. i++;
  231. } else if (x > y) {
  232. j++;
  233. } else {
  234. indices[offset++] = x; i++; j++;
  235. }
  236. }
  237. return ofSortedArray(indices);
  238. }
  239. export function subtract(a: Nums, b: Nums) {
  240. if (a === b) return Empty;
  241. const lenA = a.length;
  242. const { startI: sI, startJ: sJ, endI, endJ } = getSuitableIntersectionRange(a, b);
  243. let i = sI, j = sJ;
  244. let commonCount = 0;
  245. while (i < endI && j < endJ) {
  246. const x = a[i], y = b[j];
  247. if (x < y) {
  248. i++;
  249. } else if (x > y) {
  250. j++;
  251. } else {
  252. i++; j++; commonCount++;
  253. }
  254. }
  255. // A isnt intersecting B ===> A
  256. if (!commonCount) return a;
  257. // A === B || A is subset of B ===> Empty
  258. if (commonCount >= lenA) return Empty;
  259. const indices = new Int32Array(lenA - commonCount);
  260. let offset = 0;
  261. // insert the "prefix"
  262. for (let k = 0; k < sI; k++) indices[offset++] = a[k];
  263. i = sI;
  264. j = sJ;
  265. while (i < endI && j < endJ) {
  266. const x = a[i], y = b[j];
  267. if (x < y) {
  268. indices[offset++] = x; i++;
  269. } else if (x > y) {
  270. j++;
  271. } else {
  272. i++; j++;
  273. }
  274. }
  275. // insert the "tail"
  276. for (; i < lenA; i++) indices[offset++] = a[i];
  277. return ofSortedArray(indices);
  278. }
  279. export function deduplicate(xs: Nums) {
  280. if (xs.length < 2) return xs;
  281. let count = 1;
  282. for (let i = 0, _i = xs.length - 1; i < _i; i++) {
  283. if (xs[i] !== xs[i + 1]) count++;
  284. }
  285. if (count === xs.length) return xs;
  286. const ret = new Int32Array(count);
  287. let o = 0;
  288. for (let i = 0, _i = xs.length - 1; i < _i; i++) {
  289. if (xs[i] !== xs[i + 1]) ret[o++] = xs[i];
  290. }
  291. ret[o] = xs[xs.length - 1];
  292. return ret;
  293. }
  294. export function indicesOf(a: Nums, b: Nums): Nums {
  295. if (a === b) return ofSortedArray(createRangeArray(0, a.length - 1));
  296. const { startI: sI, startJ: sJ, endI, endJ } = getSuitableIntersectionRange(a, b);
  297. let i = sI, j = sJ;
  298. let commonCount = 0;
  299. while (i < endI && j < endJ) {
  300. const x = a[i], y = b[j];
  301. if (x < y) {
  302. i++;
  303. } else if (x > y) {
  304. j++;
  305. } else {
  306. i++; j++; commonCount++;
  307. }
  308. }
  309. const lenA = a.length;
  310. // no common elements
  311. if (!commonCount) return Empty;
  312. // A is subset of B ==> A
  313. if (commonCount === lenA) return ofSortedArray(createRangeArray(0, a.length - 1));
  314. const indices = new Int32Array(commonCount);
  315. let offset = 0;
  316. i = sI;
  317. j = sJ;
  318. while (i < endI && j < endJ) {
  319. const x = a[i], y = b[j];
  320. if (x < y) {
  321. i++;
  322. } else if (x > y) {
  323. j++;
  324. } else {
  325. indices[offset++] = i; i++; j++;
  326. }
  327. }
  328. return ofSortedArray(indices);
  329. }
  330. const _maxIntRangeRet = { startI: 0, startJ: 0, endI: 0, endJ: 0 };
  331. // for small sets, just gets the whole range, for large sets does a bunch of binary searches
  332. function getSuitableIntersectionRange(a: Nums, b: Nums) {
  333. const la = a.length, lb = b.length;
  334. const ratio = la / lb;
  335. if (la >= 128 || lb >= 128 || ratio <= 0.34 || ratio >= 2.99) {
  336. _maxIntRangeRet.startI = findPredecessorIndex(a, start(b));
  337. _maxIntRangeRet.startJ = findPredecessorIndex(b, start(a));
  338. _maxIntRangeRet.endI = findPredecessorIndex(a, end(b));
  339. _maxIntRangeRet.endJ = findPredecessorIndex(b, end(a));
  340. } else {
  341. _maxIntRangeRet.startI = 0;
  342. _maxIntRangeRet.startJ = 0;
  343. _maxIntRangeRet.endI = la;
  344. _maxIntRangeRet.endJ = lb;
  345. }
  346. return _maxIntRangeRet;
  347. }