diff --git a/src/data-structures/hash-table/HashTable.js b/src/data-structures/hash-table/HashTable.js index f8fec1cd..d241dfb8 100644 --- a/src/data-structures/hash-table/HashTable.js +++ b/src/data-structures/hash-table/HashTable.js @@ -21,16 +21,32 @@ export default class HashTable { insert(key, value) { const bucketLinkedList = this.buckets[this.hash(key)]; - bucketLinkedList.appendUnique({ key, value }); + const node = bucketLinkedList.find({ callback: nodeValue => nodeValue.key === key }); + + if (!node) { + // Insert new node. + bucketLinkedList.append({ key, value }); + } else { + // Update value of existing node. + node.value.value = value; + } } delete(key) { const bucketLinkedList = this.buckets[this.hash(key)]; - return bucketLinkedList.deleteByKey(key); + const node = bucketLinkedList.find({ callback: nodeValue => nodeValue.key === key }); + + if (node) { + return bucketLinkedList.delete(node.value); + } + + return null; } get(key) { const bucketLinkedList = this.buckets[this.hash(key)]; - return bucketLinkedList.findByKey(key); + const node = bucketLinkedList.find({ callback: nodeValue => nodeValue.key === key }); + + return node ? node.value.value : null; } } diff --git a/src/data-structures/hash-table/__test__/HashTable.test.js b/src/data-structures/hash-table/__test__/HashTable.test.js index 71e18cb8..a39fcd24 100644 --- a/src/data-structures/hash-table/__test__/HashTable.test.js +++ b/src/data-structures/hash-table/__test__/HashTable.test.js @@ -31,19 +31,34 @@ describe('HashTable', () => { hashTable.insert('c', 'earth'); hashTable.insert('d', 'ocean'); - expect(hashTable.buckets[0].toString()).toBe('c:earth'); - expect(hashTable.buckets[1].toString()).toBe('a:sky,d:ocean'); - expect(hashTable.buckets[2].toString()).toBe('b:sea'); + const stringifier = value => `${value.key}:${value.value}`; - expect(hashTable.get('a').value).toBe('sky'); - expect(hashTable.get('d').value).toBe('ocean'); + expect(hashTable.buckets[0].toString(stringifier)).toBe('c:earth'); + expect(hashTable.buckets[1].toString(stringifier)).toBe('a:sky,d:ocean'); + expect(hashTable.buckets[2].toString(stringifier)).toBe('b:sea'); + + expect(hashTable.get('a')).toBe('sky'); + expect(hashTable.get('d')).toBe('ocean'); hashTable.delete('a'); + expect(hashTable.delete('not-existing')).toBeNull(); + expect(hashTable.get('a')).toBeNull(); - expect(hashTable.get('d').value).toBe('ocean'); + expect(hashTable.get('d')).toBe('ocean'); hashTable.insert('d', 'ocean-new'); - expect(hashTable.get('d').value).toBe('ocean-new'); + expect(hashTable.get('d')).toBe('ocean-new'); + }); + + it('should be possible to add objects to hash table', () => { + const hashTable = new HashTable(); + + hashTable.insert('objectKey', { prop1: 'a', prop2: 'b' }); + + const object = hashTable.get('objectKey'); + expect(object).toBeDefined(); + expect(object.prop1).toBe('a'); + expect(object.prop2).toBe('b'); }); }); diff --git a/src/data-structures/linked-list/LinkedList.js b/src/data-structures/linked-list/LinkedList.js index f8968aaa..d4ab790c 100644 --- a/src/data-structures/linked-list/LinkedList.js +++ b/src/data-structures/linked-list/LinkedList.js @@ -2,74 +2,39 @@ import LinkedListNode from './LinkedListNode'; export default class LinkedList { constructor() { + /** @var LinkedListNode */ this.head = null; + + /** @var LinkedListNode */ this.tail = null; } - prepend({ value, key = null }) { - const newNode = new LinkedListNode({ value, key, next: this.head }); - + prepend(value) { // Make new node to be a head. - this.head = newNode; + this.head = new LinkedListNode(value, this.head); - return newNode; + return this; } - append({ value, key = null }) { - const newNode = new LinkedListNode({ value, key }); + append(value) { + const newNode = new LinkedListNode(value); // If there is no head yet let's make new node a head. if (!this.head) { this.head = newNode; this.tail = newNode; - return newNode; + return this; } // Attach new node to the end of linked list. this.tail.next = newNode; this.tail = newNode; - return newNode; + return this; } - appendUnique({ value, key = null }) { - const newNode = new LinkedListNode({ value, key }); - - // If there is no head yet let's make new node a head. - if (!this.head) { - this.head = newNode; - this.tail = newNode; - - return newNode; - } - - // Rewind to last node. - let currentNode = this.head; - while (currentNode.next !== null) { - // If there is a node with specified key exists then update it instead of adding new one. - if (key && currentNode.key === key) { - currentNode.value = value; - return currentNode; - } - - currentNode = currentNode.next; - } - - // If there is a node with specified key exists then update it instead of adding new one. - if (key && currentNode.key === key) { - currentNode.value = value; - return currentNode; - } - - // Attach new node to the end of linked list. - currentNode.next = newNode; - this.tail = newNode; - - return newNode; - } - - deleteByValue(value) { + delete(value) { if (!this.head) { return null; } @@ -102,37 +67,28 @@ export default class LinkedList { return deletedNode; } - deleteByKey(key) { + find({ value = undefined, callback = undefined }) { if (!this.head) { return null; } - let deletedNode = null; - - // If the head must be deleted then make 2nd node to be a head. - if (this.head.key === key) { - deletedNode = this.head; - this.head = this.head.next; - } - let currentNode = this.head; - // If next node must be deleted then make next node to be a next next one. - while (currentNode.next) { - if (currentNode.next.key === key) { - deletedNode = currentNode.next; - currentNode.next = currentNode.next.next; - } else { - currentNode = currentNode.next; + while (currentNode) { + // If callback is specified then try to find node by callback. + if (callback && callback(currentNode.value)) { + return currentNode; } + + // If value is specified then try to compare by value.. + if (value !== undefined && currentNode.value === value) { + return currentNode; + } + + currentNode = currentNode.next; } - // Check if tail must be deleted. - if (this.tail.key === key) { - this.tail = currentNode; - } - - return deletedNode; + return null; } deleteTail() { @@ -177,32 +133,15 @@ export default class LinkedList { return deletedHead; } - findByKey(key) { - let currentNode = this.head; + toString(callback) { + const nodeStrings = []; + let currentNode = this.head; while (currentNode) { - if (currentNode.key === key) { - return currentNode; - } + nodeStrings.push(currentNode.toString(callback)); currentNode = currentNode.next; } - return null; - } - - toArray() { - const listArray = []; - let currentNode = this.head; - - while (currentNode) { - listArray.push(currentNode.toString()); - currentNode = currentNode.next; - } - - return listArray; - } - - toString() { - return this.toArray().toString(); + return nodeStrings.toString(); } } diff --git a/src/data-structures/linked-list/LinkedListNode.js b/src/data-structures/linked-list/LinkedListNode.js index 58d900db..d136e863 100644 --- a/src/data-structures/linked-list/LinkedListNode.js +++ b/src/data-structures/linked-list/LinkedListNode.js @@ -1,17 +1,10 @@ export default class LinkedListNode { - constructor({ value, next = null, key = null }) { + constructor(value, next = null) { this.value = value; this.next = next; - - // Key is added to make this linked list nodes to be reusable in hash tables. - this.key = key; } - toString() { - if (this.key) { - return `${this.key}:${this.value}`; - } - - return `${this.value}`; + toString(callback) { + return callback ? callback(this.value) : `${this.value}`; } } diff --git a/src/data-structures/linked-list/__test__/LinkedList.test.js b/src/data-structures/linked-list/__test__/LinkedList.test.js index 2806e68e..3f877f83 100644 --- a/src/data-structures/linked-list/__test__/LinkedList.test.js +++ b/src/data-structures/linked-list/__test__/LinkedList.test.js @@ -12,30 +12,17 @@ describe('LinkedList', () => { expect(linkedList.head).toBeNull(); expect(linkedList.tail).toBeNull(); - const node1 = linkedList.append({ value: 1 }); - const node2 = linkedList.append({ value: 2, key: 'test' }); + linkedList.append(1); + linkedList.append(2); - expect(node1.value).toBe(1); - expect(node2.value).toBe(2); - expect(node2.key).toBe('test'); - - expect(linkedList.head.toString()).toBe('1'); - expect(linkedList.tail.toString()).toBe('test:2'); - - expect(linkedList.toString()).toBe('1,test:2'); + expect(linkedList.toString()).toBe('1,2'); }); it('should prepend node to linked list', () => { const linkedList = new LinkedList(); - const node1 = linkedList.append({ value: 1 }); - const node2 = linkedList.prepend({ value: 2 }); - - expect(node1.value).toBe(1); - expect(node2.value).toBe(2); - - expect(linkedList.head.toString()).toBe('2'); - expect(linkedList.tail.toString()).toBe('1'); + linkedList.append(1); + linkedList.prepend(2); expect(linkedList.toString()).toBe('2,1'); }); @@ -43,74 +30,51 @@ describe('LinkedList', () => { it('should delete node by value from linked list', () => { const linkedList = new LinkedList(); - expect(linkedList.deleteByValue(5)).toBeNull(); + expect(linkedList.delete(5)).toBeNull(); - linkedList.append({ value: 1 }); - linkedList.append({ value: 2 }); - linkedList.append({ value: 3 }); - linkedList.append({ value: 3 }); - linkedList.append({ value: 3 }); - linkedList.append({ value: 4 }); - linkedList.append({ value: 5 }); + linkedList.append(1); + linkedList.append(2); + linkedList.append(3); + linkedList.append(3); + linkedList.append(3); + linkedList.append(4); + linkedList.append(5); expect(linkedList.head.toString()).toBe('1'); expect(linkedList.tail.toString()).toBe('5'); - const deletedNode = linkedList.deleteByValue(3); + const deletedNode = linkedList.delete(3); expect(deletedNode.value).toBe(3); expect(linkedList.toString()).toBe('1,2,4,5'); - linkedList.deleteByValue(3); + linkedList.delete(3); expect(linkedList.toString()).toBe('1,2,4,5'); - linkedList.deleteByValue(1); + linkedList.delete(1); expect(linkedList.toString()).toBe('2,4,5'); expect(linkedList.head.toString()).toBe('2'); expect(linkedList.tail.toString()).toBe('5'); - linkedList.deleteByValue(5); + linkedList.delete(5); expect(linkedList.toString()).toBe('2,4'); expect(linkedList.head.toString()).toBe('2'); expect(linkedList.tail.toString()).toBe('4'); - linkedList.deleteByValue(4); + linkedList.delete(4); expect(linkedList.toString()).toBe('2'); expect(linkedList.head.toString()).toBe('2'); expect(linkedList.tail.toString()).toBe('2'); }); - it('should delete node by key from linked list', () => { - const linkedList = new LinkedList(); - - expect(linkedList.deleteByKey('key')).toBeNull(); - - linkedList.append({ value: 1, key: 'test1' }); - linkedList.append({ value: 2, key: 'test2' }); - linkedList.append({ value: 3, key: 'test3' }); - linkedList.append({ value: 4, key: 'test4' }); - - const deletedNode1 = linkedList.deleteByKey('test2'); - expect(deletedNode1.key).toBe('test2'); - expect(linkedList.toString()).toBe('test1:1,test3:3,test4:4'); - - const deletedNode2 = linkedList.deleteByKey('test1'); - expect(deletedNode2.key).toBe('test1'); - expect(linkedList.toString()).toBe('test3:3,test4:4'); - - const deletedNode3 = linkedList.deleteByKey('test4'); - expect(deletedNode3.key).toBe('test4'); - expect(linkedList.toString()).toBe('test3:3'); - }); - it('should delete linked list tail', () => { const linkedList = new LinkedList(); - linkedList.append({ value: 1 }); - linkedList.append({ value: 2 }); - linkedList.append({ value: 3 }); + linkedList.append(1); + linkedList.append(2); + linkedList.append(3); expect(linkedList.head.toString()).toBe('1'); expect(linkedList.tail.toString()).toBe('3'); @@ -142,8 +106,8 @@ describe('LinkedList', () => { expect(linkedList.deleteHead()).toBeNull(); - linkedList.append({ value: 1 }); - linkedList.append({ value: 2 }); + linkedList.append(1); + linkedList.append(2); expect(linkedList.head.toString()).toBe('1'); expect(linkedList.tail.toString()).toBe('2'); @@ -163,26 +127,52 @@ describe('LinkedList', () => { expect(linkedList.tail).toBeNull(); }); - it('should append unique nodes', () => { + it('should be possible to store objects in the list and to print them out', () => { const linkedList = new LinkedList(); - linkedList.appendUnique({ value: 1, key: 'test1' }); - linkedList.appendUnique({ value: 2, key: 'test2' }); - linkedList.appendUnique({ value: 3, key: 'test2' }); - linkedList.appendUnique({ value: 5, key: 'test1' }); + const nodeValue1 = { value: 1, key: 'key1' }; + const nodeValue2 = { value: 2, key: 'key2' }; - expect(linkedList.toString()).toBe('test1:5,test2:3'); + linkedList + .append(nodeValue1) + .prepend(nodeValue2); + + const nodeStringifier = value => `${value.key}:${value.value}`; + + expect(linkedList.toString(nodeStringifier)).toBe('key2:2,key1:1'); }); - it('should find node by its key', () => { + it('should find node by value', () => { const linkedList = new LinkedList(); - expect(linkedList.findByKey('test')).toBeNull(); + expect(linkedList.find({ value: 5 })).toBeNull(); - linkedList.appendUnique({ value: 1, key: 'test1' }); - linkedList.appendUnique({ value: 2, key: 'test2' }); - linkedList.appendUnique({ value: 3, key: 'test3' }); + linkedList.append(1); + expect(linkedList.find({ value: 1 })).toBeDefined(); - expect(linkedList.findByKey('test3').toString()).toBe('test3:3'); + linkedList + .append(2) + .append(3); + + const node = linkedList.find({ value: 2 }); + + expect(node.value).toBe(2); + expect(linkedList.find({ value: 5 })).toBeNull(); + }); + + it('should find node by callback', () => { + const linkedList = new LinkedList(); + + linkedList + .append({ value: 1, key: 'test1' }) + .append({ value: 2, key: 'test2' }) + .append({ value: 3, key: 'test3' }); + + const node = linkedList.find({ callback: value => value.key === 'test2' }); + + expect(node).toBeDefined(); + expect(node.value.value).toBe(2); + expect(node.value.key).toBe('test2'); + expect(linkedList.find({ callback: value => value.key === 'test5' })).toBeNull(); }); }); diff --git a/src/data-structures/linked-list/__test__/LinkedListNode.test.js b/src/data-structures/linked-list/__test__/LinkedListNode.test.js index 19bf3c9b..0a989fc8 100644 --- a/src/data-structures/linked-list/__test__/LinkedListNode.test.js +++ b/src/data-structures/linked-list/__test__/LinkedListNode.test.js @@ -1,18 +1,46 @@ import LinkedListNode from '../LinkedListNode'; describe('LinkedListNode', () => { - it('should create list node with kay and value', () => { - const node = new LinkedListNode({ value: 1, key: 'test' }); + it('should create list node with value', () => { + const node = new LinkedListNode(1); + expect(node.value).toBe(1); - expect(node.key).toBe('test'); expect(node.next).toBeNull(); }); + it('should create list node with object as a value', () => { + const nodeValue = { value: 1, key: 'test' }; + const node = new LinkedListNode(nodeValue); + + expect(node.value.value).toBe(1); + expect(node.value.key).toBe('test'); + expect(node.next).toBeNull(); + }); + + it('should link nodes together', () => { + const node2 = new LinkedListNode(2); + const node1 = new LinkedListNode(1, node2); + + expect(node1.next).toBeDefined(); + expect(node2.next).toBeNull(); + expect(node1.value).toBe(1); + expect(node1.next.value).toBe(2); + }); + it('should convert node to string', () => { - const node = new LinkedListNode({ value: 1 }); - const nodeWithKey = new LinkedListNode({ value: 1, key: 'test' }); + const node = new LinkedListNode(1); expect(node.toString()).toBe('1'); - expect(nodeWithKey.toString()).toBe('test:1'); + + node.value = 'string value'; + expect(node.toString()).toBe('string value'); + }); + + it('should convert node to string with custom stringifier', () => { + const nodeValue = { value: 1, key: 'test' }; + const node = new LinkedListNode(nodeValue); + const toStringCallback = value => `value: ${value.value}, key: ${value.key}`; + + expect(node.toString(toStringCallback)).toBe('value: 1, key: test'); }); }); diff --git a/src/data-structures/queue/Queue.js b/src/data-structures/queue/Queue.js index b2d50458..49332400 100644 --- a/src/data-structures/queue/Queue.js +++ b/src/data-structures/queue/Queue.js @@ -18,11 +18,15 @@ export default class Queue { } enqueue(value) { - this.linkedList.append({ value }); + this.linkedList.append(value); } dequeue() { const removedHead = this.linkedList.deleteHead(); return removedHead ? removedHead.value : null; } + + toString(callback) { + return this.linkedList.toString(callback); + } } diff --git a/src/data-structures/queue/__test__/Queue.test.js b/src/data-structures/queue/__test__/Queue.test.js index 587e37b5..3e0fad8c 100644 --- a/src/data-structures/queue/__test__/Queue.test.js +++ b/src/data-structures/queue/__test__/Queue.test.js @@ -13,7 +13,20 @@ describe('Queue', () => { queue.enqueue(1); queue.enqueue(2); - expect(queue.linkedList.toString()).toBe('1,2'); + expect(queue.toString()).toBe('1,2'); + }); + + it('should be possible to enqueue/dequeue objects', () => { + const queue = new Queue(); + + queue.enqueue({ value: 'test1', key: 'key1' }); + queue.enqueue({ value: 'test2', key: 'key2' }); + + const stringifier = value => `${value.key}:${value.value}`; + + expect(queue.toString(stringifier)).toBe('key1:test1,key2:test2'); + expect(queue.dequeue().value).toBe('test1'); + expect(queue.dequeue().value).toBe('test2'); }); it('should peek data from queue', () => { diff --git a/src/data-structures/stack/Stack.js b/src/data-structures/stack/Stack.js index 3c2b0c45..665bec2e 100644 --- a/src/data-structures/stack/Stack.js +++ b/src/data-structures/stack/Stack.js @@ -18,11 +18,15 @@ export default class Stack { } push(value) { - this.linkedList.append({ value }); + this.linkedList.append(value); } pop() { const removedTail = this.linkedList.deleteTail(); return removedTail ? removedTail.value : null; } + + toString(callback) { + return this.linkedList.toString(callback); + } } diff --git a/src/data-structures/stack/__test__/Stack.test.js b/src/data-structures/stack/__test__/Stack.test.js index 63978243..291bc430 100644 --- a/src/data-structures/stack/__test__/Stack.test.js +++ b/src/data-structures/stack/__test__/Stack.test.js @@ -13,7 +13,7 @@ describe('Stack', () => { stack.push(1); stack.push(2); - expect(stack.linkedList.toString()).toBe('1,2'); + expect(stack.toString()).toBe('1,2'); }); it('should peek data from stack', () => { @@ -49,4 +49,17 @@ describe('Stack', () => { expect(stack.pop()).toBeNull(); expect(stack.isEmpty()).toBeTruthy(); }); + + it('should be possible to push/pop objects', () => { + const stack = new Stack(); + + stack.push({ value: 'test1', key: 'key1' }); + stack.push({ value: 'test2', key: 'key2' }); + + const stringifier = value => `${value.key}:${value.value}`; + + expect(stack.toString(stringifier)).toBe('key1:test1,key2:test2'); + expect(stack.pop().value).toBe('test2'); + expect(stack.pop().value).toBe('test1'); + }); });