Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 17 additions & 8 deletions collections/sort_by.ts
Original file line number Diff line number Diff line change
Expand Up @@ -124,22 +124,31 @@ export function sortBy<T>(
| ((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;

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;
}
77 changes: 77 additions & 0 deletions collections/sort_by_test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down Expand Up @@ -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() {
Expand Down Expand Up @@ -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() {
Expand Down Expand Up @@ -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);
},
});
Loading