Home Reference Source Test

src/main/generic/utils/array/UniqueLinkedList.js

class UniqueLinkedList extends LinkedList {
    /**
     * @param {function(o: object): string} [fnHash]
     */
    constructor(fnHash) {
        super();
        this._map = new HashMap(fnHash);
    }

    /**
     * @param {V|*} value
     * @param {boolean} moveBack
     * @returns {void}
     * @override
     */
    push(value, moveBack = false) {
        const entry = this._map.get(value);
        if (!entry) {
            super.push(value);
        } else {
            entry.value = value;
            if (moveBack) {
                this._moveBack(entry);
            }
        }
    }

    /**
     * @param {LinkedListEntry} entry
     * @returns {void}
     * @protected
     * @override
     */
    _push(entry) {
        super._push(entry);
        this._map.put(entry.value, entry);
    }

    /**
     * @param {V|*} value
     * @returns {void}
     */
    unshift(value) {
        if (!this._map.contains(value)) {
            super.unshift(value);
        }
    }

    /**
     * @param {LinkedListEntry} entry
     * @returns {void}
     * @protected
     */
    _unshift(entry) {
        super._unshift(entry);
        this._map.put(entry.value, entry);
    }

    /**
     * @returns {V|*}
     */
    pop() {
        const value = super.pop();
        this._map.remove(value);
        return value;
    }

    /**
     * @returns {V|*}
     */
    shift() {
        const value = super.shift();
        this._map.remove(value);
        return value;
    }

    /**
     * @returns {void}
     */
    clear() {
        super.clear();
        this._map.clear();
    }

    /**
     * @param {V|*} value
     * @returns {V|*}
     */
    get(value) {
        const entry = this._map.get(value);
        return entry && entry.value;
    }

    /**
     * @param {V|*} value
     * @returns {boolean}
     */
    contains(value) {
        return this._map.contains(value);
    }

    /**
     * @param {V|*} value
     * @returns {void}
     */
    remove(value) {
        const entry = this._map.get(value);
        if (entry) {
            super._remove(entry);
            this._map.remove(value);
        }
    }

    /**
     * @param {V|*} value
     * @returns {void}
     */
    moveBack(value) {
        /*
         * Just removing and inserting the key again may take seconds (yes, seconds!).
         * This is due to the JavaScript Map implementation as illustrated in this benchmark:
         * https://gist.github.com/paberr/1d916343631c0e42f8311a6f2782f30d
         *
         * 100,000 accesses using Map remove/insert: ~4s
         * 100,000 accesses using optimised version: ~9ms
         */
        const entry = this._map.get(value);
        if (entry) {
            this._moveBack(entry);
        } else {
            // Do not check again for presence in the map.
            super.push(value);
        }
    }

    /**
     * @param {LinkedListEntry} entry
     * @returns {void}
     * @protected
     */
    _moveBack(entry) {
        if (entry === this._head) {
            return;
        } else if (entry === this._tail) {
            entry.next.prev = null;
            this._tail = entry.next;
        } else {
            entry.prev.next = entry.next;
            entry.next.prev = entry.prev;
        }
        entry.next = null;
        entry.prev = this._head;
        this._head.next = entry;
        this._head = entry;
    }
}
Class.register(UniqueLinkedList);