diff --git a/src/dataStructures/doubleLinkedList.js b/src/dataStructures/doubleLinkedList.js new file mode 100644 index 0000000..2d291b0 --- /dev/null +++ b/src/dataStructures/doubleLinkedList.js @@ -0,0 +1,136 @@ +class Node { + constructor(value) { + this.value = value; + this.next = null; + this.prev = null; + } +} + +class DoubleLinkedList { + + constructor() { + this.head = null + this.tail = null; + this.length = 0; + this.current = null; + } + + push(value) { + const node = new Node(value); + + if (!this.head) { + this.head = node; + this.tail = node; + this.length = 1; + this.current = this.head; + } else { + node.prev = this.tail; + this.tail.next = node; + this.tail = node; + this.length++; + } + } + + pop() { + if (!this.head) return null; + const previousNode = this.tail.prev; + const value = this.tail.value; + if (previousNode) { + if (this.current === this.tail) { + this.current = previousNode; + } + previousNode.next = null; + this.tail = previousNode; + this.length--; + } else { + this.head = null; + this.tail = null; + this.length = 0; + this.current = null; + } + return { value, start: this.current === this.head, end: this.current === this.tail} ; + } + + + insertAtBegining(value) { + const node = new Node(value); + + if (!this.head) { + this.head = node; + this.tail = node; + this.length = 1; + } else { + this.head.prev = node; + node.next = this.head; + this.head = node; + this.length++; + } + } + + removeFirst() { + if (!this.head) return null; + const node = this.head.next; + if (node) { + node.prev = null; + this.head = node; + this.current = this.head; + + this.length--; + } else { + this.head = null; + this.tail = null; + this.length = 0; + this.current = null; + } + } + + contains(value) { + if (!this.head) return false; + let position = this.head; + while (position) { + if (position.value === value) return true; + position = position.next; + } + return false; + } + + next() { + if (this.length === 0) return null; + if (!this.current.next) return {value: null, end: true, start: this.current.prev === null}; + const { value } = this.current; + this.current = this.current.next; + + return {value, end: this.current.next === null, start: this.current.prev === null} + } + + prev() { + if (this.length === 0) return null; + if (!this.current.prev) return {value: null, end:this.current.next === null, start: true }; + const {value} = this.current; + this.current = this.current.prev; + return {value, end: this.current.next === null, start:this.current === null}; + } + + moveToStart() { + this.current = this.head; + return this.currentValue(); + } + + moveToEnd() { + this.current = this.tail; + return this.currentValue(); + } + + /** + * Return null if the current item is null - should only happen in empty list + * Otherwise returns an object with the value, and boolean for start and end of list + */ + currentValue() { + if (!this.current) return null; + let {value, next } = this.current; + return {value, end: this.current === this.tail, start: this.current === this.head} + } +} + + +module.exports = DoubleLinkedList; \ No newline at end of file diff --git a/test/dataStructures/doubleLinkedList.test.js b/test/dataStructures/doubleLinkedList.test.js new file mode 100644 index 0000000..3876e28 --- /dev/null +++ b/test/dataStructures/doubleLinkedList.test.js @@ -0,0 +1,213 @@ +const DoubleLinkedList = require('../../src/dataStructures/doubleLinkedList'); + +describe("Double Linked List - adding and removing", () => { + const dll = new DoubleLinkedList(); + + it("Should return null", () => { + let result = dll.pop(); + expect(result).toBe(null); + expect(dll.contains("Yo!")).toBe(false); + }) + + it("Should start empty", () => { + expect(dll.head).toBe(null); + expect(dll.tail).toBe(null); + expect(dll.length).toBe(0); + expect(dll.contains("Yo!")).toBe(false); + }); + + it("Should add 1 item", () =>{ + dll.push("Hello"); + expect(dll.head).toBe(dll.tail); + expect(dll.tail).toBe(dll.head); + expect(dll.length).toBe(1); + expect(dll.contains("Hello")).toBe(true); + expect(dll.contains("World")).toBe(false); + }); + it("Should add 3 items", () =>{ + dll.push("World"); + dll.push("!"); + expect(dll.head.value).toBe("Hello"); + expect(dll.tail.value).toBe("!"); + expect(dll.length).toBe(3); + expect(dll.contains("Hello")).toBe(true); + expect(dll.contains("World")).toBe(true); + expect(dll.contains("1")).toBe(false); + expect(dll.contains("2")).toBe(false); + expect(dll.contains("random string sdfsdfsdf")).toBe(false); + }); + + it("Should remove and return the last item", () =>{ + const result = dll.pop(); + expect(result.value).toBe("!"); + expect(dll.tail.value).toBe("World"); + expect(dll.length).toBe(2); + }); + + it("Should remove the last 2 items down to empty", () =>{ + let result = dll.pop(); + expect(result.value).toBe("World"); + expect(dll.tail.value).toBe("Hello"); + expect(dll.length).toBe(1); + result = dll.pop(); + expect(result.value).toBe("Hello"); + expect(dll.tail).toBe(null); + expect(dll.head).toBe(null); + expect(dll.length).toBe(0); + }); + + it("Should insert 2 before 1", () => { + dll.insertAtBegining("1"); + expect(dll.tail.value).toBe("1"); + expect(dll.head.value).toBe("1"); + expect(dll.length).toBe(1); + dll.insertAtBegining("2"); + expect(dll.tail.value).toBe("1"); + expect(dll.head.value).toBe("2"); + expect(dll.length).toBe(2); + + expect(dll.contains("Hello")).toBe(false); + expect(dll.contains("World")).toBe(false); + expect(dll.contains("1")).toBe(true); + expect(dll.contains("2")).toBe(true); + expect(dll.contains("random string sdfsdfsdf")).toBe(false); + + }); + + it("Should remove 2 then 1", () => { + dll.removeFirst(); + expect(dll.tail.value).toBe("1"); + expect(dll.head.value).toBe("1"); + expect(dll.length).toBe(1); + + dll.removeFirst(); + expect(dll.tail).toBe(null); + expect(dll.head).toBe(null); + expect(dll.length).toBe(0); + + + dll.removeFirst(); + expect(dll.tail).toBe(null); + expect(dll.head).toBe(null); + expect(dll.length).toBe(0); + }); +}); + +describe("DoubleLinkedList - moving around", () => { + const dll = new DoubleLinkedList(); + + expect(dll.currentValue()).toBe(null); + expect(dll.next()).toBe(null); + expect(dll.prev()).toBe(null); + dll.push("1"); + expect(dll.prev().value).toBe(null); + expect(dll.next().value).toBe(null); + dll.push("2"); + dll.push("3"); + dll.push("4"); + dll.push("5"); + + it("Should be at the start", () => { + let {value, start, end} = dll.currentValue(); + expect(value).toBe("1"); + expect(start).toBe(true); + expect(end).toBe(false); + }); + + it("Should move to the next",()=>{ + dll.next(); + let {value, start, end} = dll.currentValue(); + expect(value).toBe("2"); + expect(start).toBe(false); + expect(end).toBe(false); + }); + + it("and the next",()=>{ + dll.next(); + let {value, start, end} = dll.currentValue(); + expect(value).toBe("3"); + expect(start).toBe(false); + expect(end).toBe(false); + }); + + + it("penultimate",()=>{ + dll.next(); + let {value, start, end} = dll.currentValue(); + expect(value).toBe("4"); + expect(start).toBe(false); + expect(end).toBe(false); + }); + + it("Should be at the end",()=>{ + dll.next(); + let {value, start, end} = dll.currentValue(); + expect(value).toBe("5"); + expect(start).toBe(false); + expect(end).toBe(true); + }); + + it("Should move back 2",()=>{ + dll.prev(); + dll.prev(); + let {value, start, end} = dll.currentValue(); + expect(value).toBe("3"); + expect(start).toBe(false); + expect(end).toBe(false); + }); + + it("and 2 more",()=>{ + dll.prev(); + dll.prev(); + let {value, start, end} = dll.currentValue(); + expect(value).toBe("1"); + expect(start).toBe(true); + expect(end).toBe(false); + }); + + it("and 2 more (already at the start)",()=>{ + dll.prev(); + dll.prev(); + let {value, start, end} = dll.currentValue(); + expect(value).toBe("1"); + expect(start).toBe(true); + expect(end).toBe(false); + }); + + it("Should move to the end", () => { + dll.moveToEnd(); + let {value, start, end} = dll.currentValue(); + expect(value).toBe("5"); + expect(start).toBe(false); + expect(end).toBe(true); + }); + + it("Should move to the start", () => { + dll.moveToStart(); + let {value, start, end} = dll.currentValue(); + expect(value).toBe("1"); + expect(start).toBe(true); + expect(end).toBe(false); + }); + + it("Should remove the 1st item and move to the 2nd", () => { + expect(dll.length).toBe(5); + dll.removeFirst(); + let {value, start, end} = dll.currentValue(); + + expect(dll.length).toBe(4); + expect(value).toBe("2"); + expect(start).toBe(true); + expect(end).toBe(false); + }); + + it("Should remove the last itema and move the current back one", () => { + expect(dll.length).toBe(4); + dll.moveToEnd(); + let {value, start, end} = dll.pop(); + expect(value).toBe("5"); + expect(start).toBe(false); + expect(end).toBe(true); + expect(dll.currentValue().value).toBe("4"); + }); +}) \ No newline at end of file