Repository: marpple/FxTS (ts)

Issue:
Add `nth` function
# Suggestion

<!--
  Please fill in each section completely. Thank you!
-->

## ⭐ Suggestion

<!-- What would you like to be added? -->

Returns the nth element of the given Iterable/AsyncIterable

## 💻 Use Cases

<!-- What do you want to use this for? -->

```ts
nth(2, [1,2,3,4]); // 1
nth(5, [1,2,3,4]); // undefined
nth(2, ['name', 'gender', 'age']); // 'age'
nth(3, ['name', 'gender', 'age']); // undefined
nth(2, 'abcdefg'); // 'b'
nth(10, 'abcdefg'); // undefined
```


Pull request:
feat: add nth function
Fixes #47


Patch:
diff --git a/src/Lazy/concurrent.ts b/src/Lazy/concurrent.ts
index ec9ee66a..7dce9d0e 100644
--- a/src/Lazy/concurrent.ts
+++ b/src/Lazy/concurrent.ts
@@ -86,11 +86,11 @@ function concurrent<A>(
   }
 
   if (length === Infinity) {
-    throw new TypeError("'length' cannot be infinite");
+    throw new RangeError("'length' cannot be infinite");
   }
 
   if (length <= 0) {
-    throw new TypeError("'length' must be over 0");
+    throw new RangeError("'length' must be over 0");
   }
 
   if (!isAsyncIterable(iterable)) {
diff --git a/src/index.ts b/src/index.ts
index 720e140a..2e818cb2 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -14,6 +14,7 @@ import isString from "./isString";
 import join from "./join";
 import last from "./last";
 import not from "./not";
+import nth from "./nth";
 import omit from "./omit";
 import partition from "./partition";
 import pick from "./pick";
@@ -42,6 +43,7 @@ export {
   join,
   last,
   not,
+  nth,
   omit,
   partition,
   pick,
diff --git a/src/nth.ts b/src/nth.ts
new file mode 100644
index 00000000..7ecfdfe6
--- /dev/null
+++ b/src/nth.ts
@@ -0,0 +1,91 @@
+import IterableInfer from "./types/IterableInfer";
+import ReturnValueType from "./types/ReturnValueType";
+import { isAsyncIterable, isIterable } from "./_internal/utils";
+
+function sync<T>(index: number, iterable: Iterable<T>): T | undefined {
+  let idx = 0;
+  for (const item of iterable) {
+    if (idx++ === index) {
+      return item;
+    }
+  }
+}
+
+async function async<T>(
+  index: number,
+  iterable: AsyncIterable<T>,
+): Promise<T | undefined> {
+  let idx = 0;
+  for await (const item of iterable) {
+    if (idx++ === index) {
+      return item;
+    }
+  }
+}
+
+/**
+ * Returns the nth element of the given Iterable/AsyncIterable
+ *
+ * @example
+ * ```ts
+ * nth(2, [1,2,3,4]); // 1
+ * nth(5, [1,2,3,4]); // undefined
+ * nth(2, ['name', 'gender', 'age']); // 'age'
+ * nth(3, ['name', 'gender', 'age']); // undefined
+ * nth(2, 'abcdefg'); // 'b'
+ * nth(10, 'abcdefg'); // undefined
+ * ```
+ */
+// prettier-ignore
+function nth(
+  index: number, 
+  iterable: readonly []
+): undefined;
+
+// prettier-ignore
+function nth<T>(
+  index: number, 
+  iterable: Iterable<T>,
+): T | undefined;
+
+function nth<T>(
+  index: number,
+  iterable: AsyncIterable<T>,
+): Promise<T | undefined>;
+
+function nth<T extends Iterable<unknown> | AsyncIterable<unknown>>(
+  index: number,
+): (iterable: T) => ReturnValueType<T, IterableInfer<T> | undefined>;
+
+function nth<T extends Iterable<unknown> | AsyncIterable<unknown>>(
+  index: number,
+  iterable?: T,
+):
+  | undefined
+  | IterableInfer<T>
+  | Promise<IterableInfer<T> | undefined>
+  | ((iterable: T) => ReturnValueType<T, IterableInfer<T> | undefined>) {
+  if (iterable === undefined) {
+    return (iterable: T) => {
+      return nth(index, iterable as any) as ReturnValueType<
+        T,
+        IterableInfer<T> | undefined
+      >;
+    };
+  }
+
+  if (index < 0) {
+    throw new RangeError("'index' must be over 0");
+  }
+
+  if (isIterable(iterable)) {
+    return sync(index, iterable as Iterable<IterableInfer<T>>);
+  }
+  if (isAsyncIterable(iterable)) {
+    return async(index, iterable as AsyncIterable<IterableInfer<T>>);
+  }
+
+  throw new TypeError("'iterable' must be type of Iterable or AsyncIterable");
+}
+
+export default nth;


Test patch:
diff --git a/test/nth.spec.ts b/test/nth.spec.ts
new file mode 100644
index 00000000..d71311dc
--- /dev/null
+++ b/test/nth.spec.ts
@@ -0,0 +1,44 @@
+import { filter, nth, map, pipe, toAsync } from "../src/index";
+
+describe("nth", function () {
+  describe("sync", function () {
+    it("should return nth element", function () {
+      expect(nth(2, "marpple")).toEqual("r");
+      expect(nth(10, "marpple")).toEqual(undefined);
+      expect(() => nth(-1, "marpple")).toThrow();
+
+      expect(nth(1, [1, 2, 3, 4])).toEqual(2);
+      expect(nth(5, [1, 2, 3, 4])).toEqual(undefined);
+    });
+
+    it("should be able to be used as a curried function in the pipeline", function () {
+      const res = pipe(
+        [1, 2, 3, 4],
+        map((a) => a + 10),
+        filter((a) => a % 2 === 0),
+        nth(1),
+      );
+      expect(res).toEqual(14);
+    });
+  });
+
+  describe("async", function () {
+    it("should return nth element", async function () {
+      expect(await nth(2, toAsync("marpple"))).toEqual("r");
+      expect(await nth(10, toAsync("marpple"))).toEqual(undefined);
+
+      expect(await nth(1, toAsync([1, 2, 3, 4]))).toEqual(2);
+      expect(await nth(5, toAsync([1, 2, 3, 4]))).toEqual(undefined);
+    });
+  });
+
+  it("should be able to be used as a curried function in the pipeline", async function () {
+    const res1 = await pipe(
+      toAsync([1, 2, 3, 4]),
+      map((a) => a + 10),
+      filter((a) => a % 2 === 0),
+      nth(1),
+    );
+    expect(res1).toEqual(14);
+  });
+});
diff --git a/type-check/nth.test.ts b/type-check/nth.test.ts
new file mode 100644
index 00000000..6a513155
--- /dev/null
+++ b/type-check/nth.test.ts
@@ -0,0 +1,21 @@
+import * as Test from "../src/types/Test";
+import { nth, pipe, toAsync } from "../src";
+
+const { checks, check } = Test;
+
+const res1 = nth(3, []);
+const res2 = nth(3, [1, 2, 3, 4, 5]);
+const res3 = nth(3, ["a", "b", "c", "d"]);
+const res4 = nth(3, toAsync(["a", "b", "c", "d"]));
+
+const res5 = pipe([1, 2, 3, 4, 5], nth(3));
+const res6 = pipe(["a", "b", "c", "d"], toAsync, nth(3));
+
+checks([
+  check<typeof res1, undefined, Test.Pass>(),
+  check<typeof res2, number | undefined, Test.Pass>(),
+  check<typeof res3, string | undefined, Test.Pass>(),
+  check<typeof res4, Promise<string | undefined>, Test.Pass>(),
+  check<typeof res5, number | undefined, Test.Pass>(),
+  check<typeof res6, Promise<string | undefined>, Test.Pass>(),
+]);