UNPKG

doubly

Version:

Doubly linked list in TypeScript

356 lines (273 loc) 7.19 kB
import Node, { nodeTraverseNextNodes, nodeTraverseNextValues, nodeTraversePrevValues, } from './Node' import BaseError from 'baseerr' export { default as DoublyNode } from './Node' class DoublyError extends BaseError<{}> {} export default class Doubly<T> { head: Node<T> | null = null tail: Node<T> | null = null size: number = 0 constructor(opts?: { head?: Node<T> }) { if (opts?.head == null) return this.head = opts.head let cursor: Node<T> | null = opts.head let tail: Node<T> | null = null while (cursor) { tail = cursor cursor = cursor.next } this.tail = tail } [Symbol.iterator](): Iterator<T> { return nodeTraverseNextValues<T>(this.head) } nodes(): IterableIterator<Node<T>> { return nodeTraverseNextNodes<T>(this.head) } at(index: number) { DoublyError.assert(index >= 0, '"at" negative index not supported', { index, }) return this.node(index)?.value } node(index: number) { DoublyError.assert(index >= 0, '"node" negative index not supported', { index, }) let i = 0 for (let node of this.nodes()) { if (i === index) return node i++ } } concat(list: Iterable<T>) { const result = new Doubly<T>() for (let value of this) { result.push(value) } for (let value of list) { result.push(value) } return result } delete(index: number): boolean { DoublyError.assert(index >= 0, '"delete" negative index not supported', { index, }) const node = this.node(index) if (node == null) return false this.deleteNode(node) return true } deleteNode(node: Node<T>) { if (node === this.head) this.shift() else if (node === this.tail) this.pop() else if (node.unlink()) this.size-- } every(cb: (value: T, i: number, list: Doubly<T>) => unknown): boolean { let i = 0 for (let value of this) { if (!cb(value, i, this)) return false i++ } return true } forEach(cb: (value: T, i: number, list: Doubly<T>) => void) { let i = 0 for (let value of this) { cb(value, i, this) i++ } } forEachRight(cb: (value: T, i: number, list: Doubly<T>) => void) { let i = 0 for (let value of nodeTraversePrevValues(this.tail)) { cb(value, i, this) i++ } } filter(cb: (value: T, i: number, list: Doubly<T>) => unknown): Doubly<T> { const result = new Doubly<T>() let i = 0 for (let value of this) { const keep = cb(value, i, this) if (keep) result.push(value) i++ } return result } find(cb: (value: T, i: number, list: Doubly<T>) => unknown): T | void { let i = 0 for (let value of this) { const found = cb(value, i, this) if (found) return value i++ } } findIndex(cb: (value: T, i: number, list: Doubly<T>) => unknown): number { let i = 0 for (let value of this) { const found = cb(value, i, this) if (found) return i i++ } return -1 } includes(compare: T): boolean { if (this.head != null && compare === this.head.value) return true if (this.tail != null && compare === this.tail.value) return true return this.some((value, i, list) => compare === value) } indexOf(compare: T): number { return this.findIndex((value, i, list) => compare === value) } map<R>(cb: (value: T, i: number, list: Doubly<T>) => R): Doubly<R> { const mapped = new Doubly<R>() let i = 0 for (let value of this) { mapped.push(cb(value, i, this)) i++ } return mapped } pop() { const node = this.popNode() return node?.value } popNode() { const prevTail = this.tail if (prevTail == null) return this.size-- const nextTail = this.tail?.prev ?? null this.tail = nextTail this.tail?.linkNext(null) if (this.tail == null) this.head = null prevTail.unlink() return prevTail } push(val: T) { const node = new Node(val) this.pushNode(node) } pushNode(node: Node<T>) { this.size++ if (this.tail == null) { // this.head == null this.head = node this.tail = node return } this.tail = this.tail.linkNext(node) this.tail.next = null } reduce<M>(cb: (memo: M | T, value: T, i: number, list: Doubly<T>) => M): M reduce<M>( cb: (memo: M, value: T, i: number, list: Doubly<T>) => M, initialValue: M, ): M reduce<M>( cb: (memo: M | T, value: T, i: number, list: Doubly<T>) => M, initialValue?: M, ): M { let node = this.head let i = 0 let memo if (arguments.length === 1) { DoublyError.assert( node != null, 'cannot "reduce" empty list with no initial value', ) memo = node.value node = node.next i = 1 } else { memo = initialValue as M } while (node) { memo = cb(memo, node.value, i, this) node = node.next i++ } return memo as M } shift() { const node = this.shiftNode() return node?.value } shiftNode() { const prevHead = this.head if (prevHead == null) return this.size-- const nextHead = this.head?.next ?? null this.head = nextHead if (this.head == null) this.tail = null prevHead.unlink() return prevHead } unshift(val: T) { const node = new Node(val) this.unshiftNode(node) } unshiftNode(node: Node<T>) { this.size++ if (this.head == null) { // this.head == null this.head = node this.tail = node return } node.linkNext(this.head) this.head = node this.head.prev = null } some(cb: (value: T, i: number, list: Doubly<T>) => unknown): boolean { let node = this.head let i = 0 while (node) { const result = cb(node.value, i, this) if (Boolean(result)) return true node = node.next i++ } return false } splice(index: number, removeCount: number, ...items: Array<T>): Doubly<T> { const removed = new Doubly<T>() const start = this.node(index) const prev = start?.prev ?? null if (start == null) { items.forEach((item) => this.push(item)) return removed } // remove items let count = 0 let restHead = prev?.next ?? null for (let next of nodeTraverseNextNodes(start)) { if (count >= removeCount) break count++ restHead = next?.next this.deleteNode(next) removed.pushNode(next) } // insert items if (index === 0) { // insert items at the head items.reverse().forEach((item) => this.unshift(item)) } else if (start === this.tail) { // insert items at the tail items.forEach((item) => this.push(item)) } else { // insert items in the middle // this.head ... prev ... inserted ... this.tail const inserted = new Doubly<T>() items.forEach((item) => inserted.push(item)) if (inserted.head) inserted.head.linkPrev(prev) if (inserted.tail) inserted.tail.linkNext(restHead) this.size += inserted.size } return removed } }