@tim-code/my-util 0.5.6 → 0.5.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/array.js +62 -0
- package/src/array.test.js +39 -1
package/package.json
CHANGED
package/src/array.js
CHANGED
|
@@ -185,3 +185,65 @@ export function multilevel(...comparators) {
|
|
|
185
185
|
return 0
|
|
186
186
|
}
|
|
187
187
|
}
|
|
188
|
+
|
|
189
|
+
function siftDown(heap, i, compare) {
|
|
190
|
+
const n = heap.length
|
|
191
|
+
// eslint-disable-next-line no-constant-condition
|
|
192
|
+
while (true) {
|
|
193
|
+
const left = 2 * i + 1
|
|
194
|
+
const right = left + 1
|
|
195
|
+
let largest = i
|
|
196
|
+
if (left < n && compare(heap[left], heap[largest]) > 0) {
|
|
197
|
+
largest = left
|
|
198
|
+
}
|
|
199
|
+
if (right < n && compare(heap[right], heap[largest]) > 0) {
|
|
200
|
+
largest = right
|
|
201
|
+
}
|
|
202
|
+
if (largest === i) {
|
|
203
|
+
break
|
|
204
|
+
}
|
|
205
|
+
const swap = heap[largest]
|
|
206
|
+
heap[largest] = heap[i]
|
|
207
|
+
heap[i] = swap
|
|
208
|
+
i = largest
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
function maxHeapify(heap, compare) {
|
|
213
|
+
// (heap.length >>> 1) is equivalent to Math.floor(heap.length / 2)
|
|
214
|
+
// eslint-disable-next-line no-bitwise
|
|
215
|
+
for (let i = (heap.length >>> 1) - 1; i >= 0; i--) {
|
|
216
|
+
siftDown(heap, i, compare)
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Get the first N elements of the sorted array efficiently.
|
|
222
|
+
* @template T
|
|
223
|
+
* @param {Array<T>} array
|
|
224
|
+
* @param {number} N
|
|
225
|
+
* @param {Function} compare
|
|
226
|
+
* @returns {Array<T>}
|
|
227
|
+
*/
|
|
228
|
+
export function sortN(array, N, compare = ascending()) {
|
|
229
|
+
if (N <= 0) {
|
|
230
|
+
return []
|
|
231
|
+
}
|
|
232
|
+
if (N >= array.length) {
|
|
233
|
+
return [...array].sort(compare)
|
|
234
|
+
}
|
|
235
|
+
if (array.length <= 100 && N / array.length >= 0.1) {
|
|
236
|
+
// seems to be faster to do it the "normal" way in this case
|
|
237
|
+
return [...array].sort(compare).slice(0, N)
|
|
238
|
+
}
|
|
239
|
+
const heap = array.slice(0, N)
|
|
240
|
+
maxHeapify(heap, compare)
|
|
241
|
+
for (let i = N; i < array.length; i++) {
|
|
242
|
+
const element = array[i]
|
|
243
|
+
if (compare(element, heap[0]) < 0) {
|
|
244
|
+
heap[0] = element
|
|
245
|
+
siftDown(heap, 0, compare)
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
return heap.sort(compare)
|
|
249
|
+
}
|
package/src/array.test.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/* eslint-disable no-restricted-syntax */
|
|
2
2
|
import { describe, expect, it, jest } from "@jest/globals"
|
|
3
3
|
|
|
4
|
-
const { chunk, unique, duplicates, ascending, descending, multilevel } = await import(
|
|
4
|
+
const { chunk, unique, duplicates, ascending, descending, multilevel, sortN } = await import(
|
|
5
5
|
"./array.js"
|
|
6
6
|
)
|
|
7
7
|
|
|
@@ -511,3 +511,41 @@ describe("multilevel", () => {
|
|
|
511
511
|
expect(cmp("a", "b")).toBe(0)
|
|
512
512
|
})
|
|
513
513
|
})
|
|
514
|
+
|
|
515
|
+
describe("sortN", () => {
|
|
516
|
+
it("returns empty array when N <= 0", () => {
|
|
517
|
+
expect(sortN([1, 2, 3], 0)).toEqual([])
|
|
518
|
+
expect(sortN([1, 2, 3], -5)).toEqual([])
|
|
519
|
+
})
|
|
520
|
+
|
|
521
|
+
it("returns the entire array sorted when N >= array.length and does not mutate original", () => {
|
|
522
|
+
const arr = [3, 1, 2]
|
|
523
|
+
const result = sortN(arr, 10)
|
|
524
|
+
expect(result).toEqual([1, 2, 3])
|
|
525
|
+
expect(arr).toEqual([3, 1, 2])
|
|
526
|
+
expect(result).not.toBe(arr)
|
|
527
|
+
})
|
|
528
|
+
|
|
529
|
+
it("returns the first N smallest elements (default ascending comparator)", () => {
|
|
530
|
+
expect(sortN([5, 1, 3, 2, 4], 3)).toEqual([1, 2, 3])
|
|
531
|
+
expect(sortN([3, 1, 3, 2, 2], 4)).toEqual([1, 2, 2, 3])
|
|
532
|
+
})
|
|
533
|
+
|
|
534
|
+
it("respects a descending comparator (returns top N largest)", () => {
|
|
535
|
+
const arr = [5, 1, 3, 2, 4]
|
|
536
|
+
expect(sortN(arr, 2, descending())).toEqual([5, 4])
|
|
537
|
+
})
|
|
538
|
+
|
|
539
|
+
it("works with key-based comparator and defers undefined/null values to the end", () => {
|
|
540
|
+
const arr = [{ v: 3 }, {}, { v: 1 }, { v: null }, { v: 2 }, { v: undefined }]
|
|
541
|
+
const out = sortN(arr, 3, ascending("v"))
|
|
542
|
+
expect(out.map((o) => o.v)).toEqual([1, 2, 3])
|
|
543
|
+
})
|
|
544
|
+
|
|
545
|
+
it("does not mutate the original array when N < array.length", () => {
|
|
546
|
+
const arr = [5, 1, 3, 2, 4]
|
|
547
|
+
const out = sortN(arr, 3)
|
|
548
|
+
expect(out).toEqual([1, 2, 3])
|
|
549
|
+
expect(arr).toEqual([5, 1, 3, 2, 4])
|
|
550
|
+
})
|
|
551
|
+
})
|