Skip to content

Commit

Permalink
feat: add index arg to collection methods
Browse files Browse the repository at this point in the history
  • Loading branch information
canac committed Feb 5, 2025
1 parent fd2d1e5 commit 65c0d65
Show file tree
Hide file tree
Showing 32 changed files with 240 additions and 64 deletions.
5 changes: 3 additions & 2 deletions collections/distinct_by.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,13 @@
*/
export function distinctBy<T, D>(
array: Iterable<T>,
discriminator: (el: T) => D,
discriminator: (el: T, index: number) => D,
): T[] {
const keys = new Set<D>();
const result: T[] = [];
let index = 0;
for (const element of array) {
const key = discriminator(element);
const key = discriminator(element, index++);
if (!keys.has(key)) {
keys.add(key);
result.push(element);
Expand Down
13 changes: 12 additions & 1 deletion collections/distinct_by_test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { distinctBy } from "./distinct_by.ts";

function distinctByTest<I>(
array: Array<I>,
selector: (element: I) => unknown,
selector: (element: I, index: number) => unknown,
expected: Array<I>,
message?: string,
) {
Expand Down Expand Up @@ -117,3 +117,14 @@ Deno.test({
);
},
});

Deno.test({
name: "distinctBy() passes index to selector",
fn() {
distinctByTest(
[25, "asdf", true],
(_, index) => index > 1,
[25, true],
);
},
});
4 changes: 2 additions & 2 deletions collections/drop_last_while.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,10 @@
*/
export function dropLastWhile<T>(
array: readonly T[],
predicate: (el: T) => boolean,
predicate: (el: T, index: number) => boolean,
): T[] {
let offset = array.length;
while (0 < offset && predicate(array[offset - 1] as T)) offset--;
while (0 < offset && predicate(array[offset - 1] as T, offset - 1)) offset--;

return array.slice(0, offset);
}
8 changes: 8 additions & 0 deletions collections/drop_last_while_test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,3 +50,11 @@ Deno.test("dropLastWhile() returns empty array when all elements get dropped", (

assertEquals(actual, []);
});

Deno.test("dropLastWhile() passes index to predicate", () => {
const array = [20, 30, 20];

const actual = dropLastWhile(array, (_, index) => index > 1);

assertEquals(actual, [20, 30]);
});
4 changes: 2 additions & 2 deletions collections/drop_while.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,12 @@
*/
export function dropWhile<T>(
array: readonly T[],
predicate: (el: T) => boolean,
predicate: (el: T, index: number) => boolean,
): T[] {
let offset = 0;
const length = array.length;

while (length > offset && predicate(array[offset] as T)) {
while (length > offset && predicate(array[offset] as T, offset)) {
offset++;
}

Expand Down
8 changes: 8 additions & 0 deletions collections/drop_while_test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,3 +55,11 @@ Deno.test("dropWhile() returns the same array when all elements match the predic

assertEquals(actual, []);
});

Deno.test("dropWhile() passes index to predicate", () => {
const array = [20, 30, 20];

const actual = dropWhile(array, (_, index) => index < 1);

assertEquals(actual, [30, 20]);
});
5 changes: 3 additions & 2 deletions collections/find_single.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,13 @@
*/
export function findSingle<T>(
array: Iterable<T>,
predicate: (el: T) => boolean,
predicate: (el: T, index: number) => boolean,
): T | undefined {
let match: T | undefined;
let found = false;
let index = 0;
for (const element of array) {
if (predicate(element)) {
if (predicate(element, index++)) {
if (found) return undefined;
found = true;
match = element;
Expand Down
12 changes: 11 additions & 1 deletion collections/find_single_test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { assertEquals } from "@std/assert";
import { findSingle } from "./find_single.ts";

function findSingleTest<I>(
input: [Array<I>, (element: I) => boolean],
input: [Array<I>, (element: I, index: number) => boolean],
expected: I | undefined,
message?: string,
) {
Expand Down Expand Up @@ -103,3 +103,13 @@ Deno.test({
);
},
});

Deno.test({
name: "findSingle() passes index to predicate",
fn() {
findSingleTest(
[[9, 12, 13], (_, index) => index === 1],
12,
);
},
});
5 changes: 3 additions & 2 deletions collections/first_not_nullish_of.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,11 @@
*/
export function firstNotNullishOf<T, O>(
array: Iterable<T>,
selector: (item: T) => O | undefined | null,
selector: (item: T, index: number) => O | undefined | null,
): NonNullable<O> | undefined {
let index = 0;
for (const current of array) {
const selected = selector(current);
const selected = selector(current, index++);

if (selected !== null && selected !== undefined) {
return selected as NonNullable<O>;
Expand Down
12 changes: 11 additions & 1 deletion collections/first_not_nullish_of_test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { assertEquals } from "@std/assert";
import { firstNotNullishOf } from "./first_not_nullish_of.ts";

function firstNotNullishOfTest<T, O>(
input: [Array<T>, (el: T) => O | undefined | null],
input: [Array<T>, (el: T, index: number) => O | undefined | null],
expected: NonNullable<O> | undefined,
message?: string,
) {
Expand Down Expand Up @@ -85,3 +85,13 @@ Deno.test({
);
},
});

Deno.test({
name: "firstNotNullishOf() passes index to selector",
fn() {
firstNotNullishOfTest(
[[1, 2, 3, 4], (it, index) => index < 1 ? null : it],
2,
);
},
});
4 changes: 2 additions & 2 deletions collections/join_to_string.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ export type JoinToStringOptions = {
*/
export function joinToString<T>(
array: Iterable<T>,
selector: (el: T) => string,
selector: (el: T, index: number) => string,
options: Readonly<JoinToStringOptions> = {},
): string {
const {
Expand All @@ -101,7 +101,7 @@ export function joinToString<T>(
break;
}

result += selector(el);
result += selector(el, index);
index++;
}

Expand Down
11 changes: 11 additions & 0 deletions collections/join_to_string_test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -144,3 +144,14 @@ Deno.test({
assertEquals(out, "result: Kim and others are winners");
},
});

Deno.test({
name: "joinToString() passes index to selector",
fn() {
const arr = ["Kim", "Anna", "Tim"];

const out = joinToString(arr, (it, index) => it + index);

assertEquals(out, "Kim0,Anna1,Tim2");
},
});
5 changes: 3 additions & 2 deletions collections/map_not_nullish.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,13 @@
*/
export function mapNotNullish<T, O>(
array: Iterable<T>,
transformer: (el: T) => O,
transformer: (el: T, index: number) => O,
): NonNullable<O>[] {
const result: NonNullable<O>[] = [];
let index = 0;

for (const element of array) {
const transformedElement = transformer(element);
const transformedElement = transformer(element, index++);

if (transformedElement !== undefined && transformedElement !== null) {
result.push(transformedElement as NonNullable<O>);
Expand Down
15 changes: 14 additions & 1 deletion collections/map_not_nullish_test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { assertEquals } from "@std/assert";
import { mapNotNullish } from "./map_not_nullish.ts";

function mapNotNullishTest<T, O>(
input: [Array<T>, (el: T) => O | undefined | null],
input: [Array<T>, (el: T, index: number) => O | undefined | null],
expected: Array<O>,
message?: string,
) {
Expand Down Expand Up @@ -90,3 +90,16 @@ Deno.test({
);
},
});

Deno.test({
name: "mapNotNullish() passes index to transformer",
fn() {
mapNotNullishTest(
[
[1, 2, 3, 4],
(it, index) => index === 1 ? null : it + index,
],
[1, 5, 7],
);
},
});
19 changes: 10 additions & 9 deletions collections/max_by.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
*/
export function maxBy<T>(
array: Iterable<T>,
selector: (el: T) => number,
selector: (el: T, index: number) => number,
): T | undefined;
/**
* Returns the first element that is the largest value of the given function or
Expand Down Expand Up @@ -63,7 +63,7 @@ export function maxBy<T>(
*/
export function maxBy<T>(
array: Iterable<T>,
selector: (el: T) => string,
selector: (el: T, index: number) => string,
): T | undefined;
/**
* Returns the first element that is the largest value of the given function or
Expand Down Expand Up @@ -95,7 +95,7 @@ export function maxBy<T>(
*/
export function maxBy<T>(
array: Iterable<T>,
selector: (el: T) => bigint,
selector: (el: T, index: number) => bigint,
): T | undefined;
/**
* Returns the first element that is the largest value of the given function or
Expand Down Expand Up @@ -127,21 +127,22 @@ export function maxBy<T>(
*/
export function maxBy<T>(
array: Iterable<T>,
selector: (el: T) => Date,
selector: (el: T, index: number) => Date,
): T | undefined;
export function maxBy<T>(
array: Iterable<T>,
selector:
| ((el: T) => number)
| ((el: T) => string)
| ((el: T) => bigint)
| ((el: T) => Date),
| ((el: T, index: number) => number)
| ((el: T, index: number) => string)
| ((el: T, index: number) => bigint)
| ((el: T, index: number) => Date),
): T | undefined {
let max: T | undefined;
let maxValue: ReturnType<typeof selector> | undefined;
let index = 0;

for (const current of array) {
const currentValue = selector(current);
const currentValue = selector(current, index++);

if (maxValue === undefined || currentValue > maxValue) {
max = current;
Expand Down
11 changes: 11 additions & 0 deletions collections/max_by_test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -136,3 +136,14 @@ Deno.test({
assertEquals(maxBy(input, (it) => new Date(it)), "February 1, 2022");
},
});

Deno.test({
name: "maxBy() passes index to selector",
fn() {
const input = [4, 3, 2, 1];

const max = maxBy(input, (_, index) => index);

assertEquals(max, 1);
},
});
14 changes: 10 additions & 4 deletions collections/max_of.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
*/
export function maxOf<T>(
array: Iterable<T>,
selector: (el: T) => number,
selector: (el: T, index: number) => number,
): number | undefined;
/**
* Applies the given selector to all elements of the provided collection and
Expand Down Expand Up @@ -65,16 +65,22 @@ export function maxOf<T>(
*/
export function maxOf<T>(
array: Iterable<T>,
selector: (el: T) => bigint,
selector: (el: T, index: number) => bigint,
): bigint | undefined;
export function maxOf<T, S extends ((el: T) => number) | ((el: T) => bigint)>(
export function maxOf<
T,
S extends
| ((el: T, index: number) => number)
| ((el: T, index: number) => bigint),
>(
array: Iterable<T>,
selector: S,
): ReturnType<S> | undefined {
let maximumValue: ReturnType<S> | undefined;
let index = 0;

for (const element of array) {
const currentValue = selector(element) as ReturnType<S>;
const currentValue = selector(element, index++) as ReturnType<S>;

if (maximumValue === undefined || currentValue > maximumValue) {
maximumValue = currentValue;
Expand Down
11 changes: 11 additions & 0 deletions collections/max_of_test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,3 +100,14 @@ Deno.test("maxOf() handles infinity", () => {

assertEquals(actual, Infinity);
});

Deno.test({
name: "maxBy() passes index to selector",
fn() {
const input = [4, 3, 2, 1];

const max = maxOf(input, (it, index) => it * index);

assertEquals(max, 4);
},
});
Loading

0 comments on commit 65c0d65

Please sign in to comment.