diff --git a/data_structures/linked_list/doubly_linked_list.dart b/data_structures/linked_list/doubly_linked_list.dart new file mode 100644 index 00000000..d73ee70b --- /dev/null +++ b/data_structures/linked_list/doubly_linked_list.dart @@ -0,0 +1,356 @@ +/** + * Doubly Linked List + */ + +/** + * Node class + */ +class Node { + T data; + Node next; + Node prev; + + Node(this.data, [this.next, this.prev]); +} + +/** + * Doubly Linked List class + */ +class DoublyLinkedList extends Iterator { + Node _head; + Node _tail; + int _length = 0; + Node _current; + + DoublyLinkedList() { + this._head = null; + this._tail = null; + this._length = 0; + } + + /** + * Push + */ + void push(T data) { + Node newNode = new Node(data); + + if (this._length == 0) { + this._head = newNode; + this._tail = newNode; + } else { + this._tail.next = newNode; + newNode.prev = this._tail; + this._tail = newNode; + } + + this._length++; + } + + /** + * Pop + */ + Node pop() { + if (this._length == 0) return null; + + Node poppedNode = this._tail; + + if (this._length == 1) { + this._head = null; + this._tail = null; + } else { + this._tail = poppedNode.prev; + this._tail.next = null; + poppedNode.prev = null; + } + + this._length--; + + return poppedNode; + } + + /** + * Shift + */ + Node shift() { + if (this._length == 0) return null; + + Node shiftedNode = this._head; + + if (this._length == 1) { + this._head = null; + this._tail = null; + } else { + this._head = shiftedNode.next; + this._head.prev = null; + shiftedNode.next = null; + } + + this._length--; + + return shiftedNode; + } + + /** + * Unshift + */ + void unshift(T data) { + Node newNode = new Node(data); + + if (this._length == 0) { + this._head = newNode; + this._tail = newNode; + } else { + this._head.prev = newNode; + newNode.next = this._head; + this._head = newNode; + } + + this._length++; + } + + /** + * Get + */ + Node get(int index) { + if (index < 0 || index >= this._length) return null; + + Node current; + int count; + + if (index <= this._length / 2) { + count = 0; + current = this._head; + + while (count != index) { + current = current.next; + count++; + } + } else { + count = this._length - 1; + current = this._tail; + + while (count != index) { + current = current.prev; + count--; + } + } + + return current; + } + + /** + * Set + */ + bool set(int index, T data) { + Node foundNode = this.get(index); + + if (foundNode != null) { + foundNode.data = data; + return true; + } + + return false; + } + + /** + * Insert + */ + bool insert(int index, T data) { + if (index < 0 || index > this._length) return false; + + if (index == 0) { + this.unshift(data); + return true; + } + + if (index == this._length) { + this.push(data); + return true; + } + + Node newNode = new Node(data); + Node beforeNode = this.get(index - 1); + Node afterNode = beforeNode.next; + + beforeNode.next = newNode; + newNode.prev = beforeNode; + newNode.next = afterNode; + afterNode.prev = newNode; + + this._length++; + + return true; + } + + /** + * Remove + */ + Node remove(int index) { + if (index < 0 || index >= this._length) return null; + + if (index == 0) return this.shift(); + + if (index == this._length - 1) return this.pop(); + + Node removedNode = this.get(index); + Node beforeNode = removedNode.prev; + Node afterNode = removedNode.next; + + beforeNode.next = afterNode; + afterNode.prev = beforeNode; + removedNode.next = null; + removedNode.prev = null; + + this._length--; + + return removedNode; + } + + /** + * Reverse + */ + void reverse() { + Node temp = this._head; + this._head = this._tail; + this._tail = temp; + + Node next; + Node prev = null; + + for (int i = 0; i < this._length; i++) { + next = temp.next; + temp.next = prev; + temp.prev = next; + prev = temp; + temp = next; + } + } + + /** + * Iterator + */ + bool moveNext() { + if (this._current == null) { + this._current = this._head; + } else { + this._current = this._current.next; + } + + return this._current != null; + } + + T get current => this._current?.data; + + /** + * Iterable + */ + Iterator get iterator => this; + + /** + * Length + */ + int get length => this._length; + + /** + * Head + */ + Node get head => this._head; + + /** + * Tail + */ + Node get tail => this._tail; + + /** + * Print + */ + void printList() { + List list = []; + + Node currentNode = this._head; + + while (currentNode != null) { + list.add(currentNode.data); + currentNode = currentNode.next; + } + + print(list); + } + + /** + * Print reverse + */ + void printListReverse() { + List list = []; + + Node currentNode = this._tail; + + while (currentNode != null) { + list.add(currentNode.data); + currentNode = currentNode.prev; + } + + print(list); + } + + /** + * Clear + */ + void clear() { + this._head = null; + this._tail = null; + this._length = 0; + } + + /** + * Search + */ + int search(T data) { + Node currentNode = this._head; + int index = 0; + + while (currentNode != null) { + if (currentNode.data == data) return index; + + currentNode = currentNode.next; + index++; + } + + return -1; + } + + /** + * Contains + */ + bool contains(T data) { + return this.search(data) != -1; + } + + /** + * Is empty + */ + bool get isEmpty => this._length == 0; + + /** + * To List + */ + List toList() { + List list = []; + + Node currentNode = this._head; + + while (currentNode != null) { + list.add(currentNode.data); + currentNode = currentNode.next; + } + + return list; + } + + /** + * To String + */ + String toString() { + return this.toList().toString(); + } +} diff --git a/tests/data_structures/doubly_linked_list_test.dart b/tests/data_structures/doubly_linked_list_test.dart new file mode 100644 index 00000000..eb57077a --- /dev/null +++ b/tests/data_structures/doubly_linked_list_test.dart @@ -0,0 +1,715 @@ +import 'package:test/test.dart'; +import '../../data_structures/linked_list/doubly_linked_list.dart'; + +class CustomClass { + int value; + + CustomClass(this.value); + + @override + bool operator ==(Object other) => + identical(this, other) || + other is CustomClass && + runtimeType == other.runtimeType && + value == other.value; + + String toString() { + return this.value.toString(); + } +} + +void main() { + group("Doubly Linked List Tests", () { + test("Push", () { + DoublyLinkedList list = new DoublyLinkedList(); + + expect(list.length, 0); + expect(list.head, null); + expect(list.tail, null); + + list.push(1); + + expect(list.length, 1); + expect(list.head.data, 1); + expect(list.tail.data, 1); + + list.push(2); + + expect(list.length, 2); + expect(list.head.data, 1); + expect(list.tail.data, 2); + + list.push(3); + + expect(list.length, 3); + expect(list.head.data, 1); + expect(list.tail.data, 3); + }); + + test("Pop", () { + DoublyLinkedList list = new DoublyLinkedList(); + + expect(list.length, 0); + expect(list.head, null); + expect(list.tail, null); + + list.push(1); + list.push(2); + list.push(3); + + expect(list.length, 3); + expect(list.head.data, 1); + expect(list.tail.data, 3); + + Node poppedNode = list.pop(); + + expect(list.length, 2); + expect(list.head.data, 1); + expect(list.tail.data, 2); + expect(poppedNode.data, 3); + + poppedNode = list.pop(); + + expect(list.length, 1); + expect(list.head.data, 1); + expect(list.tail.data, 1); + expect(poppedNode.data, 2); + + poppedNode = list.pop(); + + expect(list.length, 0); + expect(list.head, null); + expect(list.tail, null); + expect(poppedNode.data, 1); + + poppedNode = list.pop(); + + expect(list.length, 0); + expect(list.head, null); + expect(list.tail, null); + expect(poppedNode, null); + }); + + test("Shift", () { + DoublyLinkedList list = new DoublyLinkedList(); + + expect(list.length, 0); + expect(list.head, null); + expect(list.tail, null); + + list.push(1); + list.push(2); + list.push(3); + + expect(list.length, 3); + expect(list.head.data, 1); + expect(list.tail.data, 3); + + Node shiftedNode = list.shift(); + + expect(list.length, 2); + expect(list.head.data, 2); + expect(list.tail.data, 3); + expect(shiftedNode.data, 1); + + shiftedNode = list.shift(); + + expect(list.length, 1); + expect(list.head.data, 3); + expect(list.tail.data, 3); + expect(shiftedNode.data, 2); + + shiftedNode = list.shift(); + + expect(list.length, 0); + expect(list.head, null); + expect(list.tail, null); + expect(shiftedNode.data, 3); + + shiftedNode = list.shift(); + + expect(list.length, 0); + expect(list.head, null); + expect(list.tail, null); + expect(shiftedNode, null); + }); + + test("Unshift", () { + DoublyLinkedList list = new DoublyLinkedList(); + + expect(list.length, 0); + expect(list.head, null); + expect(list.tail, null); + + list.unshift(1); + + expect(list.length, 1); + expect(list.head.data, 1); + expect(list.tail.data, 1); + + list.unshift(2); + + expect(list.length, 2); + expect(list.head.data, 2); + expect(list.tail.data, 1); + + list.unshift(3); + + expect(list.length, 3); + expect(list.head.data, 3); + expect(list.tail.data, 1); + }); + + test("Get", () { + DoublyLinkedList list = new DoublyLinkedList(); + + expect(list.length, 0); + expect(list.head, null); + expect(list.tail, null); + + list.push(1); + list.push(2); + list.push(3); + + expect(list.length, 3); + expect(list.head.data, 1); + expect(list.tail.data, 3); + + Node node = list.get(0); + + expect(node.data, 1); + + node = list.get(1); + + expect(node.data, 2); + + node = list.get(2); + + expect(node.data, 3); + + node = list.get(3); + + expect(node, null); + + node = list.get(-1); + + expect(node, null); + }); + + test("Set", () { + bool result; + DoublyLinkedList list = new DoublyLinkedList(); + + expect(list.length, 0); + expect(list.head, null); + expect(list.tail, null); + + list.push(1); + list.push(2); + list.push(3); + + expect(list.length, 3); + expect(list.head.data, 1); + expect(list.tail.data, 3); + + result = list.set(0, 4); + expect(result, true); + expect(list.head.data, 4); + + result = list.set(1, 5); + expect(result, true); + expect(list.head.next.data, 5); + + result = list.set(2, 6); + expect(result, true); + expect(list.tail.data, 6); + }); + + test("Insert", () { + bool result; + DoublyLinkedList list = new DoublyLinkedList(); + + expect(list.length, 0); + expect(list.head, null); + expect(list.tail, null); + + result = list.insert(0, 1); + expect(result, true); + expect(list.length, 1); + expect(list.head.data, 1); + expect(list.tail.data, 1); + + result = list.insert(1, 2); + expect(result, true); + expect(list.length, 2); + expect(list.head.data, 1); + expect(list.tail.data, 2); + + result = list.insert(1, 3); + expect(result, true); + expect(list.length, 3); + expect(list.head.data, 1); + expect(list.head.next.data, 3); + + result = list.insert(3, 4); + expect(result, true); + expect(list.length, 4); + expect(list.head.data, 1); + expect(list.tail.data, 4); + + result = list.insert(5, 5); + expect(result, false); + expect(list.length, 4); + expect(list.head.data, 1); + expect(list.tail.data, 4); + + result = list.insert(-1, 6); + expect(result, false); + expect(list.length, 4); + expect(list.head.data, 1); + expect(list.tail.data, 4); + + result = list.insert(0, 7); + expect(result, true); + expect(list.length, 5); + expect(list.head.data, 7); + expect(list.tail.data, 4); + }); + + test("Remove", () { + Node removedNode; + DoublyLinkedList list = new DoublyLinkedList(); + + expect(list.length, 0); + expect(list.head, null); + expect(list.tail, null); + + removedNode = list.remove(0); + expect(removedNode, null); + expect(list.length, 0); + expect(list.head, null); + expect(list.tail, null); + + list.push(1); + list.push(2); + list.push(3); + list.push(4); + removedNode = list.remove(0); + expect(removedNode.data, 1); + expect(list.length, 3); + expect(list.head.data, 2); + expect(list.tail.data, 4); + + list.push(1); + removedNode = list.remove(2); + expect(removedNode.data, 4); + expect(list.length, 3); + expect(list.head.data, 2); + expect(list.tail.data, 1); + + removedNode = list.remove(1); + expect(removedNode.data, 3); + expect(list.length, 2); + expect(list.head.data, 2); + expect(list.tail.data, 1); + + removedNode = list.remove(1); + expect(removedNode.data, 1); + expect(list.length, 1); + expect(list.head.data, 2); + expect(list.tail.data, 2); + + removedNode = list.remove(0); + expect(removedNode.data, 2); + expect(list.length, 0); + expect(list.head, null); + expect(list.tail, null); + }); + + test("Reverse", () { + DoublyLinkedList list = new DoublyLinkedList(); + + expect(list.length, 0); + expect(list.head, null); + expect(list.tail, null); + + list.push(1); + list.push(2); + list.push(3); + list.push(4); + + expect(list.length, 4); + expect(list.head.data, 1); + expect(list.tail.data, 4); + + list.reverse(); + + expect(list.length, 4); + expect(list.head.data, 4); + expect(list.tail.data, 1); + }); + + test("Iterator", () { + DoublyLinkedList list = new DoublyLinkedList(); + + expect(list.length, 0); + expect(list.head, null); + expect(list.tail, null); + expect(list.current, null); + + list.push(1); + list.push(2); + list.push(3); + list.push(4); + + expect(list.length, 4); + expect(list.head.data, 1); + expect(list.tail.data, 4); + expect(list.current, null); + + list.moveNext(); + + expect(list.current, 1); + + list.moveNext(); + + expect(list.current, 2); + + list.moveNext(); + + expect(list.current, 3); + + list.moveNext(); + + expect(list.current, 4); + + list.moveNext(); + + expect(list.current, null); + }); + + test("Length", () { + DoublyLinkedList list = new DoublyLinkedList(); + + expect(list.length, 0); + + list.push(1); + list.push(2); + list.push(3); + list.push(4); + + expect(list.length, 4); + + list.pop(); + + expect(list.length, 3); + + list.shift(); + + expect(list.length, 2); + + list.unshift(1); + + expect(list.length, 3); + + list.insert(1, 2); + + expect(list.length, 4); + + list.remove(1); + + expect(list.length, 3); + + list.reverse(); + + expect(list.length, 3); + }); + + test("Head", () { + DoublyLinkedList list = new DoublyLinkedList(); + + expect(list.head, null); + + list.push(1); + list.push(2); + list.push(3); + list.push(4); + + expect(list.head.data, 1); + + list.pop(); + + expect(list.head.data, 1); + + list.shift(); + + expect(list.head.data, 2); + + list.unshift(1); + + expect(list.head.data, 1); + + list.insert(1, 2); + + expect(list.head.data, 1); + + list.remove(1); + + expect(list.head.data, 1); + + list.reverse(); + + expect(list.head.data, 3); + }); + + test("Tail", () { + DoublyLinkedList list = new DoublyLinkedList(); + + expect(list.tail, null); + + list.push(1); + list.push(2); + list.push(3); + list.push(4); + + expect(list.tail.data, 4); + + list.pop(); + + expect(list.tail.data, 3); + + list.shift(); + + expect(list.tail.data, 3); + + list.unshift(1); + + expect(list.tail.data, 3); + + list.insert(1, 2); + + expect(list.tail.data, 3); + + list.remove(1); + + expect(list.tail.data, 3); + + list.reverse(); + + expect(list.tail.data, 1); + }); + + test("Clear", () { + DoublyLinkedList list = new DoublyLinkedList(); + + expect(list.length, 0); + expect(list.head, null); + expect(list.tail, null); + + list.push(1); + list.push(2); + list.push(3); + list.push(4); + + expect(list.length, 4); + expect(list.head.data, 1); + expect(list.tail.data, 4); + + list.clear(); + + expect(list.length, 0); + expect(list.head, null); + expect(list.tail, null); + }); + + test("Search", () { + DoublyLinkedList list = new DoublyLinkedList(); + + expect(list.search(1), -1); + + list.push(1); + list.push(2); + list.push(3); + list.push(4); + + expect(list.search(1), 0); + expect(list.search(2), 1); + expect(list.search(3), 2); + expect(list.search(4), 3); + expect(list.search(5), -1); + }); + + test("Contains", () { + DoublyLinkedList list = new DoublyLinkedList(); + + expect(list.contains(1), false); + + list.push(1); + list.push(2); + list.push(3); + list.push(4); + + expect(list.contains(1), true); + expect(list.contains(2), true); + expect(list.contains(3), true); + expect(list.contains(4), true); + expect(list.contains(5), false); + }); + + test("IsEmpty", () { + DoublyLinkedList list = new DoublyLinkedList(); + + expect(list.isEmpty, true); + + list.push(1); + list.push(2); + list.push(3); + list.push(4); + + expect(list.isEmpty, false); + }); + + test("ToList", () { + DoublyLinkedList list = new DoublyLinkedList(); + + expect(list.toList(), []); + + list.push(1); + list.push(2); + list.push(3); + list.push(4); + + expect(list.toList(), [1, 2, 3, 4]); + }); + + test("ToString", () { + DoublyLinkedList list = new DoublyLinkedList(); + + expect(list.toString(), "[]"); + + list.push(1); + list.push(2); + list.push(3); + list.push(4); + + expect(list.toString(), "[1.0, 2.0, 3.0, 4.0]"); + }); + + test("Different Data Type - String", () { + DoublyLinkedList list = new DoublyLinkedList(); + + expect(list.length, 0); + expect(list.head, null); + expect(list.tail, null); + expect(list.current, null); + + list.push("1"); + list.push("2"); + list.push("3"); + list.push("4"); + + expect(list.length, 4); + expect(list.head.data, "1"); + expect(list.tail.data, "4"); + expect(list.current, null); + + list.moveNext(); + + expect(list.current, "1"); + + list.moveNext(); + + expect(list.current, "2"); + + list.moveNext(); + + expect(list.current, "3"); + + list.moveNext(); + + expect(list.current, "4"); + + list.moveNext(); + + expect(list.current, null); + }); + + test("Different Data Type - Object", () { + DoublyLinkedList list = new DoublyLinkedList(); + + expect(list.length, 0); + expect(list.head, null); + expect(list.tail, null); + expect(list.current, null); + + list.push(new Object()); + list.push(new Object()); + list.push(new Object()); + list.push(new Object()); + + expect(list.length, 4); + expect(list.head.data, isNotNull); + expect(list.tail.data, isNotNull); + expect(list.current, null); + + list.moveNext(); + + expect(list.current, isNotNull); + + list.moveNext(); + + expect(list.current, isNotNull); + + list.moveNext(); + + expect(list.current, isNotNull); + + list.moveNext(); + + expect(list.current, isNotNull); + + list.moveNext(); + + expect(list.current, null); + }); + + test("Different Data Type - Custom Class", () { + DoublyLinkedList list = new DoublyLinkedList(); + + expect(list.length, 0); + expect(list.head, null); + expect(list.tail, null); + expect(list.current, null); + + list.push(new CustomClass(1)); + list.push(new CustomClass(2)); + list.push(new CustomClass(3)); + list.push(new CustomClass(4)); + + expect(list.length, 4); + expect(list.head.data, new CustomClass(1)); + expect(list.tail.data, new CustomClass(4)); + expect(list.current, null); + + list.moveNext(); + + expect(list.current, new CustomClass(1)); + + list.moveNext(); + + expect(list.current, new CustomClass(2)); + + list.moveNext(); + + expect(list.current, new CustomClass(3)); + + list.moveNext(); + + expect(list.current, new CustomClass(4)); + + list.moveNext(); + + expect(list.current, null); + }); + }); +}