Repository: marpple/FxTS (ts)

Issue:
Add `slice` function
# Suggestion

<!--
  Please fill in each section completely. Thank you!
-->

## ⭐ Suggestion

<!-- What would you like to be added? -->

Returns an `Iterable/AsyncIterable` of the given elements from `startIndex`(inclusive) to `endIndex`(exclusive).

## 💻 Use Cases

<!-- What do you want to use this for? -->

```ts
const iter1 = slice(0, 2, [1,2,3,4,5]); // [1, 2]
iter1.next(); // {value:1, done:false}
iter1.next(); // {value:2, done:false}
iter1.next(); // {value:undefined, done:true}


const iter2 = slice(0, 2, 'abcde'); // [1, 2]
iter2.next(); // {value:'a', done:false}
iter2.next(); // {value:'b', done:false}
iter2.next(); // {value:undefined, done:true}
```


Pull request description:
feat: add `slice` function
Fixes #73 


Patch:
diff --git a/src/Lazy/index.ts b/src/Lazy/index.ts
index 7a43d6fe..e7dd61ae 100644
--- a/src/Lazy/index.ts
+++ b/src/Lazy/index.ts
@@ -23,6 +23,7 @@ import reject from "./reject";
 import repeat from "./repeat";
 import reverse from "./reverse";
 import scan from "./scan";
+import slice from "./slice";
 import split from "./split";
 import take from "./take";
 import takeUntil from "./takeUntil";
@@ -60,6 +61,7 @@ export {
   repeat,
   reverse,
   scan,
+  slice,
   split,
   take,
   takeUntil,
diff --git a/src/Lazy/slice.ts b/src/Lazy/slice.ts
new file mode 100644
index 00000000..80446245
--- /dev/null
+++ b/src/Lazy/slice.ts
@@ -0,0 +1,176 @@
+import isNumber from "../isNumber";
+import ReturnIterableIteratorType from "../types/ReturnIterableIteratorType";
+import { isAsyncIterable, isIterable } from "../_internal/utils";
+import concurrent, { isConcurrent } from "./concurrent";
+
+function* sync<T>(
+  start: number,
+  end: number,
+  iterable: Iterable<T>,
+): IterableIterator<T> {
+  let i = 0;
+  for (const item of iterable) {
+    if (i >= start && i < end) {
+      yield item;
+    }
+    i += 1;
+  }
+}
+
+async function* asyncSequential<T>(
+  start: number,
+  end: number,
+  iterable: AsyncIterable<T>,
+): AsyncIterableIterator<T> {
+  let i = 0;
+  for await (const item of iterable) {
+    if (i >= start && i < end) {
+      yield item;
+    }
+    i += 1;
+  }
+}
+
+function async<T>(
+  start: number,
+  end: number,
+  iterable: AsyncIterable<T>,
+): AsyncIterableIterator<T> {
+  let iterator: AsyncIterator<T>;
+  return {
+    [Symbol.asyncIterator]() {
+      return this;
+    },
+
+    async next(_concurrent: any) {
+      if (iterator === undefined) {
+        // prettier-ignore
+        iterator = isConcurrent(_concurrent)
+          ? asyncSequential(start, end, concurrent(_concurrent.length, iterable))
+          : asyncSequential(start, end, iterable);
+      }
+
+      return iterator.next(_concurrent);
+    },
+  };
+}
+
+function _slice<T extends Iterable<unknown> | AsyncIterable<unknown>>(
+  start: number,
+  end: number,
+  iterable: T,
+) {
+  if (!isNumber(start) || !isNumber(end)) {
+    throw new TypeError("'start' and 'end' must be type of number");
+  }
+
+  if (isIterable(iterable)) {
+    return sync(start, end, iterable);
+  }
+
+  if (isAsyncIterable(iterable)) {
+    return async(start, end, iterable);
+  }
+
+  throw new TypeError("'iterable' must be type of Iterable or AsyncIterable");
+}
+
+/**
+ * Returns Iterable/AsyncIterable of the given elements from startIndex(inclusive) to endIndex(exclusive).
+ *
+ * @example
+ * ```ts
+ * const iter1 = slice(1, 3, ['a', 'b', 'c', 'd', 'e']);
+ * iter1.next(); // {value: 'b', done: false};
+ * iter1.next(); // {value: 'c', done: false};
+ * iter1.next(); // {value: undefined, done: true};
+ *
+ * const iter2 = slice(3, ['a', 'b', 'c', 'd', 'e']);
+ * iter2.next(); // {value: 'd', done: false};
+ * iter2.next(); // {value: 'e', done: false};
+ * iter2.next(); // {value: undefined, done: true};
+ *
+ * const iter3 = slice(3, "abcde");
+ * iter3.next(); // {value: 'd', done: false};
+ * iter3.next(); // {value: 'e', done: false};
+ * iter3.next(); // {value: undefined, done: true};
+ *
+ * // with pipe
+ * pipe(
+ *  [1, 2, 3, 4, 5],
+ *  slice(3),
+ *  toArray,
+ * ); // [4, 5]
+ *
+ * pipe(
+ *  [1, 2, 3, 4, 5],
+ *  slice(1, 3),
+ *  toArray,
+ * ); // [2, 3]
+ *
+ * see {@link https://fxts.dev/docs/pipe | pipe}, {@link https://fxts.dev/docs/toArray | toArray}
+ * ```
+ */
+// prettier-ignore
+function slice<T>(
+  start: number, 
+  iterable: Iterable<T>
+): IterableIterator<T>;
+
+function slice<T>(
+  start: number,
+  iterable: AsyncIterable<T>,
+): AsyncIterableIterator<T>;
+
+function slice<T>(
+  start: number,
+  end: number,
+  iterable: Iterable<T>,
+): IterableIterator<T>;
+
+function slice<T>(
+  start: number,
+  end: number,
+  iterable: AsyncIterable<T>,
+): AsyncIterableIterator<T>;
+
+function slice<A extends Iterable<unknown> | AsyncIterable<unknown>>(
+  start: number,
+): (iterable: A) => ReturnIterableIteratorType<A>;
+
+function slice<A extends Iterable<unknown> | AsyncIterable<unknown>>(
+  start: number,
+  end: number,
+): (iterable: A) => ReturnIterableIteratorType<A>;
+
+function slice<T extends Iterable<unknown> | AsyncIterable<unknown>>(
+  start: number | T,
+  end?: number | T,
+  iterable?: T,
+) {
+  if (iterable === undefined) {
+    if (end === undefined) {
+      return (iterable: T) => {
+        return _slice(start as number, Infinity, iterable);
+      };
+    }
+
+    if (isIterable(end) || isAsyncIterable(end)) {
+      return _slice(start as number, Infinity, end);
+    }
+
+    if (isNumber(end)) {
+      return (iterable: T) => {
+        return _slice(start as number, end, iterable);
+      };
+    }
+
+    return (iterable: T) => {
+      return _slice(0, Infinity, iterable);
+    };
+  }
+
+  return _slice(start as number, end as number, iterable as T);
+}
+
+export default slice;


Test patch:
diff --git a/test/Lazy/slice.spec.ts b/test/Lazy/slice.spec.ts
new file mode 100644
index 00000000..a6023bae
--- /dev/null
+++ b/test/Lazy/slice.spec.ts
@@ -0,0 +1,95 @@
+import { pipe, slice, toArray, toAsync } from "../../src";
+import { Concurrent } from "../../src/Lazy/concurrent";
+import { generatorMock } from "../utils";
+
+describe("slice", function () {
+  describe("sync", function () {
+    it.each([
+      [1, 3, [1, 2, 3, 4, 5], [2, 3]],
+      [-1, 3, [1, 2, 3, 4, 5], [1, 2, 3]],
+      [2, Infinity, [1, 2, 3, 4, 5], [3, 4, 5]],
+      [7, 3, [1, 2, 3, 4, 5], []],
+      [1, 3, "abcde", ["b", "c"]],
+    ])(
+      "should return elements from startIndex(%i) to endIndex(%i) of %s",
+      function (startIndex, endIndex, iterable, result) {
+        expect(
+          toArray(slice(startIndex, endIndex, iterable as Iterable<any>)),
+        ).toEqual(result);
+      },
+    );
+
+    it.each([
+      [1, [1, 2, 3, 4, 5], [2, 3, 4, 5]],
+      [-1, [1, 2, 3, 4, 5], [1, 2, 3, 4, 5]],
+      [2, [1, 2, 3, 4, 5], [3, 4, 5]],
+      [7, [1, 2, 3, 4, 5], []],
+      [1, "abcde", ["b", "c", "d", "e"]],
+    ])(
+      "should return elements from startIndex(%i) to end of %s",
+      function (startIndex, iterable, result) {
+        expect(toArray(slice(startIndex, iterable as Iterable<any>))).toEqual(
+          result,
+        );
+      },
+    );
+
+    it("should be able to be used as a curried function in the pipeline", function () {
+      const res1 = pipe([1, 2, 3, 4, 5], slice(2), toArray);
+      expect(res1).toEqual([3, 4, 5]);
+
+      const res2 = pipe([1, 2, 3, 4, 5], slice(1, 3), toArray);
+      expect(res2).toEqual([2, 3]);
+    });
+  });
+
+  describe("async", function () {
+    it.each([
+      [1, 3, [1, 2, 3, 4, 5], [2, 3]],
+      [-1, 3, [1, 2, 3, 4, 5], [1, 2, 3]],
+      [2, Infinity, [1, 2, 3, 4, 5], [3, 4, 5]],
+      [7, 3, [1, 2, 3, 4, 5], []],
+      [1, 3, "abcde", ["b", "c"]],
+    ])(
+      "should return elements from startIndex(%i) to endIndex(%i) of %s",
+      async function (startIndex, endIndex, iterable, result) {
+        expect(
+          await toArray(
+            slice(startIndex, endIndex, toAsync(iterable as Iterable<any>)),
+          ),
+        ).toEqual(result);
+      },
+    );
+
+    it.each([
+      [1, [1, 2, 3, 4, 5], [2, 3, 4, 5]],
+      [-1, [1, 2, 3, 4, 5], [1, 2, 3, 4, 5]],
+      [2, [1, 2, 3, 4, 5], [3, 4, 5]],
+      [7, [1, 2, 3, 4, 5], []],
+      [1, "abcde", ["b", "c", "d", "e"]],
+    ])(
+      "should return elements from startIndex(%i) to end of %s",
+      async function (startIndex, iterable, result) {
+        expect(
+          await toArray(slice(startIndex, toAsync(iterable as Iterable<any>))),
+        ).toEqual(result);
+      },
+    );
+
+    it("should be able to be used as a curried function in the pipeline", async function () {
+      const res1 = await pipe([1, 2, 3, 4, 5], toAsync, slice(2), toArray);
+      expect(res1).toEqual([3, 4, 5]);
+
+      const res2 = await pipe([1, 2, 3, 4, 5], toAsync, slice(1, 3), toArray);
+      expect(res2).toEqual([2, 3]);
+    });
+
+    it("should be passed concurrent object when job works concurrently", async function () {
+      const mock = generatorMock();
+      const iter = slice(1, 2, mock);
+      const concurrent = Concurrent.of(2) as any;
+      await iter.next(concurrent);
+      expect((mock as any).getConcurrent()).toEqual(concurrent);
+    });
+  });
+});
diff --git a/type-check/Lazy/slice.test.ts b/type-check/Lazy/slice.test.ts
new file mode 100644
index 00000000..a22858a6
--- /dev/null
+++ b/type-check/Lazy/slice.test.ts
@@ -0,0 +1,47 @@
+import * as Test from "../../src/types/Test";
+import { slice, pipe, toAsync } from "../../src";
+
+const { checks, check } = Test;
+
+const res1 = slice(2, [1, 2, 3, 4]);
+const res2 = slice(1, 2, [1, 2, 3, 4]);
+const res3 = slice(2, "abcde");
+const res4 = slice(1, 2, "abcde");
+
+const res5 = slice(2, toAsync([1, 2, 3, 4]));
+const res6 = slice(1, 2, toAsync([1, 2, 3, 4]));
+
+const res7 = pipe([1, 2, 3, 4, 5, 6, 7], slice(2));
+const res8 = pipe([1, 2, 3, 4, 5, 6, 7], slice(2));
+
+const res9 = pipe([1, 2, 3, 4, 5, 6, 7], slice(2, 3));
+const res10 = pipe([1, 2, 3, 4, 5, 6, 7], slice(2, 3));
+
+const res11 = pipe([1, 2, 3, 4, 5, 6, 7], toAsync, slice(2));
+const res12 = pipe([1, 2, 3, 4, 5, 6, 7], toAsync, slice(2));
+
+const res13 = pipe([1, 2, 3, 4, 5, 6, 7], toAsync, slice(2, 3));
+const res14 = pipe([1, 2, 3, 4, 5, 6, 7], toAsync, slice(2, 3));
+
+checks([
+  check<typeof res1, IterableIterator<number>, Test.Pass>(),
+  check<typeof res2, IterableIterator<number>, Test.Pass>(),
+
+  check<typeof res3, IterableIterator<string>, Test.Pass>(),
+  check<typeof res4, IterableIterator<string>, Test.Pass>(),
+
+  check<typeof res5, AsyncIterableIterator<number>, Test.Pass>(),
+  check<typeof res6, AsyncIterableIterator<number>, Test.Pass>(),
+
+  check<typeof res7, IterableIterator<number>, Test.Pass>(),
+  check<typeof res8, IterableIterator<number>, Test.Pass>(),
+
+  check<typeof res9, IterableIterator<number>, Test.Pass>(),
+  check<typeof res10, IterableIterator<number>, Test.Pass>(),
+
+  check<typeof res11, AsyncIterableIterator<number>, Test.Pass>(),
+  check<typeof res12, AsyncIterableIterator<number>, Test.Pass>(),
+
+  check<typeof res13, AsyncIterableIterator<number>, Test.Pass>(),
+  check<typeof res14, AsyncIterableIterator<number>, Test.Pass>(),
+]);