UNPKG

sangja

Version:

JavaScript data structures library

697 lines (624 loc) 17 kB
const Utils = require('./utils'); /** * @class * @memberof sangja */ class LinkedList { /** * Creates a new LinkedList. * Iterable parameter is optional. * @constructor * @param {iterable} [iterable] - Iterator for initialize the list. * @throws {TypeError} When given parameter is not iterable. */ constructor(iterable = []) { this._linkedList = { size: 0, front: null, end: null, }; if (!Utils.isIterable(iterable)) { throw TypeError(); } [...iterable].forEach(v => this.addLast(v)); } /** * Return the node at the given index of the linked list. * @param {number} index - The index to get node. * @private * @return {node} The node at the given index. * When the given index < 0 or index >= size of the linked list, return undefined. */ _getNode(index) { if (typeof index !== 'number' || index < 0 || index >= this._linkedList.size) { return undefined; } if (index < this._linkedList.size / 2) { let now = this._linkedList.front; for (let i = 0; i < index; i += 1) { now = now.next; } return now; } let now = this._linkedList.end; for (let i = this._linkedList.size - 1; i > index; i -= 1) { now = now.prev; } return now; } /** * Add value at the given index of the linked list. * @param {number} [index] - The index to add the value. * @param {*} value - The value to add. * @throws {RangeError} When the given index < 0 or index > size of the linked list. */ add(index, value) { // Index not given. index is the value to add. if (value === undefined) { this.addLast(index); return; } if (typeof index !== 'number' || index < 0 || index > this._linkedList.size) { throw RangeError(); } if (index === 0) { const item = { prev: null, next: this._linkedList.front, value, }; if (this._linkedList.front) { this._linkedList.front.prev = item; } else { // No elements until now. this._linkedList.end = item; } this._linkedList.front = item; } else if (index === this._linkedList.size) { const item = { prev: this._linkedList.end, next: null, value, }; if (this._linkedList.end) { this._linkedList.end.next = item; } this._linkedList.end = item; } else { const original = this._getNode(index); const item = { prev: original.prev, next: original, value, }; item.prev.next = item; item.next.prev = item; } this._linkedList.size += 1; } /** * Add values in given iterator at the given index of the linked list. * @param {number} [index] - The index to add the value. * @param {iterable} iterable - The iterable object that contain values to add. * @throws {RangeError} When the given index < 0 or index > size of the linked list. */ addAll(index, iterable) { // Index not given. index is iterable object. if (iterable === undefined) { this.addAllLast(index); return; } const values = [...iterable]; for (let i = values.length - 1; i >= 0; i -= 1) { this.add(index, values[i]); } } /** * Add value at the front of the linked list. * @param {*} value - The value to add. */ addFirst(value) { this.add(0, value); } /** * Add values in given iterator at the front of the linked list. * @param {iterable} iterable - The iterable object that contain values to add. */ addAllFirst(iterable) { const values = [...iterable]; for (let i = values.length - 1; i >= 0; i -= 1) { this.add(0, values[i]); } } /** * Add value at the end of the linked list. * @param {*} value - The value to add. */ addLast(value) { this.add(this.size(), value); } /** * Add values in given iterator at the end of the linked list. * @param {iterable} iterable - The iterable object that contain values to add. */ addAllLast(iterable) { const values = [...iterable]; for (let i = 0; i < values.length; i += 1) { this.addLast(values[i]); } } /** * Removes the given index of the linked list and * returns the value at the given index of the linked list. * @param {number} [index] - The index to remove the value. * @return {*} The value at the given index of the linked list. If not found, return undefined. */ pop(index) { if (index === undefined) { return this.popLast(); } if (typeof index !== 'number' || index < 0 || index >= this._linkedList.size) { return undefined; } const now = this._getNode(index); if (now.prev) { now.prev.next = now.next; } else { this._linkedList.front = now.next; } if (now.next) { now.next.prev = now.prev; } else { this._linkedList.end = now.prev; } this._linkedList.size -= 1; return now.value; } /** * Removes the first of the linked list and * returns the value at the first of the linked list. * @return {*} The value at the front of the linked list. If empty, return undefined. */ popFirst() { return this.pop(0); } /** * Removes the end of the linked list and * returns the value at the end of the linked list. * @return {*} The value at the end of the linked list. If empty, return undefined. */ popLast() { return this.pop(this.size() - 1); } /** * Same with pop(i) except that index is required. * @param {number} index - The index to remove the value. * @return {(*|undefined)} The value at the given index of the linked list. * If not found, return undefined. */ removeAt(index) { if (typeof index !== 'number' || index < 0 || index >= this._linkedList.size) { return undefined; } return this.pop(index); } /** * Removes the first occurance of the given value in the linked list and * returns true if the given value is in the linked list. * @param {*} value - The value to remove * @return {boolean} True if the given value is in the linked list else false. */ remove(value) { let now = this._linkedList.front; while (now) { if (now.value === value) { break; } now = now.next; } if (!now) { return false; } if (now.prev) { now.prev.next = now.next; } else { // now is front this._linkedList.front = now.next; } if (now.next) { now.next.prev = now.prev; } else { // now is end this._linkedList.end = now.prev; } this._linkedList.size -= 1; return true; } /** * Removes the every occurance of the given value in the linked list and * returns the number of removed values. * @param {*} value - The value to remove * @return {number} The number of removed values. */ removeAll(value) { let now = this._linkedList.front; let result = 0; while (now) { if (now.value === value) { if (now.prev) { now.prev.next = now.next; } else { // now is front this._linkedList.front = now.next; } if (now.next) { now.next.prev = now.prev; } else { // now is end this._linkedList.end = now.prev; } this._linkedList.size -= 1; result += 1; } now = now.next; } return result; } /** * Same with remove(value) if value is given. * If value is not given, same with popFirst(). * @param {*} [value] - The value to remove * @return {(*|undefined|boolean)} If remove success, return true. * If value not given, return popFirst(). */ removeFirst(value) { if (value === undefined) { return this.popFirst(); } return this.remove(value); } /** * Same with remove(value) but find from start searching from last. * @param {*} [value] - The value to remove * @return {(*|undefined|boolean)} If remove success, return true. * If value not given, return popLast(). */ removeLast(value) { let now = this._linkedList.end; while (now) { if (now.value === value) { break; } now = now.prev; } if (!now) { return false; } if (now.prev) { now.prev.next = now.next; } else { // now is front this._linkedList.front = now.next; } if (now.next) { now.next.prev = now.prev; } else { // now is end this._linkedList.end = now.prev; } this._linkedList.size -= 1; return true; } /** * Removes the first value that matches given predicate f and * returns value in the linked list. * @param {Function} f - Predicate * @return {(*|undefined)} Found value. If not found, return undefined. * @throws {TypeError} When the given predicate is not a function. */ removeMatch(f) { if (typeof f !== 'function') { throw TypeError(); } let now = this._linkedList.front; while (now) { if (f(now.value)) { break; } now = now.next; } if (!now) { return undefined; } if (now.prev) { now.prev.next = now.next; } else { // now is front this._linkedList.front = now.next; } if (now.next) { now.next.prev = now.prev; } else { // now is end this._linkedList.end = now.prev; } this._linkedList.size -= 1; return now.value; } /** * Removes all values that matches given predicate f and * returns the removed values. * @param {Function} f - Predicate * @return {any[]} Removed values. * @throws {TypeError} When the given predicate is not a function. */ removeMatchAll(f) { if (typeof f !== 'function') { throw TypeError(); } let now = this._linkedList.front; const result = []; while (now) { if (f(now.value)) { if (now.prev) { now.prev.next = now.next; } else { // now is front this._linkedList.front = now.next; } if (now.next) { now.next.prev = now.prev; } else { // now is end this._linkedList.end = now.prev; } this._linkedList.size -= 1; result.push(now.value); } now = now.next; } return result; } /** * Same with removeMatch(f) * @param {Function} f - Predicate * @return {(*|undefined)} Found value. If not found, return undefined. * @throws {TypeError} When the given predicate is not a function. */ removeMatchFirst(f) { return this.removeMatch(f); } /** * Same with removeMatch(f) but find from start searching from last. * @param {Function} f - Predicate * @return {(*|undefined)} Found value. If not found, return undefined. * @throws {TypeError} When the given predicate is not a function. */ removeMatchLast(f) { if (typeof f !== 'function') { throw TypeError(); } let now = this._linkedList.end; while (now) { if (f(now.value)) { break; } now = now.prev; } if (!now) { return undefined; } if (now.prev) { now.prev.next = now.next; } else { // now is front this._linkedList.front = now.next; } if (now.next) { now.next.prev = now.prev; } else { // now is end this._linkedList.end = now.prev; } this._linkedList.size -= 1; return now.value; } /** * Removes all values in the linked list. */ clear() { this._linkedList = { size: 0, front: null, end: null, }; } /** * Returns the value at the given index of the linked list. * @param {number} index - The index to get the value. * @return {*} The value at the given index of the linked list. * When the given index < 0 or index >= size of the linked list, return undefined. */ get(index) { if (typeof index !== 'number' || index < 0 || index >= this._linkedList.size) { return undefined; } const now = this._getNode(index); return now.value; } /** * Returns the value at the first of the linked list. * @return {*} The value at the front of the linked list. If empty, return undefined. */ getFirst() { return this.get(0); } /** * Returns the value at the end of the linked list. * @return {*} The value at the end of the linked list. If empty, return undefined. */ getLast() { return this.get(this.size() - 1); } /** * Updates the value at the given index of the linked list and * returns the value at the given index of the linked list. * @param {number} index - The index to update the value. * @param {*} value - The value to update. * @throws {RangeError} When the given index < 0 or index >= size of the linked list. * @return {*} The value at the given index of the linked list. */ set(index, value) { if (typeof index !== 'number' || index < 0 || index >= this._linkedList.size) { throw RangeError(); } const now = this._getNode(index); const ret = now.value; now.value = value; return ret; } /** * Returns the value of the first element in the linked list * that satisfies the provided testing function. * @param {function} f - Testing function. * @return {*} The the first value in the linked list * that satisfies the provided testing function. * When no element in the linked list satisfies the provided testing function, return undefined. */ find(f) { let now = this._linkedList.front; while (now && !f(now.value)) { now = now.next; } if (now) { return now.value; } return undefined; } /** * Returns the number of elements in the linked list. * @return {number} The number of elements in the linked list. */ size() { return this._linkedList.size; } /** * Returns whether the linked list is empty. * @return {boolean} True if the linked list is empty. */ isEmpty() { return this._linkedList.size === 0; } /** * For each values in the list, execute the given procedure f. * @param {function} f - Procedure to execute */ forEach(f) { let now = this._linkedList.front; while (now) { f(now.value); now = now.next; } } /** * Returns a new LinkedList whose values are mapped with given function f. * @param {function} f - Function to map values * @return {LinkedList} LinkedList([mapped values]) */ map(f) { const list = new LinkedList(); this.forEach(v => list.addLast(f(v))); return list; } /** * Returns a new LinkedList whose values are mapped with given function f and flattened. * @param {function} f - Function (this.value) => iterable * @return {LinkedList} LinkedList([mapped and flattened values]) */ flatMap(f) { const list = new LinkedList(); this.forEach(v => list.addAllLast([...f(v)])); return list; } /** * Returns a new LinkedList whose values are filtered with given predicate f. * @param {function} f - Predicate (this.value) => boolean * @return {LinkedList} LinkedList([filtered values]) */ filter(f) { const list = new LinkedList(); this.forEach((v) => { if (f(v)) { list.addLast(v); } }); return list; } /** * Returns a new LinkedList whose values are reversed in order. * @return {LinkedList} LinkedList([reversed values]) */ reversed() { const list = new LinkedList(); this.forEach(v => list.addFirst(v)); return list; } /** * If any of containing values satisfies given f, return true. * If none of values satisfy f or not contain any value, return false. * @param {function} f - Predicate * @return {boolean} */ some(f) { if (this.size() === 0) { return false; } let now = this._linkedList.front; while (now) { if (f(now.value)) { return true; } now = now.next; } return false; } /** * If all of containing values satisfies given f or not contain any value, return true. * If any of value doesn't satisfy f, return false. * @param {function} f - Predicate * @return {boolean} */ every(f) { let now = this._linkedList.front; while (now) { if (!f(now.value)) { return false; } now = now.next; } return true; } /** * If contains given value v, return true. * @param {*} v * @return {boolean} */ includes(v) { let now = this._linkedList.front; while (now) { if (now.value === v) { return true; } now = now.next; } return false; } * [Symbol.iterator]() { let now = this._linkedList.front; while (now) { yield now.value; now = now.next; } } } module.exports = LinkedList;