Home Reference Source Test

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

/**
 * @template V
 * @implements {Iterable.<V>}
 * @typedef {{next: ?LinkedListEntry, prev: ?LinkedListEntry, value: V|*}} LinkedListEntry
 */
class LinkedList {
    /**
     * @param {*} args
     */
    constructor(...args) {
        /** @type {number} */
        this._length = 0;
        /** @type {LinkedListEntry} */
        this._head = null;
        /** @type {LinkedListEntry} */
        this._tail = null;

        const values = args.length === 1 && Array.isArray(args[0]) ? args[0] : args;
        for (const value of values) {
            this.push(value);
        }
    }

    /**
     * @param {V|*} value
     * @returns {void}
     */
    push(value) {
        const entry = {
            next: null,
            prev: this._head,
            value: value
        };
        this._push(entry);
    }

    /**
     * @param {LinkedListEntry} entry
     * @returns {void}
     * @protected
     */
    _push(entry) {
        this._length++;

        if (!this._head) {
            this._head = entry;
            this._tail = entry;
            return;
        }

        this._head.next = entry;
        this._head = entry;
    }

    /**
     * @param {V|*} value
     */
    unshift(value) {
        const entry = {
            next: this._tail,
            prev: null,
            value: value
        };
        this._unshift(entry);
    }

    /**
     * @param {LinkedListEntry} entry
     * @returns {void}
     * @protected
     */
    _unshift(entry) {
        this._length++;

        if (!this._head) {
            this._head = entry;
            this._tail = entry;
            return;
        }

        this._tail.prev = entry;
        this._tail = entry;
    }

    /**
     * @returns {V|*}
     */
    pop() {
        if (!this._head) {
            return null;
        }

        this._length--;

        const entry = this._head;
        const prev = entry.prev;
        if (!prev) {
            this._head = null;
            this._tail = null;
            return entry.value;
        }

        prev.next = null;
        this._head = prev;
        return entry.value;
    }

    /**
     * @returns {V|*}
     */
    shift() {
        if (!this._head) {
            return null;
        }

        this._length--;

        const entry = this._tail;
        const next = entry.next;
        if (!next) {
            this._head = null;
            this._tail = null;
            return entry.value;
        }

        next.prev = null;
        this._tail = next;
        return entry.value;
    }

    /**
     * @param {LinkedListEntry} entry
     * @returns {void}
     * @protected
     */
    _remove(entry) {
        if (entry === this._head) {
            this.pop();
        } else if (entry === this._tail) {
            this.shift();
        } else {
            this._length--;
            entry.prev.next = entry.next;
            entry.next.prev = entry.prev;
        }
    }

    /**
     * @returns {void}
     */
    clear() {
        this._length = 0;
        this._head = null;
        this._tail = null;
    }

    /**
     * @returns {Iterator.<V|*>}
     */
    [Symbol.iterator]() {
        return this.iterator();
    }

    /**
     * @returns {Iterator.<V|*>}
     */
    *iterator() {
        let entry = this._tail;
        while (entry) {
            yield entry.value;
            entry = entry.next;
        }
    }

    /**
     * @returns {boolean}
     */
    isEmpty() {
        return this._length === 0;
    }

    /** @type {V|*} */
    get first() {
        return this._tail ? this._tail.value : null;
    }

    /** @type {V|*} */
    get last() {
        return this._head ? this._head.value : null;
    }

    /** @type {number} */
    get length() {
        return this._length;
    }
}
Class.register(LinkedList);