Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add fork #244

Draft
wants to merge 5 commits into
base: main
Choose a base branch
from
Draft
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
feat: add fork (iterable)
ppeeou committed Mar 10, 2024
commit 9f96fc06c5d46e2390a7b88d4301651c96a5273f
102 changes: 102 additions & 0 deletions src/Lazy/fork.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
import { isAsyncIterable, isIterable } from "../_internal/utils";
import isNil from "../isNil";
import type IterableInfer from "../types/IterableInfer";

type ReturnForkType<A extends Iterable<unknown> | AsyncIterable<unknown>> =
A extends AsyncIterable<any>
? AsyncIterableIterator<IterableInfer<A>>
: IterableIterator<IterableInfer<A>>;

type Node<T> = { value: T; done?: boolean };

class ForkItem<T> {
node: Node<T>;
nextNode: ForkItem<T> | null;

constructor(node: Node<T>) {
this.node = node;
this.nextNode = null;
}
}

class ForkQueue<T> {
head: ForkItem<T>;

current: ForkItem<T>;

constructor() {
this.head = new ForkItem(null as any);
this.current = this.head;
}

toString() {
const arr = [];
let cur: ForkItem<T> | null = this.head.nextNode;
while (cur) {
arr.push(cur.node.value);
cur = cur.nextNode;
}

return arr.join(", ");
}
}
hg-pyun marked this conversation as resolved.
Show resolved Hide resolved

const forkMap = new WeakMap<any, ForkQueue<any>>();

function sync<T>(iterable: Iterable<T>) {
const iterator = iterable[Symbol.iterator]();
let queue = forkMap.get(iterator) as ForkQueue<any>;
if (!queue) {
queue = new ForkQueue();
forkMap.set(iterator, queue);
}

let cur = queue.current;
let done = false;

return {
[Symbol.iterator]() {
return iterator;
},

next() {
if (done) {
return {
done,
value: undefined,
};
}

const item = cur.nextNode;
if (isNil(item)) {
const node = iterator.next();
cur.nextNode = new ForkItem(node);
cur = cur.nextNode;

queue.current = cur;
done = node.done ?? true;

return cur.node;
}

cur = item;
return cur.node;
},
};
}

function fork<A extends Iterable<unknown> | AsyncIterable<unknown>>(
iterable: A,
): ReturnForkType<A> {
if (isIterable(iterable)) {
return sync(iterable) as ReturnForkType<A>;
}

if (isAsyncIterable(iterable)) {
throw new TypeError("'fork' asyncIterable isn't supported not yet");
}

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

export default fork;
2 changes: 2 additions & 0 deletions src/Lazy/index.ts
Original file line number Diff line number Diff line change
@@ -15,6 +15,7 @@ import entries from "./entries";
import filter from "./filter";
import flat from "./flat";
import flatMap from "./flatMap";
import fork from "./fork";
import intersection from "./intersection";
import intersectionBy from "./intersectionBy";
import keys from "./keys";
@@ -60,6 +61,7 @@ export {
filter,
flat,
flatMap,
fork,
intersection,
intersectionBy,
keys,
79 changes: 79 additions & 0 deletions test/Lazy/fork.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import { fork, map, pipe } from "../../src/index";

describe("fork", function () {
describe("sync", function () {
it("should be forked iterable(number)", function () {
const arr = [1, 2, 3];

const iter1 = fork(arr);
const iter2 = fork(arr);

expect(iter1.next()).toEqual({ value: 1, done: false });
expect(iter1.next()).toEqual({ value: 2, done: false });
expect(iter1.next()).toEqual({ value: 3, done: false });
expect(iter1.next()).toEqual({ value: undefined, done: true });

expect(iter2.next()).toEqual({ value: 1, done: false });
expect(iter2.next()).toEqual({ value: 2, done: false });
expect(iter2.next()).toEqual({ value: 3, done: false });
expect(iter2.next()).toEqual({ value: undefined, done: true });
});

it("should be forked iterable(string)", function () {
const arr = "abc";

const iter1 = fork(arr);
const iter2 = fork(arr);

expect(iter1.next()).toEqual({ value: "a", done: false });
expect(iter1.next()).toEqual({ value: "b", done: false });
expect(iter1.next()).toEqual({ value: "c", done: false });
expect(iter1.next()).toEqual({ value: undefined, done: true });

expect(iter2.next()).toEqual({ value: "a", done: false });
expect(iter2.next()).toEqual({ value: "b", done: false });
expect(iter2.next()).toEqual({ value: "c", done: false });
expect(iter2.next()).toEqual({ value: undefined, done: true });
});

it("should be able to be used as a forked function in the pipeline", function () {
const arr = pipe(
[1, 2, 3],
map((a) => a + 10),
);

const iter1 = fork(arr);
const iter2 = fork(arr);
expect(iter1.next()).toEqual({ value: 11, done: false });
expect(iter2.next()).toEqual({ value: 11, done: false });

expect(iter1.next()).toEqual({ value: 12, done: false });
expect(iter1.next()).toEqual({ value: 13, done: false });
expect(iter1.next()).toEqual({ value: undefined, done: true });

expect(iter2.next()).toEqual({ value: 12, done: false });
expect(iter2.next()).toEqual({ value: 13, done: false });
expect(iter2.next()).toEqual({ value: undefined, done: true });
});

it("should be forked in the middle of iterable progress", function () {
const arr = pipe(
[1, 2, 3],
map((a) => a + 10),
);

const iter1 = fork(arr);
const iter2 = fork(iter1);

expect(iter1.next()).toEqual({ value: 11, done: false });
expect(iter2.next()).toEqual({ value: 11, done: false });

const iter3 = fork(iter1);
expect(iter1.next()).toEqual({ value: 12, done: false });
expect(iter1.next()).toEqual({ value: 13, done: false });
expect(iter1.next()).toEqual({ value: undefined, done: true });
expect(iter2.next()).toEqual({ value: 12, done: false });
expect(iter3.next()).toEqual({ value: 12, done: false });
});
});
});
17 changes: 17 additions & 0 deletions type-check/Lazy/fork.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { fork, pipe } from "../../src";
import * as Test from "../../src/types/Test";

const { checks, check } = Test;

const res1 = fork([1, 2, 3]);
const res2 = fork("abc");

const res3 = pipe([1, 2, 3], fork);
const res4 = pipe("abc", fork);

checks([
check<typeof res1, IterableIterator<number>, Test.Pass>(),
check<typeof res2, IterableIterator<string>, Test.Pass>(),
check<typeof res3, IterableIterator<number>, Test.Pass>(),
check<typeof res4, IterableIterator<string>, Test.Pass>(),
]);