Skip to content

Commit

Permalink
Merge pull request #23 from marpple/feature/#22
Browse files Browse the repository at this point in the history
feat: add `dropWhile` function
  • Loading branch information
ppeeou authored Nov 28, 2021
2 parents 0a27333 + 6932b72 commit 0378a65
Show file tree
Hide file tree
Showing 4 changed files with 341 additions and 0 deletions.
153 changes: 153 additions & 0 deletions src/Lazy/dropWhile.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
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<A, B>(f: (a: A) => B, iterable: Iterable<A>) {
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();
}

if (res) {
continue;
}
yield a;
yield* iterableIterator;
}
}

async function* asyncSequential<A, B>(
f: (a: A) => B,
iterable: AsyncIterable<A>,
): AsyncIterableIterator<A> {
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;
}
}

function async<A, B>(
f: (a: A) => B,
iterable: AsyncIterable<A>,
): AsyncIterableIterator<A> {
let iterator: AsyncIterator<A>;
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<A, B = unknown>(
f: (a: A) => B,
iterable: Iterable<A>,
): IterableIterator<A>;

function dropWhile<A, B = unknown>(
f: (a: A) => B,
iterable: AsyncIterable<A>,
): AsyncIterableIterator<A>;

function dropWhile<A extends Iterable<unknown> | AsyncIterable<unknown>, B>(
f: (a: IterableInfer<A>) => B,
): (iterable: A) => ReturnIterableIteratorType<A>;

function dropWhile<A extends Iterable<unknown> | AsyncIterable<unknown>, B>(
f: (a: IterableInfer<A>) => B,
iterable?: A,
):
| IterableIterator<IterableInfer<A>>
| AsyncIterableIterator<IterableInfer<A>>
| ((iterable: A) => ReturnIterableIteratorType<A>) {
if (iterable === undefined) {
return (iterable: A) => {
return dropWhile(f, iterable as any) as ReturnIterableIteratorType<A>;
};
}

if (isIterable(iterable)) {
return sync(f, iterable as Iterable<IterableInfer<A>>);
}

if (isAsyncIterable(iterable)) {
return async(f, iterable as AsyncIterable<IterableInfer<A>>);
}

throw new TypeError("iterable must be type of Iterable or AsyncIterable");
}

export default dropWhile;
2 changes: 2 additions & 0 deletions src/Lazy/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand All @@ -28,6 +29,7 @@ export {
concat,
concurrent,
drop,
dropWhile,
filter,
flat,
flatMap,
Expand Down
162 changes: 162 additions & 0 deletions test/Lazy/dropWhile.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
import {
concurrent,
delay,
dropWhile,
filter,
map,
pipe,
toArray,
toAsync,
} from "../../src/index";
import { Concurrent } from "../../src/Lazy/concurrent";
import { AsyncFunctionException } from "../../src/_internal/error";
import { callFuncAfterTime, generatorMock } from "../utils";

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, 1, 5])) {
acc.push(a);
}
expect(acc).toEqual([3, 1, 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]);
});

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());
});
});

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, 1, 5]))) {
acc.push(a);
}
expect(acc).toEqual([3, 1, 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");
});

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, 1);
yield delay(100, 10);
})(),
),
dropWhile((a) => a < 7),
concurrent(5),
toArray,
);
expect(fn).toBeCalled();
expect(res).toEqual([7, 8, 1, 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);
});
});
});
24 changes: 24 additions & 0 deletions type-check/Lazy/dropWhile.test.ts
Original file line number Diff line number Diff line change
@@ -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<typeof res1, IterableIterator<never>, Test.Pass>(),
check<typeof res2, IterableIterator<number>, Test.Pass>(),
check<typeof res3, AsyncIterableIterator<number>, Test.Pass>(),
check<typeof res4, IterableIterator<number>, Test.Pass>(),
check<typeof res5, AsyncIterableIterator<number>, Test.Pass>(),
]);

0 comments on commit 0378a65

Please sign in to comment.