From fab1358332014e5254a8d7e6f4e1720c8d43453f Mon Sep 17 00:00:00 2001 From: Rajkumar Dusad Date: Wed, 26 Apr 2023 14:08:19 +0530 Subject: [PATCH] bug fixes --- package.json | 2 +- src/cache.cjs | 106 +++++++++-------- src/cache.mjs | 106 +++++++++-------- src/linkedlist/index.cjs | 243 +++++++++++++++++++++++++++++++++++++++ src/linkedlist/index.mjs | 243 +++++++++++++++++++++++++++++++++++++++ src/linkedlist/node.cjs | 7 ++ src/linkedlist/node.mjs | 7 ++ tests/cache.test.js | 22 ++-- tests/linkedlist.test.js | 135 ++++++++++++++++++++++ 9 files changed, 763 insertions(+), 108 deletions(-) create mode 100644 src/linkedlist/index.cjs create mode 100644 src/linkedlist/index.mjs create mode 100644 src/linkedlist/node.cjs create mode 100644 src/linkedlist/node.mjs create mode 100644 tests/linkedlist.test.js diff --git a/package.json b/package.json index 09aec6c..effad30 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@opensnip/lrujs", - "version": "1.0.8", + "version": "1.0.9", "description": "Fast and lightweight lru cache for javascript", "main": "index.mjs", "type": "module", diff --git a/src/cache.cjs b/src/cache.cjs index 4b8d995..2dc3e35 100644 --- a/src/cache.cjs +++ b/src/cache.cjs @@ -1,8 +1,11 @@ +const LinkedList = require("./linkedlist/index.cjs"); + module.exports = class Cache { + #linkedList = null; #cache = null; #config = { ttl: 0, - maxLength: 0, + maxLength: 250, interval: 0, intervalId: null, enableInterval: false, @@ -55,12 +58,12 @@ module.exports = class Cache { ? options.enableInterval : false; - this.#config.evictionPolicy = options.evictionPolicy; this.#config.maxLength = options.maxLength; this.#config.ttl = options.ttl; this.#config.interval = options.interval; this.#config.enableInterval = options.interval > 0 ? options.enableInterval : false; + this.#linkedList = new LinkedList(); this.#cache = new Map(); // Automatically remove expires cache @@ -72,7 +75,6 @@ module.exports = class Cache { } set(key, value, options = {}) { - // Insert a new node at head if ( (typeof options.ttl !== "undefined" && typeof options.ttl !== "number") || options.ttl < 0 @@ -83,7 +85,7 @@ module.exports = class Cache { options.ttl = typeof options.ttl === "number" ? options.ttl : this.#config.ttl; - const node = { + const nodeValue = { key: key, value: value, createdAt: Date.now(), @@ -91,23 +93,24 @@ module.exports = class Cache { ttl: options.ttl, frequency: 0, }; - if (node.ttl > 0) { - node.expiresAt = node.createdAt + node.ttl; + if (nodeValue.ttl > 0) { + nodeValue.expiresAt = nodeValue.createdAt + nodeValue.ttl; } + // Insert a new node at head + const existingNode = this.#cache.get(key); // Update node data if node is already exists - if (this.#cache.has(key)) { - const existingNode = this.#cache.get(key); - existingNode.value = node.value; + if (typeof existingNode !== "undefined") { + existingNode.value = nodeValue; // Move current node to the head - this.#cache.delete(key); - this.#cache.set(key, existingNode); + this.#linkedList.setHead(existingNode); } else { // Remove node if cache is full if (this.length === this.#config.maxLength) { this.#evict(); } // Create new node and make attach it to the head + const node = this.#linkedList.insertHead(nodeValue); this.#cache.set(key, node); } } @@ -118,26 +121,26 @@ module.exports = class Cache { throw new TypeError("callback should be a function"); } - if (this.#cache.has(key)) { - const node = this.#cache.get(key); + const node = this.#cache.get(key); + + if (typeof node !== "undefined") { // Check node is live or not if (this.#isStale(node)) { this.delete(key); - throw new Error(key + " key not found"); + throw new Error(key + " Key not found"); } // Move current node to the head - this.#cache.delete(key); - this.#cache.set(key, node); + this.#linkedList.setHead(node); if (callback) { - return callback(null, node.value); + return callback(null, node.value.value); } else { - return node.value; + return node.value.value; } } - throw new Error(key + " key not found"); + throw new Error(key + " Key not found"); } catch (err) { if (callback) { return callback(err, undefined); @@ -148,27 +151,41 @@ module.exports = class Cache { } delete(key) { - if (this.#cache.has(key)) { + const node = this.#cache.get(key); + + if (typeof node !== "undefined") { + this.#linkedList.delete(node); // Delete node this.#cache.delete(key); } } #evict() { - if (this.length === 0) return; + if (this.#linkedList.tail === null) return; if (this.length !== this.#config.maxLength) return; - - for (const key of this.#cache.keys()) { - this.delete(key); - break; - } + this.delete(this.#linkedList.tail.value.key); } clear() { // Delete all data from cache + this.#linkedList.clear(); this.#cache.clear(); } + has(key) { + const node = this.#cache.get(key); + + if (typeof node !== "undefined") { + // Check node is live or not + if (this.#isStale(node)) { + this.delete(key); + } else { + return true; + } + } + return false; + } + startInterval() { // Interval already running if (this.#config.intervalId) return; @@ -193,31 +210,21 @@ module.exports = class Cache { } } - has(key) { - if (this.#cache.has(key)) { - const node = this.#cache.get(key); - // Check node is live or not - if (this.#isStale(node)) { - this.delete(key); - } else { - return true; - } - } - return false; - } - // Iterate over cache using forEach loop forEach(callback) { if (callback && typeof callback !== "function") { throw new TypeError("callback should be a function"); } + let node = this.#linkedList.head; let index = 0; - for (const data of this.#cache.entries()) { - if (this.has(data[0])) { - callback({ [data[0]]: data[1].value }, index); - index++; + while (node) { + let next = node.next; + if (this.has(node.value.key)) { + callback({ [node.value.key]: node.value.value }, index); } + node = next; + index++; } } @@ -230,16 +237,19 @@ module.exports = class Cache { } #isStale(node) { - if (!node.expiresAt) return false; - return node.expiresAt - Date.now() <= 0; + if (!node.value.expiresAt) return false; + return node.value.expiresAt - Date.now() <= 0; } // Iterator to iterate over cache with a 'for...of' loop *[Symbol.iterator]() { - for (const data of this.#cache.entries()) { - if (this.has(data[0])) { - yield { [data[0]]: data[1].value }; + let node = this.#linkedList.head; + while (node) { + let next = node.next; + if (this.has(node.value.key)) { + yield { [node.value.key]: node.value.value }; } + node = next; } } }; diff --git a/src/cache.mjs b/src/cache.mjs index 449afc1..120bc75 100644 --- a/src/cache.mjs +++ b/src/cache.mjs @@ -1,8 +1,11 @@ +import LinkedList from "./linkedlist/index.mjs"; + export default class Cache { + #linkedList = null; #cache = null; #config = { ttl: 0, - maxLength: 0, + maxLength: 250, interval: 0, intervalId: null, enableInterval: false, @@ -55,12 +58,12 @@ export default class Cache { ? options.enableInterval : false; - this.#config.evictionPolicy = options.evictionPolicy; this.#config.maxLength = options.maxLength; this.#config.ttl = options.ttl; this.#config.interval = options.interval; this.#config.enableInterval = options.interval > 0 ? options.enableInterval : false; + this.#linkedList = new LinkedList(); this.#cache = new Map(); // Automatically remove expires cache @@ -72,7 +75,6 @@ export default class Cache { } set(key, value, options = {}) { - // Insert a new node at head if ( (typeof options.ttl !== "undefined" && typeof options.ttl !== "number") || options.ttl < 0 @@ -83,7 +85,7 @@ export default class Cache { options.ttl = typeof options.ttl === "number" ? options.ttl : this.#config.ttl; - const node = { + const nodeValue = { key: key, value: value, createdAt: Date.now(), @@ -91,23 +93,24 @@ export default class Cache { ttl: options.ttl, frequency: 0, }; - if (node.ttl > 0) { - node.expiresAt = node.createdAt + node.ttl; + if (nodeValue.ttl > 0) { + nodeValue.expiresAt = nodeValue.createdAt + nodeValue.ttl; } + // Insert a new node at head + const existingNode = this.#cache.get(key); // Update node data if node is already exists - if (this.#cache.has(key)) { - const existingNode = this.#cache.get(key); - existingNode.value = node.value; + if (typeof existingNode !== "undefined") { + existingNode.value = nodeValue; // Move current node to the head - this.#cache.delete(key); - this.#cache.set(key, existingNode); + this.#linkedList.setHead(existingNode); } else { // Remove node if cache is full if (this.length === this.#config.maxLength) { this.#evict(); } // Create new node and make attach it to the head + const node = this.#linkedList.insertHead(nodeValue); this.#cache.set(key, node); } } @@ -118,26 +121,26 @@ export default class Cache { throw new TypeError("callback should be a function"); } - if (this.#cache.has(key)) { - const node = this.#cache.get(key); + const node = this.#cache.get(key); + + if (typeof node !== "undefined") { // Check node is live or not if (this.#isStale(node)) { this.delete(key); - throw new Error(key + " key not found"); + throw new Error(key + " Key not found"); } // Move current node to the head - this.#cache.delete(key); - this.#cache.set(key, node); + this.#linkedList.setHead(node); if (callback) { - return callback(null, node.value); + return callback(null, node.value.value); } else { - return node.value; + return node.value.value; } } - throw new Error(key + " key not found"); + throw new Error(key + " Key not found"); } catch (err) { if (callback) { return callback(err, undefined); @@ -148,27 +151,41 @@ export default class Cache { } delete(key) { - if (this.#cache.has(key)) { + const node = this.#cache.get(key); + + if (typeof node !== "undefined") { + this.#linkedList.delete(node); // Delete node this.#cache.delete(key); } } #evict() { - if (this.length === 0) return; + if (this.#linkedList.tail === null) return; if (this.length !== this.#config.maxLength) return; - - for (const key of this.#cache.keys()) { - this.delete(key); - break; - } + this.delete(this.#linkedList.tail.value.key); } clear() { // Delete all data from cache + this.#linkedList.clear(); this.#cache.clear(); } + has(key) { + const node = this.#cache.get(key); + + if (typeof node !== "undefined") { + // Check node is live or not + if (this.#isStale(node)) { + this.delete(key); + } else { + return true; + } + } + return false; + } + startInterval() { // Interval already running if (this.#config.intervalId) return; @@ -193,31 +210,21 @@ export default class Cache { } } - has(key) { - if (this.#cache.has(key)) { - const node = this.#cache.get(key); - // Check node is live or not - if (this.#isStale(node)) { - this.delete(key); - } else { - return true; - } - } - return false; - } - // Iterate over cache using forEach loop forEach(callback) { if (callback && typeof callback !== "function") { throw new TypeError("callback should be a function"); } + let node = this.#linkedList.head; let index = 0; - for (const data of this.#cache.entries()) { - if (this.has(data[0])) { - callback({ [data[0]]: data[1].value }, index); - index++; + while (node) { + let next = node.next; + if (this.has(node.value.key)) { + callback({ [node.value.key]: node.value.value }, index); } + node = next; + index++; } } @@ -230,16 +237,19 @@ export default class Cache { } #isStale(node) { - if (!node.expiresAt) return false; - return node.expiresAt - Date.now() <= 0; + if (!node.value.expiresAt) return false; + return node.value.expiresAt - Date.now() <= 0; } // Iterator to iterate over cache with a 'for...of' loop *[Symbol.iterator]() { - for (const data of this.#cache.entries()) { - if (this.has(data[0])) { - yield { [data[0]]: data[1].value }; + let node = this.#linkedList.head; + while (node) { + let next = node.next; + if (this.has(node.value.key)) { + yield { [node.value.key]: node.value.value }; } + node = next; } } } diff --git a/src/linkedlist/index.cjs b/src/linkedlist/index.cjs new file mode 100644 index 0000000..5457d7d --- /dev/null +++ b/src/linkedlist/index.cjs @@ -0,0 +1,243 @@ +const Node = require("./node.cjs"); + +module.exports = class LinkedList { + #head = null; + #tail = null; + #length = 0; + + get head() { + return this.#head; + } + + get tail() { + return this.#tail; + } + + get length() { + return this.#length; + } + + clear() { + this.#head = null; + this.#tail = null; + this.#length = 0; + } + + insertHead(value) { + const node = new Node(value); + if (this.#head === null) { + this.#head = this.#tail = node; + } else { + this.#head.prev = node; + node.next = this.#head; + this.#head = node; + } + this.#length++; + return node; + } + + insertTail(value) { + if (this.#tail === null) { + return this.insertHead(value); + } + + const node = new Node(value); + this.#tail.next = node; + node.prev = this.#tail; + this.#tail = node; + this.#length++; + return node; + } + + insertAfter(node, value) { + if (!(node instanceof Node)) { + throw new TypeError("node should be a valid Node instance"); + } + const newNode = new Node(value); + if (node.next != null) { + node.next.prev = newNode; + newNode.next = node.next; + } + newNode.prev = node; + node.next = newNode; + this.#length++; + return node; + } + + setHead(node) { + if (!(node instanceof Node)) { + throw new TypeError("node should be a valid Node instance"); + } + + if (this.#head === node) return this.#head; + + if (this.#head === null) { + return this.insertHead(node.value); + } + + this.detach(node); + node.prev = null; + node.next = this.#head; + this.#head.prev = node; + this.#head = node; + this.#length++; + return this.#head; + } + + setTail(node) { + if (!(node instanceof Node)) { + throw new TypeError("node should be a valid Node instance"); + } + + if (this.#tail === node) return this.#tail; + + if (this.#tail === null) { + return this.insertTail(node.value); + } + + this.detach(node); + this.#tail.next = node; + node.prev = this.#tail; + node.next = null; + this.#tail = node; + this.#length++; + return this.#tail; + } + + swap(leftNode, rightNode) { + if (!(leftNode instanceof Node)) { + throw new TypeError("leftNode should be a valid Node instance"); + } + if (!(rightNode instanceof Node)) { + throw new TypeError("rightNode should be a valid Node instance"); + } + + if (leftNode === rightNode) return [leftNode, rightNode]; + + // Replace left node with right node + let tmpRight = new Node(rightNode.value); + if (leftNode.prev != null) { + leftNode.prev.next = tmpRight; + } + if (leftNode.next != null) { + leftNode.next.prev = tmpRight; + } + tmpRight.prev = leftNode.prev; + tmpRight.next = leftNode.next; + if (leftNode === this.#head) this.#head = tmpRight; + if (leftNode === this.#tail) this.#tail = tmpRight; + + // Replace right node with left node + let tmpLeft = new Node(leftNode.value); + if (rightNode.prev != null) { + rightNode.prev.next = tmpLeft; + } + if (rightNode.next != null) { + rightNode.next.prev = tmpLeft; + } + tmpLeft.prev = rightNode.prev; + tmpLeft.next = rightNode.next; + if (rightNode === this.#head) this.#head = tmpLeft; + if (rightNode === this.#tail) this.#tail = tmpLeft; + + leftNode = null; + rightNode = null; + return [tmpLeft, tmpRight]; + } + + deleteHead() { + if (this.#head === null) return; + if (this.#head.next != null) { + this.#head.next.prev = null; + } + delete this.#head.value; + this.#head = this.#head.next; + this.#length--; + } + + deleteTail() { + if (this.#tail === null) return; + if (this.#tail.prev != null) { + this.#tail.prev.next = null; + } + delete this.#tail.value; + this.#tail = this.#tail.prev; + this.#length--; + } + + delete(node) { + this.detach(node); + node = null; + } + + detach(node) { + if (!(node instanceof Node)) { + throw new TypeError("node should be a valid Node instance"); + } + + if (node.prev != null) { + node.prev.next = node.next; + } + if (node.next != null) { + node.next.prev = node.prev; + } + if (this.#head === node) { + this.#head = node.next; + } + if (this.#tail === node) { + this.#tail = node.prev; + } + node.prev = null; + node.next = null; + this.#length--; + } + + search(value) { + let nodes = []; + this.forEach(function (node) { + if (node.value === value) { + nodes.push(node); + } + }); + return nodes; + } + + find(value) { + for (let node of this) { + if (node.value === value) { + return node; + } + } + return null; + } + + forEach(callback) { + if (callback && typeof callback != "function") { + throw new TypeError("callback should be a function"); + } + + let node = this.#head; + let index = 0; + while (node) { + callback(node, index); + node = node.next; + index++; + } + } + + toArray() { + let values = []; + this.forEach(function (node) { + values.push(node.value); + }); + return values; + } + + *[Symbol.iterator]() { + let node = this.#head; + while (node) { + yield node; + node = node.next; + } + } +}; diff --git a/src/linkedlist/index.mjs b/src/linkedlist/index.mjs new file mode 100644 index 0000000..5e4991d --- /dev/null +++ b/src/linkedlist/index.mjs @@ -0,0 +1,243 @@ +import Node from "./node.mjs"; + +export default class LinkedList { + #head = null; + #tail = null; + #length = 0; + + get head() { + return this.#head; + } + + get tail() { + return this.#tail; + } + + get length() { + return this.#length; + } + + clear() { + this.#head = null; + this.#tail = null; + this.#length = 0; + } + + insertHead(value) { + const node = new Node(value); + if (this.#head === null) { + this.#head = this.#tail = node; + } else { + this.#head.prev = node; + node.next = this.#head; + this.#head = node; + } + this.#length++; + return node; + } + + insertTail(value) { + if (this.#tail === null) { + return this.insertHead(value); + } + + const node = new Node(value); + this.#tail.next = node; + node.prev = this.#tail; + this.#tail = node; + this.#length++; + return node; + } + + insertAfter(node, value) { + if (!(node instanceof Node)) { + throw new TypeError("node should be a valid Node instance"); + } + const newNode = new Node(value); + if (node.next != null) { + node.next.prev = newNode; + newNode.next = node.next; + } + newNode.prev = node; + node.next = newNode; + this.#length++; + return node; + } + + setHead(node) { + if (!(node instanceof Node)) { + throw new TypeError("node should be a valid Node instance"); + } + + if (this.#head === node) return this.#head; + + if (this.#head === null) { + return this.insertHead(node.value); + } + + this.detach(node); + node.prev = null; + node.next = this.#head; + this.#head.prev = node; + this.#head = node; + this.#length++; + return this.#head; + } + + setTail(node) { + if (!(node instanceof Node)) { + throw new TypeError("node should be a valid Node instance"); + } + + if (this.#tail === node) return this.#tail; + + if (this.#tail === null) { + return this.insertTail(node.value); + } + + this.detach(node); + this.#tail.next = node; + node.prev = this.#tail; + node.next = null; + this.#tail = node; + this.#length++; + return this.#tail; + } + + swap(leftNode, rightNode) { + if (!(leftNode instanceof Node)) { + throw new TypeError("leftNode should be a valid Node instance"); + } + if (!(rightNode instanceof Node)) { + throw new TypeError("rightNode should be a valid Node instance"); + } + + if (leftNode === rightNode) return [leftNode, rightNode]; + + // Replace left node with right node + let tmpRight = new Node(rightNode.value); + if (leftNode.prev != null) { + leftNode.prev.next = tmpRight; + } + if (leftNode.next != null) { + leftNode.next.prev = tmpRight; + } + tmpRight.prev = leftNode.prev; + tmpRight.next = leftNode.next; + if (leftNode === this.#head) this.#head = tmpRight; + if (leftNode === this.#tail) this.#tail = tmpRight; + + // Replace right node with left node + let tmpLeft = new Node(leftNode.value); + if (rightNode.prev != null) { + rightNode.prev.next = tmpLeft; + } + if (rightNode.next != null) { + rightNode.next.prev = tmpLeft; + } + tmpLeft.prev = rightNode.prev; + tmpLeft.next = rightNode.next; + if (rightNode === this.#head) this.#head = tmpLeft; + if (rightNode === this.#tail) this.#tail = tmpLeft; + + leftNode = null; + rightNode = null; + return [tmpLeft, tmpRight]; + } + + deleteHead() { + if (this.#head === null) return; + if (this.#head.next != null) { + this.#head.next.prev = null; + } + delete this.#head.value; + this.#head = this.#head.next; + this.#length--; + } + + deleteTail() { + if (this.#tail === null) return; + if (this.#tail.prev != null) { + this.#tail.prev.next = null; + } + delete this.#tail.value; + this.#tail = this.#tail.prev; + this.#length--; + } + + delete(node) { + this.detach(node); + node = null; + } + + detach(node) { + if (!(node instanceof Node)) { + throw new TypeError("node should be a valid Node instance"); + } + + if (node.prev != null) { + node.prev.next = node.next; + } + if (node.next != null) { + node.next.prev = node.prev; + } + if (this.#head === node) { + this.#head = node.next; + } + if (this.#tail === node) { + this.#tail = node.prev; + } + node.prev = null; + node.next = null; + this.#length--; + } + + search(value) { + let nodes = []; + this.forEach(function (node) { + if (node.value === value) { + nodes.push(node); + } + }); + return nodes; + } + + find(value) { + for (let node of this) { + if (node.value === value) { + return node; + } + } + return null; + } + + forEach(callback) { + if (callback && typeof callback != "function") { + throw new TypeError("callback should be a function"); + } + + let node = this.#head; + let index = 0; + while (node) { + callback(node, index); + node = node.next; + index++; + } + } + + toArray() { + let values = []; + this.forEach(function (node) { + values.push(node.value); + }); + return values; + } + + *[Symbol.iterator]() { + let node = this.#head; + while (node) { + yield node; + node = node.next; + } + } +} diff --git a/src/linkedlist/node.cjs b/src/linkedlist/node.cjs new file mode 100644 index 0000000..7524f88 --- /dev/null +++ b/src/linkedlist/node.cjs @@ -0,0 +1,7 @@ +module.exports = class Node { + constructor(value) { + this.prev = null; + this.next = null; + this.value = value; + } +}; diff --git a/src/linkedlist/node.mjs b/src/linkedlist/node.mjs new file mode 100644 index 0000000..8444ae3 --- /dev/null +++ b/src/linkedlist/node.mjs @@ -0,0 +1,7 @@ +export default class Node { + constructor(value) { + this.prev = null; + this.next = null; + this.value = value; + } +} diff --git a/tests/cache.test.js b/tests/cache.test.js index c98875c..f8431ed 100644 --- a/tests/cache.test.js +++ b/tests/cache.test.js @@ -1,6 +1,6 @@ const Cache = require("../index.cjs"); -describe("LRU Cache test", () => { +describe("Cache test", () => { let cache = null; beforeEach(() => { cache = new Cache({ maxLength: 4, ttl: 100 }); @@ -72,10 +72,10 @@ describe("LRU Cache test", () => { cache.set("d", 40); cache.set("e", 50); expect(cache.toArray()).toEqual([ - { b: 20 }, - { c: 30 }, - { d: 40 }, { e: 50 }, + { d: 40 }, + { c: 30 }, + { b: 20 }, ]); }); @@ -87,10 +87,10 @@ describe("LRU Cache test", () => { cache.get("a"); cache.set("e", 50); expect(cache.toArray()).toEqual([ - { c: 30 }, - { d: 40 }, - { a: 10 }, { e: 50 }, + { a: 10 }, + { d: 40 }, + { c: 30 }, ]); }); @@ -102,10 +102,10 @@ describe("LRU Cache test", () => { cache.get("d"); cache.set("e", 50); expect(cache.toArray()).toEqual([ - { b: 20 }, - { c: 30 }, - { d: 40 }, { e: 50 }, + { d: 40 }, + { c: 30 }, + { b: 20 }, ]); }); @@ -124,7 +124,7 @@ describe("LRU Cache test", () => { test("Get all data as array", async () => { cache.set("a", 10); cache.set("b", 20, { ttl: 0 }); - expect(cache.toArray()).toEqual([{ a: 10 }, { b: 20 }]); + expect(cache.toArray()).toEqual([{ b: 20 }, { a: 10 }]); await new Promise((resolve, reject) => { setTimeout(() => resolve(), 200); }); diff --git a/tests/linkedlist.test.js b/tests/linkedlist.test.js new file mode 100644 index 0000000..2a69a84 --- /dev/null +++ b/tests/linkedlist.test.js @@ -0,0 +1,135 @@ +const LinkedList = require("../src/linkedlist/index.cjs"); + +describe("Linkedlist test", () => { + let linkedlist = null; + beforeEach(() => { + linkedlist = new LinkedList(); + }); + + test("Set data at head in linked list", () => { + linkedlist.insertHead(10); + linkedlist.insertHead(15); + linkedlist.insertHead(20); + expect(linkedlist.toArray()).toEqual([20, 15, 10]); + }); + + test("Set data at the end of linked list", () => { + linkedlist.insertTail(10); + linkedlist.insertTail(15); + linkedlist.insertTail(20); + expect(linkedlist.toArray()).toEqual([10, 15, 20]); + }); + + test("Set data after a node", () => { + linkedlist.insertHead(10); + linkedlist.insertTail(20); + let node = linkedlist.find(10); + linkedlist.insertAfter(node, 15); + expect(linkedlist.toArray()).toEqual([10, 15, 20]); + }); + + test("Set head", () => { + linkedlist.insertHead(10); + linkedlist.insertHead(15); + linkedlist.insertHead(20); + let node = linkedlist.find(15); + linkedlist.setHead(node); + expect(linkedlist.toArray()).toEqual([15, 20, 10]); + }); + + test("Set tail", () => { + linkedlist.insertHead(10); + linkedlist.insertHead(15); + linkedlist.insertHead(20); + let node = linkedlist.find(15); + linkedlist.setTail(node); + expect(linkedlist.toArray()).toEqual([20, 10, 15]); + }); + + test("Swap nodes", () => { + linkedlist.insertTail(10); + linkedlist.insertTail(15); + linkedlist.insertTail(20); + linkedlist.insertTail(25); + linkedlist.insertTail(30); + let left = linkedlist.find(10); + let right = linkedlist.find(25); + linkedlist.swap(left, right); + expect(linkedlist.toArray()).toEqual([25, 15, 20, 10, 30]); + }); + + test("Swap neighbor nodes", () => { + linkedlist.insertTail(10); + linkedlist.insertTail(15); + let left = linkedlist.find(10); + let right = linkedlist.find(15); + linkedlist.swap(left, right); + expect(linkedlist.toArray()).toEqual([15, 10]); + }); + + test("Swap single node", () => { + linkedlist.insertTail(10); + let left = linkedlist.find(10); + linkedlist.swap(left, left); + expect(linkedlist.toArray()).toEqual([10]); + }); + + test("Delete head", () => { + linkedlist.insertHead(10); + linkedlist.insertHead(15); + linkedlist.insertHead(20); + linkedlist.deleteHead(); + expect(linkedlist.toArray()).toEqual([15, 10]); + }); + + test("Delete tail", () => { + linkedlist.insertHead(10); + linkedlist.insertHead(15); + linkedlist.insertHead(20); + linkedlist.deleteTail(); + expect(linkedlist.toArray()).toEqual([20, 15]); + }); + + test("Delete node", () => { + linkedlist.insertHead(10); + linkedlist.insertHead(15); + linkedlist.insertHead(20); + let node = linkedlist.find(15); + linkedlist.delete(node); + expect(linkedlist.toArray()).toEqual([20, 10]); + }); + + test("Detach node", () => { + linkedlist.insertHead(10); + linkedlist.insertHead(15); + linkedlist.insertHead(20); + let node = linkedlist.find(15); + linkedlist.detach(node); + expect(linkedlist.toArray()).toEqual([20, 10]); + }); + + test("Search nodes by value", () => { + linkedlist.insertHead(10); + linkedlist.insertHead(15); + linkedlist.insertHead(10); + linkedlist.insertHead(20); + let nodes = linkedlist.search(10); + expect(nodes.map((e) => e.value)).toEqual([10, 10]); + }); + + test("Find a first node by value", () => { + linkedlist.insertHead(10); + linkedlist.insertHead(15); + linkedlist.insertHead(10); + linkedlist.insertHead(20); + let node = linkedlist.find(10); + expect(node.value).toEqual(10); + }); + + test("Get all values as array", () => { + linkedlist.insertHead(10); + linkedlist.insertHead(15); + linkedlist.insertHead(20); + expect(linkedlist.toArray()).toEqual([20, 15, 10]); + }); +});