mirror of
https://github.com/denoland/std.git
synced 2024-11-21 20:50:22 +00:00
feat(collections/unstable): support iterators in sortBy()
(#5919)
* feat(collections/sortBy): support iterators * update jsdoc * cleanup * add experimental tag
This commit is contained in:
parent
7f4d35ce17
commit
f1b3525b9f
@ -46,6 +46,7 @@
|
|||||||
"./take-last-while": "./take_last_while.ts",
|
"./take-last-while": "./take_last_while.ts",
|
||||||
"./take-while": "./take_while.ts",
|
"./take-while": "./take_while.ts",
|
||||||
"./union": "./union.ts",
|
"./union": "./union.ts",
|
||||||
|
"./unstable-sort-by": "./unstable_sort_by.ts",
|
||||||
"./unstable-take-while": "./unstable_take_while.ts",
|
"./unstable-take-while": "./unstable_take_while.ts",
|
||||||
"./unzip": "./unzip.ts",
|
"./unzip": "./unzip.ts",
|
||||||
"./without-all": "./without_all.ts",
|
"./without-all": "./without_all.ts",
|
||||||
|
@ -20,6 +20,9 @@ export type SortByOptions = {
|
|||||||
* element. Ascending or descending order can be specified through the `order`
|
* element. Ascending or descending order can be specified through the `order`
|
||||||
* option. By default, the elements are sorted in ascending order.
|
* option. By default, the elements are sorted in ascending order.
|
||||||
*
|
*
|
||||||
|
* Note: If you want to process any iterable, use the new version of
|
||||||
|
* `sortBy` from `@std/collections/unstable-sort-by`.
|
||||||
|
*
|
||||||
* @typeParam T The type of the array elements.
|
* @typeParam T The type of the array elements.
|
||||||
*
|
*
|
||||||
* @param array The array to sort.
|
* @param array The array to sort.
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
import { assertEquals } from "@std/assert";
|
import { assertEquals } from "@std/assert";
|
||||||
import { sortBy } from "./sort_by.ts";
|
import { sortBy } from "./sort_by.ts";
|
||||||
|
import { sortBy as unstableSortBy } from "./unstable_sort_by.ts";
|
||||||
|
|
||||||
Deno.test({
|
Deno.test({
|
||||||
name: "sortBy() handles no mutation",
|
name: "sortBy() handles no mutation",
|
||||||
@ -221,3 +222,255 @@ Deno.test({
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Deno.test({
|
||||||
|
name: "(unstable) sortBy() handles no mutation",
|
||||||
|
fn() {
|
||||||
|
const array = ["a", "abc", "ba"];
|
||||||
|
unstableSortBy(array, (it) => it.length);
|
||||||
|
|
||||||
|
assertEquals(array, ["a", "abc", "ba"]);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
Deno.test({
|
||||||
|
name: "(unstable) sortBy() calls the selector function once",
|
||||||
|
fn() {
|
||||||
|
let callCount = 0;
|
||||||
|
const array = [0, 1, 2];
|
||||||
|
unstableSortBy(array, (it) => {
|
||||||
|
callCount++;
|
||||||
|
return it;
|
||||||
|
});
|
||||||
|
|
||||||
|
assertEquals(callCount, array.length);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
Deno.test({
|
||||||
|
name: "(unstable) sortBy() handles empty input",
|
||||||
|
fn() {
|
||||||
|
assertEquals(unstableSortBy([], () => 5), []);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
Deno.test({
|
||||||
|
name: "(unstable) sortBy() handles identity selector",
|
||||||
|
fn() {
|
||||||
|
assertEquals(unstableSortBy([2, 3, 1], (it) => it), [1, 2, 3]);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
Deno.test({
|
||||||
|
name: "(unstable) sortBy() handles stable sort",
|
||||||
|
fn() {
|
||||||
|
assertEquals(
|
||||||
|
unstableSortBy([
|
||||||
|
{ id: 1, date: "February 1, 2022" },
|
||||||
|
{ id: 2, date: "December 17, 1995" },
|
||||||
|
{ id: 3, date: "June 12, 2012" },
|
||||||
|
{ id: 4, date: "December 17, 1995" },
|
||||||
|
{ id: 5, date: "June 12, 2012" },
|
||||||
|
], (it) => new Date(it.date)),
|
||||||
|
[
|
||||||
|
{ id: 2, date: "December 17, 1995" },
|
||||||
|
{ id: 4, date: "December 17, 1995" },
|
||||||
|
{ id: 3, date: "June 12, 2012" },
|
||||||
|
{ id: 5, date: "June 12, 2012" },
|
||||||
|
{ id: 1, date: "February 1, 2022" },
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
assertEquals(
|
||||||
|
unstableSortBy([
|
||||||
|
{ id: 1, str: "c" },
|
||||||
|
{ id: 2, str: "a" },
|
||||||
|
{ id: 3, str: "b" },
|
||||||
|
{ id: 4, str: "a" },
|
||||||
|
{ id: 5, str: "b" },
|
||||||
|
], (it) => it.str),
|
||||||
|
[
|
||||||
|
{ id: 2, str: "a" },
|
||||||
|
{ id: 4, str: "a" },
|
||||||
|
{ id: 3, str: "b" },
|
||||||
|
{ id: 5, str: "b" },
|
||||||
|
{ id: 1, str: "c" },
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
Deno.test({
|
||||||
|
name: "(unstable) sortBy() handles special number values",
|
||||||
|
fn() {
|
||||||
|
assertEquals(
|
||||||
|
unstableSortBy([
|
||||||
|
1,
|
||||||
|
Number.POSITIVE_INFINITY,
|
||||||
|
2,
|
||||||
|
Number.NEGATIVE_INFINITY,
|
||||||
|
3,
|
||||||
|
Number.NaN,
|
||||||
|
4,
|
||||||
|
Number.NaN,
|
||||||
|
], (it) => it),
|
||||||
|
[
|
||||||
|
Number.NEGATIVE_INFINITY,
|
||||||
|
1,
|
||||||
|
2,
|
||||||
|
3,
|
||||||
|
4,
|
||||||
|
Number.POSITIVE_INFINITY,
|
||||||
|
Number.NaN,
|
||||||
|
Number.NaN,
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
assertEquals(
|
||||||
|
unstableSortBy([
|
||||||
|
Number.NaN,
|
||||||
|
1,
|
||||||
|
Number.POSITIVE_INFINITY,
|
||||||
|
Number.NaN,
|
||||||
|
7,
|
||||||
|
Number.NEGATIVE_INFINITY,
|
||||||
|
Number.NaN,
|
||||||
|
2,
|
||||||
|
6,
|
||||||
|
5,
|
||||||
|
9,
|
||||||
|
], (it) => it),
|
||||||
|
[
|
||||||
|
Number.NEGATIVE_INFINITY,
|
||||||
|
1,
|
||||||
|
2,
|
||||||
|
5,
|
||||||
|
6,
|
||||||
|
7,
|
||||||
|
9,
|
||||||
|
Number.POSITIVE_INFINITY,
|
||||||
|
Number.NaN,
|
||||||
|
Number.NaN,
|
||||||
|
Number.NaN,
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
// Test that NaN sort is stable.
|
||||||
|
const nanArray = [
|
||||||
|
{ id: 1, nan: Number.NaN },
|
||||||
|
{ id: 2, nan: Number.NaN },
|
||||||
|
{ id: 3, nan: Number.NaN },
|
||||||
|
{ id: 4, nan: Number.NaN },
|
||||||
|
];
|
||||||
|
assertEquals(unstableSortBy(nanArray, ({ nan }) => nan), nanArray);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
Deno.test({
|
||||||
|
name: "(unstable) sortBy() handles sortings",
|
||||||
|
fn() {
|
||||||
|
const testArray = [
|
||||||
|
{ name: "benchmark", stage: 3 },
|
||||||
|
{ name: "test", stage: 2 },
|
||||||
|
{ name: "build", stage: 1 },
|
||||||
|
{ name: "deploy", stage: 4 },
|
||||||
|
];
|
||||||
|
|
||||||
|
assertEquals(unstableSortBy(testArray, (it) => it.stage), [
|
||||||
|
{ name: "build", stage: 1 },
|
||||||
|
{ name: "test", stage: 2 },
|
||||||
|
{ name: "benchmark", stage: 3 },
|
||||||
|
{ name: "deploy", stage: 4 },
|
||||||
|
]);
|
||||||
|
|
||||||
|
assertEquals(unstableSortBy(testArray, (it) => it.name), [
|
||||||
|
{ name: "benchmark", stage: 3 },
|
||||||
|
{ name: "build", stage: 1 },
|
||||||
|
{ name: "deploy", stage: 4 },
|
||||||
|
{ name: "test", stage: 2 },
|
||||||
|
]);
|
||||||
|
|
||||||
|
assertEquals(
|
||||||
|
unstableSortBy([
|
||||||
|
"9007199254740999",
|
||||||
|
"9007199254740991",
|
||||||
|
"9007199254740995",
|
||||||
|
], (it) => BigInt(it)),
|
||||||
|
[
|
||||||
|
"9007199254740991",
|
||||||
|
"9007199254740995",
|
||||||
|
"9007199254740999",
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
assertEquals(
|
||||||
|
unstableSortBy([
|
||||||
|
"February 1, 2022",
|
||||||
|
"December 17, 1995",
|
||||||
|
"June 12, 2012",
|
||||||
|
], (it) => new Date(it)),
|
||||||
|
[
|
||||||
|
"December 17, 1995",
|
||||||
|
"June 12, 2012",
|
||||||
|
"February 1, 2022",
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
Deno.test({
|
||||||
|
name: "(unstable) sortBy() handles desc ordering",
|
||||||
|
fn() {
|
||||||
|
assertEquals(
|
||||||
|
unstableSortBy(
|
||||||
|
[
|
||||||
|
"January 27, 1995",
|
||||||
|
"November 26, 2020",
|
||||||
|
"June 17, 1952",
|
||||||
|
"July 15, 1993",
|
||||||
|
],
|
||||||
|
(it) => new Date(it),
|
||||||
|
{ order: "desc" },
|
||||||
|
),
|
||||||
|
[
|
||||||
|
"November 26, 2020",
|
||||||
|
"January 27, 1995",
|
||||||
|
"July 15, 1993",
|
||||||
|
"June 17, 1952",
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
Deno.test({
|
||||||
|
name: "(unstable) sortBy() works with iterators",
|
||||||
|
fn() {
|
||||||
|
const set = new Set([10, 312, 99, 5.45, 100, -3, 4.6]);
|
||||||
|
|
||||||
|
assertEquals(
|
||||||
|
unstableSortBy(set, (it) => it),
|
||||||
|
[-3, 4.6, 5.45, 10, 99, 100, 312],
|
||||||
|
);
|
||||||
|
assertEquals(
|
||||||
|
unstableSortBy(set, (it) => it, { order: "desc" }),
|
||||||
|
[312, 100, 99, 10, 5.45, 4.6, -3],
|
||||||
|
);
|
||||||
|
|
||||||
|
const map = new Map([
|
||||||
|
["a", 2],
|
||||||
|
["c", 1],
|
||||||
|
["b", 3],
|
||||||
|
]);
|
||||||
|
|
||||||
|
assertEquals(unstableSortBy(map, (it) => it[0]), [
|
||||||
|
["a", 2],
|
||||||
|
["b", 3],
|
||||||
|
["c", 1],
|
||||||
|
]);
|
||||||
|
assertEquals(unstableSortBy(map, (it) => it[1], { order: "desc" }), [
|
||||||
|
["b", 3],
|
||||||
|
["a", 2],
|
||||||
|
["c", 1],
|
||||||
|
]);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
149
collections/unstable_sort_by.ts
Normal file
149
collections/unstable_sort_by.ts
Normal file
@ -0,0 +1,149 @@
|
|||||||
|
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
|
||||||
|
// This module is browser compatible.
|
||||||
|
|
||||||
|
/** Order option for {@linkcode SortByOptions}. */
|
||||||
|
export type Order = "asc" | "desc";
|
||||||
|
|
||||||
|
/** Options for {@linkcode sortBy}. */
|
||||||
|
export type SortByOptions = {
|
||||||
|
/**
|
||||||
|
* The order to sort the elements in.
|
||||||
|
*
|
||||||
|
* @default {"asc"}
|
||||||
|
*/
|
||||||
|
order: Order;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Types that can be compared with other values of the same type
|
||||||
|
* using comparison operators.
|
||||||
|
*/
|
||||||
|
export type Comparable = number | string | bigint | Date;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns all elements in the given collection, sorted by their result using
|
||||||
|
* the given selector. The selector function is called only once for each
|
||||||
|
* element. Ascending or descending order can be specified through the `order`
|
||||||
|
* option. By default, the elements are sorted in ascending order.
|
||||||
|
*
|
||||||
|
* @experimental **UNSTABLE**: New API, yet to be vetted.
|
||||||
|
*
|
||||||
|
* @typeParam T The type of the iterator elements.
|
||||||
|
* @typeParam U The type of the selected values.
|
||||||
|
*
|
||||||
|
* @param iterator The iterator to sort.
|
||||||
|
* @param selector The selector function to get the value to sort by.
|
||||||
|
* @param options The options for sorting.
|
||||||
|
*
|
||||||
|
* @returns A new array containing all elements sorted by the selector.
|
||||||
|
*
|
||||||
|
* @example Usage with numbers
|
||||||
|
* ```ts
|
||||||
|
* import { sortBy } from "@std/collections/sort-by";
|
||||||
|
* import { assertEquals } from "@std/assert";
|
||||||
|
*
|
||||||
|
* const people = [
|
||||||
|
* { name: "Anna", age: 34 },
|
||||||
|
* { name: "Kim", age: 42 },
|
||||||
|
* { name: "John", age: 23 },
|
||||||
|
* ];
|
||||||
|
* const sortedByAge = sortBy(people, (person) => person.age);
|
||||||
|
*
|
||||||
|
* assertEquals(sortedByAge, [
|
||||||
|
* { name: "John", age: 23 },
|
||||||
|
* { name: "Anna", age: 34 },
|
||||||
|
* { name: "Kim", age: 42 },
|
||||||
|
* ]);
|
||||||
|
*
|
||||||
|
* const sortedByAgeDesc = sortBy(people, (person) => person.age, { order: "desc" });
|
||||||
|
*
|
||||||
|
* assertEquals(sortedByAgeDesc, [
|
||||||
|
* { name: "Kim", age: 42 },
|
||||||
|
* { name: "Anna", age: 34 },
|
||||||
|
* { name: "John", age: 23 },
|
||||||
|
* ]);
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* @example Usage with strings
|
||||||
|
* ```ts
|
||||||
|
* import { sortBy } from "@std/collections/sort-by";
|
||||||
|
* import { assertEquals } from "@std/assert";
|
||||||
|
*
|
||||||
|
* const people = [
|
||||||
|
* { name: "Anna" },
|
||||||
|
* { name: "Kim" },
|
||||||
|
* { name: "John" },
|
||||||
|
* ];
|
||||||
|
* const sortedByName = sortBy(people, (it) => it.name);
|
||||||
|
*
|
||||||
|
* assertEquals(sortedByName, [
|
||||||
|
* { name: "Anna" },
|
||||||
|
* { name: "John" },
|
||||||
|
* { name: "Kim" },
|
||||||
|
* ]);
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* @example Usage with bigints
|
||||||
|
* ```ts
|
||||||
|
* import { sortBy } from "@std/collections/sort-by";
|
||||||
|
* import { assertEquals } from "@std/assert";
|
||||||
|
*
|
||||||
|
* const people = [
|
||||||
|
* { name: "Anna", age: 34n },
|
||||||
|
* { name: "Kim", age: 42n },
|
||||||
|
* { name: "John", age: 23n },
|
||||||
|
* ];
|
||||||
|
*
|
||||||
|
* const sortedByAge = sortBy(people, (person) => person.age);
|
||||||
|
*
|
||||||
|
* assertEquals(sortedByAge, [
|
||||||
|
* { name: "John", age: 23n },
|
||||||
|
* { name: "Anna", age: 34n },
|
||||||
|
* { name: "Kim", age: 42n },
|
||||||
|
* ]);
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* @example Usage with Date objects
|
||||||
|
* ```ts
|
||||||
|
* import { sortBy } from "@std/collections/sort-by";
|
||||||
|
* import { assertEquals } from "@std/assert";
|
||||||
|
*
|
||||||
|
* const people = [
|
||||||
|
* { name: "Anna", startedAt: new Date("2020-01-01") },
|
||||||
|
* { name: "Kim", startedAt: new Date("2020-03-01") },
|
||||||
|
* { name: "John", startedAt: new Date("2020-06-01") },
|
||||||
|
* ];
|
||||||
|
*
|
||||||
|
* const sortedByStartedAt = sortBy(people, (people) => people.startedAt);
|
||||||
|
*
|
||||||
|
* assertEquals(sortedByStartedAt, [
|
||||||
|
* { name: "Anna", startedAt: new Date("2020-01-01") },
|
||||||
|
* { name: "Kim", startedAt: new Date("2020-03-01") },
|
||||||
|
* { name: "John", startedAt: new Date("2020-06-01") },
|
||||||
|
* ]);
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
export function sortBy<T, U extends Comparable>(
|
||||||
|
iterator: Iterable<T>,
|
||||||
|
selector: (el: T) => U,
|
||||||
|
options?: SortByOptions,
|
||||||
|
): T[] {
|
||||||
|
const array: { value: T; selected: U }[] = [];
|
||||||
|
|
||||||
|
for (const item of iterator) {
|
||||||
|
array.push({ value: item, selected: selector(item) });
|
||||||
|
}
|
||||||
|
|
||||||
|
array.sort((oa, ob) => {
|
||||||
|
const a = oa.selected;
|
||||||
|
const b = ob.selected;
|
||||||
|
const order = options?.order === "desc" ? -1 : 1;
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user