Skip to content

Instantly share code, notes, and snippets.

@Vap0r1ze
Last active January 28, 2023 06:20
Show Gist options
  • Save Vap0r1ze/30efcbe6980b34ffb9ef7649b34361c2 to your computer and use it in GitHub Desktop.
Save Vap0r1ze/30efcbe6980b34ffb9ef7649b34361c2 to your computer and use it in GitHub Desktop.
doubley linked list implementation in typescript/javascript
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