Last active
January 28, 2023 06:20
-
-
Save Vap0r1ze/30efcbe6980b34ffb9ef7649b34361c2 to your computer and use it in GitHub Desktop.
doubley linked list implementation in typescript/javascript
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const clamp = (value: number, min: number, max: number) => Math.min(Math.max(value, min), max); | |
/* eslint-disable consistent-return */ | |
export type ListNode<T = any> = { | |
value: T; | |
next: ListNode<T> | null; | |
prev: ListNode<T> | null; | |
}; | |
export type ListHead<T = any> = ListNode<T> & { | |
prev: null; | |
}; | |
export type ListTail<T = any> = ListNode<T> & { | |
next: null; | |
}; | |
export class LinkedList<T = any> { | |
constructor(); | |
constructor( | |
private head: ListHead<T> | null = null, | |
private tail: ListTail<T> | null = null, | |
private size: number = 0, | |
) { } | |
get length() { return this.size; } | |
static from<T = any>(arrayLike: ArrayLike<T>): LinkedList<T> { | |
const array = arrayLike instanceof Array ? arrayLike : Array.from(arrayLike); | |
const list = new LinkedList<T>(); | |
if (array.length === 0) return list; | |
let node: ListTail<T> = list.head = { | |
value: array[0], | |
next: null, | |
prev: null, | |
}; | |
array.forEach((value, i) => { | |
if (i === 0) return; | |
// @ts-expect-error: node can be given a "next" as it will be overwritten immediately after | |
node = node.next = { value, prev: node, next: null }; | |
}); | |
list.tail = node; | |
list.size = array.length; | |
return list; | |
} | |
private getNode(index: number): ListNode<T> | null { | |
const absIndex = index < 0 ? this.size + index : index; | |
if (absIndex < 0) return null; | |
return this.nodeSlice(index, absIndex + 1)[0] ?? null; | |
} | |
private nodeSlice(start: number, end: number = this.size): ListNode<T>[] { | |
if (this.size === 0) return []; | |
start = Math.trunc(start); | |
end = Math.trunc(end); | |
const absStart = clamp(start < 0 ? this.size + start : start, 0, this.size); | |
const absEnd = clamp(end < 0 ? this.size + end : end, 0, this.size); | |
if (absStart >= absEnd) return []; | |
const reverse = absStart > this.size - absEnd; | |
const nodes: ListNode<T>[] = []; | |
const iter = this.nodes(reverse); | |
// Skip to slice boundary | |
for (let i = 0; i < (reverse ? this.size - absEnd : absStart); i++) iter.next(); | |
for (const node of iter) { | |
if (reverse) nodes.unshift(node); | |
else nodes.push(node); | |
if (nodes.length === absEnd - absStart) break; | |
} | |
return nodes; | |
} | |
private *nodes(reverse: boolean = false): Generator<ListNode<T>> { | |
let node: ListNode<T> | null = reverse ? this.tail : this.head; | |
while (node) { | |
yield node; | |
node = reverse ? node.prev : node.next; | |
} | |
} | |
*[Symbol.iterator](): Generator<T> { | |
for (const node of this.nodes()) yield node.value; | |
} | |
at(index: number): T | undefined { | |
const node = this.getNode(index); | |
return node?.value; | |
} | |
slice(start: number, end: number = this.size): T[] { | |
return this.nodeSlice(start, end).map(node => node.value); | |
} | |
push(item: T) { | |
const node: ListNode<T> = { value: item, prev: this.tail, next: null }; | |
// @ts-expect-error: node.prev will be null if size is 0 | |
if (this.size === 0) this.head = node; | |
// @ts-expect-error: tail will be overwritten immediately after | |
// non-null: size > 0 so tail must exist | |
else this.tail!.next = node; | |
this.tail = node as ListTail<T>; | |
this.size++; | |
} | |
pop(): T | undefined { | |
if (this.size === 0) return undefined; | |
if (this.size === 1) { | |
// non-null: size > 0 so head must exist | |
const { value } = this.head!; | |
this.head = this.tail = null; | |
this.size = 0; | |
return value; | |
} | |
// non-null: size > 0 so tail must exist, size > 1 so tail.prev should exist | |
this.tail = Object.assign(this.tail!.prev!, { next: null }); | |
this.size--; | |
} | |
unshift(item: T) { | |
const node = { value: item, prev: null, next: this.head }; | |
// @ts-expect-error: node.next will be null if size is 0 | |
if (this.size === 0) this.tail = node; | |
// @ts-expect-error: head will be overwritten immediately after | |
// non-null: size > 0 so head must exist | |
else this.head!.prev = node; | |
this.head = node; | |
this.size++; | |
} | |
shift(): T | undefined { | |
if (this.size === 0) return undefined; | |
if (this.size === 1) { | |
// non-null: size > 0 so head must exist | |
const { value } = this.head!; | |
this.head = this.tail = null; | |
this.size = 0; | |
return value; | |
} | |
// non-null: size > 0 so head must exist, size > 1 so head.next should exist | |
this.head = Object.assign(this.head!.next!, { prev: null }); | |
this.size--; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment