How to clone an Iterator in javascript?
Asked Answered
M

5

5

In ES6, is there any possible to clone an iterator states?

var ma=[1,2,3,4];
var it=ma[Symbol.iterator]();
it.next();

if I want to remember here the it states how should I do in javascritp?

what is remebered in it? since the

JSON.stringify(it) //it would just return {}
Marilyn answered 26/9, 2017 at 0:57 Comment(1)
fitzgen.github.io/wu.js/#teeArchduke
C
4

You can’t clone an arbitrary iterator, but you can create many distinct iterators from one by holding onto some state:

function tee(iterable) {
    const source = iterable[Symbol.iterator]();
    const buffers = [[], []];  // substitute in queue type for efficiency
    const DONE = Object.create(null);

    const next = i => {
        if (buffers[i].length !== 0) {
            return buffers[i].shift();
        }

        const x = source.next();

        if (x.done) {
            return DONE;
        }

        buffers[1 - i].push(x.value);
        return x.value;
    };

    return buffers.map(function* (_, i) {
        for (;;) {
            const x = next(i);

            if (x === DONE) {
                break;
            }

            yield x;
        }
    });
}

Usage:

const [a, b] = tee(iterator);
assert(a.next().value === b.next().value);
Canotas answered 26/9, 2017 at 1:11 Comment(0)
P
4

It's not possible to clone an iterator. Iterator state is basically completely arbitrary and any given iterator may require or produce side effects (e.g. reading from or writing to a network stream) which are not repeatable on demand.

Prove answered 26/9, 2017 at 1:0 Comment(0)
C
4

You can’t clone an arbitrary iterator, but you can create many distinct iterators from one by holding onto some state:

function tee(iterable) {
    const source = iterable[Symbol.iterator]();
    const buffers = [[], []];  // substitute in queue type for efficiency
    const DONE = Object.create(null);

    const next = i => {
        if (buffers[i].length !== 0) {
            return buffers[i].shift();
        }

        const x = source.next();

        if (x.done) {
            return DONE;
        }

        buffers[1 - i].push(x.value);
        return x.value;
    };

    return buffers.map(function* (_, i) {
        for (;;) {
            const x = next(i);

            if (x === DONE) {
                break;
            }

            yield x;
        }
    });
}

Usage:

const [a, b] = tee(iterator);
assert(a.next().value === b.next().value);
Canotas answered 26/9, 2017 at 1:11 Comment(0)
C
1

You can create multiple iterators that rely on the original iterator. Use buffering to keep track of which values were already consumed for serving one of those new iterators, but not yet all.

Here is an implementation that uses a linked list as buffer, so that all returned iterators can use the same list:

function tee(iterable, n=2) {
    const it = iterable[Symbol.iterator]();
    
    function* gen(current) {
        while (true) {
            if (!current.next) {   // when at the tail of the linked list
                const {done, value} = it.next(); // fetch a new value and
                if (done) return;
                current.next = { value }; // append it to the linked list
            }
            current = current.next; // Move up to the next node
            yield current.value;
        }
    }
    
    // All generators start at the same node {}, i.e. the empty linked list
    return Array(n).fill({}).map(gen);
}

// Example usage
const [a, b] = tee(Array(10).keys());
console.log("a", a.next().value);
console.log("b", b.next().value, b.next().value);
console.log("a", a.next().value, a.next().value, a.next().value);
console.log("b", b.next().value, b.next().value, b.next().value);
console.log("a leftover", ...a);
console.log("b leftover", ...b);

A nice effect is also that nodes in this linked list become candidates for garbage collection once the slowest iterator has consumed a value.

Coda answered 29/2 at 22:44 Comment(0)
J
0

I built a library that allows you to fork an iterator here: https://github.com/tjenkinson/forkable-iterator

Means you can do something like:

import { buildForkableIterator, fork } from 'forkable-iterator';

function* Source() {
  yield 1;
  yield 2;
  return 'return';
}

const forkableIterator = buildForkableIterator(Source());

console.log(forkableIterator.next()); // { value: 1, done: false }

const child1 = fork(forkableIterator);
// { value: 2, done: false }
console.log(child1.next());
// { value: 2, done: false }
console.log(forkableIterator.next());

// { value: 'return', done: true }
console.log(child1.next());
// { value: 'return', done: true }
console.log(forkableIterator.next());

If you no longer need to keep consuming from a fork providing you loose references to it there also shouldn’t be a memory leak.

Jaquelynjaquenetta answered 20/8, 2022 at 11:50 Comment(0)
J
0

It's not official yet, but I think there might be a solution in a stage 2 proposal for Iterator Helpers. If these methods don't affect the original iterator, then doing something like iter.take(Infinity) or iter.drop(0) would have the same effect as cloning.

Jacaranda answered 25/8, 2022 at 16:6 Comment(1)
This doesn't work. From MDN: "The iterator helper [e.g. .take or .drop] shares the same data source as the underlying iterator, so iterating the iterator helper causes the underlying iterator to be iterated as well."Chiromancy

© 2022 - 2024 — McMap. All rights reserved.