Implementing a Set in typescript, specifically IterableIterator<[T, T]>

Viewed 273

I'm playing with a Set implementation in typescript. I want it to adopt the interface of set, but with some differences, I seem to have a problem with: entries() however:

I have implemented next() on my object, which returns either:

{
 value: elementAt(index),
 done: false;
}

or

{
value: undefined,
done: true
}

but this does not work for

entries() : IterableIterator<[T, T]>

and I can't seem to find any references for how to do this. Can someone help out here? My first thought was that the value of value has to be different but so far nothing i have tried has worked.

2 Answers

From the documentation of Set.prototype.entries():

The entries() method returns a new Iterator object that contains an array of [value, value] for each element in the Set object, in insertion order. For Set objects there is no key like in Map objects. However, to keep the API similar to the Map object, each entry has the same value for its key and value here, so that an array [value, value] is returned.

That means for your object to satisfy the Set interface, it needs to return a different iterator for its entries() method than it does for its values() and keys() methods. Specifically you need to make the value of the iterator result be a pair, so next() returns something like

{
 value: [elementAt(index), elementAt(index)]
 done: false;
}

Hope that helps; good luck!

Here's how I implemented (extended) the Set interface in an abstract class. In my case, I wanted users to define their own (extended) implementations of the abstract methods.

I delegated most things to the _values set, and so I didn't have to mess with a lot.

VS Code completed most of this (it helps you with methods to implement). I Googled a bit to find a foreach implementation.

At the bottom is an example of how to instantiate the new class with the methods.

/** Wrap a Set to support multiple values, 
    e.g., foos are objects in a set, but each foo has an "opposite" */
export abstract class SetWithOpposite<T> implements Set<T> {

    abstract setOpposite(value: T) : this
    abstract clearOpposite(value: T) : this

    add(value: T): this {
        if (!this._values.has(value)) {
            this._values.add(value)
            this.setOpposite(value)
        }
        return this
    }

    constructor(iterable?: T[]) {
        this.clear();

        if (iterable === undefined) {
            return;
        }

        if (!Array.isArray(iterable)) {
            throw new Error("Non-array iterables not supported.");
        }

        for (const value of iterable) {
            this.add(value);
        }
    }

    clear(): void {
        this._values.clear()
    }

    delete(value: T): boolean {
        if (this._values.has(value)) this.clearOpposite(value)
        return this._values.delete(value)
    }

    forEach(callbackfn: (value: T, value2: T, set: Set<T>) => void, thisArg?: any): void {
        for (const element of this._values.keys()) {
            callbackfn.call(thisArg, element, element, this);
        }
    }

    has(value: T): boolean {
        return this._values.has(value)
    }

    get size(): number {
        return this._values.size;
    }

    entries(): IterableIterator<[T, T]> {
        return this._values.entries()
    }

    keys(): IterableIterator<T> {
        return this._values.keys()
    }

    values(): IterableIterator<T> {
        return this._values.values()
    }

    [Symbol.iterator](): IterableIterator<T> {
        return this.values()
    }

    [Symbol.toStringTag]: string;

    _values = new Set<T>()

}

Client defining and using a concrete implementation of SetWithOpposite:

import { SetWithOpposite } from "./setWithOpposite";

const incomingAccesses = new class extends SetWithOpposite<String> {
    setOpposite(value: String): this {
        console.log(`setOpposite: ${value}`)
        return this
    }
    clearOpposite(value: String): this {
        console.log(`clearOpposite: ${value}`)
        return this
    }
}

let entity = new String('Fred')

incomingAccesses.add(entity)

entity = new String('Nancy')

incomingAccesses.add(entity)

incomingAccesses.forEach(element => {
    console.log(`foreach element: ${element}`)
});

incomingAccesses.delete(entity)

for (const entry of incomingAccesses) {
    console.log(entry)
}

Related