import type LinkedNode from './linked-node.js'; class LinkedList { public head: T | null; public tail: T | null; public length: number; constructor() { this.head = null; this.tail = null; this.length = 0; } public append(...nodes: T[]): void { this.insertBefore(nodes[0], null); if (nodes.length > 1) { const rest = nodes.slice(1); this.append(...rest); } } public at(index: number): T | null { const next = this.iterator(); let cur = next(); while (cur && index > 0) { index -= 1; cur = next(); } return cur; } public contains(node: T): boolean { const next = this.iterator(); let cur = next(); while (cur) { if (cur === node) { return true; } cur = next(); } return false; } public indexOf(node: T): number { const next = this.iterator(); let cur = next(); let index = 0; while (cur) { if (cur === node) { return index; } index += 1; cur = next(); } return -1; } public insertBefore(node: T | null, refNode: T | null): void { if (node == null) { return; } this.remove(node); node.next = refNode; if (refNode != null) { node.prev = refNode.prev; if (refNode.prev != null) { refNode.prev.next = node; } refNode.prev = node; if (refNode === this.head) { this.head = node; } } else if (this.tail != null) { this.tail.next = node; node.prev = this.tail; this.tail = node; } else { node.prev = null; this.head = this.tail = node; } this.length += 1; } public offset(target: T): number { let index = 0; let cur = this.head; while (cur != null) { if (cur === target) { return index; } index += cur.length(); cur = cur.next as T; } return -1; } public remove(node: T): void { if (!this.contains(node)) { return; } if (node.prev != null) { node.prev.next = node.next; } if (node.next != null) { node.next.prev = node.prev; } if (node === this.head) { this.head = node.next as T; } if (node === this.tail) { this.tail = node.prev as T; } this.length -= 1; } public iterator(curNode: T | null = this.head): () => T | null { // TODO use yield when we can return (): T | null => { const ret = curNode; if (curNode != null) { curNode = curNode.next as T; } return ret; }; } public find(index: number, inclusive = false): [T | null, number] { const next = this.iterator(); let cur = next(); while (cur) { const length = cur.length(); if ( index < length || (inclusive && index === length && (cur.next == null || cur.next.length() !== 0)) ) { return [cur, index]; } index -= length; cur = next(); } return [null, 0]; } public forEach(callback: (cur: T) => void): void { const next = this.iterator(); let cur = next(); while (cur) { callback(cur); cur = next(); } } public forEachAt( index: number, length: number, callback: (cur: T, offset: number, length: number) => void, ): void { if (length <= 0) { return; } const [startNode, offset] = this.find(index); let curIndex = index - offset; const next = this.iterator(startNode); let cur = next(); while (cur && curIndex < index + length) { const curLength = cur.length(); if (index > curIndex) { callback( cur, index - curIndex, Math.min(length, curIndex + curLength - index), ); } else { callback(cur, 0, Math.min(curLength, index + length - curIndex)); } curIndex += curLength; cur = next(); } } public map(callback: (cur: T) => any): any[] { return this.reduce((memo: T[], cur: T) => { memo.push(callback(cur)); return memo; }, []); } public reduce(callback: (memo: M, cur: T) => M, memo: M): M { const next = this.iterator(); let cur = next(); while (cur) { memo = callback(memo, cur); cur = next(); } return memo; } } export default LinkedList;