buckets.ts 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105
  1. /**
  2. * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
  3. *
  4. * @author David Sehnal <david.sehnal@gmail.com>
  5. */
  6. import { sort, arraySwap } from './sort';
  7. import { AssignableArrayLike } from '../../mol-util/type-helpers';
  8. type Bucket = {
  9. key: any,
  10. count: number,
  11. offset: number
  12. }
  13. function sortAsc(bs: Bucket[], i: number, j: number) { return bs[i].key < bs[j].key ? -1 : 1; }
  14. function _makeBuckets(indices: AssignableArrayLike<number>,
  15. getKey: (i: number) => any, sortBuckets: boolean, start: number, end: number) {
  16. const buckets = new Map<any, Bucket>();
  17. const bucketList: Bucket[] = [];
  18. let prevKey = getKey(indices[0]);
  19. let isBucketed = true;
  20. for (let i = start; i < end; i++) {
  21. const key = getKey(indices[i]);
  22. if (buckets.has(key)) {
  23. buckets.get(key)!.count++;
  24. if (prevKey !== key) isBucketed = false;
  25. } else {
  26. const bucket: Bucket = { key, count: 1, offset: i };
  27. buckets.set(key, bucket);
  28. bucketList[bucketList.length] = bucket;
  29. }
  30. prevKey = key;
  31. }
  32. const bucketOffsets = new Int32Array(bucketList.length + 1);
  33. bucketOffsets[bucketList.length] = end;
  34. let sorted = true;
  35. if (sortBuckets) {
  36. for (let i = 1, _i = bucketList.length; i < _i; i++) {
  37. if (bucketList[i - 1].key > bucketList[i].key) {
  38. sorted = false;
  39. break;
  40. }
  41. }
  42. }
  43. if (isBucketed && sorted) {
  44. for (let i = 0; i < bucketList.length; i++) bucketOffsets[i] = bucketList[i].offset;
  45. return bucketOffsets;
  46. }
  47. if (sortBuckets && !sorted) {
  48. sort(bucketList, 0, bucketList.length, sortAsc, arraySwap);
  49. }
  50. let offset = 0;
  51. for (let i = 0; i < bucketList.length; i++) {
  52. const b = bucketList[i];
  53. b.offset = offset;
  54. offset += b.count;
  55. }
  56. const reorderedIndices = new Int32Array(end - start);
  57. for (let i = start; i < end; i++) {
  58. const key = getKey(indices[i]);
  59. const bucket = buckets.get(key)!;
  60. reorderedIndices[bucket.offset++] = indices[i];
  61. }
  62. for (let i = 0, _i = reorderedIndices.length; i < _i; i++) {
  63. indices[i + start] = reorderedIndices[i];
  64. }
  65. bucketOffsets[0] = start;
  66. for (let i = 1; i < bucketList.length; i++) bucketOffsets[i] = bucketList[i - 1].offset + start;
  67. return bucketOffsets;
  68. }
  69. export interface MakeBucketsOptions<K> {
  70. // If specified, will be sorted
  71. sort?: boolean,
  72. // inclusive start indidex
  73. start?: number,
  74. // exclusive end index
  75. end?: number
  76. }
  77. /**
  78. * Reorders indices so that the same keys are next to each other, [start, end)
  79. * Returns the offsets of buckets. So that [offsets[i], offsets[i + 1]) determines the range.
  80. */
  81. export function makeBuckets<K extends string | number>(
  82. indices: AssignableArrayLike<number>, getKey: (i: number) => K, options?: MakeBucketsOptions<K>): ArrayLike<number> {
  83. const s = (options && options.start) || 0;
  84. const e = (options && options.end) || indices.length;
  85. if (e - s <= 0) throw new Error('Can only bucket non-empty collections.');
  86. return _makeBuckets(indices, getKey, !!(options && options.sort), s, e);
  87. }