From c9b1f25a37ff297cbca0dd3e8dfd84c9c9e35f69 Mon Sep 17 00:00:00 2001 From: "hyunwoo.jo" Date: Fri, 26 Nov 2021 15:18:34 +0900 Subject: [PATCH 1/8] feat: dropWhile sync --- src/Lazy/dropWhile.ts | 19 +++++++++++++++++++ src/Lazy/index.ts | 2 ++ test/Lazy/dropWhile.spec.ts | 13 +++++++++++++ 3 files changed, 34 insertions(+) create mode 100644 src/Lazy/dropWhile.ts create mode 100644 test/Lazy/dropWhile.spec.ts diff --git a/src/Lazy/dropWhile.ts b/src/Lazy/dropWhile.ts new file mode 100644 index 00000000..58039f39 --- /dev/null +++ b/src/Lazy/dropWhile.ts @@ -0,0 +1,19 @@ +function* sync(f: (a: A) => B, iterable: Iterable) { + for (const a of iterable) { + if (f(a)) { + continue; + } + yield a; + } +} + +function dropWhile( + f: (a: A) => B, + iterable: Iterable, +): IterableIterator; + +function dropWhile(f: (a: A) => B, iterable: Iterable) { + return sync(f, iterable); +} + +export default dropWhile; diff --git a/src/Lazy/index.ts b/src/Lazy/index.ts index 5d2a369b..78f88b83 100644 --- a/src/Lazy/index.ts +++ b/src/Lazy/index.ts @@ -4,6 +4,7 @@ import compact from "./compact"; import concat from "./concat"; import concurrent from "./concurrent"; import drop from "./drop"; +import dropWhile from "./dropWhile"; import filter from "./filter"; import flat from "./flat"; import flatMap from "./flatMap"; @@ -28,6 +29,7 @@ export { concat, concurrent, drop, + dropWhile, filter, flat, flatMap, diff --git a/test/Lazy/dropWhile.spec.ts b/test/Lazy/dropWhile.spec.ts new file mode 100644 index 00000000..00ab273c --- /dev/null +++ b/test/Lazy/dropWhile.spec.ts @@ -0,0 +1,13 @@ +import { dropWhile } from "../../src/index"; + +describe("drop", function () { + describe("sync", function () { + it("should be dropped elements until the value applied to callback returns falsey", function () { + const acc = []; + for (const a of dropWhile((a) => a < 3, [1, 2, 3, 4, 5])) { + acc.push(a); + } + expect(acc).toEqual([3, 4, 5]); + }); + }); +}); From ef94b55e13ff9fcaa5673509dafaa739e95fe9ec Mon Sep 17 00:00:00 2001 From: "hyunwoo.jo" Date: Fri, 26 Nov 2021 15:24:08 +0900 Subject: [PATCH 2/8] feat: dropWhile enable curry --- src/Lazy/dropWhile.ts | 28 +++++++++++++++++++++++++--- test/Lazy/dropWhile.spec.ts | 14 +++++++++++++- 2 files changed, 38 insertions(+), 4 deletions(-) diff --git a/src/Lazy/dropWhile.ts b/src/Lazy/dropWhile.ts index 58039f39..501a258c 100644 --- a/src/Lazy/dropWhile.ts +++ b/src/Lazy/dropWhile.ts @@ -1,3 +1,6 @@ +import IterableInfer from "../types/IterableInfer"; +import { isIterable } from "../_internal/utils"; + function* sync(f: (a: A) => B, iterable: Iterable) { for (const a of iterable) { if (f(a)) { @@ -7,13 +10,32 @@ function* sync(f: (a: A) => B, iterable: Iterable) { } } -function dropWhile( +function dropWhile( f: (a: A) => B, iterable: Iterable, ): IterableIterator; -function dropWhile(f: (a: A) => B, iterable: Iterable) { - return sync(f, iterable); +function dropWhile, B>( + f: (a: IterableInfer) => B, +): (iterable: A) => IterableIterator>; + +function dropWhile, B>( + f: (a: IterableInfer) => B, + iterable?: A, +): + | IterableIterator> + | ((iterable: A) => IterableIterator>) { + if (iterable === undefined) { + return (iterable: A) => { + return dropWhile(f, iterable as Iterable>); + }; + } + + if (isIterable(iterable)) { + return sync(f, iterable as Iterable>); + } + + throw new TypeError("iterable must be type of Iterable or AsyncIterable"); } export default dropWhile; diff --git a/test/Lazy/dropWhile.spec.ts b/test/Lazy/dropWhile.spec.ts index 00ab273c..842d483e 100644 --- a/test/Lazy/dropWhile.spec.ts +++ b/test/Lazy/dropWhile.spec.ts @@ -1,4 +1,4 @@ -import { dropWhile } from "../../src/index"; +import { dropWhile, filter, map, pipe, toArray } from "../../src/index"; describe("drop", function () { describe("sync", function () { @@ -9,5 +9,17 @@ describe("drop", function () { } expect(acc).toEqual([3, 4, 5]); }); + + it("should be able to be used as a curried function in the pipeline", function () { + const res = pipe( + [1, 2, 3, 4, 5, 6, 7, 8], + map((a) => a + 10), + filter((a) => a % 2 === 0), + dropWhile((a) => a < 16), + toArray, + ); + + expect(res).toEqual([16, 18]); + }); }); }); From 5d58d6b89590b8c8923ffcb09abab55b6f26c9ca Mon Sep 17 00:00:00 2001 From: "hyunwoo.jo" Date: Fri, 26 Nov 2021 15:25:10 +0900 Subject: [PATCH 3/8] feat: dropWhile throw an error when callback is asynchronous --- src/Lazy/dropWhile.ts | 8 +++++++- test/Lazy/dropWhile.spec.ts | 6 ++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/src/Lazy/dropWhile.ts b/src/Lazy/dropWhile.ts index 501a258c..e78528ed 100644 --- a/src/Lazy/dropWhile.ts +++ b/src/Lazy/dropWhile.ts @@ -1,9 +1,15 @@ import IterableInfer from "../types/IterableInfer"; +import { AsyncFunctionException } from "../_internal/error"; import { isIterable } from "../_internal/utils"; function* sync(f: (a: A) => B, iterable: Iterable) { for (const a of iterable) { - if (f(a)) { + const res = f(a); + if (res instanceof Promise) { + throw new AsyncFunctionException(); + } + + if (res) { continue; } yield a; diff --git a/test/Lazy/dropWhile.spec.ts b/test/Lazy/dropWhile.spec.ts index 842d483e..926b8b80 100644 --- a/test/Lazy/dropWhile.spec.ts +++ b/test/Lazy/dropWhile.spec.ts @@ -1,4 +1,5 @@ import { dropWhile, filter, map, pipe, toArray } from "../../src/index"; +import { AsyncFunctionException } from "../../src/_internal/error"; describe("drop", function () { describe("sync", function () { @@ -21,5 +22,10 @@ describe("drop", function () { expect(res).toEqual([16, 18]); }); + + it("should throw an error when the callback is asynchronous", function () { + const res = () => [...dropWhile(async (a) => a > 5, [1, 2, 3, 4, 5])]; + expect(res).toThrowError(new AsyncFunctionException()); + }); }); }); From 030d5eca61dac371288e07da28e01f201f0f5b3a Mon Sep 17 00:00:00 2001 From: "hyunwoo.jo" Date: Fri, 26 Nov 2021 15:41:10 +0900 Subject: [PATCH 4/8] feat: dropWhile async --- src/Lazy/dropWhile.ts | 34 +++++++++++++++++++++++---- test/Lazy/dropWhile.spec.ts | 46 ++++++++++++++++++++++++++++++++++++- 2 files changed, 74 insertions(+), 6 deletions(-) diff --git a/src/Lazy/dropWhile.ts b/src/Lazy/dropWhile.ts index e78528ed..c4ff1bf7 100644 --- a/src/Lazy/dropWhile.ts +++ b/src/Lazy/dropWhile.ts @@ -1,6 +1,7 @@ import IterableInfer from "../types/IterableInfer"; +import ReturnIterableIteratorType from "../types/ReturnIterableIteratorType"; import { AsyncFunctionException } from "../_internal/error"; -import { isIterable } from "../_internal/utils"; +import { isAsyncIterable, isIterable } from "../_internal/utils"; function* sync(f: (a: A) => B, iterable: Iterable) { for (const a of iterable) { @@ -16,24 +17,43 @@ function* sync(f: (a: A) => B, iterable: Iterable) { } } +async function* async( + f: (a: A) => B, + iterable: AsyncIterable, +): AsyncIterableIterator { + for await (const a of iterable) { + if (await f(a)) { + continue; + } + + yield a; + } +} + function dropWhile( f: (a: A) => B, iterable: Iterable, ): IterableIterator; -function dropWhile, B>( +function dropWhile( + f: (a: A) => B, + iterable: AsyncIterable, +): AsyncIterableIterator; + +function dropWhile | AsyncIterable, B>( f: (a: IterableInfer) => B, -): (iterable: A) => IterableIterator>; +): (iterable: A) => ReturnIterableIteratorType; function dropWhile, B>( f: (a: IterableInfer) => B, iterable?: A, ): | IterableIterator> - | ((iterable: A) => IterableIterator>) { + | AsyncIterableIterator> + | ((iterable: A) => ReturnIterableIteratorType) { if (iterable === undefined) { return (iterable: A) => { - return dropWhile(f, iterable as Iterable>); + return dropWhile(f, iterable as any) as ReturnIterableIteratorType; }; } @@ -41,6 +61,10 @@ function dropWhile, B>( return sync(f, iterable as Iterable>); } + if (isAsyncIterable(iterable)) { + return async(f, iterable as AsyncIterable>); + } + throw new TypeError("iterable must be type of Iterable or AsyncIterable"); } diff --git a/test/Lazy/dropWhile.spec.ts b/test/Lazy/dropWhile.spec.ts index 926b8b80..8e0efe88 100644 --- a/test/Lazy/dropWhile.spec.ts +++ b/test/Lazy/dropWhile.spec.ts @@ -1,4 +1,11 @@ -import { dropWhile, filter, map, pipe, toArray } from "../../src/index"; +import { + dropWhile, + filter, + map, + pipe, + toArray, + toAsync, +} from "../../src/index"; import { AsyncFunctionException } from "../../src/_internal/error"; describe("drop", function () { @@ -28,4 +35,41 @@ describe("drop", function () { expect(res).toThrowError(new AsyncFunctionException()); }); }); + + describe("async", function () { + it("should be dropped elements until the value applied to callback returns falsey", async function () { + const acc = []; + for await (const a of dropWhile((a) => a < 3, toAsync([1, 2, 3, 4, 5]))) { + acc.push(a); + } + expect(acc).toEqual([3, 4, 5]); + }); + + it("should be able to be used as a curried function in the pipeline", async function () { + const res = await pipe( + toAsync([1, 2, 3, 4, 5, 6, 7, 8]), + map((a) => a + 10), + filter((a) => a % 2 === 0), + dropWhile((a) => a < 16), + toArray, + ); + + expect(res).toEqual([16, 18]); + }); + + it("should be able to handle an error when asynchronous", async function () { + await expect( + pipe( + toAsync([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]), + dropWhile((a) => { + if (a > 5) { + throw new Error("err"); + } + return true; + }), + toArray, + ), + ).rejects.toThrow("err"); + }); + }); }); From 6b5ee0be144f8c662f37ecdc30d7db36b20ce848 Mon Sep 17 00:00:00 2001 From: "hyunwoo.jo" Date: Fri, 26 Nov 2021 16:25:20 +0900 Subject: [PATCH 5/8] feat: dropWhile concurrent --- src/Lazy/dropWhile.ts | 70 ++++++++++++++++++++++++++++- test/Lazy/dropWhile.spec.ts | 87 +++++++++++++++++++++++++++++++++++++ 2 files changed, 155 insertions(+), 2 deletions(-) diff --git a/src/Lazy/dropWhile.ts b/src/Lazy/dropWhile.ts index c4ff1bf7..2e9c779c 100644 --- a/src/Lazy/dropWhile.ts +++ b/src/Lazy/dropWhile.ts @@ -2,6 +2,7 @@ import IterableInfer from "../types/IterableInfer"; import ReturnIterableIteratorType from "../types/ReturnIterableIteratorType"; import { AsyncFunctionException } from "../_internal/error"; import { isAsyncIterable, isIterable } from "../_internal/utils"; +import concurrent, { isConcurrent } from "./concurrent"; function* sync(f: (a: A) => B, iterable: Iterable) { for (const a of iterable) { @@ -17,7 +18,7 @@ function* sync(f: (a: A) => B, iterable: Iterable) { } } -async function* async( +async function* asyncSequential( f: (a: A) => B, iterable: AsyncIterable, ): AsyncIterableIterator { @@ -30,6 +31,71 @@ async function* async( } } +function async( + f: (a: A) => B, + iterable: AsyncIterable, +): AsyncIterableIterator { + let iterator: AsyncIterator; + return { + [Symbol.asyncIterator]() { + return this; + }, + + async next(_concurrent: any) { + if (iterator === undefined) { + iterator = isConcurrent(_concurrent) + ? asyncSequential(f, concurrent(_concurrent.length, iterable)) + : asyncSequential(f, iterable); + } + + return iterator.next(_concurrent); + }, + }; +} + +/** + * Returns Iterable/AsyncIterable excluding elements dropped from the beginning. Elements are dropped until the value applied to `f` returns falsey. + * + * @example + * ```ts + * const iter = dropWhile((a) => a < 3, [1, 2, 3, 4, 5]); + * iter.next(); // {done:false, value: 3} + * iter.next(); // {done:false, value: 4} + * iter.next(); // {done:false, value: 5} + * + * // with pipe + * pipe( + * [1, 2, 3, 4, 5], + * dropWhile((a) => a < 3), + * toArray, + * ); // [3, 4, 5] + * + * await pipe( + * Promise.resolve([1, 2, 3, 4, 5]), + * dropWhile((a) => a < 3), + * toArray, + * ); // [3, 4, 5] + * + * // if you want to use asynchronous callback + * await pipe( + * Promise.resolve([1, 2, 3, 4, 5]), + * toAsync, + * dropWhile(async (a) => a < 3), + * toArray, + * ); // [3, 4, 5] + * + * // with toAsync + * await pipe( + * [Promise.resolve(1), Promise.resolve(2), Promise.resolve(3), Promise.resolve(4), Promise.resolve(5)], + * toAsync, + * dropWhile((a) => a < 3), + * toArray, + * ); // [3, 4, 5] + * + * see {@link https://fxts.dev/docs/pipe | pipe}, {@link https://fxts.dev/docs/toAsync | toAsync}, + * {@link https://fxts.dev/docs/toArray | toArray} + * ``` + */ function dropWhile( f: (a: A) => B, iterable: Iterable, @@ -44,7 +110,7 @@ function dropWhile | AsyncIterable, B>( f: (a: IterableInfer) => B, ): (iterable: A) => ReturnIterableIteratorType; -function dropWhile, B>( +function dropWhile | AsyncIterable, B>( f: (a: IterableInfer) => B, iterable?: A, ): diff --git a/test/Lazy/dropWhile.spec.ts b/test/Lazy/dropWhile.spec.ts index 8e0efe88..85b539fc 100644 --- a/test/Lazy/dropWhile.spec.ts +++ b/test/Lazy/dropWhile.spec.ts @@ -1,4 +1,6 @@ import { + concurrent, + delay, dropWhile, filter, map, @@ -6,7 +8,9 @@ import { toArray, toAsync, } from "../../src/index"; +import { Concurrent } from "../../src/Lazy/concurrent"; import { AsyncFunctionException } from "../../src/_internal/error"; +import { callFuncAfterTime, generatorMock } from "../utils"; describe("drop", function () { describe("sync", function () { @@ -71,5 +75,88 @@ describe("drop", function () { ), ).rejects.toThrow("err"); }); + + it("should be dropped elements concurrently", async function () { + const fn = jest.fn(); + callFuncAfterTime(fn, 400); + const res = await pipe( + toAsync([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]), + map((a) => delay(100, a)), + filter((a) => a % 2 === 0), + dropWhile((a) => a < 6), + concurrent(3), + toArray, + ); + expect(fn).toBeCalled(); + expect(res).toEqual([6, 8, 10]); + }, 450); + + it("should be controlled the order when concurrency", async function () { + const fn = jest.fn(); + callFuncAfterTime(fn, 1000); + + const res = await pipe( + toAsync( + (function* () { + yield delay(500, 1); + yield delay(400, 2); + yield delay(300, 3); + yield delay(200, 4); + yield delay(100, 5); + yield delay(500, 6); + yield delay(400, 7); + yield delay(300, 8); + yield delay(200, 9); + yield delay(100, 10); + })(), + ), + dropWhile((a) => a < 7), + concurrent(5), + toArray, + ); + expect(fn).toBeCalled(); + expect(res).toEqual([7, 8, 9, 10]); + }, 1050); + + it("should be able to handle an error when working concurrent", async function () { + await expect( + pipe( + toAsync([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]), + dropWhile((a) => { + if (a > 5) { + throw new Error("err"); + } + return true; + }), + concurrent(3), + toArray, + ), + ).rejects.toThrow("err"); + }); + + it("should be able to handle an error when working concurrent - Promise.reject", async function () { + await expect( + pipe( + toAsync([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]), + dropWhile((a) => { + if (a > 5) { + return Promise.reject(new Error("err")); + } + return true; + }), + concurrent(3), + toArray, + ), + ).rejects.toThrow("err"); + }); + + it("should be passed concurrent object when job works concurrently", async function () { + const mock = generatorMock(); + const iter = dropWhile((a) => a, mock); + const concurrent = Concurrent.of(2) as any; + + await iter.next(concurrent); + expect((mock as any).getConcurrent()).toEqual(concurrent); + }); }); }); From b621977b13df72c1f32b0b910859cdd0eb0d529d Mon Sep 17 00:00:00 2001 From: "hyunwoo.jo" Date: Fri, 26 Nov 2021 16:25:33 +0900 Subject: [PATCH 6/8] feat: dropWhile type test --- type-check/Lazy/dropWhile.test.ts | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 type-check/Lazy/dropWhile.test.ts diff --git a/type-check/Lazy/dropWhile.test.ts b/type-check/Lazy/dropWhile.test.ts new file mode 100644 index 00000000..c5a4db05 --- /dev/null +++ b/type-check/Lazy/dropWhile.test.ts @@ -0,0 +1,24 @@ +import * as Test from "../../src/types/Test"; +import { toAsync, dropWhile, pipe } from "../../src"; + +const { checks, check } = Test; + +const res1 = dropWhile((a) => a, []); +const res2 = dropWhile((a) => a, [1, 2, 3, 4]); +const res3 = dropWhile((a) => a, toAsync([1, 2, 3, 4])); +const res4 = pipe( + [1, 2, 3, 4], + dropWhile((a) => a), +); +const res5 = pipe( + toAsync([1, 2, 3, 4]), + dropWhile((a) => a), +); + +checks([ + check, Test.Pass>(), + check, Test.Pass>(), + check, Test.Pass>(), + check, Test.Pass>(), + check, Test.Pass>(), +]); From 64fc70a7415570113b0203bdcdaeb18ea9c4a443 Mon Sep 17 00:00:00 2001 From: "hyunwoo.jo" Date: Fri, 26 Nov 2021 17:00:49 +0900 Subject: [PATCH 7/8] chore: fix typo --- test/Lazy/dropWhile.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/Lazy/dropWhile.spec.ts b/test/Lazy/dropWhile.spec.ts index 85b539fc..b241d6cf 100644 --- a/test/Lazy/dropWhile.spec.ts +++ b/test/Lazy/dropWhile.spec.ts @@ -12,7 +12,7 @@ import { Concurrent } from "../../src/Lazy/concurrent"; import { AsyncFunctionException } from "../../src/_internal/error"; import { callFuncAfterTime, generatorMock } from "../utils"; -describe("drop", function () { +describe("dropWhile", function () { describe("sync", function () { it("should be dropped elements until the value applied to callback returns falsey", function () { const acc = []; From 6932b72e9bfda43e514a7a249ec9c9b3ac9f8b39 Mon Sep 17 00:00:00 2001 From: "hyunwoo.jo" Date: Fri, 26 Nov 2021 18:27:41 +0900 Subject: [PATCH 8/8] fix: dropWhile must be included after conditional statement --- src/Lazy/dropWhile.ts | 20 ++++++++++++++++++-- test/Lazy/dropWhile.spec.ts | 12 ++++++------ 2 files changed, 24 insertions(+), 8 deletions(-) diff --git a/src/Lazy/dropWhile.ts b/src/Lazy/dropWhile.ts index 2e9c779c..c5806a38 100644 --- a/src/Lazy/dropWhile.ts +++ b/src/Lazy/dropWhile.ts @@ -5,7 +5,14 @@ import { isAsyncIterable, isIterable } from "../_internal/utils"; import concurrent, { isConcurrent } from "./concurrent"; function* sync(f: (a: A) => B, iterable: Iterable) { - for (const a of iterable) { + const iterator = iterable[Symbol.iterator](); + const iterableIterator = { + [Symbol.iterator]() { + return iterator; + }, + }; + + for (const a of iterableIterator) { const res = f(a); if (res instanceof Promise) { throw new AsyncFunctionException(); @@ -15,6 +22,7 @@ function* sync(f: (a: A) => B, iterable: Iterable) { continue; } yield a; + yield* iterableIterator; } } @@ -22,12 +30,20 @@ async function* asyncSequential( f: (a: A) => B, iterable: AsyncIterable, ): AsyncIterableIterator { - for await (const a of iterable) { + const iterator = iterable[Symbol.asyncIterator](); + const iterableIterator = { + [Symbol.asyncIterator]() { + return iterator; + }, + }; + + for await (const a of iterableIterator) { if (await f(a)) { continue; } yield a; + yield* iterableIterator; } } diff --git a/test/Lazy/dropWhile.spec.ts b/test/Lazy/dropWhile.spec.ts index b241d6cf..a878bdd4 100644 --- a/test/Lazy/dropWhile.spec.ts +++ b/test/Lazy/dropWhile.spec.ts @@ -16,10 +16,10 @@ describe("dropWhile", function () { describe("sync", function () { it("should be dropped elements until the value applied to callback returns falsey", function () { const acc = []; - for (const a of dropWhile((a) => a < 3, [1, 2, 3, 4, 5])) { + for (const a of dropWhile((a) => a < 3, [1, 2, 3, 1, 5])) { acc.push(a); } - expect(acc).toEqual([3, 4, 5]); + expect(acc).toEqual([3, 1, 5]); }); it("should be able to be used as a curried function in the pipeline", function () { @@ -43,10 +43,10 @@ describe("dropWhile", function () { describe("async", function () { it("should be dropped elements until the value applied to callback returns falsey", async function () { const acc = []; - for await (const a of dropWhile((a) => a < 3, toAsync([1, 2, 3, 4, 5]))) { + for await (const a of dropWhile((a) => a < 3, toAsync([1, 2, 3, 1, 5]))) { acc.push(a); } - expect(acc).toEqual([3, 4, 5]); + expect(acc).toEqual([3, 1, 5]); }); it("should be able to be used as a curried function in the pipeline", async function () { @@ -106,7 +106,7 @@ describe("dropWhile", function () { yield delay(500, 6); yield delay(400, 7); yield delay(300, 8); - yield delay(200, 9); + yield delay(200, 1); yield delay(100, 10); })(), ), @@ -115,7 +115,7 @@ describe("dropWhile", function () { toArray, ); expect(fn).toBeCalled(); - expect(res).toEqual([7, 8, 9, 10]); + expect(res).toEqual([7, 8, 1, 10]); }, 1050); it("should be able to handle an error when working concurrent", async function () {