Skip to content

Instantly share code, notes, and snippets.

@rbuckton
Last active October 20, 2017 00:39
Show Gist options
  • Select an option

  • Save rbuckton/0aebbdc9c723cab0b39c0a0c6684ebb5 to your computer and use it in GitHub Desktop.

Select an option

Save rbuckton/0aebbdc9c723cab0b39c0a0c6684ebb5 to your computer and use it in GitHub Desktop.
const nodeFriend = new FriendKey();
export class LinkedList {
#head;
#size;
get first() { return this.#head; }
get last() { return this.#head !== undefined ? nodeFriend.get(this.#head, "#previous") : undefined; }
get size() { return this.#size; }
addLast(value) {
return this.#insertBefore(this.#head, new LinkedListNode(value), /*replaceHead*/ false);
}
addNodeLast(node) {
this.#checkNewNode(node);
this.#insertBefore(this.#head, node, /*replaceHead*/ false);
}
addFirst(value) {
return this.#insertBefore(this.#head, new LinkedListNode(value), /*replaceHead*/ true);
}
addNodeFirst(node) {
this.#checkNewNode(node);
this.#insertBefore(this.#head, node, /*replaceHead*/ true);
}
addBefore(node, value) {
this.#checkNode(node);
return this.#insertBefore(node, new LinkedListNode(value), /*replaceHead*/ this.#head === node);
}
addNodeBefore(node, newNode) {
this.#checkNode(node);
this.#checkNewNode(newNode);
this.#insertBefore(node, newNode, /*replaceHead*/ this.#head === node);
}
addAfter(node, value) {
this.#checkNode(node);
return this.insertBefore(nodeFriend.get(node, "#next"), new LinkedListNode(value), /*replaceHead*/ false);
}
addNodeAfter(node, newNode) {
this.#checkNode(node);
this.#checkNewNode(newNode);
this.#insertBefore(nodeFriend.get(node, "#next"), newNode, /*replaceHead*/ false);
}
has(value) {
return this.find(value) !== undefined;
}
find(value) {
for (let node = this.first; node !== undefined; node = node.next) {
if (node.value === value) return node;
}
return undefined;
}
findLast(value) {
for (let node = this.last; node !== undefined; node = node.previous) {
if (node.value === value) return node;
}
return undefined;
}
delete(value) {
const node = this.find(value);
if (node) {
this.#delete(node);
return true;
}
return false;
}
deleteFirst() {
if (this.#head !== undefined) {
return this.#delete(this.#head).value;
}
return undefined;
}
deleteLast() {
if (this.#head !== undefined) {
return this.#delete(nodeFriend.get(this.#head, "#previous")).value;
}
return undefined;
}
deleteNode(node) {
this.#checkNode(node);
this.#delete(node);
}
deleteNodeFirst() {
if (this.#head !== undefined) {
this.#delete(this.#head);
return true;
}
return false;
}
deleteNodeLast() {
if (this.#head !== undefined) {
this.#delete(nodeFriend.get(this.#head, "#previous"));
return true;
}
return false;
}
clear() {
let next = this.#head;
while (next !== undefined) {
const node = next;
next = nodeFriend.get(node, "#next");
this.#invalidate(node);
if (next === this.#head) break;
}
this.#size = 0;
}
forEach(callback, thisArg) {
let next = this.#head;
while (next !== undefined) {
const node = next;
next = nodeFriend.get(node, "#next");
callback.call(thisArg, node.value, node, this);
if (next === this.#head || next.list !== this) break;
}
}
*values() {
let next = this.#head;
while (next !== undefined) {
const node = next;
next = nodeFriend.get(node, "#next");
yield node.value;
if (next === this.#head || next.list !== this) break;
}
}
*nodes() {
let next = this.#head;
while (next !== undefined) {
const node = next;
next = nodeFriend.get(node, "#next");
yield node;
if (next === this.#head || next.list !== this) break;
}
}
[Symbol.iterator]() { return this.values(); }
#checkNode(node) {
if (!node) throw new TypeError("Argument not optional: node");
if (node.list !== this) throw new Error("Wrong list.");
}
#checkNewNode(newNode) {
if (!newNode) throw new TypeError("Argument not optional: newNode");
if (newNode.list) throw new Error("Node is already attached to a list.");
}
#insertBefore(node, newNode, replaceHead) {
nodeFriend.set(newNode, "#list", this);
if (node === undefined) {
nodeFriend.set(newNode, "#next", newNode);
nodeFriend.set(newNode, "#previous", newNode);
this.#head = newNode;
}
else {
nodeFriend.set(newNode, "#next", node);
nodeFriend.set(newNode, "#previous", nodeFriend.get(node, "#previous"));
nodeFriend.set(nodeFriend.get(node, "#previous"), "#next", newNode);
nodeFriend.set(node, "#previous", newNode);
if (replaceHead) this.#head = newNode;
}
this.#size++;
return newNode;
}
#delete(node) {
if (nodeFriend.get(node, "#next") === node) {
this.#head = undefined;
}
else {
nodeFriend.set(nodeFriend.get(node, "#next"), "#previous", nodeFriend.get(node, "#previous"));
nodeFriend.set(nodeFriend.get(node, "#previous"), "#next", nodeFriend.get(node, "#next"));
if (this.#head === node) {
this.#head = nodeFriend.get(node, "#next");
}
}
this.#invalidate(node);
this.#size--;
return node;
}
#invalidate(node) {
nodeFriend.set(node, "#list", undefined);
nodeFriend.set(node, "#next", undefined);
nodeFriend.set(node, "#previous", undefined);
}
}
export class LinkedListNode {
value;
@nodeFriend.expose #list;
@nodeFriend.expose #previous;
@nodeFriend.expose #next;
constructor(value) { this.value = value; }
get list() { return this.#list; }
get previous() { return this.#previous !== undefined && this !== this.#list.first ? this.#previous : undefined; }
get next() { return this.#next !== undefined && this !== this.#list.last ? this.#next : undefined; }
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment