@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tim-code/my-util",
3
- "version": "0.5.6",
3
+ "version": "0.5.7",
4
4
  "description": "",
5
5
  "type": "module",
6
6
  "author": "Tim Sprowl",
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
+ })