Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

typescript: make class objects iterable

Tags:

typescript

How to make Typescript objects iterable? In Python 3 I can do

class EndlessCounter:
    def __init__(self):
        self.count = 0

    def __iter__(self):
        return self

    def __next__(self):
        self.count += 1
        return self.count

but what is the Typescript equivalent of this code?

Thanks!

like image 404
mdrndv Avatar asked Jul 21 '16 15:07

mdrndv


3 Answers

Javascript supports Iterators and generators, typescript doesn't add much to it: typescript Iterators and Generators.

Your code can be done like this in javascript:

function* generator() {
    let counter = 0;
    while (true) {
        yield counter++;
    }
}

var iterator = generator();
console.log(iterator.next().value); // 0
console.log(iterator.next().value); // 1
console.log(iterator.next().value); // 2

Edit

You can do the same with a class:

class Counter implements Iterator<number> {
    private counter = 0;

    public next(): IteratorResult<number> {
        return {
            done: false,
            value: this.counter++
        }
    }
}

let c = new Counter();
console.log(c.next().value); // 0
console.log(c.next().value); // 1
console.log(c.next().value); // 2

2nd Edit

The first solution with the generator works well with the for/of loop:

function* generator() {
    let counter = 0;
    while (counter < 5) {
        yield counter++;
    }
}
for (let i of generator()) console.log(i);

Prints 0 to 5, however, to do that with an instance you'll need to do:

class Counter implements Iterable<number> {
    private counter = 0;

    public [Symbol.iterator]() {
        return {
            next: function() {
                return {
                    done: this.counter === 5,
                    value: this.counter++
                }
            }.bind(this)
        }
    }
}
let c = new Counter();
for (let i of c) console.log(i);
like image 187
Nitzan Tomer Avatar answered Oct 21 '22 10:10

Nitzan Tomer


The accepted answer will only work for a single iteration (thus it can also not support nested iterations) and the worst thing is that it will enter an endless loop when you try to iterate again.

here's a version that fixes these issues:

class Counter implements Iterable<number> {
    [Symbol.iterator]() {
        let counter = 0;
        return {
            next: () => {
                return {
                    done: counter >= 5,
                    value: counter++
                }
            }
        }
    }
}
  • whenever an iterator is requested, we return an iterator with count=0
  • each iteration will increase the closure variable counter until we are done

now multiple iterations work:

let c = new Counter();
for (let i of c) console.log(i);
for (let i of c) console.log(i);

and nested iterations also works:

let c = new Counter();
for (let i of c) {
    console.log('OUTER', i);
    for (let i of c) {
        console.log('--inner', i);
    }
}

Playground example

Generator function

An alternative is to use a generator function which results in shorter code (see Krisztián Ballas answer):

class Counter implements Iterable<number> {
    *[Symbol.iterator](): IterableIterator<number> {
        let counter = 0;
        while (counter < 5) {
            yield counter++;
        }
    }
}

Playground example

like image 27
TmTron Avatar answered Oct 21 '22 11:10

TmTron


Let's say you have a simple class that wraps a string object:

class StringWrapper {
    private readonly wrappedString: string;

    public constructor(str: string) {
        this.wrappedString = str;
    }

    *[Symbol.iterator](): IterableIterator<string> {
        for (let i = 0; i < this.wrappedString.length; ++i) {
            yield this.wrappedString[i];
        }
    }
}

The iterator is a generator function which yields one character after another from the wrapped string.

Now you can do things like this:

const wstr = new StringWrapper("abcd");

for (const char of wstr) {
    console.log(char); // will output "a", "b", "c", "d" separately
}

const anotherString == [..wstr]; // will be "abcd"
like image 1
Krisztián Balla Avatar answered Oct 21 '22 10:10

Krisztián Balla