Skip to content

Commit e14364a

Browse files
authored
Merge pull request #17 from jsr-core/add-nth
feat(nth): add `nth` operator
2 parents 14868c7 + 4de0405 commit e14364a

File tree

14 files changed

+275
-0
lines changed

14 files changed

+275
-0
lines changed

README.md

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -857,6 +857,43 @@ const iter = pipe(
857857
console.log(await Array.fromAsync(iter)); // [2, 4, 6]
858858
```
859859

860+
### nth
861+
862+
Returns the n-th element of an iterable. If the length of the iterable is less,
863+
returns `undefined`.
864+
865+
```ts
866+
import { nth } from "@core/iterutil/nth";
867+
868+
const result = nth([1, 2, 3], 1);
869+
console.log(result); // 2
870+
```
871+
872+
```ts
873+
import { nth } from "@core/iterutil/async/nth";
874+
875+
const result = await nth([1, 2, 3], 1);
876+
console.log(result); // 2
877+
```
878+
879+
Use `pipe` and `pipe/async` modules for [@core/pipe] package like.
880+
881+
```ts
882+
import { pipe } from "@core/pipe";
883+
import { nth } from "@core/iterutil/pipe/nth";
884+
885+
const result = pipe([1, 2, 3], nth(1));
886+
console.log(result); // 2
887+
```
888+
889+
```ts
890+
import { pipe } from "@core/pipe";
891+
import { nth } from "@core/iterutil/pipe/async/nth";
892+
893+
const result = await pipe([1, 2, 3], nth(1));
894+
console.log(result); // 2
895+
```
896+
860897
### pairwise
861898

862899
Returns an iterable that pairs adjacent elements from the input iterable.

async/mod.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ export * from "./for_each.ts";
1616
export * from "./iter.ts";
1717
export * from "./last.ts";
1818
export * from "./map.ts";
19+
export * from "./nth.ts";
1920
export * from "./pairwise.ts";
2021
export * from "./partition.ts";
2122
export * from "./reduce.ts";

async/nth.ts

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
/**
2+
* Returns the n-th element of an iterable. If the length of the iterable is less, returns `undefined`.
3+
*
4+
* Use {@linkcode https://jsr.io/@core/iterutil/doc/async/first/~/first first} to get the first element of an iterable.
5+
* Use {@linkcode https://jsr.io/@core/iterutil/doc/async/last/~/last last} to get the last element of an iterable.
6+
* Use {@linkcode https://jsr.io/@core/iterutil/doc/async/find/~/find find} to get the first element that matches a predicate.
7+
* Use {@linkcode https://jsr.io/@core/iterutil/doc/nth/~/nth nth} to get the n-th element synchronously.
8+
*
9+
* @param iterable The iterable to get the first element from.
10+
* @param index The index of the element to get (0-based).
11+
* @returns The first element of the iterable, or `undefined` if the iterable is empty.
12+
* @throws {RangeError} If `index` is not zero nor a positive safe integer.
13+
*
14+
* @example
15+
* ```ts
16+
* import { nth } from "@core/iterutil/async/nth";
17+
*
18+
* const value = await nth([1, 2, 3], 1);
19+
* console.log(value); // 2
20+
* ```
21+
*/
22+
export function nth<T>(
23+
iterable: Iterable<T> | AsyncIterable<T>,
24+
index: number,
25+
): Promise<T | undefined> {
26+
if (index < 0 || !Number.isSafeInteger(index)) {
27+
throw new RangeError(
28+
`index must be 0 or positive safe integer, but got ${index}.`,
29+
);
30+
}
31+
return async function () {
32+
let i = 0;
33+
for await (const value of iterable) {
34+
if (index === i++) {
35+
return value;
36+
}
37+
}
38+
return undefined;
39+
}();
40+
}

async/nth_test.ts

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import { assertEquals, assertThrows } from "@std/assert";
2+
import { assertType, type IsExact } from "@std/testing/types";
3+
import { toAsyncIterable } from "./to_async_iterable.ts";
4+
import { nth } from "./nth.ts";
5+
6+
Deno.test("last", async (t) => {
7+
await t.step("with async iterable", async (t) => {
8+
await t.step("with non empty iterable", async () => {
9+
const result = await nth(toAsyncIterable([1, 2, 3, 4, 5]), 2);
10+
const expected = 3;
11+
assertEquals(result, expected);
12+
assertType<IsExact<typeof result, number | undefined>>(true);
13+
});
14+
15+
await t.step("with empty iterable", async () => {
16+
const result = await nth(toAsyncIterable([] as number[]), 2);
17+
const expected = undefined;
18+
assertEquals(result, expected);
19+
assertType<IsExact<typeof result, number | undefined>>(true);
20+
});
21+
});
22+
23+
await t.step("with iterable", async (t) => {
24+
await t.step("with non empty iterable", async () => {
25+
const result = await nth([1, 2, 3, 4, 5], 2);
26+
const expected = 3;
27+
assertEquals(result, expected);
28+
assertType<IsExact<typeof result, number | undefined>>(true);
29+
});
30+
31+
await t.step("with empty iterable", async () => {
32+
const result = await nth([] as number[], 2);
33+
const expected = undefined;
34+
assertEquals(result, expected);
35+
assertType<IsExact<typeof result, number | undefined>>(true);
36+
});
37+
});
38+
39+
await t.step("throws RangeError", async (t) => {
40+
await t.step("if the index is not 0 nor positive safe integer", () => {
41+
assertThrows(() => nth([], NaN), RangeError);
42+
assertThrows(() => nth([], Infinity), RangeError);
43+
assertThrows(() => nth([], -Infinity), RangeError);
44+
assertThrows(() => nth([], -1), RangeError);
45+
assertThrows(() => nth([], 1.1), RangeError);
46+
});
47+
});
48+
});

deno.jsonc

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
"./async/iter": "./async/iter.ts",
2323
"./async/last": "./async/last.ts",
2424
"./async/map": "./async/map.ts",
25+
"./async/nth": "./async/nth.ts",
2526
"./async/pairwise": "./async/pairwise.ts",
2627
"./async/partition": "./async/partition.ts",
2728
"./async/reduce": "./async/reduce.ts",
@@ -50,6 +51,7 @@
5051
"./iter": "./iter.ts",
5152
"./last": "./last.ts",
5253
"./map": "./map.ts",
54+
"./nth": "./nth.ts",
5355
"./pairwise": "./pairwise.ts",
5456
"./partition": "./partition.ts",
5557
"./pipe": "./pipe/mod.ts",
@@ -71,6 +73,7 @@
7173
"./pipe/async/for-each": "./pipe/async/for_each.ts",
7274
"./pipe/async/last": "./pipe/async/last.ts",
7375
"./pipe/async/map": "./pipe/async/map.ts",
76+
"./pipe/async/nth": "./pipe/async/nth.ts",
7477
"./pipe/async/pairwise": "./pipe/async/pairwise.ts",
7578
"./pipe/async/partition": "./pipe/async/partition.ts",
7679
"./pipe/async/reduce": "./pipe/async/reduce.ts",
@@ -96,6 +99,7 @@
9699
"./pipe/for-each": "./pipe/for_each.ts",
97100
"./pipe/last": "./pipe/last.ts",
98101
"./pipe/map": "./pipe/map.ts",
102+
"./pipe/nth": "./pipe/nth.ts",
99103
"./pipe/pairwise": "./pipe/pairwise.ts",
100104
"./pipe/partition": "./pipe/partition.ts",
101105
"./pipe/reduce": "./pipe/reduce.ts",
@@ -148,6 +152,7 @@
148152
"@core/iterutil/async/iter": "./async/iter.ts",
149153
"@core/iterutil/async/last": "./async/last.ts",
150154
"@core/iterutil/async/map": "./async/map.ts",
155+
"@core/iterutil/async/nth": "./async/nth.ts",
151156
"@core/iterutil/async/pairwise": "./async/pairwise.ts",
152157
"@core/iterutil/async/partition": "./async/partition.ts",
153158
"@core/iterutil/async/reduce": "./async/reduce.ts",
@@ -176,6 +181,7 @@
176181
"@core/iterutil/iter": "./iter.ts",
177182
"@core/iterutil/last": "./last.ts",
178183
"@core/iterutil/map": "./map.ts",
184+
"@core/iterutil/nth": "./nth.ts",
179185
"@core/iterutil/pairwise": "./pairwise.ts",
180186
"@core/iterutil/partition": "./partition.ts",
181187
"@core/iterutil/pipe": "./pipe/mod.ts",
@@ -199,6 +205,7 @@
199205
"@core/iterutil/pipe/async/iter": "./pipe/async/iter.ts",
200206
"@core/iterutil/pipe/async/last": "./pipe/async/last.ts",
201207
"@core/iterutil/pipe/async/map": "./pipe/async/map.ts",
208+
"@core/iterutil/pipe/async/nth": "./pipe/async/nth.ts",
202209
"@core/iterutil/pipe/async/pairwise": "./pipe/async/pairwise.ts",
203210
"@core/iterutil/pipe/async/partition": "./pipe/async/partition.ts",
204211
"@core/iterutil/pipe/async/reduce": "./pipe/async/reduce.ts",
@@ -226,6 +233,7 @@
226233
"@core/iterutil/pipe/iter": "./pipe/iter.ts",
227234
"@core/iterutil/pipe/last": "./pipe/last.ts",
228235
"@core/iterutil/pipe/map": "./pipe/map.ts",
236+
"@core/iterutil/pipe/nth": "./pipe/nth.ts",
229237
"@core/iterutil/pipe/pairwise": "./pipe/pairwise.ts",
230238
"@core/iterutil/pipe/partition": "./pipe/partition.ts",
231239
"@core/iterutil/pipe/reduce": "./pipe/reduce.ts",

mod.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ export * from "./for_each.ts";
1717
export * from "./iter.ts";
1818
export * from "./last.ts";
1919
export * from "./map.ts";
20+
export * from "./nth.ts";
2021
export * from "./pairwise.ts";
2122
export * from "./partition.ts";
2223
export * from "./range.ts";

nth.ts

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
/**
2+
* Returns the n-th element of an iterable. If the length of the iterable is less, returns `undefined`.
3+
*
4+
* Use {@linkcode https://jsr.io/@core/iterutil/doc/first/~/first first} to get the first element of an iterable.
5+
* Use {@linkcode https://jsr.io/@core/iterutil/doc/last/~/last last} to get the last element of an iterable.
6+
* Use {@linkcode https://jsr.io/@core/iterutil/doc/find/~/find find} to get the first element that matches a predicate.
7+
* Use {@linkcode https://jsr.io/@core/iterutil/doc/async/nth/~/nth nth} to get the n-th element asynchronously.
8+
*
9+
* @param iterable The iterable to get the first element from.
10+
* @param index The index of the element to get (0-based).
11+
* @returns The first element of the iterable, or `undefined` if the iterable is empty.
12+
* @throws {RangeError} If `index` is not zero nor a positive safe integer.
13+
*
14+
* @example
15+
* ```ts
16+
* import { nth } from "@core/iterutil/nth";
17+
*
18+
* const result = nth([1, 2, 3], 1);
19+
* console.log(result); // 2
20+
* ```
21+
*/
22+
export function nth<T>(iterable: Iterable<T>, index: number): T | undefined {
23+
if (index < 0 || !Number.isSafeInteger(index)) {
24+
throw new RangeError(
25+
`index must be 0 or positive safe integer, but got ${index}.`,
26+
);
27+
}
28+
let i = 0;
29+
for (const value of iterable) {
30+
if (index === i++) {
31+
return value;
32+
}
33+
}
34+
return undefined;
35+
}

nth_test.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import { assertEquals, assertThrows } from "@std/assert";
2+
import { assertType, type IsExact } from "@std/testing/types";
3+
import { nth } from "./nth.ts";
4+
5+
Deno.test("nth", async (t) => {
6+
await t.step("with non empty iterable", () => {
7+
const result = nth([1, 2, 3, 4, 5], 2);
8+
const expected = 3;
9+
assertEquals(result, expected);
10+
assertType<IsExact<typeof result, number | undefined>>(true);
11+
});
12+
13+
await t.step("with empty iterable", () => {
14+
const result = nth([] as number[], 2);
15+
const expected = undefined;
16+
assertEquals(result, expected);
17+
assertType<IsExact<typeof result, number | undefined>>(true);
18+
});
19+
20+
await t.step("throws RangeError", async (t) => {
21+
await t.step("if the index is not 0 nor positive safe integer", () => {
22+
assertThrows(() => nth([], NaN), RangeError);
23+
assertThrows(() => nth([], Infinity), RangeError);
24+
assertThrows(() => nth([], -Infinity), RangeError);
25+
assertThrows(() => nth([], -1), RangeError);
26+
assertThrows(() => nth([], 1.1), RangeError);
27+
});
28+
});
29+
});

pipe/async/mod.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ export * from "./flatten.ts";
1515
export * from "./for_each.ts";
1616
export * from "./last.ts";
1717
export * from "./map.ts";
18+
export * from "./nth.ts";
1819
export * from "./pairwise.ts";
1920
export * from "./partition.ts";
2021
export * from "./reduce.ts";

pipe/async/nth.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import { nth as base } from "@core/iterutil/async/nth";
2+
3+
/**
4+
* Returns an operator that returns the n-th element of an iterable. If the length of the iterable is less, returns `undefined`.
5+
*
6+
* See {@linkcode https://jsr.io/@core/iterutil/doc/async/nth/~/nth nth} for native nth.
7+
*
8+
* @example
9+
* ```ts
10+
* import { pipe } from "@core/pipe";
11+
* import { nth } from "@core/iterutil/pipe/async/nth";
12+
*
13+
* const value = await pipe(
14+
* [1, 2, 3],
15+
* nth(1),
16+
* );
17+
* console.log(value); // 2
18+
* ```
19+
*/
20+
export function nth(
21+
index: number,
22+
): <T>(iterable: Iterable<T> | AsyncIterable<T>) => Promise<T | undefined> {
23+
return (iterable) => base(iterable, index);
24+
}

0 commit comments

Comments
 (0)