diff --git a/collections/sort_by.ts b/collections/sort_by.ts index a9c2d1b474d3..ac70be68e2b7 100644 --- a/collections/sort_by.ts +++ b/collections/sort_by.ts @@ -124,16 +124,21 @@ export function sortBy( | ((el: T) => Date), options?: SortByOptions, ): T[] { - const array: { value: T; selected: string | number | bigint | Date }[] = []; + const array = Array.isArray(iterator) ? iterator : Array.from(iterator); + const len = array.length; + const selected: (string | number | bigint | Date)[] = new Array(len); + const indices: number[] = new Array(len); - for (const item of iterator) { - array.push({ value: item, selected: selector(item) }); + for (let i = 0; i < len; i++) { + selected[i] = selector(array[i]!); + indices[i] = i; } - array.sort((oa, ob) => { - const a = oa.selected; - const b = ob.selected; - const order = options?.order === "desc" ? -1 : 1; + const order = options?.order === "desc" ? -1 : 1; + + indices.sort((ia, ib) => { + const a = selected[ia]!; + const b = selected[ib]!; if (Number.isNaN(a)) return order; if (Number.isNaN(b)) return -order; @@ -141,5 +146,9 @@ export function sortBy( return order * (a > b ? 1 : a < b ? -1 : 0); }); - return array.map((item) => item.value); + const result: T[] = new Array(len); + for (let i = 0; i < len; i++) { + result[i] = array[indices[i]!]!; + } + return result; } diff --git a/collections/sort_by_test.ts b/collections/sort_by_test.ts index 3260fa67caae..4bffec09cd06 100644 --- a/collections/sort_by_test.ts +++ b/collections/sort_by_test.ts @@ -34,6 +34,22 @@ Deno.test({ }, }); +Deno.test({ + name: "sortBy() handles single element", + fn() { + const single = [{ value: 42 }]; + assertEquals(sortBy(single, (it) => it.value), [{ value: 42 }]); + }, +}); + +Deno.test({ + name: "sortBy() handles two elements", + fn() { + assertEquals(sortBy([2, 1], (it) => it), [1, 2]); + assertEquals(sortBy([1, 2], (it) => it), [1, 2]); + }, +}); + Deno.test({ name: "sortBy() handles identity selector", fn() { @@ -146,6 +162,23 @@ Deno.test({ }, }); +Deno.test({ + name: "sortBy() handles invalid dates via getTime", + fn() { + // Use .getTime() to get numeric NaN which triggers proper NaN handling + const items = [ + { id: 1, date: new Date("2020-01-01") }, + { id: 2, date: new Date("invalid") }, + { id: 3, date: new Date("2019-01-01") }, + ]; + const result = sortBy(items, (it) => it.date.getTime()); + // NaN from invalid date sorts to end + assertEquals(result[0], { id: 3, date: new Date("2019-01-01") }); + assertEquals(result[1], { id: 1, date: new Date("2020-01-01") }); + assertEquals(result[2]!.id, 2); + }, +}); + Deno.test({ name: "sortBy() handles sortings", fn() { @@ -222,6 +255,16 @@ Deno.test({ }, }); +Deno.test({ + name: "sortBy() handles explicit asc ordering", + fn() { + assertEquals( + sortBy([3, 1, 2], (it) => it, { order: "asc" }), + [1, 2, 3], + ); + }, +}); + Deno.test({ name: "sortBy() works with iterators", fn() { @@ -254,3 +297,37 @@ Deno.test({ ]); }, }); + +Deno.test({ + name: "sortBy() works with generators", + fn() { + function* gen() { + yield { value: 3 }; + yield { value: 1 }; + yield { value: 2 }; + } + assertEquals(sortBy(gen(), (it) => it.value), [ + { value: 1 }, + { value: 2 }, + { value: 3 }, + ]); + }, +}); + +Deno.test({ + name: "sortBy() handles large arrays", + fn() { + const large = Array.from( + { length: 10000 }, + (_, i) => ({ i, rand: Math.random() }), + ); + const sorted = sortBy(large, (it) => it.rand); + + // Verify sorted order + for (let i = 1; i < sorted.length; i++) { + assertEquals(sorted[i - 1]!.rand <= sorted[i]!.rand, true); + } + // Verify no elements lost + assertEquals(sorted.length, 10000); + }, +});