Source: queue/TrueSet.js

import { Queue } from "../core/Queue.js";
import { Classifier } from "./Classifier.js";
import { Each } from "@fizzwiz/fluent";

/**
 * A TrueSet is a multiset-like collection where item equivalence
 * is determined by a user-provided `repr(item)` function.
 *
 * Internally, the set stores elements inside a {@link Classifier} as:
 *     classifier.add(repr(item), item)
 *
 * Unlike a normal Set, equivalent items may coexist.  
 * Both classes (repr values) and the items inside each class are sorted
 * according to the sorting configuration of the underlying Classifier.
 *
 * This class exposes:
 *  - A Collection interface (add, remove, has, n, clear, iteration)
 *  - A Queue interface (peek, poll, reverse)
 *
 * The `.classifier` property is intentionally public so users may
 * inspect or query the internal structure.
 */
export class TrueSet extends Queue {

    /**
     * @param {function(*): *} repr  A function producing the class key for each item.
     * @param {Classifier} [classifier=new Classifier()]  The internal classifier.
     */
    constructor(repr, classifier = new Classifier()) {
        super();
        
        this.repr = item => Each.as(repr(item));
        this.classifier = classifier;
    }

    // -----------------------------------------------------------
    // Collection interface
    // -----------------------------------------------------------

    /** @returns {Iterator<*>} Iterates stored items */
    *[Symbol.iterator]() { 
        yield* this.classifier.values(); 
    }

    /** Total number of stored items */
    n() { 
        return this.classifier.n(); 
    }

    /**
     * @param {*} item
     * @returns {boolean} whether an equivalent item exists
     */
    has(item) { 
        return this.classifier.has(this.repr(item)); 
    }

    /**
     * Adds the item `xTimes` times.
     * @param {*} item 
     * @param {number} [xTimes=1]
     * @returns {this}
     */
    add(item, xTimes = 1) { 
        this.classifier.add(this.repr(item), item, xTimes);
        return this;
    }

    /**
     * Removes up to `xTimes` occurrences of the item.
     * @param {*} item 
     * @param {number} [xTimes=Infinity]
     * @returns {boolean} true if removal occurred
     */
    remove(item, xTimes = Infinity) { 
        return this.classifier.remove(this.repr(item), xTimes);
    }

    /** Remove all items */
    clear() { 
        this.classifier.clear(); 
    }

    /** Iterates all the equivalent items */
    get(item, first = true) {
        return this.classifier.get(this.repr(item), first)
    }

    // -----------------------------------------------------------
    // Queue interface
    // -----------------------------------------------------------

    /**
     * Returns (without removing) the first or last stored item.
     * @param {boolean} [first=true]
     * @returns {*} the peeked item or undefined
     */
    peek(first = true) { 
        const node = this.classifier.peek(first);
        return node;
    }

    /**
     * Returns an iterator of all items in reverse order.
     * @returns {Iterator<*>}
     */
    reverse() { 
        return this.classifier.values(false); 
    }
}