sort.ts 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159
  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. export type Comparer<T = any> = (data: T, i: number, j: number) => number
  7. export type Swapper<T = any> = (data: T, i: number, j: number) => void
  8. type Ctx = { cmp: Comparer, swap: Swapper, parts: number[], data: any }
  9. export function arrayLess(arr: ArrayLike<number>, i: number, j: number) {
  10. return arr[i] - arr[j];
  11. }
  12. export function arraySwap(arr: any[], i: number, j: number) {
  13. const temp = arr[i];
  14. arr[i] = arr[j];
  15. arr[j] = temp;
  16. }
  17. function medianPivotIndex(data: any, cmp: Comparer, l: number, r: number) {
  18. const m = (l + r) >> 1;
  19. if (cmp(data, l, r) > 0) return cmp(data, l, m) > 0 ? cmp(data, m, r) > 0 ? m : r : l;
  20. else return cmp(data, r, m) > 0 ? cmp(data, m, l) > 0 ? m : l : r;
  21. }
  22. function partition(ctx: Ctx, l: number, r: number) {
  23. const { cmp, swap, data, parts } = ctx;
  24. let equals = l + 1, tail = r;
  25. // move the median to the 1st spot
  26. swap(data, l, medianPivotIndex(data, cmp, l, r));
  27. while (cmp(data, tail, l) > 0) { --tail; }
  28. for (let i = l + 1; i <= tail; i++) {
  29. const c = cmp(data, i, l);
  30. if (c > 0) {
  31. swap(data, i, tail);
  32. --tail;
  33. while (cmp(data, tail, l) > 0) { --tail; }
  34. i--;
  35. } else if (c === 0) {
  36. swap(data, i, equals);
  37. equals++;
  38. }
  39. }
  40. // move the medians to the correct spots
  41. for (let i = l; i < equals; i++) { swap(data, i, l + tail - i); }
  42. parts[0] = tail - equals + l + 1;
  43. parts[1] = tail;
  44. }
  45. function insertionSort({ data, cmp, swap }: Ctx, start: number, end: number) {
  46. for (let i = start + 1; i <= end; i++) {
  47. let j = i - 1;
  48. while (j >= start && cmp(data, j, j + 1) > 0) {
  49. swap(data, j, j + 1);
  50. j = j - 1;
  51. }
  52. }
  53. }
  54. function quickSort(ctx: Ctx, low: number, high: number) {
  55. const { parts } = ctx;
  56. while (low < high) {
  57. if (high - low < 16) {
  58. insertionSort(ctx, low, high);
  59. return;
  60. }
  61. partition(ctx, low, high);
  62. const li = parts[0], ri = parts[1];
  63. if (li - low < high - ri) {
  64. quickSort(ctx, low, li - 1);
  65. low = ri + 1;
  66. } else {
  67. quickSort(ctx, ri + 1, high);
  68. high = li - 1;
  69. }
  70. }
  71. }
  72. function partitionArrayAsc(data: number[], parts: number[], l: number, r: number) {
  73. let equals = l + 1, tail = r;
  74. // move the median to the 1st spot
  75. arraySwap(data, l, medianPivotIndex(data, arrayLess, l, r));
  76. const pivot = data[l];
  77. while (data[tail] > pivot) { --tail; }
  78. for (let i = l + 1; i <= tail; i++) {
  79. const v = data[i];
  80. if (v > pivot) {
  81. arraySwap(data, i, tail);
  82. --tail;
  83. while (data[tail] > pivot) { --tail; }
  84. i--;
  85. } else if (v === pivot) {
  86. arraySwap(data, i, equals);
  87. ++equals;
  88. }
  89. }
  90. // move all medians to the correct spots
  91. for (let i = l; i < equals; i++) { arraySwap(data, i, l + tail - i); }
  92. parts[0] = tail - equals + l + 1;
  93. parts[1] = tail;
  94. }
  95. function insertionSortArrayAsc(data: number[], start: number, end: number) {
  96. for (let i = start + 1; i <= end; i++) {
  97. const key = data[i];
  98. let j = i - 1;
  99. while (j >= start && data[j] > key) {
  100. data[j + 1] = data[j];
  101. j = j - 1;
  102. }
  103. data[j + 1] = key;
  104. }
  105. }
  106. function quickSortArrayAsc(data: number[], parts: number[], low: number, high: number) {
  107. while (low < high) {
  108. if (high - low < 16) {
  109. insertionSortArrayAsc(data, low, high);
  110. return;
  111. }
  112. partitionArrayAsc(data, parts, low, high);
  113. const li = parts[0], ri = parts[1];
  114. if (li - low < high - ri) {
  115. quickSortArrayAsc(data, parts, low, li - 1);
  116. low = ri + 1;
  117. } else {
  118. quickSortArrayAsc(data, parts, ri + 1, high);
  119. high = li - 1;
  120. }
  121. }
  122. }
  123. export function sortArray(data: ArrayLike<number>, cmp: Comparer<ArrayLike<number>> = arrayLess): ArrayLike<number> {
  124. return sortArrayRange(data, 0, data.length, cmp);
  125. }
  126. export function sortArrayRange(data: ArrayLike<number>, start: number, end: number, cmp: Comparer<ArrayLike<number>> = arrayLess): ArrayLike<number> {
  127. if (cmp === arrayLess) quickSortArrayAsc(data as any, [0, 0], start, end - 1);
  128. else quickSort({ data, cmp, swap: arraySwap, parts: [0, 0] }, start, end - 1);
  129. return data;
  130. }
  131. export function sort<T>(data: T, start: number, end: number, cmp: Comparer<T>, swap: Swapper<T>): T {
  132. const ctx: Ctx = { data, cmp, swap, parts: [0, 0] };
  133. quickSort(ctx, start, end - 1);
  134. return data;
  135. }