Skip to content

Commit

Permalink
Merge pull request #17 from jsr-core/add-nth
Browse files Browse the repository at this point in the history
feat(nth): add `nth` operator
  • Loading branch information
lambdalisue authored Aug 13, 2024
2 parents 14868c7 + 4de0405 commit e14364a
Show file tree
Hide file tree
Showing 14 changed files with 275 additions and 0 deletions.
37 changes: 37 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -857,6 +857,43 @@ const iter = pipe(
console.log(await Array.fromAsync(iter)); // [2, 4, 6]
```

### nth

Returns the n-th element of an iterable. If the length of the iterable is less,
returns `undefined`.

```ts
import { nth } from "@core/iterutil/nth";

const result = nth([1, 2, 3], 1);
console.log(result); // 2
```

```ts
import { nth } from "@core/iterutil/async/nth";

const result = await nth([1, 2, 3], 1);
console.log(result); // 2
```

Use `pipe` and `pipe/async` modules for [@core/pipe] package like.

```ts
import { pipe } from "@core/pipe";
import { nth } from "@core/iterutil/pipe/nth";

const result = pipe([1, 2, 3], nth(1));
console.log(result); // 2
```

```ts
import { pipe } from "@core/pipe";
import { nth } from "@core/iterutil/pipe/async/nth";

const result = await pipe([1, 2, 3], nth(1));
console.log(result); // 2
```

### pairwise

Returns an iterable that pairs adjacent elements from the input iterable.
Expand Down
1 change: 1 addition & 0 deletions async/mod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ export * from "./for_each.ts";
export * from "./iter.ts";
export * from "./last.ts";
export * from "./map.ts";
export * from "./nth.ts";
export * from "./pairwise.ts";
export * from "./partition.ts";
export * from "./reduce.ts";
Expand Down
40 changes: 40 additions & 0 deletions async/nth.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/**
* Returns the n-th element of an iterable. If the length of the iterable is less, returns `undefined`.
*
* Use {@linkcode https://jsr.io/@core/iterutil/doc/async/first/~/first first} to get the first element of an iterable.
* Use {@linkcode https://jsr.io/@core/iterutil/doc/async/last/~/last last} to get the last element of an iterable.
* Use {@linkcode https://jsr.io/@core/iterutil/doc/async/find/~/find find} to get the first element that matches a predicate.
* Use {@linkcode https://jsr.io/@core/iterutil/doc/nth/~/nth nth} to get the n-th element synchronously.
*
* @param iterable The iterable to get the first element from.
* @param index The index of the element to get (0-based).
* @returns The first element of the iterable, or `undefined` if the iterable is empty.
* @throws {RangeError} If `index` is not zero nor a positive safe integer.
*
* @example
* ```ts
* import { nth } from "@core/iterutil/async/nth";
*
* const value = await nth([1, 2, 3], 1);
* console.log(value); // 2
* ```
*/
export function nth<T>(
iterable: Iterable<T> | AsyncIterable<T>,
index: number,
): Promise<T | undefined> {
if (index < 0 || !Number.isSafeInteger(index)) {
throw new RangeError(
`index must be 0 or positive safe integer, but got ${index}.`,
);
}
return async function () {
let i = 0;
for await (const value of iterable) {
if (index === i++) {
return value;
}
}
return undefined;
}();
}
48 changes: 48 additions & 0 deletions async/nth_test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { assertEquals, assertThrows } from "@std/assert";
import { assertType, type IsExact } from "@std/testing/types";
import { toAsyncIterable } from "./to_async_iterable.ts";
import { nth } from "./nth.ts";

Deno.test("last", async (t) => {
await t.step("with async iterable", async (t) => {
await t.step("with non empty iterable", async () => {
const result = await nth(toAsyncIterable([1, 2, 3, 4, 5]), 2);
const expected = 3;
assertEquals(result, expected);
assertType<IsExact<typeof result, number | undefined>>(true);
});

await t.step("with empty iterable", async () => {
const result = await nth(toAsyncIterable([] as number[]), 2);
const expected = undefined;
assertEquals(result, expected);
assertType<IsExact<typeof result, number | undefined>>(true);
});
});

await t.step("with iterable", async (t) => {
await t.step("with non empty iterable", async () => {
const result = await nth([1, 2, 3, 4, 5], 2);
const expected = 3;
assertEquals(result, expected);
assertType<IsExact<typeof result, number | undefined>>(true);
});

await t.step("with empty iterable", async () => {
const result = await nth([] as number[], 2);
const expected = undefined;
assertEquals(result, expected);
assertType<IsExact<typeof result, number | undefined>>(true);
});
});

await t.step("throws RangeError", async (t) => {
await t.step("if the index is not 0 nor positive safe integer", () => {
assertThrows(() => nth([], NaN), RangeError);
assertThrows(() => nth([], Infinity), RangeError);
assertThrows(() => nth([], -Infinity), RangeError);
assertThrows(() => nth([], -1), RangeError);
assertThrows(() => nth([], 1.1), RangeError);
});
});
});
8 changes: 8 additions & 0 deletions deno.jsonc
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
"./async/iter": "./async/iter.ts",
"./async/last": "./async/last.ts",
"./async/map": "./async/map.ts",
"./async/nth": "./async/nth.ts",
"./async/pairwise": "./async/pairwise.ts",
"./async/partition": "./async/partition.ts",
"./async/reduce": "./async/reduce.ts",
Expand Down Expand Up @@ -50,6 +51,7 @@
"./iter": "./iter.ts",
"./last": "./last.ts",
"./map": "./map.ts",
"./nth": "./nth.ts",
"./pairwise": "./pairwise.ts",
"./partition": "./partition.ts",
"./pipe": "./pipe/mod.ts",
Expand All @@ -71,6 +73,7 @@
"./pipe/async/for-each": "./pipe/async/for_each.ts",
"./pipe/async/last": "./pipe/async/last.ts",
"./pipe/async/map": "./pipe/async/map.ts",
"./pipe/async/nth": "./pipe/async/nth.ts",
"./pipe/async/pairwise": "./pipe/async/pairwise.ts",
"./pipe/async/partition": "./pipe/async/partition.ts",
"./pipe/async/reduce": "./pipe/async/reduce.ts",
Expand All @@ -96,6 +99,7 @@
"./pipe/for-each": "./pipe/for_each.ts",
"./pipe/last": "./pipe/last.ts",
"./pipe/map": "./pipe/map.ts",
"./pipe/nth": "./pipe/nth.ts",
"./pipe/pairwise": "./pipe/pairwise.ts",
"./pipe/partition": "./pipe/partition.ts",
"./pipe/reduce": "./pipe/reduce.ts",
Expand Down Expand Up @@ -148,6 +152,7 @@
"@core/iterutil/async/iter": "./async/iter.ts",
"@core/iterutil/async/last": "./async/last.ts",
"@core/iterutil/async/map": "./async/map.ts",
"@core/iterutil/async/nth": "./async/nth.ts",
"@core/iterutil/async/pairwise": "./async/pairwise.ts",
"@core/iterutil/async/partition": "./async/partition.ts",
"@core/iterutil/async/reduce": "./async/reduce.ts",
Expand Down Expand Up @@ -176,6 +181,7 @@
"@core/iterutil/iter": "./iter.ts",
"@core/iterutil/last": "./last.ts",
"@core/iterutil/map": "./map.ts",
"@core/iterutil/nth": "./nth.ts",
"@core/iterutil/pairwise": "./pairwise.ts",
"@core/iterutil/partition": "./partition.ts",
"@core/iterutil/pipe": "./pipe/mod.ts",
Expand All @@ -199,6 +205,7 @@
"@core/iterutil/pipe/async/iter": "./pipe/async/iter.ts",
"@core/iterutil/pipe/async/last": "./pipe/async/last.ts",
"@core/iterutil/pipe/async/map": "./pipe/async/map.ts",
"@core/iterutil/pipe/async/nth": "./pipe/async/nth.ts",
"@core/iterutil/pipe/async/pairwise": "./pipe/async/pairwise.ts",
"@core/iterutil/pipe/async/partition": "./pipe/async/partition.ts",
"@core/iterutil/pipe/async/reduce": "./pipe/async/reduce.ts",
Expand Down Expand Up @@ -226,6 +233,7 @@
"@core/iterutil/pipe/iter": "./pipe/iter.ts",
"@core/iterutil/pipe/last": "./pipe/last.ts",
"@core/iterutil/pipe/map": "./pipe/map.ts",
"@core/iterutil/pipe/nth": "./pipe/nth.ts",
"@core/iterutil/pipe/pairwise": "./pipe/pairwise.ts",
"@core/iterutil/pipe/partition": "./pipe/partition.ts",
"@core/iterutil/pipe/reduce": "./pipe/reduce.ts",
Expand Down
1 change: 1 addition & 0 deletions mod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export * from "./for_each.ts";
export * from "./iter.ts";
export * from "./last.ts";
export * from "./map.ts";
export * from "./nth.ts";
export * from "./pairwise.ts";
export * from "./partition.ts";
export * from "./range.ts";
Expand Down
35 changes: 35 additions & 0 deletions nth.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/**
* Returns the n-th element of an iterable. If the length of the iterable is less, returns `undefined`.
*
* Use {@linkcode https://jsr.io/@core/iterutil/doc/first/~/first first} to get the first element of an iterable.
* Use {@linkcode https://jsr.io/@core/iterutil/doc/last/~/last last} to get the last element of an iterable.
* Use {@linkcode https://jsr.io/@core/iterutil/doc/find/~/find find} to get the first element that matches a predicate.
* Use {@linkcode https://jsr.io/@core/iterutil/doc/async/nth/~/nth nth} to get the n-th element asynchronously.
*
* @param iterable The iterable to get the first element from.
* @param index The index of the element to get (0-based).
* @returns The first element of the iterable, or `undefined` if the iterable is empty.
* @throws {RangeError} If `index` is not zero nor a positive safe integer.
*
* @example
* ```ts
* import { nth } from "@core/iterutil/nth";
*
* const result = nth([1, 2, 3], 1);
* console.log(result); // 2
* ```
*/
export function nth<T>(iterable: Iterable<T>, index: number): T | undefined {
if (index < 0 || !Number.isSafeInteger(index)) {
throw new RangeError(
`index must be 0 or positive safe integer, but got ${index}.`,
);
}
let i = 0;
for (const value of iterable) {
if (index === i++) {
return value;
}
}
return undefined;
}
29 changes: 29 additions & 0 deletions nth_test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { assertEquals, assertThrows } from "@std/assert";
import { assertType, type IsExact } from "@std/testing/types";
import { nth } from "./nth.ts";

Deno.test("nth", async (t) => {
await t.step("with non empty iterable", () => {
const result = nth([1, 2, 3, 4, 5], 2);
const expected = 3;
assertEquals(result, expected);
assertType<IsExact<typeof result, number | undefined>>(true);
});

await t.step("with empty iterable", () => {
const result = nth([] as number[], 2);
const expected = undefined;
assertEquals(result, expected);
assertType<IsExact<typeof result, number | undefined>>(true);
});

await t.step("throws RangeError", async (t) => {
await t.step("if the index is not 0 nor positive safe integer", () => {
assertThrows(() => nth([], NaN), RangeError);
assertThrows(() => nth([], Infinity), RangeError);
assertThrows(() => nth([], -Infinity), RangeError);
assertThrows(() => nth([], -1), RangeError);
assertThrows(() => nth([], 1.1), RangeError);
});
});
});
1 change: 1 addition & 0 deletions pipe/async/mod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export * from "./flatten.ts";
export * from "./for_each.ts";
export * from "./last.ts";
export * from "./map.ts";
export * from "./nth.ts";
export * from "./pairwise.ts";
export * from "./partition.ts";
export * from "./reduce.ts";
Expand Down
24 changes: 24 additions & 0 deletions pipe/async/nth.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { nth as base } from "@core/iterutil/async/nth";

/**
* Returns an operator that returns the n-th element of an iterable. If the length of the iterable is less, returns `undefined`.
*
* See {@linkcode https://jsr.io/@core/iterutil/doc/async/nth/~/nth nth} for native nth.
*
* @example
* ```ts
* import { pipe } from "@core/pipe";
* import { nth } from "@core/iterutil/pipe/async/nth";
*
* const value = await pipe(
* [1, 2, 3],
* nth(1),
* );
* console.log(value); // 2
* ```
*/
export function nth(
index: number,
): <T>(iterable: Iterable<T> | AsyncIterable<T>) => Promise<T | undefined> {
return (iterable) => base(iterable, index);
}
13 changes: 13 additions & 0 deletions pipe/async/nth_test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { assertEquals } from "@std/assert";
import { assertType, type IsExact } from "@std/testing/types";
import { pipe } from "@core/pipe";
import { nth } from "./nth.ts";

Deno.test("nth", async (t) => {
await t.step("usage", async () => {
const result = await pipe([1, 2, 3], nth(1));
const expected = 2;
assertEquals(result, expected);
assertType<IsExact<typeof result, number | undefined>>(true);
});
});
1 change: 1 addition & 0 deletions pipe/mod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export * from "./flatten.ts";
export * from "./for_each.ts";
export * from "./last.ts";
export * from "./map.ts";
export * from "./nth.ts";
export * from "./pairwise.ts";
export * from "./partition.ts";
export * from "./reduce.ts";
Expand Down
24 changes: 24 additions & 0 deletions pipe/nth.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { nth as base } from "@core/iterutil/nth";

/**
* Returns an operator that returns the n-th element of an iterable. If the length of the iterable is less, returns `undefined`.
*
* See {@linkcode https://jsr.io/@core/iterutil/doc/nth/~/nth nth} for native nth.
*
* @example
* ```ts
* import { pipe } from "@core/pipe";
* import { nth } from "@core/iterutil/pipe/nth";
*
* const value = pipe(
* [1, 2, 3],
* nth(1),
* );
* console.log(value); // 2
* ```
*/
export function nth(
index: number,
): <T>(iterable: Iterable<T>) => T | undefined {
return (iterable) => base(iterable, index);
}
13 changes: 13 additions & 0 deletions pipe/nth_test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { assertEquals } from "@std/assert";
import { assertType, type IsExact } from "@std/testing/types";
import { pipe } from "@core/pipe";
import { nth } from "./nth.ts";

Deno.test("nth", async (t) => {
await t.step("usage", () => {
const result = pipe([1, 2, 3], nth(1));
const expected = 2;
assertEquals(result, expected);
assertType<IsExact<typeof result, number | undefined>>(true);
});
});

0 comments on commit e14364a

Please sign in to comment.