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
Show file tree
Hide file tree
Changes from 4 commits
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
227 changes: 227 additions & 0 deletions src/Lazy/fork.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,227 @@
import { isAsyncIterable, isIterable } from "../_internal/utils";
import { LinkedList } from "../dataStructure/linkedList/linkedList";
import type { LinkedListNode } from "../dataStructure/linkedList/linkedListNode";
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 Value = any;

type ForkItem = {
queue: LinkedList<IteratorResult<Value>>;
originNext: () => IteratorResult<Value, any>;
};

const forkMap = new WeakMap<Iterator<Value>, ForkItem>();

ppeeou marked this conversation as resolved.
Show resolved Hide resolved
function sync<T>(iterable: Iterable<T>) {
const iterator = iterable[Symbol.iterator]();

const getNext = (forkItem: ForkItem) => {
let current: LinkedListNode<IteratorResult<T>> | null =
forkItem.queue.getLastNode();

const done = () => {
iterator.next = forkItem.originNext;

return {
done: true,
value: undefined,
} as const;
};

let isDone = false;
const next = () => {
if (isDone) {
return done();
}

const item = current?.getNext();

if (isNil(item) || item === forkItem.queue.getTail()) {
const node = forkItem.originNext();

current = forkItem.queue.insertLast(node);
isDone = node.done ?? true;
if (isDone) {
return done();
}

return node;
}

current = item;
return current.getValue();
};

return next;
};

let forkItem = forkMap.get(iterator) as ForkItem;

if (!forkItem) {
const originNext = iterator.next.bind(iterator);
forkItem = {
queue: new LinkedList(),
originNext: originNext,
};

iterator.next = getNext(forkItem);
forkMap.set(iterator, forkItem);
}

const next = getNext(forkItem);

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

next: next,
};
}

type ForkAsyncItem = {
queue: LinkedList<IteratorResult<Value>>;
next: (...args: any) => Promise<IteratorResult<Value, any>>;
};

const forkAsyncMap = new WeakMap<AsyncIterator<Value>, ForkAsyncItem>();

function async<T>(iterable: AsyncIterable<T>) {
const iterator = iterable[Symbol.asyncIterator]();

const getNext = (forkItem: ForkAsyncItem) => {
let current: Promise<LinkedListNode<IteratorResult<T>> | null> =
Promise.resolve(forkItem.queue.getLastNode());

const done = () => {
iterator.next = forkItem.next;

return {
done: true,
value: undefined,
} as const;
};

let isDone = false;
const next = async (_concurrent: any) => {
if (isDone) {
return done();
}

const itemCurrent = await current;
const item = itemCurrent?.getNext();

return new Promise((resolve, reject) => {
if (isNil(item) || item === forkItem.queue.getTail()) {
return forkItem
.next(_concurrent)
.then((node) => {
current = current.then(() => {
return forkItem.queue.insertLast(node);
});

isDone = node.done ?? true;
if (isDone) {
return resolve(done());
}

return resolve(node);
})
.catch(reject);
}

current = current.then(() => {
return item;
});

resolve(item.getValue());
});
};

return next;
};

let forkItem = forkAsyncMap.get(iterator) as ForkAsyncItem;
if (!forkItem) {
const originNext = iterator.next.bind(iterator);
forkItem = {
queue: new LinkedList(),
next: originNext,
};

iterator.next = getNext(forkItem) as any;
forkAsyncMap.set(iterator, forkItem);
}

const next = getNext(forkItem);
return {
[Symbol.asyncIterator]() {
return this;
},
next: next,
};
}

/**
* Returns an iterable of forks of original source. Each fork contains the same values as source, and can be consumed independently.
*
* @example
* ```ts
* const arr = [1, 2, 3];
* const iter1 = fork(arr);
* const iter2 = fork(arr);
*
* iter1.next() // {done:false, value: 1}
* iter1.next() // {done:false, value: 2}
* iter2.next() // {done:false, value: 1}
* iter2.next() // {done:false, value: 2}
*
* const str = 'abc'
* const strIter1 = fork(str);
* const strIter2 = fork(str);
*
* strIter1.next() // {done:false, value: 'a'}
* strIter1.next() // {done:false, value: 'b'}
* strIter2.next() // {done:false, value: 'a'}
* strIter2.next() // {done:false, value: 'b'}
*
* // with pipe
* const arrAdd10 = pipe(
* [1, 2, 3],
* map((a) => a + 10),
* );
*
* const arrAdd10Iter1 = fork(arrAdd10);
* const arrAdd10Iter2 = fork(arrAdd10);
* arrAdd10Iter1.next() // { value: 11, done: false }
* arrAdd10Iter2.next() // { value: 11, done: false }
*
* const arrAdd10Iter3 = fork(arrAdd10Iter1);
* arrAdd10Iter1.next() // { value: 12, done: false }
* arrAdd10Iter1.next() // { value: 13, done: false }
* arrAdd10Iter2.next() // { value: 12, done: false }
* arrAdd10Iter3.next() // { value: 12, done: false }
* ```
*/
// prettier-ignore
function fork<A extends Iterable<unknown> | AsyncIterable<unknown>>(
iterable: A,
): ReturnForkType<A> {
if (isIterable(iterable)) {
return sync(iterable) as any;
}

if (isAsyncIterable(iterable)) {
return async(iterable) as any;
}

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
Expand Up @@ -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";
Expand Down Expand Up @@ -60,6 +61,7 @@ export {
filter,
flat,
flatMap,
fork,
intersection,
intersectionBy,
keys,
Expand Down
85 changes: 85 additions & 0 deletions src/dataStructure/linkedList/linkedList.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import { LinkedListNode } from "./linkedListNode";

export class LinkedList<T> {
private head: LinkedListNode<T>;
private tail: LinkedListNode<T>;

constructor() {
this.head = new LinkedListNode(null as unknown as T);
this.tail = new LinkedListNode(null as unknown as T);

this.head.setNextNode(this.tail);
this.tail.setPrevNode(this.head);
}

insertFirst(value: T): LinkedListNode<T> {
const node = new LinkedListNode(value);
if (this.isEmpty()) {
this.tail.setPrevNode(node);
this.head.setNextNode(node);

node.setNextNode(this.tail);
node.setPrevNode(this.head);
} else {
const firstNode = this.head.getNext();
if (!firstNode) {
throw new TypeError("firstNode must be a LinkedListNode");
}

node.setPrevNode(this.head);
node.setNextNode(firstNode);
firstNode.setPrevNode(node);
this.head.setNextNode(node);
}

return node;
}

insertLast(value: T): LinkedListNode<T> {
if (this.isEmpty()) {
return this.insertFirst(value);
}

const node = new LinkedListNode(value);
const lastNode = this.tail.getPrev();

if (!lastNode) {
throw new TypeError("lastNode must be a LinkedListNode");
}

node.setPrevNode(lastNode);
node.setNextNode(this.tail);
lastNode.setNextNode(node);
this.tail.setPrevNode(node);

return node;
}

isEmpty() {
return !this.head.hasNext();
}

getHead() {
return this.head;
}

getTail() {
return this.tail;
}

getLastNode() {
return this.tail.getPrev();
}

toArray() {
const arr = [];
let cur = this.head;
while (cur.getNext() !== this.tail) {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
cur = cur.getNext()!;
arr.push(cur.getValue());
}

return arr;
}
}
39 changes: 39 additions & 0 deletions src/dataStructure/linkedList/linkedListNode.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
export class LinkedListNode<T> {
private value: T;
private next: LinkedListNode<T> | null;
private prev: LinkedListNode<T> | null;

constructor(value: T) {
this.value = value;
this.next = null;
this.prev = null;
}

setNextNode(node: LinkedListNode<T>) {
this.next = node;

return node;
}

setPrevNode(node: LinkedListNode<T>) {
this.prev = node;

return node;
}

getValue() {
return this.value;
}

getNext() {
return this.next;
}

getPrev() {
return this.prev;
}

hasNext() {
return this.next instanceof LinkedListNode;
}
}
Loading
Loading