UNPKG

@v4fire/core

Version:
359 lines (289 loc) 6.01 kB
/*! * V4Fire Core * https://github.com/V4Fire/Core * * Released under the MIT license * https://github.com/V4Fire/Core/blob/master/LICENSE */ import Node from 'core/linked-list/node'; /** * Double-ended two-way linked list * @typeparam T - linked list node data */ export default class List<T> { /** * Number of nodes in the list */ get length(): number { return this.lengthStore; } /** * Data of the first node in the list */ get first(): CanUndef<T> { return this.firstNode?.data; } /** * Data of the last node in the list */ get last(): CanUndef<T> { return this.lastNode?.data; } /** * A link to the first node of the list */ protected firstNode: Nullable<Node<T>> = null; /** * A link to the last node of the list */ protected lastNode: Nullable<Node<T>> = null; /** * Internal length value of the list */ protected lengthStore: number = 0; /** * @param [iterable] - data to add to the list */ constructor(iterable?: Iterable<T>) { if (Object.isIterable(iterable)) { for (const el of iterable) { this.push(el); } } } /** * Returns an iterator over the data from the list */ [Symbol.iterator](): IterableIterator<T> { return this.values(); } /** * Adds the passed data to the beginning of the list and returns its new length * @param data */ unshift(...data: T[]): number { data.forEach((data) => { const link = new Node<T>(data); if (this.firstNode != null) { this.firstNode.prev = link; } else { this.lastNode = link; } link.next = this.firstNode; this.firstNode = link; this.lengthStore++; }); return this.lengthStore; } /** * Removes the first node from the list and returns its data as the result. * This method changes the length of the list. */ shift(): CanUndef<T> { if (this.lengthStore === 0) { return; } this.lengthStore--; const first = this.firstNode; if (first == null) { return; } this.firstNode = first.next; if (this.firstNode == null) { this.clear(); return first.data; } first.next = null; return first.data; } /** * Adds the passed data to the end of the list and returns its new length * @param data */ push(...data: T[]): number { data.forEach((data) => { const link = new Node<T>(data); if (this.lastNode == null) { this.firstNode = link; } else { this.lastNode.next = link; link.prev = this.lastNode; } this.lastNode = link; this.lengthStore++; }); return this.lengthStore; } /** * Removes the last node from the list and returns its data as the result. * This method changes the length of the list. */ pop(): CanUndef<T> { if (this.lengthStore === 0) { return; } this.lengthStore--; const last = this.lastNode; if (last == null) { return; } this.lastNode = last.prev; if (this.lastNode == null) { this.clear(); return last.data; } this.lastNode.next = null; last.prev = null; return last.data; } /** * Returns true if the list contains a node with the given data * @param data */ includes(data: T): boolean { if (Number.isNaN(data)) { for (const el of this) { if (Number.isNaN(el)) { return true; } } } else { for (const el of this) { if (el === data) { return true; } } } return false; } /** * Clears all nodes from the list */ clear(): void { this.lengthStore = 0; this.firstNode = null; this.lastNode = null; } /** * Returns a shallow copy of a portion of a list into a new LinkedList selected from start to end (end not included) * where start and end represent the index of nodes in that list. The original list will not be modified. * * @param [start] * @param [end] */ slice(start?: number, end?: number): List<T> { if (start == null) { return new List<T>(this); } if (start < 0 && end != null) { return new List<T>([...this].slice(start, end)); } let iter: IterableIterator<T>, reversed = false; if (start < 0) { start = Math.abs(start) - 1; end = start + 1; iter = this.reverse(); reversed = true; } else { iter = this.values(); } end ??= this.length; if (end < 0) { end += this.length; } let i = 0, done = false; const sliceIter = { [Symbol.iterator]() { return this; }, next() { // eslint-disable-next-line no-constant-condition while (true) { if (done) { return {value: undefined, done: true}; } const chunk = iter.next(); if (chunk.done) { done = true; return chunk; } if (i >= start!) { if (i++ < end!) { return chunk; } done = true; return {value: chunk.value, done}; } i++; } } }; return new List<T>(reversed ? [...sliceIter].reverse() : sliceIter); } /** * Returns an iterator over the data from the list */ values(): IterableIterator<T> { let current = this.firstNode, cursor = 0; const length = this.lengthStore; return { [Symbol.iterator]() { return this; }, next(): IteratorResult<T> { const done = length <= cursor++, value = current; current = value?.next; if (done || value == null) { return { done: true, value: undefined }; } return { done, value: value.data }; } }; } /** * Returns an iterator over the data in the list. * The traversal will proceed from the last node to the first. */ reverse(): IterableIterator<T> { let current = this.lastNode, cursor = 0; const length = this.lengthStore; return { [Symbol.iterator]() { return this; }, next(): IteratorResult<T> { const done = length <= cursor++, value = current; current = value?.prev; if (done || value == null) { return {done: true, value: undefined}; } return {done, value: value.data}; } }; } }