diff --git a/README.md b/README.md index ef641cc..e3df412 100644 --- a/README.md +++ b/README.md @@ -22,8 +22,10 @@ The problem sets below contain information on key data structures, sorting/searc - [Stack](solutions/data-structures/stack) - [Linked List](solutions/data-structures/linked-list) - [Doubly Linked List](solutions/data-structures/doubly-linked-list) + - [Heap](solutions/data-structures/heap) + - [Binary Tree](solutions/data-structures/binary-tree) - [Hash Table](solutions/data-structures/hash-table) - - [Binary Search Tree](solutions/data-structures/tree/binary-search-tree) + - [Binary Search Tree](solutions/data-structures/binary-search-tree) - **Sorting** - [Bubble Sort](solutions/algorithms/sorting/bubble-sort) - [Selection Sort](solutions/algorithms/sorting/selection-sort) @@ -39,6 +41,9 @@ The problem sets below contain information on key data structures, sorting/searc - [Factorial](solutions/algorithms/numbers/factorial) - [Fibonacci Number](solutions/algorithms/numbers/fibonacci) - [Prime Number](solutions/algorithms/numbers/prime) + - [Happy Number](solutions/algorithms/numbers/happy) + - [Ugly Number](solutions/algorithms/numbers/ugly) + - [Lexicographical Numbers](solutions/algorithms/numbers/lexicographical) ## How it works diff --git a/assets/Binary_tree.svg b/assets/Binary_tree.svg new file mode 100644 index 0000000..b56e325 --- /dev/null +++ b/assets/Binary_tree.svg @@ -0,0 +1,242 @@ + + + +2 + +7 + +5 + +2 + +6 + +9 + +5 + +11 + +4 + + \ No newline at end of file diff --git a/skeletons/data-structures/binary-tree/BinaryTree.js b/skeletons/data-structures/binary-tree/BinaryTree.js new file mode 100644 index 0000000..0f4915f --- /dev/null +++ b/skeletons/data-structures/binary-tree/BinaryTree.js @@ -0,0 +1,76 @@ + + +import BinaryTreeNode from './BinaryTreeNode'; + +export default class BinaryTree { + constructor() { + this.store = new BinaryTreeNode(); + } + + + + +//Traverse the tree in-order and call callback on the value of each node. + + inOrder(callback){ + +} + + + postOrder(callback){ + + +} + + + + preOrder(callback){ + + +} + + +numberOfNodes(){ + +} + +numberOfLeaves(){ + +} + + +height(){ + +} + + numberOfLeaves(){ + +} + +numberOfNodes(){ + +} + + +balanced(){ + +} + + +degenerate(){ + } + +perfect(){ +} + +complete(){ +} + + + + toString(){ + + } + +} + diff --git a/skeletons/data-structures/binary-tree/BinaryTreeNode.js b/skeletons/data-structures/binary-tree/BinaryTreeNode.js new file mode 100644 index 0000000..05f5a77 --- /dev/null +++ b/skeletons/data-structures/binary-tree/BinaryTreeNode.js @@ -0,0 +1,63 @@ + +export default class BinaryTreeNode { + constructor(value, left = null, right = null) { + this.value = value; + this.left = left; + this.right = right; + } + + +//Traverse the nodes in-order and call callback on the value of each node. + inOrder(callback){ + +} + + + +preOrder(callback){ + +} + +postOrder(callback){ + +} + + +height(){ + + +} + +numberOfLeaves(){ + +} + +numberOfNodes(){ +} + + +balanced(){ + +} + + +balancedHeight(){ + +} + + +degenerate(){ + } + +perfect(){ +} + + +complete(){ +} + + toString(){ + + } + +} diff --git a/skeletons/data-structures/binary-tree/InOrderIterative.js b/skeletons/data-structures/binary-tree/InOrderIterative.js new file mode 100644 index 0000000..ac0225e --- /dev/null +++ b/skeletons/data-structures/binary-tree/InOrderIterative.js @@ -0,0 +1,16 @@ + + +import BinaryTreeNode from './BinaryTreeNode'; +import Stack from './../stack/Stack'; + + +export function inOrderIterative(tree,callback){ +} + + + + + + + + diff --git a/skeletons/data-structures/binary-tree/PostOrderIterative.js b/skeletons/data-structures/binary-tree/PostOrderIterative.js new file mode 100644 index 0000000..2c8a3aa --- /dev/null +++ b/skeletons/data-structures/binary-tree/PostOrderIterative.js @@ -0,0 +1,17 @@ + + +import BinaryTreeNode from './BinaryTreeNode'; +import Stack from './../stack/Stack'; + + +export function postOrderIterative(tree,callback){ + +} + + + + + + + + diff --git a/skeletons/data-structures/binary-tree/PreOrderIterative.js b/skeletons/data-structures/binary-tree/PreOrderIterative.js new file mode 100644 index 0000000..d1f243c --- /dev/null +++ b/skeletons/data-structures/binary-tree/PreOrderIterative.js @@ -0,0 +1,18 @@ +f + +import BinaryTreeNode from './BinaryTreeNode'; +import Stack from './../stack/Stack'; + + +export function preOrderIterative(tree,callback){ + + +} + + + + + + + + diff --git a/skeletons/data-structures/binary-tree/README.md b/skeletons/data-structures/binary-tree/README.md new file mode 100644 index 0000000..759e1b4 --- /dev/null +++ b/skeletons/data-structures/binary-tree/README.md @@ -0,0 +1,48 @@ +# Binary Tree + + +## Description + +Just as a list is a data structure with nodes, where each node has one child, the next node, a binart tree is a data structure made up of nodex where each node has two children, a left child and a right child. The first node is called the root, and in Computer Science, trees are drawn downwards, for some reason. + +![Binary Tree](../../../assets/Binary_tree.svg) + +As well as a left and right child, binary tree nodes usually have a value. In the aboe picture, the number displayed is the value of the node. Sometimes nodes have a parent pointer, and sometimes they even have a pointer to their siblings or uncles. + +We say a node's height is one greater than the max height of its children. A node without children has height one. A node without children is called a leaf. A node with children is called an interior node. Sometimes nodes will kepp track of their height. The depth of a node is how far it is from the root. The root has depth 1, its children have depth 2, etc. + + Serveral kinds of binary tree are distinguised. A degenerate binary tree is one that have only one or zero children at each node. This means it is similar to a list. + A full binary tree has 0 or 2 nodes at every level. A complete binary tree has two nodes at every level, save the lowest level. At the lowest level, all nodes are as far left as possible. This becomes important later when we discuss heaps. + + A perfect binary tree of height h, has all interior nodes with two children, and all leaves are at depth n. A balanced binary tree is one where the left and right subtrees of every node differ in height by no more than 1. + + + +## Implementation + +The usual minimal implementation of a binary tree node has a constructor that sets its value, left and right. Helper functions can copute the `height` of a node recursively. The `numberOfLeaves` and `numberOfNodes` are also easily computed recursively. + +Test functions that determines where a binary node is `balanced`, `degenerate`, `perfect` or `complete` can be written. + +A binary tree is a data structre that stores the root binary tree node. + +## Binary Tree Problems + +A binary tree can be traversed in several ways. In order traversal visits the left tree, then the right tree, and then visits the root. Pre-order visits the root, then the left and then the right subtree. Post-order visits the left subtree, then the right sibtree, then the root. + +Write a method that takes a callback, and calls it on each node in pre-order, in-order, and post-order. + +These methods are most easily written recursively. It is worthwhile to write these iteratively using a stack. Write + +`inOrderIterative(tree,callback)` + + `preOrderIterative(tree,callback)` + +`postOrderIterative(tree,callback)` + + + + + + + diff --git a/skeletons/data-structures/binary-tree/__test__/BinaryTree.test.js b/skeletons/data-structures/binary-tree/__test__/BinaryTree.test.js new file mode 100644 index 0000000..c5c079e --- /dev/null +++ b/skeletons/data-structures/binary-tree/__test__/BinaryTree.test.js @@ -0,0 +1,227 @@ + +import BinaryTreeNode from '../BinaryTreeNode'; +import BinaryTree from '../BinaryTree'; + +describe('BinaryTreeNode', () => { + it('should create empty BinaryTreeNode', () => { + const node = new BinaryTree(); + expect( node).not.toBeUndefined(); + expect( node ).not.toBeUndefined(); + }); + + + + it('should traverse a BinaryTree in Order.', () => { + + const node1 = new BinaryTreeNode(1); + const node3 = new BinaryTreeNode(3); + const node2 = new BinaryTreeNode(2,node1,node3); + const node5= new BinaryTreeNode(5); + const node4 = new BinaryTreeNode(4,node2,node5); + + const tree = new BinaryTree(); + tree.store = node4; + + expect( tree.toString()).toBe('1,2,3,4,5'); +}); + + + + it('should traverse a BinaryTree in post Order.', () => { + + const node1 = new BinaryTreeNode(1); + const node3 = new BinaryTreeNode(3); + const node2 = new BinaryTreeNode(2,node1,node3); + const node5= new BinaryTreeNode(5); + const node4 = new BinaryTreeNode(4,node2,node5); + + + const tree = new BinaryTree(); + tree.store = node4; + + var res = []; + tree.postOrder(a => { + res.push(a.value);} ); + + + + expect( res.toString()).toBe('1,3,2,5,4'); +}); + + + it('should traverse a BinaryTree in preOrder.', () => { + + const node1 = new BinaryTreeNode(1); + const node3 = new BinaryTreeNode(3); + const node2 = new BinaryTreeNode(2,node1,node3); + const node5= new BinaryTreeNode(5); + const node4 = new BinaryTreeNode(4,node2,node5); + const tree = new BinaryTree(); + tree.store = node4; + + + + var res = []; + tree.preOrder(a => { + res.push(a.value);} ); + + expect(res.toString()).toBe('4,2,1,3,5'); +}); + + + + it('should count the numbers of nodes of BinaryTree' , () => { + + const node1 = new BinaryTreeNode(1); + const node3 = new BinaryTreeNode(3); + const node2 = new BinaryTreeNode(2,node1,node3); + const node6= new BinaryTreeNode(6); + const node5= new BinaryTreeNode(5, null, node6); + const node4 = new BinaryTreeNode(4,node2,node5); + + const tree = new BinaryTree(); + tree.store = node4; + + + expect(tree.numberOfNodes()).toBe(6); + + +}); + + + + + it('should count the numbers of leaves of BinaryTree', () => { + + const node1 = new BinaryTreeNode(1); + const node3 = new BinaryTreeNode(3); + const node2 = new BinaryTreeNode(2,node1,node3); + + const node6= new BinaryTreeNode(6); + const node5= new BinaryTreeNode(5, null, node6); + const node4 = new BinaryTreeNode(4,node2,node5); + + const tree = new BinaryTree(); + tree.store = node4; + + + expect(tree.numberOfLeaves()).toBe(3); + + +}); + + + + it('should compute the height of a BinaryTree ', () => { + + const node1 = new BinaryTreeNode(1); + const node3 = new BinaryTreeNode(3); + const node2 = new BinaryTreeNode(2,node1,node3); + + const node6= new BinaryTreeNode(6); + const node5= new BinaryTreeNode(5, null, node6); + const node4 = new BinaryTreeNode(4,node2,node5); + + const tree = new BinaryTree(); + tree.store = node4; + + expect(tree.height()).toBe(3); + + +}); + + + + + it('should check is a BinaryTree is balanced', () => { + + const node1 = new BinaryTreeNode(1); + const node3 = new BinaryTreeNode(3); + const node2 = new BinaryTreeNode(2,node1,node3); + const node7= new BinaryTreeNode(7); + const node6= new BinaryTreeNode(6,null, node7); + const node5= new BinaryTreeNode(5, null, node6); + const node4 = new BinaryTreeNode(4,node2,node5); + const tree = new BinaryTree(); + tree.store = node4; + + expect(node4.balanced()).toBeFalsy(); + +}); + + +it('should check is a BinaryTree is degenerate', () => { + + const node1 = new BinaryTreeNode(1); + const node3 = new BinaryTreeNode(3); + const node2 = new BinaryTreeNode(2,node1,node3); + + const node6= new BinaryTreeNode(6); + const node5= new BinaryTreeNode(5, null, node6); + const node4 = new BinaryTreeNode(4,node2,node5); + const tree = new BinaryTree(); + tree.store = node4; + const tree1 = new BinaryTree(); + tree1.store = node5; + + expect(tree.degenerate()).toBeFalsy(); + expect(tree1.degenerate()).toBeTruthy(); + +}); + + +it('should check is a BinaryTree is perfect', () => { + + const node1 = new BinaryTreeNode(1); + const node3 = new BinaryTreeNode(3); + const node2 = new BinaryTreeNode(2,node1,node3); + + const node6= new BinaryTreeNode(6); + const node5= new BinaryTreeNode(5, null, node6); + const node4 = new BinaryTreeNode(4,node2,node5); + + const tree = new BinaryTree(); + tree.store = node4; + const tree1 = new BinaryTree(); + tree1.store = node3; + + expect(tree.perfect()).toBeFalsy(); + expect(tree1.perfect()).toBeTruthy(); + + +}); + + + +it('should check is a BinaryTree is complete', () => { + + const node1 = new BinaryTreeNode(1); + const node3 = new BinaryTreeNode(3); + const node2 = new BinaryTreeNode(2,node1,node3); + + const node6= new BinaryTreeNode(6); + var node5= new BinaryTreeNode(5, null, node6); + var node4 = new BinaryTreeNode(4,node2,node5); + + var tree = new BinaryTree(); + tree.store = node4; + const tree1 = new BinaryTree(); + tree1.store = node3; + + expect(tree.complete()).toBeFalsy(); + expect(tree1.complete()).toBeTruthy(); + + + node5 = new BinaryTreeNode(5, node6,null); + node4 = new BinaryTreeNode(4,node2,node5); + tree.store = node4; + + + expect(tree.complete()).toBeTruthy(); + + +}); + + + +}); diff --git a/skeletons/data-structures/binary-tree/__test__/BinaryTreeNode.test.js b/skeletons/data-structures/binary-tree/__test__/BinaryTreeNode.test.js new file mode 100644 index 0000000..5a294aa --- /dev/null +++ b/skeletons/data-structures/binary-tree/__test__/BinaryTreeNode.test.js @@ -0,0 +1,232 @@ + +import BinaryTreeNode from '../BinaryTreeNode'; + +describe('BinaryTreeNode', () => { + it('should create empty BinaryTreeNode', () => { + const node = new BinaryTreeNode(); + expect( node).not.toBeUndefined(); + expect( node ).not.toBeUndefined(); + }); + + + it('should create BinaryTreeNode with a value', () => { + + const node = new BinaryTreeNode(1); + expect( node ).not.toBeUndefined(); + expect( node.value ).toBe(1); + expect( node.left ).toBeNull(); + expect( node.right ).toBeNull(); +}); + + + it('should traverse a BinaryTreeNode in Order.', () => { + + const node1 = new BinaryTreeNode(1); + const node3 = new BinaryTreeNode(3); + const node2 = new BinaryTreeNode(2,node1,node3); + const node5= new BinaryTreeNode(5); + const node4 = new BinaryTreeNode(4,node2,node5); + + expect( node1 ).not.toBeUndefined(); + expect( node1.value ).toBe(1); + expect( node4.toString()).toBe('1,2,3,4,5'); +}); + + + + it('should traverse a BinaryTreeNode in post Order.', () => { + + const node1 = new BinaryTreeNode(1); + const node3 = new BinaryTreeNode(3); + const node2 = new BinaryTreeNode(2,node1,node3); + const node5= new BinaryTreeNode(5); + const node4 = new BinaryTreeNode(4,node2,node5); + + expect( node1 ).not.toBeUndefined(); + expect( node1.value ).toBe(1); + + var res = []; + node4.postOrder(a => { + res.push(a.value);} ); + + + expect( res.toString()).toBe('1,3,2,5,4'); +}); + + + it('should traverse a BinaryTreeNode in preOrder.', () => { + + const node1 = new BinaryTreeNode(1); + const node3 = new BinaryTreeNode(3); + const node2 = new BinaryTreeNode(2,node1,node3); + const node5= new BinaryTreeNode(5); + const node4 = new BinaryTreeNode(4,node2,node5); + + expect( node1 ).not.toBeUndefined(); + expect( node1.value ).toBe(1); + + var res = []; + node4.preOrder(a => { + res.push(a.value);} ); + + expect(res.toString()).toBe('4,2,1,3,5'); +}); + + + + it('should count the numbers of nodes of BinaryTreeNodes', () => { + + const node1 = new BinaryTreeNode(1); + const node3 = new BinaryTreeNode(3); + const node2 = new BinaryTreeNode(2,node1,node3); + const node6= new BinaryTreeNode(6); + const node5= new BinaryTreeNode(5, null, node6); + const node4 = new BinaryTreeNode(4,node2,node5); + + expect(node4.numberOfNodes()).toBe(6); + expect(node3.numberOfNodes()).toBe(1); + expect(node2.numberOfNodes()).toBe(3); + expect(node1.numberOfNodes()).toBe(1); + expect(node5.numberOfNodes()).toBe(2); + expect(node6.numberOfNodes()).toBe(1); + +}); + + + + + it('should count the numbers of leaves of BinaryTreeNodes', () => { + + const node1 = new BinaryTreeNode(1); + const node3 = new BinaryTreeNode(3); + const node2 = new BinaryTreeNode(2,node1,node3); + + const node6= new BinaryTreeNode(6); + const node5= new BinaryTreeNode(5, null, node6); + const node4 = new BinaryTreeNode(4,node2,node5); + + expect(node4.numberOfLeaves()).toBe(3); + expect(node3.numberOfLeaves()).toBe(1); + expect(node2.numberOfLeaves()).toBe(2); + expect(node1.numberOfLeaves()).toBe(1); + expect(node5.numberOfLeaves()).toBe(1); + expect(node6.numberOfLeaves()).toBe(1); + +}); + + + + it('should compute the height BinaryTreeNodes', () => { + + const node1 = new BinaryTreeNode(1); + const node3 = new BinaryTreeNode(3); + const node2 = new BinaryTreeNode(2,node1,node3); + + const node6= new BinaryTreeNode(6); + const node5= new BinaryTreeNode(5, null, node6); + const node4 = new BinaryTreeNode(4,node2,node5); + + expect(node4.height()).toBe(3); + expect(node3.height()).toBe(1); + expect(node2.height()).toBe(2); + expect(node1.height()).toBe(1); + expect(node5.height()).toBe(2); + expect(node6.height()).toBe(1); + +}); + + + + + it('should check is a BinaryTreeNode is balanced', () => { + + const node1 = new BinaryTreeNode(1); + const node3 = new BinaryTreeNode(3); + const node2 = new BinaryTreeNode(2,node1,node3); + const node7= new BinaryTreeNode(7); + const node6= new BinaryTreeNode(6,null, node7); + const node5= new BinaryTreeNode(5, null, node6); + const node4 = new BinaryTreeNode(4,node2,node5); + + expect(node4.balanced()).toBeFalsy(); + expect(node3.balanced()).toBeTruthy(); + expect(node2.balanced()).toBeTruthy(); + expect(node1.balanced()).toBeTruthy(); + expect(node5.balanced()).toBeFalsy(); + expect(node6.balanced()).toBeTruthy(); + expect(node7.balanced()).toBeTruthy(); + +}); + + +it('should check is a BinaryTreeNode is degenerate', () => { + + const node1 = new BinaryTreeNode(1); + const node3 = new BinaryTreeNode(3); + const node2 = new BinaryTreeNode(2,node1,node3); + + const node6= new BinaryTreeNode(6); + const node5= new BinaryTreeNode(5, null, node6); + const node4 = new BinaryTreeNode(4,node2,node5); + + expect(node4.degenerate()).toBeFalsy(); + expect(node3.degenerate()).toBeTruthy(); + expect(node2.degenerate()).toBeFalsy(); + expect(node1.degenerate()).toBeTruthy(); + expect(node5.degenerate()).toBeTruthy(); + expect(node6.degenerate()).toBeTruthy(); + +}); + + +it('should check is a BinaryTreeNode is perfect', () => { + + const node1 = new BinaryTreeNode(1); + const node3 = new BinaryTreeNode(3); + const node2 = new BinaryTreeNode(2,node1,node3); + + const node6= new BinaryTreeNode(6); + const node5= new BinaryTreeNode(5, null, node6); + const node4 = new BinaryTreeNode(4,node2,node5); + + expect(node4.perfect()).toBeFalsy(); + expect(node3.perfect()).toBeTruthy(); + expect(node2.perfect()).toBeTruthy(); + expect(node1.perfect()).toBeTruthy(); + expect(node5.perfect()).toBeFalsy(); + expect(node6.perfect()).toBeTruthy(); + +}); + + + +it('should check is a BinaryTreeNode is complete', () => { + + const node1 = new BinaryTreeNode(1); + const node3 = new BinaryTreeNode(3); + const node2 = new BinaryTreeNode(2,node1,node3); + + const node6= new BinaryTreeNode(6); + var node5= new BinaryTreeNode(5, null, node6); + var node4 = new BinaryTreeNode(4,node2,node5); + + expect(node4.complete()).toBeFalsy(); + expect(node3.complete()).toBeTruthy(); + expect(node2.complete()).toBeTruthy(); + expect(node1.complete()).toBeTruthy(); + expect(node5.complete()).toBeFalsy(); + expect(node6.complete()).toBeTruthy(); + + node5 = new BinaryTreeNode(5, node6,null); + node4 = new BinaryTreeNode(4,node2,node5); + + expect(node4.complete()).toBeTruthy(); + console.log("Trying 5"); + + expect(node5.complete()).toBeTruthy(); + +}); + + + +}); diff --git a/skeletons/data-structures/binary-tree/__test__/InOrderIterative.test.js b/skeletons/data-structures/binary-tree/__test__/InOrderIterative.test.js new file mode 100644 index 0000000..b6371eb --- /dev/null +++ b/skeletons/data-structures/binary-tree/__test__/InOrderIterative.test.js @@ -0,0 +1,30 @@ + +import BinaryTreeNode from '../BinaryTreeNode'; +import BinaryTree from '../BinaryTree'; + +import { inOrderIterative } from '../InOrderIterative'; + + + +describe(' inOrderIterative ', () => { + + + it('should traverse a BinaryTree in Order.', () => { + + const node1 = new BinaryTreeNode(1); + const node3 = new BinaryTreeNode(3); + const node2 = new BinaryTreeNode(2,node1,node3); + const node5= new BinaryTreeNode(5); + const node4 = new BinaryTreeNode(4,node2,node5); + + const tree = new BinaryTree(); + tree.store = node4; + + var res = []; + + inOrderIterative( tree, a => { res.push(a.value);}); + + expect( res.toString()).toBe('1,2,3,4,5'); +}); + +}); diff --git a/skeletons/data-structures/binary-tree/__test__/PostOrderIterative.test.js b/skeletons/data-structures/binary-tree/__test__/PostOrderIterative.test.js new file mode 100644 index 0000000..c55787a --- /dev/null +++ b/skeletons/data-structures/binary-tree/__test__/PostOrderIterative.test.js @@ -0,0 +1,30 @@ + +import BinaryTreeNode from '../BinaryTreeNode'; +import BinaryTree from '../BinaryTree'; + +import { postOrderIterative } from '../PostOrderIterative'; + + + +describe(' PostOrderIterative ', () => { + + + it('should traverse a BinaryTree in Order.', () => { + + const node1 = new BinaryTreeNode(1); + const node3 = new BinaryTreeNode(3); + const node2 = new BinaryTreeNode(2,node1,node3); + const node5= new BinaryTreeNode(5); + const node4 = new BinaryTreeNode(4,node2,node5); + + const tree = new BinaryTree(); + tree.store = node4; + + var res = []; + + postOrderIterative( tree, a => { res.push(a.value);}); + + expect( res.toString()).toBe('1,3,2,5,4'); +}); + +}); diff --git a/skeletons/data-structures/binary-tree/__test__/PreOrderIterative.test.js b/skeletons/data-structures/binary-tree/__test__/PreOrderIterative.test.js new file mode 100644 index 0000000..4ec2d95 --- /dev/null +++ b/skeletons/data-structures/binary-tree/__test__/PreOrderIterative.test.js @@ -0,0 +1,30 @@ + +import BinaryTreeNode from '../BinaryTreeNode'; +import BinaryTree from '../BinaryTree'; + +import { preOrderIterative } from '../PreOrderIterative'; + + + +describe(' PreOrderIterative ', () => { + + + it('should traverse a BinaryTree in Order.', () => { + + const node1 = new BinaryTreeNode(1); + const node3 = new BinaryTreeNode(3); + const node2 = new BinaryTreeNode(2,node1,node3); + const node5= new BinaryTreeNode(5); + const node4 = new BinaryTreeNode(4,node2,node5); + + const tree = new BinaryTree(); + tree.store = node4; + + var res = []; + + preOrderIterative( tree, a => { res.push(a.value);}); + + expect( res.toString()).toBe('4,2,1,3,5'); +}); + +}); diff --git a/skeletons/data-structures/doubly-linked-list/ArrayToList.js b/skeletons/data-structures/doubly-linked-list/ArrayToList.js new file mode 100644 index 0000000..a0f5910 --- /dev/null +++ b/skeletons/data-structures/doubly-linked-list/ArrayToList.js @@ -0,0 +1,7 @@ +import DoublyLinkedList from './DoublyLinkedList'; + + + + +export function arrayToList(arr){ +} diff --git a/skeletons/data-structures/doubly-linked-list/Insert.js b/skeletons/data-structures/doubly-linked-list/Insert.js new file mode 100644 index 0000000..4822c82 --- /dev/null +++ b/skeletons/data-structures/doubly-linked-list/Insert.js @@ -0,0 +1,6 @@ +import DoublyLinkedList from './DoublyLinkedList'; +import DoublyLinkedListNode from './DoublyLinkedListNode'; + +export function insert(dll, el,comp){ + +} diff --git a/skeletons/data-structures/doubly-linked-list/MergeDLL.js b/skeletons/data-structures/doubly-linked-list/MergeDLL.js new file mode 100644 index 0000000..986a8c5 --- /dev/null +++ b/skeletons/data-structures/doubly-linked-list/MergeDLL.js @@ -0,0 +1,6 @@ +import DoublyLinkedList from './DoublyLinkedList'; + + +export function merge(dll1,dll2,comp){ + +} diff --git a/skeletons/data-structures/doubly-linked-list/PriorityQueue.js b/skeletons/data-structures/doubly-linked-list/PriorityQueue.js new file mode 100644 index 0000000..2acc2e8 --- /dev/null +++ b/skeletons/data-structures/doubly-linked-list/PriorityQueue.js @@ -0,0 +1,32 @@ +import DoublyLinkedList from './DoublyLinkedList'; +import { insert } from './Insert'; + + +export default class PriorityQueue { + constructor(comp = (a,b) => { return a.value < b.value } ) { + this.store = new DoublyLinkedList(); + this.comp = comp; + } + + + isEmpty() { + + } + + peek() { + + + } + + enqueue(el) { + + } + + getHighest() { + + } + + toString() { + return this.store.toString(); + } +} diff --git a/skeletons/data-structures/doubly-linked-list/README.md b/skeletons/data-structures/doubly-linked-list/README.md index fc51be7..836afa2 100644 --- a/skeletons/data-structures/doubly-linked-list/README.md +++ b/skeletons/data-structures/doubly-linked-list/README.md @@ -77,3 +77,92 @@ To start, build `DoublyLinkedListNode`. A doubly linked list node keeps a refere - The `delete` method removes the first node with the specified value. - The delete operation for a doubly linked list is significantly simpler due to having a reference to the previous node. - Utilize `find` and `deleteHead/Tail` methods written above to write the method. + + +#Problems# + +**Create a list from an Array** + +Given an array, create a list with the same elements in the same order. + + + +``` +const arrayToList = arr => { + +} +``` + + +**Find the Smallest Value in a List** + +Given a list of numbers, find the smallest value. This just requires traversing the list, keeping track of the smallest number seen for far. You can start at either end, but most people will start at the head. Finding the smallest element is the basis for selection sort, which is about twice as slow as insertion sort, which is mentioned below. The basic idea of finding the smallest element can be greatly improved by using a heap, another data structure. + + + +``` +const smallestValue = dll => { + +} + +console.log( smallestValue( [13, 7, 6, 12 ].arrayToList() ) ) == 6 ); + +``` + + + + +**Reverse a doubly linked list** + +Sadly, just switching head and tail will not reverse a list, as the next and previous pointers will still be messed up. To revere a list it is necessary to go through each element and to swap the next and previous values in the right way. We can either create a new doubly linked list that is the reverse of this one, or we can modify the existing list. Try to do both. + + +``` +const reverseDLL = dll => { + +} + +console.log( reverseDLL( [13, 7, 6, 12 ].arrayToList() ) ) == [12, 6, 7, 13 ].arrayToList() ); +console.log( reverseInPlace( [13, 7, 6, 12 ].arrayToList() ) ) == [12, 6, 7, 13 ].arrayToList() ); + + +``` + + +**Merge two sorted Doubly Linked Lists** + +Given two sorted lists, merge the two lists to make a larger sorted list. Your function should take a comparator, a function that compares two elements. This function, mergring two sorted lists, is the basis for merge sort, one of the very first sorting algorithms. + +``` +const merge = ( dll1, dll2, comp) => { + +} + +console.log( merge( [7, 13].arrayToList(), merge( [ 6, 12 ].arrayToList(), (a,b) => { return a < b;} ) )) == [6, 7, 12, 13 ].arrayToList() ); + +``` + + +**Insert a Value in a Sorted List** + +Given a list and an element, insert the element in sorted order. This function is the basis for insertion sort, the best sorting agorithm for very small arrays. + + +``` +const insert = ( dll, el, comp) => { + +} + +console.log( merge( [7, 13].arrayToList(), 6, (a,b) => { return a.value < b.value;} ) )) == [6, 7, 13 ].arrayToList() ); + +``` + + + +**Priority Queue using DLL** + +A priority queue is a data structure that acts like a queue where you can 'enqueue' and 'dequeue' elements, but instead of returning the earliest element still in the queue, it returns the element with the highest priority. It is normal to have a comparison function, called a comparator, to tell whether one item has higher priority than another. If two elements have the same priority, you should return the earlier element. You should implement 'enqueue', `getHighest`, `peek`, and `isEmpty`. + +The implementation is a mixture of the ideas of a queue, and inserting a value in a sorted list. This pattern of problems being solved with combinations of previous solutions is very common. + + diff --git a/skeletons/data-structures/doubly-linked-list/Reverse.js b/skeletons/data-structures/doubly-linked-list/Reverse.js new file mode 100644 index 0000000..8ac7027 --- /dev/null +++ b/skeletons/data-structures/doubly-linked-list/Reverse.js @@ -0,0 +1,14 @@ +import DoublyLinkedList from './DoublyLinkedList'; + + + + +export function reverseDLL(dll){ + +} + + +export function reverseInPlace(dll){ + + +} diff --git a/skeletons/data-structures/doubly-linked-list/SmallestValue.js b/skeletons/data-structures/doubly-linked-list/SmallestValue.js new file mode 100644 index 0000000..dd157fa --- /dev/null +++ b/skeletons/data-structures/doubly-linked-list/SmallestValue.js @@ -0,0 +1,7 @@ +import DoublyLinkedList from './DoublyLinkedList'; + + +export function smallestValue(dll){ + + +} diff --git a/skeletons/data-structures/doubly-linked-list/__test__/ArrayToList.test.js b/skeletons/data-structures/doubly-linked-list/__test__/ArrayToList.test.js new file mode 100644 index 0000000..c0e51a1 --- /dev/null +++ b/skeletons/data-structures/doubly-linked-list/__test__/ArrayToList.test.js @@ -0,0 +1,19 @@ + +import { arrayToList } from '../ArrayToList'; + + +describe('ArrayToList', () => { + it('should turn arrays to lists ', () => { + + + expect( arrayToList( [ 1, 2,3] ).toString()).toBe('1,2,3'); + + expect( arrayToList( [ ] ).toString()).toBe(''); + + expect( arrayToList( [1 ] ).toString()).toBe('1'); + + expect( arrayToList( [1 ,1] ).toString()).toBe('1,1'); +}); + +}); + diff --git a/skeletons/data-structures/doubly-linked-list/__test__/ArrayToList.text.js b/skeletons/data-structures/doubly-linked-list/__test__/ArrayToList.text.js new file mode 100644 index 0000000..6bea75f --- /dev/null +++ b/skeletons/data-structures/doubly-linked-list/__test__/ArrayToList.text.js @@ -0,0 +1,14 @@ + +import { arrayToList } from '../ArrayToList'; + + +describe('ArrayToList', () => { + it('should turn arrays to lists ', () => { + + + expect( arrayToList( [ 1, 2,3] ).toString()).toBe('3'); + +}); + +}); + diff --git a/skeletons/data-structures/doubly-linked-list/__test__/DoublyLinkedList.test.js b/skeletons/data-structures/doubly-linked-list/__test__/DoublyLinkedList.test.js index dc6a971..93e5fcf 100644 --- a/skeletons/data-structures/doubly-linked-list/__test__/DoublyLinkedList.test.js +++ b/skeletons/data-structures/doubly-linked-list/__test__/DoublyLinkedList.test.js @@ -14,13 +14,27 @@ describe('DoublyLinkedList', () => { linkedList.append(1); linkedList.append(2); + linkedList.append(3); + expect(linkedList.head.next.value).toBe(2); + expect(linkedList.tail.previous.value).toBe(2); + expect(linkedList.toString()).toBe('1,2,3'); + }); + + it('should prepend node to linked list', () => { + const linkedList = new DoublyLinkedList(); + + expect(linkedList.head).toBeNull(); + expect(linkedList.tail).toBeNull(); + linkedList.prepend(1); + linkedList.prepend(2); + linkedList.prepend(3); expect(linkedList.head.next.value).toBe(2); - expect(linkedList.tail.previous.value).toBe(1); - expect(linkedList.toString()).toBe('1,2'); + expect(linkedList.tail.previous.value).toBe(2); + expect(linkedList.toString()).toBe('3,2,1'); }); - it('should prepend node to linked list', () => { + it('should append and prepend node to linked list', () => { const linkedList = new DoublyLinkedList(); linkedList.prepend(2); diff --git a/skeletons/data-structures/doubly-linked-list/__test__/Insert.test.js b/skeletons/data-structures/doubly-linked-list/__test__/Insert.test.js new file mode 100644 index 0000000..7ffb9c5 --- /dev/null +++ b/skeletons/data-structures/doubly-linked-list/__test__/Insert.test.js @@ -0,0 +1,28 @@ + + +import { insert } from '../Insert'; +import { arrayToList } from '../ArrayToList'; + +describe('Insert', () => { + it('should insert into a sorted list ', () => { + + + expect( insert( arrayToList([ 1, 3,5 ]) , 4 , function(a, b){return a.value < b.value;} ).toString()).toBe('1,3,4,5'); + + expect( insert( arrayToList([ 1, 3,4 ]) , 5 , (a, b) => {return a.value < b.value;} ).toString()).toBe('1,3,4,5'); + + + expect( insert( arrayToList([ 1, 3]) , 5 , function(a, b){return a.value < b.value;} ).toString()).toBe('1,3,5'); + expect( insert( arrayToList([ 3,5,6 ]) , 1 , function(a, b){return a.value < b.value;} ).toString()).toBe('1,3,5,6'); + +}); + it('should insert into an empty list ', () => { + expect( insert( arrayToList([ ]) , 1, function(a, b){return a.value < b.value;} ).toString()).toBe('1'); + +}); + + + +}); + + diff --git a/skeletons/data-structures/doubly-linked-list/__test__/MergeDLL.test.js b/skeletons/data-structures/doubly-linked-list/__test__/MergeDLL.test.js new file mode 100644 index 0000000..3586689 --- /dev/null +++ b/skeletons/data-structures/doubly-linked-list/__test__/MergeDLL.test.js @@ -0,0 +1,25 @@ + + +import { merge } from '../MergeDLL'; +import { arrayToList } from '../ArrayToList'; + +describe('Merge', () => { + it('should merge into a sorted list ', () => { + + + expect( merge( arrayToList([ 1, 3,5 ]) , arrayToList([ 2, 4,6 ]) , function(a, b){return a.value < b.value;} ).toString()).toBe('1,2,3,4,5,6'); + + expect( merge( arrayToList([ 1, 3]) , arrayToList([ ]) , function(a, b){return a.value < b.value;} ).toString()).toBe('1,3'); + expect( merge( arrayToList([ ]), arrayToList([ 3,5,6 ]) , function(a, b){return a.value < b.value;} ).toString()).toBe('3,5,6'); + +}); + it('should merge into an empty list ', () => { + expect( merge( arrayToList([ ]) , arrayToList([ 3,5,6 ]) , function(a, b){return a.value < b.value;} ).toString()).toBe('3,5,6'); + +}); + + + +}); + + diff --git a/skeletons/data-structures/doubly-linked-list/__test__/PriorityQueue.test.js b/skeletons/data-structures/doubly-linked-list/__test__/PriorityQueue.test.js new file mode 100644 index 0000000..73f1fe6 --- /dev/null +++ b/skeletons/data-structures/doubly-linked-list/__test__/PriorityQueue.test.js @@ -0,0 +1,70 @@ +import PriorityQueue from '../PriorityQueue'; + + + +describe('PriorityQueue', () => { + + it('should create empty priority queue', () => { + const queue = new PriorityQueue(); + expect(queue).not.toBeNull(); + expect(queue.store).not.toBeNull(); + }); + + it('should enqueue data to priority queue', () => { + const queue = new PriorityQueue( (a,b) => { return a.value < b.value } ); + + + queue.enqueue(4); + queue.enqueue(3); + queue.enqueue(1); + queue.enqueue(2); + + expect(queue.toString()).toBe('1,2,3,4'); + }); + + it('should be possible to enqueue/dequeue in order', () => { + const queue = new PriorityQueue(); + + queue.enqueue(2); + + queue.enqueue(1); + + expect(queue.getHighest()).toBe(1); + expect(queue.getHighest()).toBe(2); + }); + + it('should peek data from priority queue', () => { + const queue = new PriorityQueue(); + + expect(queue.peek()).toBeNull(); + + queue.enqueue(1); + queue.enqueue(2); + + expect(queue.peek()).toBe(1); + expect(queue.peek()).toBe(1); + }); + + it('should check if queue is empty', () => { + const queue = new PriorityQueue(); + + expect(queue.isEmpty()).toBeTruthy(); + + queue.enqueue(1); + + expect(queue.isEmpty()).toBeFalsy(); + }); + + it('should dequeue from queue in given order', () => { + const queue = new PriorityQueue(); + + queue.enqueue(2); + queue.enqueue(1); + + expect(queue.getHighest()).toBe(1); + expect(queue.getHighest()).toBe(2); + expect(queue.getHighest()).toBeNull(); + expect(queue.isEmpty()).toBeTruthy(); + }); +}); + diff --git a/skeletons/data-structures/doubly-linked-list/__test__/Reverse.test.js b/skeletons/data-structures/doubly-linked-list/__test__/Reverse.test.js new file mode 100644 index 0000000..8eb5c8a --- /dev/null +++ b/skeletons/data-structures/doubly-linked-list/__test__/Reverse.test.js @@ -0,0 +1,31 @@ + +import { reverseDLL , reverseInPlace } from '../Reverse'; +import { arrayToList } from '../ArrayToList'; + +describe('Reverse', () => { + it('should reverse list ', () => { + + + expect( reverseDLL( arrayToList([ 1, 2,3]) ).toString()).toBe('3,2,1'); + + expect( reverseDLL( arrayToList( [ ] )).toString()).toBe(''); + + expect( reverseDLL( arrayToList( [1 ] )).toString()).toBe('1'); + + expect( reverseDLL( arrayToList([1 ,1]) ).toString()).toBe('1,1'); + +}); + + it('should reverse list in place ', () => { + + expect( reverseInPlace( arrayToList([ 1, 2,3]) ).toString()).toBe('3,2,1'); + + expect( reverseInPlace( arrayToList( [ ] )).toString()).toBe(''); + + expect( reverseInPlace( arrayToList( [1 ] )).toString()).toBe('1'); + + expect( reverseInPlace( arrayToList([1 ,1]) ).toString()).toBe('1,1'); +}); + +}); + diff --git a/skeletons/data-structures/doubly-linked-list/__test__/SmallestValue.test.js b/skeletons/data-structures/doubly-linked-list/__test__/SmallestValue.test.js new file mode 100644 index 0000000..0e1dd57 --- /dev/null +++ b/skeletons/data-structures/doubly-linked-list/__test__/SmallestValue.test.js @@ -0,0 +1,18 @@ +import { smallestValue } from '../SmallestValue'; +import { arrayToList } from '../ArrayToList'; + + +describe('SmallestValue', () => { + it('should find smallets value in a list ', () => { + + + expect( smallestValue( arrayToList( [ 1, 2]) ).toString()).toBe('1'); + expect( smallestValue( arrayToList( [ ]) )).toBeNull(); + expect( smallestValue( arrayToList( [ 1, 1]) ).toString()).toBe('1'); + expect( smallestValue( arrayToList( [ -1,1, 2]) ).toString()).toBe('-1'); + expect( smallestValue( arrayToList( [ -1, -2]) ).toString()).toBe('-2'); + +}); + +}); + diff --git a/skeletons/data-structures/heap/FloydsHeapAlg.js b/skeletons/data-structures/heap/FloydsHeapAlg.js new file mode 100644 index 0000000..ea66b64 --- /dev/null +++ b/skeletons/data-structures/heap/FloydsHeapAlg.js @@ -0,0 +1,6 @@ +import Heap from './Heap'; + + + +export function makeHeap(arr){ +} diff --git a/skeletons/data-structures/heap/Heap.js b/skeletons/data-structures/heap/Heap.js new file mode 100644 index 0000000..bd3f48d --- /dev/null +++ b/skeletons/data-structures/heap/Heap.js @@ -0,0 +1,75 @@ + +function parent(i){ + } + +function left(i){ + +} + +function right(i){ + +} + + + +export default class Heap { + constructor( capacity = 100, comp = (a,b) => {return a < b} ) { + + } + + + swap(i,j){ + + + } + + + isEmpty(){ + + } + + isHeap(){ + + + } + + extractMin(){ + + } + + getMin(){ + + return this.store[0]; + } + + insertKey(el){ + + } + + decreaseKey(i,new_val){ + + } + + + deleteKey(i){ + + } + + + heapify(i){ + + } + + + + +toString(){ + + var res = this.store.slice(0,this.size); + + return res.map(node => node.toString()).toString(); + + +} + +} diff --git a/skeletons/data-structures/heap/MedianOfStream.js b/skeletons/data-structures/heap/MedianOfStream.js new file mode 100644 index 0000000..641f523 --- /dev/null +++ b/skeletons/data-structures/heap/MedianOfStream.js @@ -0,0 +1,8 @@ +import Heap from './Heap' + + +export function median(arr){ + + + +} diff --git a/skeletons/data-structures/heap/MergeNSortedArrays.js b/skeletons/data-structures/heap/MergeNSortedArrays.js new file mode 100644 index 0000000..c4740c3 --- /dev/null +++ b/skeletons/data-structures/heap/MergeNSortedArrays.js @@ -0,0 +1,8 @@ + +import Heap from './Heap'; + + +export function mergeNSortedArrays( arrs){ + + +} diff --git a/skeletons/data-structures/heap/PriorityQueue.js b/skeletons/data-structures/heap/PriorityQueue.js new file mode 100644 index 0000000..270d983 --- /dev/null +++ b/skeletons/data-structures/heap/PriorityQueue.js @@ -0,0 +1,27 @@ +import Heap from './Heap'; + + +export default class PriorityQueueHeap { + + } + + + isEmpty() { + + } + + peek() { + + } + + enqueue(el) { + + } + + getHighest() { + + } + + toString() { + } +} diff --git a/skeletons/data-structures/heap/README.md b/skeletons/data-structures/heap/README.md new file mode 100644 index 0000000..741df6a --- /dev/null +++ b/skeletons/data-structures/heap/README.md @@ -0,0 +1,69 @@ +# Heap + +## Description + +A binary heap is a data structure in the form of a binary tree. Binary heaps are a common way of implementing priority queues, which we have earlier impleented using linked lists. The binary heap was ,invented for a sorting algorithm, heapsort.[ + +A binary heap is defined as a binary tree with two constraints: + +The Shape property: a binary heap is a complete binary tree; that is, all levels of the tree, except possibly the last one (deepest) are fully filled, and, if the last level of the tree is not complete, the nodes of that level are filled from left to right. We tested this property in the section on binary trees. + +The Heap property: the key stored in each node is either greater than or equal to (≥) or less than or equal to (≤) the keys in the node's children, according to some comparator. +Heaps where the parent key is greater than or equal to (≥) the child keys are called max-heaps; those where it is less than or equal to (≤) are called min-heaps. + + + +## Implementation + +Binary heaps are usually implemented using an array, rather than a binary tree. This seems strange, as a heap is defined a kind of binary tree, but in practice, the array implementaiton is much faster. + +A complete binary tree can be stored as an array, in the followiung way. The root is the element in position 0. Its left child is in position1, and it right in position 2. In general, the left child of a node in position i is at 2*i +1, and the right child is at 2*i+2. The parent of a node can be found from the nodes position by subtracting one and dividing. + +To make an empty head, we need it capacity, and the comparator that it will use. We set its size to be 0. An empty head has size zero, and this is tested by `isEmpty`. + +We can test if a heap data structure has the heap property, that is, if every node is smaller than its children, by checking that the nodes with children, those less than size/2, are smaller than their leaft and right children. This is the `isHeap` function. + +`extractMin` returns the top element, that is, the element in position 0. It also removes this element, so needs to fix up the rest of the heap. We do this by putting the last element in the heap in position 0, and calling `heapify`. `heapify` is a function that converts a data structure into a heap, so long as the only element out of position is the top element. + +`heapify` check which of the top, left and right elements are smallest. It puts the smallest one in the root position. If this swaps the left or right node with the root, it calls `heapify` on the subtree that it has modified. As the height of the heap with n elements, is log(n), `heapify` only takes log(n) time. + +`insertKey` add an element at the end of the array, and swaps that element with its parents until the element is larger than its parent. + +`getMin`, the heap equivalent of `peek` returns the element in position 0. + +Heaps were invented for Heapsort. This algorithm takes an an array, and sorts it. The simplest way to do this is to insert each element in a heap, and then extractMin untol the heap is empty. This takes O(nlog(n)) time, as `insertKey` and `extractMin` takes log(n) time. + + +## Heap Problems + +**Priority Queue** + +Earlier we inmplemented a Priority Queue using a lined list. Now implement it using a heap. + +A priority queue is a data structure that acts like a queue where you can 'enqueue' and 'dequeue' elements, but instead of returning the earliest element still in the queue, it returns the element with the highest priority. It is normal to have a comparison function, called a comparator, to tell whether one item has higher priority than another. If two elements have the same priority, you should return the earlier element. You should implement 'enqueue', `getHighest`, `peek`, and `isEmpty`. `enqueue` and `getHighest` should take at mpst O(log(n)) time, and the other operations should take O(1). + + +**makeHeap** + + +Rather than turn an array into a heap by calling insertMin n times, Floyd invented a way of turning an array in to a heap in linear time. This works by calling heapify, starting with element size/2, and continuing down to element 0. This calls heapigy size/2 times, so naively it seems to take O( n * log(n)). However, due to math which is more complicated than we deal with here, this actually takes linear time. This is because almost all the work is done at the low levels, and `heapify` is cheaper the lower in a tree you are. + +Implement Floyd's makeHeap algorithm. + +`makeHeap([5,4,3,2,1]).toString() = '1,2,3,4,5'` + + + + +**Merge k Sorted Arrays** + +Given k sorted arrays of size n each, merge them. + +The fast way to do this is to push a triple, the array number, the index and value, to a heap. We push one triple from each array. We `extractMin` the smallest, comparing on just the value. We insert the next element from the array whose triple was returned by `extractMin`. + + + +**Median of a Stream** + +Return the running median of a stream in an efficient way. This can be done by keeping all the elements greater than the current median in a min heap, and all the elements less than the current median in a max heap. + diff --git a/skeletons/data-structures/heap/__test__/FloydsHeapAlg.test.js b/skeletons/data-structures/heap/__test__/FloydsHeapAlg.test.js new file mode 100644 index 0000000..c74a9fd --- /dev/null +++ b/skeletons/data-structures/heap/__test__/FloydsHeapAlg.test.js @@ -0,0 +1,32 @@ +import { makeHeap } from '../FloydsHeapAlg'; + + + + +describe(' Heap ', () => { + + + it('should create a Heap from an array.', () => { + + + var h = makeHeap([5,4,3,2,1]); + + expect(h.isHeap()).toBeTruthy(); +}); + + + it('should extract in order.', () => { + + var h = makeHeap([5,4,3,2,1]); + + expect(h.extractMin()).toBe(1); + expect(h.extractMin()).toBe(2); + expect(h.extractMin()).toBe(3); + expect(h.extractMin()).toBe(4); + expect(h.extractMin()).toBe(5); + expect(h.extractMin()).toBeNull(); + + +}); + +}); diff --git a/skeletons/data-structures/heap/__test__/Heap.test.js b/skeletons/data-structures/heap/__test__/Heap.test.js new file mode 100644 index 0000000..db071d4 --- /dev/null +++ b/skeletons/data-structures/heap/__test__/Heap.test.js @@ -0,0 +1,166 @@ +import Heap from '../Heap'; + + + + +describe(' Heap ', () => { + + + it('should create an empty Heap.', () => { + + var h = new Heap(); + + expect(h.store).not.toBeNull(); + + expect(h.capacity).toBe(100); + expect(h.size).toBe(0); + + h = new Heap(100); + + expect(h.capacity).toBe(100); +}); + + + it('should add values to heap.', () => { + + var h = new Heap(); + + h.insertKey(3); + h.insertKey(2); + expect(h.toString()).toBe('2,3'); + + + +}); + + + + it('should delete values .', () => { + + var h = new Heap(); + + h.insertKey(3); + h.insertKey(2); + + h.deleteKey(1); + expect(h.toString()).toBe('2'); + h.insertKey(15); + h.insertKey(5); + h.insertKey(4); + h.insertKey(45); + expect(h.toString()).toBe('2,4,5,15,45'); + + + +}); + + + + it('should extract the min.', () => { + + var h = new Heap(); + + h.insertKey(3); + h.insertKey(2); + expect(h.toString()).toBe('2,3'); + + h.deleteKey(1); + expect(h.toString()).toBe('2'); + h.insertKey(15); + h.insertKey(5); + h.insertKey(4); + h.insertKey(45); + expect(h.toString()).toBe('2,4,5,15,45'); + + + expect(h.extractMin()).toBe(2); + + +}); + + it('should extractget the min.', () => { + + var h = new Heap(); + + h.insertKey(3); + h.insertKey(2); + expect(h.toString()).toBe('2,3'); + + h.deleteKey(1); + expect(h.toString()).toBe('2'); + h.insertKey(15); + h.insertKey(5); + h.insertKey(4); + h.insertKey(45); + + + expect(h.extractMin()).toBe(2); + + + expect(h.getMin()).toBe(4); + +}); + + + it('should decrease keys .', () => { + + var h = new Heap(); + + h.insertKey(3); + h.insertKey(2); + expect(h.toString()).toBe('2,3'); + + h.deleteKey(1); + expect(h.toString()).toBe('2'); + h.insertKey(15); + h.insertKey(5); + h.insertKey(4); + h.insertKey(45); + expect(h.toString()).toBe('2,4,5,15,45'); + + + expect(h.extractMin()).toBe(2); + + expect(h.toString()).toBe('4,15,5,45'); + + expect(h.getMin()).toBe(4); + + h.decreaseKey(2, 1); + expect( h.getMin()).toBe(1); + + + +}); + + + it('should test the heap property', () => { + + var h = new Heap(); + + h.insertKey(3); + h.insertKey(2); + + expect(h.isHeap()).toBeTruthy(); + + h.deleteKey(1); + expect(h.isHeap()).toBeTruthy(); + h.insertKey(15); + h.insertKey(5); + h.insertKey(4); + h.insertKey(45); + expect(h.isHeap()).toBeTruthy(); + + h.extractMin(); + + expect(h.isHeap()).toBeTruthy(); + + + h.decreaseKey(2, 1); + + expect(h.isHeap()).toBeTruthy(); + +}); + + + +}); diff --git a/skeletons/data-structures/heap/__test__/MedianOfStream.test.js b/skeletons/data-structures/heap/__test__/MedianOfStream.test.js new file mode 100644 index 0000000..fac58d1 --- /dev/null +++ b/skeletons/data-structures/heap/__test__/MedianOfStream.test.js @@ -0,0 +1,31 @@ +import { median } from '../MedianOfStream'; + + + + +describe(' Median ', () => { + + + it('should track the median.', () => { + + expect(median([1,2,3,4,5,6,7,8,9]).toString() ).toBe('1,1.5,2,2.5,3,3.5,4,4.5,5'); + + }); + + + it('should track the median when items are out of order.', () => { + + expect(median([1,10,3,4,10,6,10,8,9]).toString() ).toBe('1,5.5,3,3.5,4,5,6,7,8'); + +}); + +it('should track the median when many items are equal.', () => { + + + expect(median([1,1,1,1,1,1,1,2,2,2,2,2,2,2,2,3,3,3,3,3,3,3,3,3,3,3,3]).toString() ).toBe('1,1,1,1,1,1,1,1,1,1,1,1,1,1.5,2,2,2,2,2,2,2,2,2,2,2,2,2'); + + +}); + +}); + diff --git a/skeletons/data-structures/heap/__test__/MergeNSortedArrrays.test.js b/skeletons/data-structures/heap/__test__/MergeNSortedArrrays.test.js new file mode 100644 index 0000000..2ca7338 --- /dev/null +++ b/skeletons/data-structures/heap/__test__/MergeNSortedArrrays.test.js @@ -0,0 +1,44 @@ + +import { mergeNSortedArrays } from '../MergeNSortedArrays' + +describe(' MergeNSortedArrays ', () => { + + + it('should merge n arrays no interleaving ', () => { + + expect( mergeNSortedArrays( [ [1,2,3], [4,5,6]]).toString()).toBe('1,2,3,4,5,6'); + + + + }); + + + it('should merge n arrays nterleaving ', () => { + + expect( mergeNSortedArrays( [ [1,3,5], [2,4,6]]).toString()).toBe('1,2,3,4,5,6'); + + + + }); + + + + it('should merge n arrays n > 3 ', () => { + + expect( mergeNSortedArrays( [ [1,5,9], [2,6,10],[3,7,11],[4,8,12] ]).toString()).toBe('1,2,3,4,5,6,7,8,9,10,11,12'); + + + + }); + + + it('should merge n arrays different lengths ', () => { + + expect( mergeNSortedArrays( [ [1,5,9,15,18], [2,6,10],[3,7,11,13],[4,8,12] ]).toString()).toBe('1,2,3,4,5,6,7,8,9,10,11,12,13,15,18'); + + + + }); + + +}); diff --git a/skeletons/data-structures/heap/__test__/PriorityQueue.test.js b/skeletons/data-structures/heap/__test__/PriorityQueue.test.js new file mode 100644 index 0000000..160b36c --- /dev/null +++ b/skeletons/data-structures/heap/__test__/PriorityQueue.test.js @@ -0,0 +1,72 @@ +import PriorityQueueHeap from '../PriorityQueue'; + + + +describe('PriorityQueueHeap', () => { + + it('should create empty priority queue', () => { + const queue = new PriorityQueueHeap(); + expect(queue).not.toBeNull(); + expect(queue.store).not.toBeNull(); + }); + + it('should enqueue data to priority queue', () => { + const queue = new PriorityQueueHeap( (a,b) => { return a < b } ); + + console.log("Starting PQ"); + + queue.enqueue(4); + queue.enqueue(3); + queue.enqueue(1); + queue.enqueue(2); + + expect(queue.toString()).toBe('1,2,3,4'); + }); + + it('should be possible to enqueue/dequeue in order', () => { + const queue = new PriorityQueueHeap(); + + queue.enqueue(2); + + queue.enqueue(1); + expect(queue.toString()).toBe('1,2'); + + expect(queue.getHighest()).toBe(1); + expect(queue.getHighest()).toBe(2); + }); + + it('should peek data from priority queue', () => { + const queue = new PriorityQueueHeap(); + + expect(queue.peek()).toBeNull(); + + queue.enqueue(1); + queue.enqueue(2); + + expect(queue.peek()).toBe(1); + expect(queue.peek()).toBe(1); + }); + + it('should check if queue is empty', () => { + const queue = new PriorityQueueHeap(); + + expect(queue.isEmpty()).toBeTruthy(); + + queue.enqueue(1); + + expect(queue.isEmpty()).toBeFalsy(); + }); + + it('should dequeue from queue in given order', () => { + const queue = new PriorityQueueHeap(); + + queue.enqueue(2); + queue.enqueue(1); + + expect(queue.getHighest()).toBe(1); + expect(queue.getHighest()).toBe(2); + expect(queue.getHighest()).toBeNull(); + expect(queue.isEmpty()).toBeTruthy(); + }); +}); + diff --git a/skeletons/data-structures/linked-list/ArrayToLinkedList.js b/skeletons/data-structures/linked-list/ArrayToLinkedList.js new file mode 100644 index 0000000..d15c8f5 --- /dev/null +++ b/skeletons/data-structures/linked-list/ArrayToLinkedList.js @@ -0,0 +1,5 @@ +import LinkedList from './LinkedList'; + + +export function arrayToLinkedList(arr){ +} diff --git a/skeletons/data-structures/linked-list/FindLoop.js b/skeletons/data-structures/linked-list/FindLoop.js new file mode 100644 index 0000000..f01ddfd --- /dev/null +++ b/skeletons/data-structures/linked-list/FindLoop.js @@ -0,0 +1,6 @@ +import LinkedList from './LinkedList'; + + +export function findLoop( ll){ + +} diff --git a/skeletons/data-structures/linked-list/Palindrome.js b/skeletons/data-structures/linked-list/Palindrome.js new file mode 100644 index 0000000..76f365e --- /dev/null +++ b/skeletons/data-structures/linked-list/Palindrome.js @@ -0,0 +1,10 @@ +import LinkedList from './LinkedList'; + +import { reverse, reverseInPlace } from "./ReverseInPlace"; + +export function palindrome(ll){ + + +} +export function palindromeInPlace(ll){ +} diff --git a/skeletons/data-structures/linked-list/README.md b/skeletons/data-structures/linked-list/README.md index fe204ed..b4a6728 100644 --- a/skeletons/data-structures/linked-list/README.md +++ b/skeletons/data-structures/linked-list/README.md @@ -6,7 +6,7 @@ A linked list is a linear colletion of elements _in sequential order_. Imagine a ![Linked List](../../../assets/linked-list-overview.png) -A linked list is composed of a smaller data structure usually called a `Node`. The node object contains the `value` of the element itself, and also a pointer to the `next` node in the chain. By utilizing `node.next`, you can traverse the list. The beginning of a linked list is denoted with `head` and the last element is `tail`. +A linked list is composed of a smaller data structure usually called a `Node`. The node object contains the `value` of the element itself, and also a pointer to the `next` node in the chain. By utilizing `node.next`, you can traverse the list. The beginning of a linked list is denoted with `head` and the last element is `tail`. The 'tail' of a list may refer either to the rest of the list after the head, or to the last node in the list. We will refer to the last node as the tail, but it is worth remembering that some people, especially those fro mthe functional programming community, consider the rest of the list the tail. In a singly-linked list, the link is established in _one direction_ only, where the node only keeps track of the next node in the chain. This means that with a singly-linked list, you can only traverse in the direction of the tail. On the other hand, a node in a doubly-linked list keeps track of both `next` and `previous` which allows you to traverse in both directions (towards `head` and `tail`). @@ -27,9 +27,9 @@ In this exercise, implement the following functions for the `LinkedListNode` and - `append(value)` - Write a method that inserts the `value` at the end of the linked list. - `delete(value)` - - Write a method that deletes the `value` in the linked list. + - Write a method that deletes the first node that contains the `value` in the linked list. - `find(value)` - - Write a method that returns the `node` that contains the `value`. + - Write a method that returns the first `node` that contains the `value`. - `insertAfter(value, targetValue)` - Write a method that inserts the `node` after the `node` that contains the `targetValue`. - `deleteHead()` @@ -106,7 +106,7 @@ Now, let's open up `LinkedList.js` in our editor. - Create a new `Node`. - Set the tail's `next` to be the new node. - Update `tail` to point at the new node. -- Again, take into consideration when this is the first item stored in the list. +- Again, take into consideration when this is the first/last item stored in the list. ### `find(value)` @@ -125,6 +125,9 @@ Now, let's open up `LinkedList.js` in our editor. - Think about how the above concept can be applied to find the target node. +A simple implementation of `find` would take a single argument, the value to be found. However, sometimes we want to find an object, and know only the value of ones of its slots. We might have a list of employee objects with slots name, address, and employee-id. Our `find` method should be able to return an employee object just by taking the id. We enable this by making the single argument to find and object with two slots, `value:` and `callback:`. If the `value:` slot is set, we use it, as normal. If the `callback:` slot if set, it must be a function that can be applied to the nodes of the list, and which returns an object. It should return true for the object that should be returned. This use of callbacks is very common in javascript, so is worth practising. + + ### `insertAfter(value, insertValue)` - The `insertAfter` method stores the `insertValue` right after the node with the `value`. @@ -134,6 +137,7 @@ Now, let's open up `LinkedList.js` in our editor. - Find the node with the target value. - Set the found node's `next` to the new node. - Set new node's `next` to the found node's `next. + - If the node is inserted at the end, the tail needs to be set correctly. - Utilize the previously written `find` method to find the node. ### `delete(value)` @@ -149,11 +153,31 @@ Now, let's open up `LinkedList.js` in our editor. - This means that we must locate the node right before the target node. Think about how you would be able to tell when the next node will be the target node. - Also, take into consideration if the `head` or `tail` node is the node to be deleted. - This is the most complex operation in a linked list. Drawing the operation on paper can help you visualize the problem. Don't hesitate to reach out to #basic-algorithms channel on Slack if you are stuck. + - You might try to use `find` to get to the node you want to delete. This does not work. Can you see why? ### `deleteHead()` / `deleteTail()` -- The `deleteHead/Tail` methods are utility methods for common operations. +- The `deleteHead/Tail` methods are utility methods for common operations. The `deleteTail()` function is slow. It needs to traverse the entire list. ## Time and Space Complexity Analysis -This section will be released in the future when the Big O notation tutorials are written. Please join #basic-algorithms channel for the update notifications. +Some of the operations on linked lists take a fixed, constant amount of instructions, no matter the length of the list. `prepend`, `append`, `inserAfter`, and `deleteHead` obviously just touch at most two nodes. Other functions may need to look at every node in the data structure. `find` and `delete` needs to look at every node if they are asked to find or delete a vlaue that is not there. The last function `deleteTail` needs to traverse the entire list, as it needs to set the tail to the second last node. As we can only go forwards, we need to travel down the entire list. + +## Problems + +**Find a loop in a list** + +Given a list, we can ask whether there is a loop in it. One obvious way to check is to remember each node that we visit in a hash table, and use this to see if there is a loop. There is another way that only requires a constant amount of extra storage (in fact, it just needs to remember two nodes). Floyd's cycle finding algorithm, as this is called, is not obvious, but it is a trick that many interviewers expect you to know. The idea is simple. You travel through the list at two speeds. One traversal goes two steps for each step the other takes. The fast traversal will eventually overtake the slow traversal if there is a loop. If there is no loop, the fast traversal will hit the end of the list. + + +**Reverse a list in place** + +A trick that people expect you to know is how to reverse a list in place. When you traverse a list, from a given node, you can find the next node. Once you have two nodes in a row, you can find the third node, and make the second node point to the first. You can continue on, flipping the pointers backwards, so they point to the previous node, rather than the next. This is unwise in any production system, especially if there is concurrency, and is very bad coding practice anywhere. Despite this, interviewers love asking about it. + + + +**Palidromes** + +A palidrome is a word that reads the same backwards as forwards. "redivider" is a palidrome. If we store each letter in a different node in a singly linked list, the problem is, how can we tell if a list represents a palindrome. We can reverse the list, but there are ways to do this without making a new reversed copy of the list. First try to check for a palindrome with an extra reversed copy. + +Now try to find the length of a list, then find the middle element, then reverse the second half of the list. You now have two lists, the original first half, and the reversed second half. If these two lists are equal, the list is a palindrome. You should re-reverse the second half to return the list to its original form. diff --git a/skeletons/data-structures/linked-list/ReverseInPlace.js b/skeletons/data-structures/linked-list/ReverseInPlace.js new file mode 100644 index 0000000..6e233b3 --- /dev/null +++ b/skeletons/data-structures/linked-list/ReverseInPlace.js @@ -0,0 +1,11 @@ +import LinkedList from './LinkedList'; + + +export function reverse(ll){ + + +} + +export function reverseInPlace(ll){ + +} diff --git a/skeletons/data-structures/linked-list/__test__/ArrayToLinkedList.test.js b/skeletons/data-structures/linked-list/__test__/ArrayToLinkedList.test.js new file mode 100644 index 0000000..8c6f3a0 --- /dev/null +++ b/skeletons/data-structures/linked-list/__test__/ArrayToLinkedList.test.js @@ -0,0 +1,25 @@ +import { arrayToLinkedList } from '../ArrayToLinkedList'; +import LinkedList from '../LinkedList'; + + +describe('ArrayToLinkedList', () => { + it('Linked Lists should work ', () => { + const linkedList = new LinkedList(); + + linkedList.append(1); + linkedList.append(2); + expect(linkedList.toString()).toBe('1,2'); + +}); + + it('should turn arrays to lists ', () => { + expect( arrayToLinkedList( [ 1, 2,3] ).toString()).toBe('1,2,3'); + + expect( arrayToLinkedList( [ ] ).toString()).toBe(''); + + expect( arrayToLinkedList( [1 ] ).toString()).toBe('1'); + + expect( arrayToLinkedList( [1 ,1] ).toString()).toBe('1,1'); +}); + +}); diff --git a/skeletons/data-structures/linked-list/__test__/FindLoop.test.js b/skeletons/data-structures/linked-list/__test__/FindLoop.test.js new file mode 100644 index 0000000..f8f0d04 --- /dev/null +++ b/skeletons/data-structures/linked-list/__test__/FindLoop.test.js @@ -0,0 +1,40 @@ +import { arrayToLinkedList } from '../ArrayToLinkedList'; +import LinkedList from '../LinkedList'; +import { findLoop } from '../FindLoop'; + + + +describe('FindLoop', () => { + it('non loops should return false ', () => { + + + expect( findLoop( arrayToLinkedList( [ 1, 2,3] ) )).toBeFalsy(); + + expect( findLoop( arrayToLinkedList( [ 1] ) )).toBeFalsy(); + + expect( findLoop( arrayToLinkedList( [ ] ) )).toBeFalsy(); + + +}); + + + it(' loops should return true ', () => { + var ll = new LinkedList(); + ll.append(1); + ll.append(2); + ll.append(3); + ll.append(4); + + /* + ll.tail.next = ll.head; + expect( arrayToLinkedList(ll )).toBeTruthy(); + + ll.head.next = ll.head; + expect( arrayToLinkedList(ll )).toBeTruthy(); +*/ + + + +}); + +}); diff --git a/skeletons/data-structures/linked-list/__test__/LinkedList.test.js b/skeletons/data-structures/linked-list/__test__/LinkedList.test.js index e7a7d01..94730bf 100644 --- a/skeletons/data-structures/linked-list/__test__/LinkedList.test.js +++ b/skeletons/data-structures/linked-list/__test__/LinkedList.test.js @@ -139,6 +139,13 @@ describe('LinkedList', () => { .insertAfter(2, 1); expect(linkedList.toString()).toBe('1,2,3'); + const linkedList1 = new LinkedList(); + linkedList1 + .append(1) + .append(2) + .insertAfter(3, 2); + + expect(linkedList1.tail.toString()).toBe('3'); }); it('should delete linked list tail', () => { diff --git a/skeletons/data-structures/linked-list/__test__/Palindrome.test.js b/skeletons/data-structures/linked-list/__test__/Palindrome.test.js new file mode 100644 index 0000000..9b843a0 --- /dev/null +++ b/skeletons/data-structures/linked-list/__test__/Palindrome.test.js @@ -0,0 +1,48 @@ +import { arrayToLinkedList } from '../ArrayToLinkedList'; +import LinkedList from '../LinkedList'; +import { palindrome, palindromeInPlace } from '../Palindrome'; + + + +describe('ReverseInPlace', () => { + it(' Check Palindromes ', () => { + + + expect( palindrome( arrayToLinkedList( [ 1, 2,1] ) )).toBeTruthy(); + + expect( palindrome( arrayToLinkedList( [ 1, 2,2,1] ) )).toBeTruthy(); + + + expect( palindrome( arrayToLinkedList( [ 1, 2,3 ] ) )).toBeFalsy(); + + +}); + + it(' Check single and empty Palindromes ', () => { + expect( palindrome( arrayToLinkedList( [ 1] ) )).toBeTruthy(); + + expect( palindrome( arrayToLinkedList( [ ] ))).toBeTruthy(); + }); + + + it(' Check Palindromes ', () => { + + + expect( palindromeInPlace( arrayToLinkedList( [ 1, 2,1] ) )).toBeTruthy(); + + expect( palindromeInPlace( arrayToLinkedList( [ 1, 2,2,1] ) )).toBeTruthy(); + + + expect( palindromeInPlace( arrayToLinkedList( [ 1, 2,3 ] ) )).toBeFalsy(); + + +}); + + it(' Check single and empty Palindromes ', () => { + expect( palindromeInPlace( arrayToLinkedList( [ 1] ) )).toBeTruthy(); + + expect( palindromeInPlace( arrayToLinkedList( [ ] ))).toBeTruthy(); + }); + + +}); diff --git a/skeletons/data-structures/linked-list/__test__/ReverseInPlace.test.js b/skeletons/data-structures/linked-list/__test__/ReverseInPlace.test.js new file mode 100644 index 0000000..85faf29 --- /dev/null +++ b/skeletons/data-structures/linked-list/__test__/ReverseInPlace.test.js @@ -0,0 +1,26 @@ +import { arrayToLinkedList } from '../ArrayToLinkedList'; +import LinkedList from '../LinkedList'; +import { reverseInPlace, reverse } from '../ReverseInPlace'; + + + +describe('ReverseInPlace', () => { + it(' lists should reverse in place ', () => { + expect( reverseInPlace( arrayToLinkedList( [ 1, 2,3] ) ).toString() ).toBe('3,2,1'); + + expect( reverseInPlace( arrayToLinkedList( [ 1] ) ).toString() ).toBe('1'); + + expect( reverseInPlace( arrayToLinkedList( [ ] ) ).toString() ).toBe(''); + + }); + + it(' lists should reverse ', () => { + expect( reverse( arrayToLinkedList( [ 1, 2,3] ) ).toString() ).toBe('3,2,1'); + + expect( reverse( arrayToLinkedList( [ 1] ) ).toString() ).toBe('1'); + + expect( reverse( arrayToLinkedList( [ ] ) ).toString() ).toBe(''); + + }); + +}); diff --git a/skeletons/data-structures/queue/QueueUsingStack.js b/skeletons/data-structures/queue/QueueUsingStack.js new file mode 100644 index 0000000..c73a58e --- /dev/null +++ b/skeletons/data-structures/queue/QueueUsingStack.js @@ -0,0 +1,25 @@ +import Stack from '../stack/Stack' + + +export default class QueueS { + constructor() { + this.store = new Stack(); + this.otherStack = new Stack(); + } + + isEmpty() { + } + + peek() { + } + + enqueue(el) { + } + + dequeue() { + } + + toString() { + } +} + diff --git a/skeletons/data-structures/queue/QueueUsingStackR.js b/skeletons/data-structures/queue/QueueUsingStackR.js new file mode 100644 index 0000000..1e34b09 --- /dev/null +++ b/skeletons/data-structures/queue/QueueUsingStackR.js @@ -0,0 +1,30 @@ +import Stack from '../stack/Stack' + + +export default class QueueR { + constructor() { + this.store = new Stack(); + this.otherStack = new Stack(); + } + + isEmpty() { + + } + + peek() { + + } + + enqueue(el) { + + } + + dequeue() { + + } + + toString() { + + } +} + diff --git a/skeletons/data-structures/queue/README.md b/skeletons/data-structures/queue/README.md index 64a5660..b0156e8 100644 --- a/skeletons/data-structures/queue/README.md +++ b/skeletons/data-structures/queue/README.md @@ -8,8 +8,8 @@ Queue implements FIFO through two operations; `enqueue` and `dequeue`, which can ![Queing operations](../../../assets/queue.png) -- `enqueue` operation stores the item at the back of the line -- `dequeue` operation retrieves the item from the front of the line +- `enqueue` operation stores the item at the back of the queue. +- `dequeue` operation retrieves the item from the front of the queue. (_In the diagram above, the right side is the front, and the left is the back. However, the same operations are applied even when the direction is reversed_) @@ -18,43 +18,70 @@ Queue implements FIFO through two operations; `enqueue` and `dequeue`, which can In this exercise, implement the following functions for the `Queue` class - `isEmpty()` - - Write a method that returns `true` if the queue is currently empty. + - Write a method that returns `true` if the queue is currently empty, and false otherwise. - `peek()` - - Write a method that returns the element at the front of the queue. -- `enqueue()` - - Write a method that stores an element into the queue. + - Write a method that returns the element at the front of the queue, or returns null if the queue is empty. +- `enqueue(el)` + - Write a method that adds an element(`el`) to the back of the queue. - `dequeue()` - - Write a method that retrieves an element from the queue. + - Write a method that removes an element from the front of the queue, and returns it. It should return null if the queue is empty. - `toString()` - The stringify method is provided for you. `toString()` is a useful method to implement into data structures for easier debugging. - For example, you could use it for logging: ``` const queue = new Queue(); - constole.log(queue.toString()); + console.log(queue.toString()); ``` - A queue is simple enough to be logged without actually needing `toString()`, but with more complex data structures, this is an invaluable method. ## Queue Exercises -Solve this exercise after writing a queue. +**Stack Queue** -**Bracket Matching** +(Solve this exercise after finishing the stack exercise). + +Implement a queue using stacks. Instead of using an array as your store, use a stack: +``` +import Stack from '../stack/Stack' + + +export default class QueueS { + constructor() { + this.store = new Stack(); + this.otherStack = new Stack(); + } + +etc. +``` + + +There are (at least) two ways to implement a queue. One way makes inserting very cheap, but makes popping and peeking expensive. The other makes popping and peeking very cheap, but makes inserting harder. Try to implement both ways. In both implementations, some operations require touching every objects that is stored. We say an algorithm that touches every object in the data structure is linear (or worse) or, in big O notation, O(n). An operation that can be completed in the same amount of steps, no matter how many objects are in the data struturem is said to be constant, or O(1). + +Ideally, in a Queue, the operations isEmpty, peek, enqueue, and dequeue should be constant or O(1). Whether or not the sample implementation is constant depends on the complexity of the underlying operations. An implementation that uses C arrays underneath should be O(1) almost all the time, save when it needs to re-allocate memory (as the array has grown or n elements have been unshifted). Re-allocating memory and copying is O(n), as it needs to touch every piece of data. + + +**Queue Stack** + +Now implement a stack using just one queue. + +peek, pop, and isEmpty are easy. The trick is to find a way to implement push so that the other functions work. As a hint, consider what would need to happen for the last element added to be the next element to be dequeued. All the other elements would need to be enqueued after it. This is another example of one function being linear, in this case enqueue, and the others being O(1). -Write an algorithm to determine if all of the delimiters in an expression are matched and closed. -- Delimiters are `(` `{` `[` - - Closed is `()` and `(()` would not be closed - - Matched is `{}`, and `{]` would not be matched. ``` -const bracketMatch = string => { +import Queue from "./Queue"; +import Stack from "../stack/Stack"; + +export default class StackQ { + constructor() { + this.store = new Queue(); + } -} +etc. -console.log(bracketMatch('{ac[bb]}') === true); -console.log(bracketMatch('[dklf(df(kl))d]{}') === true); -console.log(bracketMatch('{[[[]]]}') === true); -console.log(bracketMatch('{3234[fd') === false); -console.log(bracketMatch('{df][d}') === false); -console.log(bracketMatch('([)]') === false); ``` + + + + + diff --git a/skeletons/data-structures/queue/StackUsingQueue.js b/skeletons/data-structures/queue/StackUsingQueue.js new file mode 100644 index 0000000..a3cf62e --- /dev/null +++ b/skeletons/data-structures/queue/StackUsingQueue.js @@ -0,0 +1,27 @@ +import Queue from "./Queue"; +import Stack from "../stack/Stack"; + +export default class StackQ { + constructor() { + this.store = new Queue(); + } + + + isEmpty() { + } + + peek() { + } + + push(el) { + + } + + pop() { + + } + + toString() { + + } +} diff --git a/skeletons/data-structures/queue/__test__/Queue.test.js b/skeletons/data-structures/queue/__test__/Queue.test.js index 7bd3ba8..a1de34d 100644 --- a/skeletons/data-structures/queue/__test__/Queue.test.js +++ b/skeletons/data-structures/queue/__test__/Queue.test.js @@ -13,7 +13,7 @@ describe('Queue', () => { queue.enqueue(1); queue.enqueue(2); - expect(queue.toString()).toBe('1,2'); + expect(queue.toString()).toBe('2,1'); }); it('should be possible to enqueue/dequeue in order', () => { diff --git a/skeletons/data-structures/queue/__test__/QueueUsingStack.test.js b/skeletons/data-structures/queue/__test__/QueueUsingStack.test.js new file mode 100644 index 0000000..02b0b69 --- /dev/null +++ b/skeletons/data-structures/queue/__test__/QueueUsingStack.test.js @@ -0,0 +1,62 @@ +import QueueS from '../QueueUsingStack'; + +describe('QueueA', () => { + it('should create empty queue', () => { + const queue = new QueueS(); + expect(queue).not.toBeNull(); + expect(queue.store).not.toBeNull(); + }); + + it('should enqueue data to queue', () => { + const queue = new QueueS(); + + queue.enqueue(1); + queue.enqueue(2); + + expect(queue.toString()).toBe('2,1'); + }); + + it('should be possible to enqueue/dequeue in order', () => { + const queue = new QueueS(); + + queue.enqueue(1); + queue.enqueue(2); + + expect(queue.dequeue()).toBe(1); + expect(queue.dequeue()).toBe(2); + }); + + it('should peek data from queue', () => { + const queue = new QueueS(); + + expect(queue.peek()).toBeNull(); + + queue.enqueue(1); + queue.enqueue(2); + + expect(queue.peek()).toBe(1); + expect(queue.peek()).toBe(1); + }); + + it('should check if queue is empty', () => { + const queue = new QueueS(); + + expect(queue.isEmpty()).toBeTruthy(); + + queue.enqueue(1); + + expect(queue.isEmpty()).toBeFalsy(); + }); + + it('should dequeue from queue in FIFO order', () => { + const queue = new QueueS(); + + queue.enqueue(1); + queue.enqueue(2); + + expect(queue.dequeue()).toBe(1); + expect(queue.dequeue()).toBe(2); + expect(queue.dequeue()).toBeNull(); + expect(queue.isEmpty()).toBeTruthy(); + }); +}); diff --git a/skeletons/data-structures/queue/__test__/QueueUsingStackR.test.js b/skeletons/data-structures/queue/__test__/QueueUsingStackR.test.js new file mode 100644 index 0000000..9e3d4fb --- /dev/null +++ b/skeletons/data-structures/queue/__test__/QueueUsingStackR.test.js @@ -0,0 +1,62 @@ +import QueueR from '../QueueUsingStackR'; + +describe('QueueR', () => { + it('should create empty queue', () => { + const queue = new QueueR(); + expect(queue).not.toBeNull(); + expect(queue.store).not.toBeNull(); + }); + + it('should enqueue data to queue', () => { + const queue = new QueueR(); + + queue.enqueue(1); + queue.enqueue(2); + + expect(queue.toString()).toBe('2,1'); + }); + + it('should be possible to enqueue/dequeue in order', () => { + const queue = new QueueR(); + + queue.enqueue(1); + queue.enqueue(2); + + expect(queue.dequeue()).toBe(1); + expect(queue.dequeue()).toBe(2); + }); + + it('should peek data from queue', () => { + const queue = new QueueR(); + + expect(queue.peek()).toBeNull(); + + queue.enqueue(1); + queue.enqueue(2); + + expect(queue.peek()).toBe(1); + expect(queue.peek()).toBe(1); + }); + + it('should check if queue is empty', () => { + const queue = new QueueR(); + + expect(queue.isEmpty()).toBeTruthy(); + + queue.enqueue(1); + + expect(queue.isEmpty()).toBeFalsy(); + }); + + it('should dequeue from queue in FIFO order', () => { + const queue = new QueueR(); + + queue.enqueue(1); + queue.enqueue(2); + + expect(queue.dequeue()).toBe(1); + expect(queue.dequeue()).toBe(2); + expect(queue.dequeue()).toBeNull(); + expect(queue.isEmpty()).toBeTruthy(); + }); +}); diff --git a/skeletons/data-structures/queue/__test__/StackUsingQueue.test.js b/skeletons/data-structures/queue/__test__/StackUsingQueue.test.js new file mode 100644 index 0000000..cf85672 --- /dev/null +++ b/skeletons/data-structures/queue/__test__/StackUsingQueue.test.js @@ -0,0 +1,73 @@ +import StackQ from '../StackUsingQueue'; + +describe('StackQ', () => { + it('should create empty stack', () => { + const stack = new StackQ(); + expect(stack).not.toBeUndefined(); + expect(stack.store).not.toBeUndefined(); + }); + + it('should stack data to stack', () => { + const stack = new StackQ(); + + stack.push(1); + expect(stack.toString()).toBe('1'); + stack.push(2); + expect(stack.toString()).toBe('1,2'); + stack.push(3); + expect(stack.toString()).toBe('1,2,3'); + stack.push(4); + + expect(stack.toString()).toBe('1,2,3,4'); + expect(stack.toString()).toBe('1,2,3,4'); + }); + + it('should peek data from stack', () => { + const stack = new StackQ(); + + expect(stack.peek()).not.toBeUndefined(); + + stack.push(1); + expect(stack.peek()).toBe(1); + stack.push(2); + + expect(stack.peek()).toBe(2); + expect(stack.peek()).toBe(2); + }); + + it('should check if stack is empty', () => { + const stack = new StackQ(); + + expect(stack.isEmpty()).toBeTruthy(); + + stack.push(1); + + expect(stack.isEmpty()).toBeFalsy(); + }); + + it('should pop data from stack', () => { + const stack = new StackQ(); + + stack.push(1); + stack.push(2); + + expect(stack.pop()).toBe(2); + expect(stack.pop()).toBe(1); + expect(stack.pop()).not.toBeUndefined(); + expect(stack.isEmpty()).toBeTruthy(); + }); + + it('should be possible to push/pop', () => { + const stack = new StackQ(); + + stack.push(1); + stack.push(2); + stack.push(3); + + expect(stack.toString()).toBe('1,2,3'); + expect(stack.toString()).toBe('1,2,3'); + expect(stack.pop()).toBe(3); + expect(stack.pop()).toBe(2); + expect(stack.pop()).toBe(1); + }); +}); diff --git a/skeletons/data-structures/stack/BracketMatching.js b/skeletons/data-structures/stack/BracketMatching.js new file mode 100644 index 0000000..57ab779 --- /dev/null +++ b/skeletons/data-structures/stack/BracketMatching.js @@ -0,0 +1,5 @@ +import Stack from './Stack'; + + +export const bracketMatch = str => { +} diff --git a/skeletons/data-structures/stack/NextGreaterElement.js b/skeletons/data-structures/stack/NextGreaterElement.js new file mode 100644 index 0000000..58c1926 --- /dev/null +++ b/skeletons/data-structures/stack/NextGreaterElement.js @@ -0,0 +1,7 @@ +import Stack from './Stack'; + + + +export const nextGreaterElement = arr => { + +} diff --git a/skeletons/data-structures/stack/PostFixEval.js b/skeletons/data-structures/stack/PostFixEval.js new file mode 100644 index 0000000..e6dacf8 --- /dev/null +++ b/skeletons/data-structures/stack/PostFixEval.js @@ -0,0 +1,9 @@ +import Stack from './Stack'; + + + +export const evalPostFix = arr => { + +} + + diff --git a/skeletons/data-structures/stack/README.md b/skeletons/data-structures/stack/README.md index 305248d..bbe8859 100644 --- a/skeletons/data-structures/stack/README.md +++ b/skeletons/data-structures/stack/README.md @@ -1,25 +1,114 @@ # Stack -In computer science, a stack is an abstract data type that serves -as a collection of elements, with two principal operations: +## Description -* **push**, which adds an element to the collection, and -* **pop**, which removes the most recently added element that was not yet removed. +A stack is a data structure that can store and retrieve one item at a time, in last-in-first-out (LIFO) order. Imagine stacking plates on top of each other or grabbing them from the top one at a time. When you want a plate, you grab one from the top, and when you are done using it, you put it back on the top. -The order in which elements come off a stack gives rise to its -alternative name, LIFO (last in, first out). Additionally, a -peek operation may give access to the top without modifying -the stack. The name "stack" for this type of structure comes -from the analogy to a set of physical items stacked on top of -each other, which makes it easy to take an item off the top -of the stack, while getting to an item deeper in the stack -may require taking off multiple other items first +Stack implements LIFO through two operations: `push` and `pop`, which can be visualized in the diagram below: -Simple representation of a stack runtime with push and pop operations. +![Queing operations](../../../assets/stack.png) +(_A stack is usually visualized from bottom-to-top_) -![Stack](https://upload.wikimedia.org/wikipedia/commons/b/b4/Lifo_stack.png) +- `push` operation stores the item at the top of the stack. +- `pop` operation retrieves the item from the top of the stack. + +## Implementation + +In this exercise, implement the following functions for the `Stack` class + +- `isEmpty()` + - Write a method that returns `true` if the stack is currently empty, and false otherwise. +- `peek()` + - Write a method that returns the element from the top of the stack, and returns null if the stack is empty. +- `push(el)` + - Write a method that stores an element(`el`) into the stack. +- `pop()` + - Write a method that removes the top element from the stack and returns it, and returns null if the stack is empty.. +- `toString()` + - The stringify method has been provided for you. + +## Stack Exercises + +**Bracket Matching** + +Write an algorithm to determine if all of the delimiters in an expression are matched and closed. + + + +- Delimiters are `(` `{` `[` + - Closed is `()` and `(()` would not be closed + - Matched is `{}`, and `{]` would not be matched. + +``` +const bracketMatch = string => { + +} + +console.log(bracketMatch('{ac[bb]}') === true); +console.log(bracketMatch('[dklf(df(kl))d]{}') === true); +console.log(bracketMatch('{[[[]]]}') === true); +console.log(bracketMatch('{3234[fd') === false); +console.log(bracketMatch('{df][d}') === false); +console.log(bracketMatch('([)]') === false); +``` + +**PostFix** + +Write an algorithm to evaluate postfix. + +Postfix is a way of representing algebraic expressions without needing brackets, and it is faster to evaluate than regulat infix expressions. Where we normally would write '1 + 2' in infix, or normal mathematical notation, in postfix we write '1 2 +'. The operations =,*,-, and / take the previous two arguments. The natural way of evaluating postifx is to push each number on a stack. When you get to an operation, you pop the two numbers needed off the stack, and push the result back on. When the expression is finished, there should be exactly one number on the stack. If we ever have too few arguments on the stack, or too mant arguments at the end, we return null; + +``` +const evalPostFix = arr => { + +} + + +console.log( evalPostFix( [ 1, 2,'+'] ) ) === 3); +console.log( evalPostFix( [ 1, 2, 3,'+','+'] ) == 6); +console.log( evalPostFix( [ 0, 1, 2,'+'] ) == null ); + + +``` + +**Stock Span Problem** + +The stock span problem is a problem often posed in interviews, where we have an array of n price quotes for a stock and we want to determine the span of stock’s price for all n days. +The span, Si, of the stock on day, i, is how many you have to go back before the stock price was higher than now. This computes the largest interval when you would have made a profit from buying the stock, as its price is lower than now in all that span. + +For example, if an array of 7 days prices is given as [100, 80, 60, 70, 60, 75, 85], then the span values for corresponding 7 days are [1, 1, 1, 2, 1, 4, 6] + +Many interviewers like to ask questions about stock prices. The simplest way to do this problem is to start from each price, and look backwards until you find a larger price. If the prices happened to increase over the time period, each time you looked at a stock, you would need to look at all previous prices. This algorithm thus can look at n^2/n prices, so the algorrithm is called O(n^2). Using a stack, the problem can be solved in O(n), that is, using at most only a linear number of operations c*n, where c is some constant. + +It is not immediately clear from the solution that the algorithm is linear, as there are nested loops. How do we know that the algorithm is linear? The outside loop goes through the array once, while the inside loop pops items off the stack. We only push n items onto the stack, so we can at most pop n items off the stack. This when we add up the amount of work all the inner loops do, the total is n pops or less. The usual sign of an n^2 algorithm is nested loops, but in cases like this, where the inner loop pops a stack, or other data structure, we can prove that the inner loop only does a linear amount of work. + + +``` +const stockSpan = arr => { + +} + +console.log( stockSpan( [100, 80, 60, 70, 60, 75, 85] ) ) == [1, 1, 1, 2, 1, 4, 6] ); + +``` + +**Next Greater Element** + + +This problem is similar to the stock span problem, but looks forward rather than backwards. A huge part of solving programming challenges is recognizing when you are being asked about a problem almost identical to a well known problem. + +Given an array of numbers, for each number, find the next greater element (NGE), that is, the next number that is bigger than the current number. If no number is bigger, we set the answer to null. For every array, the next greatest number of the last element is always null, as there are no numbers to the right. For the array [13, 7, 6, 12], the NGEs are [null, 12,12, null]. + +Using a stack, in a similar way to the previous problem, we can solve this in linear time. + + +``` +const nextGreatestElement = arr => { + +} + +console.log( stockSpan( [13, 7, 6, 12 ] ) ) == [null, 12, 12, null] ); + +``` -## References -- [Wikipedia](https://en.wikipedia.org/wiki/Stack_(abstract_data_type)) -- [YouTube](https://www.youtube.com/watch?v=wjI1WNcIntg&list=PLLXdhg_r2hKA7DPDsunoDZ-Z769jWn4R8&index=3&) diff --git a/skeletons/data-structures/stack/StockSpan.js b/skeletons/data-structures/stack/StockSpan.js new file mode 100644 index 0000000..bc0a1b7 --- /dev/null +++ b/skeletons/data-structures/stack/StockSpan.js @@ -0,0 +1,7 @@ +import Stack from './Stack'; + + + +export const stockSpan = price => { +} + diff --git a/skeletons/data-structures/stack/__test__/Bracket.test.js b/skeletons/data-structures/stack/__test__/Bracket.test.js new file mode 100644 index 0000000..a726a6c --- /dev/null +++ b/skeletons/data-structures/stack/__test__/Bracket.test.js @@ -0,0 +1,26 @@ +import Stack from '../Stack'; + +import { bracketMatch } from '../BracketMatching'; + + +describe('Bracket', () => { + it('should match simple patterns', () => { + + expect( bracketMatch("(())") ).toBeTruthy(); + expect( bracketMatch("(()") ).toBeFalsy(); + expect( bracketMatch("())") ).toBeFalsy(); + expect( bracketMatch("((a)a") ).toBeFalsy(); + expect( bracketMatch("1(1(2)3)1") ).toBeTruthy(); + expect( bracketMatch("a(b)c(d)e") ).toBeTruthy(); + + expect( bracketMatch('{ac[bb]}')).toBeTruthy(); + expect (bracketMatch('[dklf(df(kl))d]{}') ).toBeTruthy(); + expect( bracketMatch('{[[[]]]}') ).toBeTruthy(); + expect( bracketMatch('{3234[fd') ).toBeFalsy(); + expect( bracketMatch('{df][d}') ).toBeFalsy(); + expect( bracketMatch('([)]') ).toBeFalsy(); + + }); + +}); + diff --git a/skeletons/data-structures/stack/__test__/NextGreaterElement.test.js b/skeletons/data-structures/stack/__test__/NextGreaterElement.test.js new file mode 100644 index 0000000..f304175 --- /dev/null +++ b/skeletons/data-structures/stack/__test__/NextGreaterElement.test.js @@ -0,0 +1,12 @@ +import { nextGreaterElement } from '../NextGreaterElement' + + +describe('NextGreaterElement', () => { + it('should should compute NGE', () => { + + expect( nextGreaterElement( [ 11,13,21,3] ).toString()).toBe( [13,21,null,null].toString() ); + +}); + +}); + diff --git a/skeletons/data-structures/stack/__test__/PostFixEval.test.js b/skeletons/data-structures/stack/__test__/PostFixEval.test.js new file mode 100644 index 0000000..f7292c9 --- /dev/null +++ b/skeletons/data-structures/stack/__test__/PostFixEval.test.js @@ -0,0 +1,18 @@ +import Stack from '../Stack'; + +import { evalPostFix } from '../PostFixEval'; + + +describe('PostFixEval', () => { + it('should eval postfix ', () => { + + + expect( evalPostFix( [ 1, 2,'+'] ).toString()).toBe('3'); + expect( evalPostFix( [ 1, 2, 3,'+','+'] ).toString()).toBe('6'); + expect( evalPostFix( [ 0, 1, 2,'+'] ) ).toBeNull(); + expect( evalPostFix( [ 1, 2 ] ) ).toBeNull(); +}); + +}); + + diff --git a/skeletons/data-structures/stack/__test__/Stack.test.js b/skeletons/data-structures/stack/__test__/Stack.test.js index ff0cbb4..112c55a 100644 --- a/skeletons/data-structures/stack/__test__/Stack.test.js +++ b/skeletons/data-structures/stack/__test__/Stack.test.js @@ -19,7 +19,7 @@ describe('Stack', () => { it('should peek data from stack', () => { const stack = new Stack(); - expect(stack.peek()).toBeUndefined(); + expect(stack.peek()).not.toBeUndefined(); stack.push(1); stack.push(2); @@ -46,7 +46,7 @@ describe('Stack', () => { expect(stack.pop()).toBe(2); expect(stack.pop()).toBe(1); - expect(stack.pop()).toBeUndefined(); + expect(stack.pop()).not.toBeUndefined(); expect(stack.isEmpty()).toBeTruthy(); }); diff --git a/skeletons/data-structures/stack/__test__/StockSpan.test.js b/skeletons/data-structures/stack/__test__/StockSpan.test.js new file mode 100644 index 0000000..1b8699c --- /dev/null +++ b/skeletons/data-structures/stack/__test__/StockSpan.test.js @@ -0,0 +1,13 @@ +import { stockSpan } from '../StockSpan' + + +describe('StockSpan', () => { + it('should should compute spans', () => { + + expect( stockSpan( [100, 80, 60, 70, 60, 75, 85] ).toString()).toBe( [1, 1, 1, 2, 1, 4, 6].toString() ); + +}); + +}); + + diff --git a/solutions/algorithms/numbers/fibonacci/NumberDecodings.js b/solutions/algorithms/numbers/fibonacci/NumberDecodings.js new file mode 100644 index 0000000..78b1bd3 --- /dev/null +++ b/solutions/algorithms/numbers/fibonacci/NumberDecodings.js @@ -0,0 +1,89 @@ +export function nwd( s, p){ + var l = s.length; + if ( l <= p) return 1; // There is only one way to decode a single digit + if ( l-1 == p) return 1; + if ( s[p+1] === '0') return nwd(s,p+2); + // If the second digit is 0, we can only parse this as a single digit. + if ( (s[p] == '1' || + ( s[p] == '2' && s[p+1] > '0' && s[p+1] <= '6' ) ) + ){ // if the string is "[1][1-9]..." or"[2][1-6]..." then we can decode this + // using just one digit, or using 2 digits. There are nwd(s, p+1) + // ways of decoding the rest of the string if we use one digit, + // and nwd(s, p+2) ways if we use 2. + return ( nwd(s, p+1) + nwd(s, p+2)); + + } + // The string starts with a 3 or higher (or 27 or higher), so we can only parse this as a single digit + return nwd(s,p+1); +} + +export function nwdMem(s,p){ + var mem = new Array(1+ s.length); + return nwdM(s,p,mem); +} + +export function nwdM( s,p, mem){ + var l = s.length; + if ( l <= p ) return 1; // There is only one way to decode a single digit + if ( l-1 === p ) return 1; + if (mem[p]){return mem[p];} + if ( s[p+1] === '0') + { + mem[p] = nwdM(s,p+2,mem); + return mem[p]; } + // If the second digit is 0, we can only parse this as a single digit. + if ((s[p] == '1' || + ( s[p] == '2' && s[p+1] > '0' && s[p+1] <= '6' ) ) + ) // if the string is "[1][1-9]..." or"[2][1-6]..." then we can decode this + // using just one digit, or using 2 digits. There are nwd(s, p+1) + // ways of decoding the rest of the string if we use one digit, + // and nwd(s, p+2) ways if we use 2. + { + mem[p] = nwdM(s, p+1,mem) + nwdM(s, p+2,mem); + + return mem[p]; + } + // The string starts with a 3 or higher (or 27 or higher), so we can only parse this as a single digit + mem[p] = nwdM(s,p+1,mem); + return mem[p]; + +} + + +export function nwd_dp( s){ + var l = s.length; + var dp = new Array(l); + dp[l] = 1; + for (var i = l-1; i>=0; i--){ + if ( l-1==i) { dp[i] = 1; + continue; } + if (s[i+1] === '0') { dp[i] = dp[ i+2]; + continue;} + if ( s[i] === '1' || + (s[i] === '2' && s[i+1] >= '0' && s[i+1] <= '6' )) + { + dp[i] = ( dp[ i+2] + dp[ i+1] ); } + else + dp[i] = dp[i+1] ; + } + return dp[0]; +} + +export function nwd_c( s){ + var l = s.length; + var c = 1; + var c1 = 1; + for (var i = l-1; i>=0;i--){ + if ( l-1===i) { c = 1; continue;} + if (s[i+1] === '0'){ c1 = c; + continue;} + if (s[i] === '1' || + ( s[i] == '2' && s[i+1] > '0' && s[i+1] <= '6' ) ) + { + var tmp = c; + c = ( c1 + c); + c1 = tmp; + } else c1 = c; + } + return c; +} diff --git a/solutions/algorithms/numbers/fibonacci/README.md b/solutions/algorithms/numbers/fibonacci/README.md index f4a5df8..dfb1083 100644 --- a/solutions/algorithms/numbers/fibonacci/README.md +++ b/solutions/algorithms/numbers/fibonacci/README.md @@ -15,6 +15,75 @@ The Fibonacci spiral: an approximation of the golden spiral created by drawing c ![Fibonacci Spiral](https://upload.wikimedia.org/wikipedia/commons/2/2e/FibonacciSpiral.svg) + +## Implemetation + +The simple implementation of Fibinacci is recursive. Write + +Note how we need two base cases, as the recursive defintion calls `Fib(n-2)`. This solution is very slow, as the call to `Fib(n-1)` redoes all the work of the call to `Fib(n-2)`. In fact, a call to `Fib(n)` makes an exponential numnber of recursive calls to itself, hence it is O(2^n), very slow. However, there is a simple way to speed it up. We can remember the intermediate results. `mem` is an array that records the value of `Fib(n)` in position n. If `mem[n]` is null, we do not know the value yet. + + +Write a recursive `fibonacciMem(n,mem)` and write `fibonacciM(n)` using it. + +This seems like a minor change, but this changes the complexity of the algorithm to linear. This kind of change, adding memoization, is a very important skill that is expected in interviews. All recursive solutions can have this applied, and if they repeatedly call the same arguments, this will result in a huge speed up. + +We are not done yet. We now can change this to an iterative solution. Rather than go backwards from n, we can compute the Fibonacci numbers forwards, iteratively. This is simpler. + +``` +FibI(){ + + var fib = [0,1]; + + for (var i = 2; i <=n;i++) + fib.push(fib[i-1]+fib[i-2]; + return fib[n]; +} +``` + +Write `fibonacci` in this style so it returns the array of Fibonacci numbers. + +This uses an array of length n. We only ever look at the last two values of that array, so we can minimize the space we use by just storing them. + +``` +fibonacciNth((){ + var twoBack = 0; + var oneBack = 1; + for (var i = 2; i <=n;i++){ + oneBack = oneBack+twoBack; + twoBack = oneBack; + } + oneBack; +} +``` + + +Write `fibonacciNth` so it computes the nth Fibonacci number in this style. + + +This seems very efficient. Can we do better? Of course, but for now, this will be fast enough. + +It is rare that someone is asked about a textbook problem in an interview. It is common, however, to be asked a textbook problem with the words changed. Consider the following interview question asked at Facebook: + +"Given the mapping a = 1, b = 2, ... z = 26, and an encoded message, count the number of ways it can be decoded." + +The nunber 11 could either be the string "aa" or the string "k", so can be parsed in 2 ways. The string "111" can be parsed as "aaa","ka", or "ak", so three ways. "11111" can be parsed as k, followed by "111", or "a" followed by "1111", so can be parsed 5 ways. In general, a string of n 1s can be parsed Fib(n) ways. This problem is just the Fibonacci problem with some edge cases. + +Let `nwd(s,p)` be the number of ways of decoding a string starting from position p. Write this recursively, with memoization, using an array and finally using just 2 variables. + + + + +This pattern, or writing a recursive program that does the problem +simply, followed by memoization, transforming the program to an +iterative one, and finally using constant space, is very common in +interviews. If you are asked a question where you can do this, you +can easily fill an hour with intelligent code that improves each time. +This usually results in a good outcome. + + + + + ## References [Wikipedia](https://en.wikipedia.org/wiki/Fibonacci_number) diff --git a/solutions/algorithms/numbers/fibonacci/__test__/NumberDecodings.test.js b/solutions/algorithms/numbers/fibonacci/__test__/NumberDecodings.test.js new file mode 100644 index 0000000..7990a5b --- /dev/null +++ b/solutions/algorithms/numbers/fibonacci/__test__/NumberDecodings.test.js @@ -0,0 +1,67 @@ +import { nwd, nwdMem, nwd_dp, nwd_c } from '../NumberDecodings'; + + +describe('NumberDecodings', () => { + it('should calculatenumber of decodings correctly', () => { + expect(nwd("11",0)).toEqual(2); + expect(nwd("1212",0)).toEqual(5); + expect(nwd("122",0)).toEqual(3); + expect(nwd("1227",0)).toEqual(3); + expect(nwd("10122",0)).toEqual(3); + expect(nwd("101227",0)).toEqual(3); + expect(nwd("101010",0)).toEqual(1); + expect(nwd("123123123",0)).toEqual(27); + expect(nwd("1230123010",0)).toEqual(9); + expect(nwd("123242526",0)).toEqual(24); + + }); + + + + it('should calculatenumber of decodings correctly with memoization', () => { + expect(nwdMem("11",0)).toEqual(2); + expect(nwdMem("1212",0)).toEqual(5); + expect(nwdMem("122",0)).toEqual(3); + expect(nwdMem("1227",0)).toEqual(3); + expect(nwdMem("10122",0)).toEqual(3); + expect(nwdMem("101227",0)).toEqual(3); + expect(nwdMem("101010",0)).toEqual(1); + expect(nwdMem("123123123",0)).toEqual(27); + expect(nwdMem("1230123010",0)).toEqual(9); + expect(nwdMem("123242526",0)).toEqual(24); + }); + + + + it('should calculate number of decodings correctly iteratively', () => { + expect(nwd_dp("11",0)).toEqual(2); + expect(nwd_dp("1212",0)).toEqual(5); + expect(nwd_dp("122",0)).toEqual(3); + expect(nwd_dp("1227",0)).toEqual(3); + expect(nwd_dp("10122",0)).toEqual(3); + expect(nwd_dp("101227",0)).toEqual(3); + expect(nwd_dp("101010",0)).toEqual(1); + expect(nwd_dp("123123123",0)).toEqual(27); + expect(nwd_dp("1230123010",0)).toEqual(9); + expect(nwd_dp("123242526",0)).toEqual(24); + }); + + + + + it('should calculate number of decodings correctly with O(1) memory', () => { + expect(nwd_c("11",0)).toEqual(2); + expect(nwd_c("1212",0)).toEqual(5); + expect(nwd_c("122",0)).toEqual(3); + expect(nwd_c("1227",0)).toEqual(3); + expect(nwd_c("10122",0)).toEqual(3); + expect(nwd_c("101227",0)).toEqual(3); + expect(nwd_c("101010",0)).toEqual(1); + expect(nwd_c("123123123",0)).toEqual(27); + expect(nwd_c("1230123010",0)).toEqual(9); + expect(nwd_c("123242526",0)).toEqual(24); + }); + + + +}); diff --git a/solutions/algorithms/numbers/fibonacci/__test__/fibonacci.test.js b/solutions/algorithms/numbers/fibonacci/__test__/fibonacci.test.js index 947c621..499db86 100644 --- a/solutions/algorithms/numbers/fibonacci/__test__/fibonacci.test.js +++ b/solutions/algorithms/numbers/fibonacci/__test__/fibonacci.test.js @@ -1,5 +1,10 @@ import fibonacci from '../fibonacci'; +import fibonacciR from '../fibonacciR'; + +import fibonacciM from '../fibonacciM'; + + describe('fibonacci', () => { it('should calculate fibonacci correctly', () => { expect(fibonacci(1)).toEqual([1]); @@ -11,4 +16,42 @@ describe('fibonacci', () => { expect(fibonacci(7)).toEqual([1, 1, 2, 3, 5, 8, 13]); expect(fibonacci(8)).toEqual([1, 1, 2, 3, 5, 8, 13, 21]); }); + + it('should calculate fibonacci correctly', () => { + expect(fibonacciR(1)).toEqual(1); + expect(fibonacciR(2)).toEqual(1); + expect(fibonacciR(3)).toEqual(2); + expect(fibonacciR(4)).toEqual(3); + expect(fibonacciR(5)).toEqual(5); + expect(fibonacciR(6)).toEqual(8); + expect(fibonacciR(7)).toEqual(13); + expect(fibonacciR(8)).toEqual(21); + }); + + + + it('should calculate fibonacci correctly', () => { + expect(fibonacciM(1)).toEqual(1); + expect(fibonacciM(2)).toEqual(1); + expect(fibonacciM(3)).toEqual(2); + expect(fibonacciM(4)).toEqual(3); + expect(fibonacciM(5)).toEqual(5); + expect(fibonacciM(6)).toEqual(8); + expect(fibonacciM(7)).toEqual(13); + expect(fibonacciM(8)).toEqual(21); + }); + + + it('should calculate fibonacci correctly', () => { + expect(fibonacciM(1)).toEqual(1); + expect(fibonacciM(2)).toEqual(1); + expect(fibonacciM(3)).toEqual(2); + expect(fibonacciM(4)).toEqual(3); + expect(fibonacciM(5)).toEqual(5); + expect(fibonacciM(6)).toEqual(8); + expect(fibonacciM(7)).toEqual(13); + expect(fibonacciM(8)).toEqual(21); + }); + + }); diff --git a/solutions/algorithms/numbers/fibonacci/fibonacciM.js b/solutions/algorithms/numbers/fibonacci/fibonacciM.js new file mode 100644 index 0000000..b1ccfe7 --- /dev/null +++ b/solutions/algorithms/numbers/fibonacci/fibonacciM.js @@ -0,0 +1,21 @@ + +/** + * Return a fibonacci sequence as an array. + * + * @param n + * @return {number[]} + */ +export default function fibonacciM(n) { +var mem = new Array(n); +return fibonacciMem(n, mem); +} + +function fibonacciMem(n,mem){ + if (n ===0) return 0; + if (n ===1) return 1; + if (mem[n]) return mem[n]; + mem[n] = fibonacciMem(n-1,mem) + fibonacciMem(n-2,mem); + return mem[n]; +} + + diff --git a/solutions/algorithms/numbers/fibonacci/fibonacciR.js b/solutions/algorithms/numbers/fibonacci/fibonacciR.js new file mode 100644 index 0000000..dd85099 --- /dev/null +++ b/solutions/algorithms/numbers/fibonacci/fibonacciR.js @@ -0,0 +1,12 @@ +/** + * Return a fibonacci sequence as an array. + * + * @param n + * @return {number[]} + */ +export default function fibonacciR(n) { + if (n ===0) return 0; + if (n ===1) return 1; + + return fibonacciR(n-1) + fibonacciR(n-2); +} diff --git a/solutions/algorithms/numbers/happy/Happy.js b/solutions/algorithms/numbers/happy/Happy.js new file mode 100644 index 0000000..7eba847 --- /dev/null +++ b/solutions/algorithms/numbers/happy/Happy.js @@ -0,0 +1,37 @@ + + +export function digitSquareSum(n) { + var sum = 0, d; + var m = n; + while(m > 9){ + d = m % 10; + sum += d * d; + m = Math.floor(m/ 10); + } + sum += m * m; + return sum; +} + + +export function isHappy( n) { + var seen = new Object(); + var curr = digitSquareSum(n); + seen[n] = true; + while(!seen[curr]){ + seen[curr] = true; + curr = digitSquareSum(curr); + } + return curr === 1; +} + + + +export function isHappyConstantSpace( n) { + var slow = digitSquareSum(n) , fast = digitSquareSum(digitSquareSum(n)) ; + + while( slow != fast){ + slow = digitSquareSum(slow); + fast = digitSquareSum(digitSquareSum(fast)); + } + return (slow == 1); +} diff --git a/solutions/algorithms/numbers/happy/README.md b/solutions/algorithms/numbers/happy/README.md new file mode 100644 index 0000000..12b9fa9 --- /dev/null +++ b/solutions/algorithms/numbers/happy/README.md @@ -0,0 +1,21 @@ +# Happy Number + +A happy number is defined by the following process: Starting with any positive integer, replace the number by the sum of the squares of its digits in base-ten, and repeat the process until the number either equals 1 (where it will stay), or it loops endlessly in a cycle that does not include 1. Those numbers for which this process ends in 1 are happy numbers, while those that do not end in 1 are unhappy numbers (or sad numbers) + + + +For example, 19 is happy, as the associated sequence is +``` +12 + 92 = 82, +82 + 22 = 68, +62 + 82 = 100, +12 + 02 + 02 = 1. +``` + +Check if a number is happy. One way uses a hash table. Try to solve the problem with only using constant space. Floyd's trick for finding loops can help. + + + +## References + +[Wikipedia](https://en.wikipedia.org/wiki/Happy_number) diff --git a/solutions/algorithms/numbers/happy/__test__/Happy.test.js b/solutions/algorithms/numbers/happy/__test__/Happy.test.js new file mode 100644 index 0000000..1b60a60 --- /dev/null +++ b/solutions/algorithms/numbers/happy/__test__/Happy.test.js @@ -0,0 +1,55 @@ +import { isHappyConstantSpace, isHappy, digitSquareSum } from '../Happy'; + + +describe('digitSquareSum', () => { + it('should sum the square of digits corrected', () => { + + expect(digitSquareSum(1)).toBe(1); + expect(digitSquareSum(2)).toBe(4); + expect(digitSquareSum(3)).toBe(9); + expect(digitSquareSum(34)).toBe(25); + +}); + + + it('should test of numbers are happy correctly', () => { + expect(isHappy(1)).toBeTruthy(); + expect(isHappy(2)).toBeFalsy(); + expect(isHappy(3)).toBeFalsy(); + expect(isHappy(4)).toBeFalsy(); + expect(isHappy(19)).toBeTruthy(); + expect(isHappy(310)).toBeTruthy(); + expect(isHappy(622)).toBeTruthy(); + expect(isHappy(881)).toBeTruthy(); + expect(isHappy(44)).toBeTruthy(); + expect(isHappy(236)).toBeTruthy(); + expect(isHappy(314)).toBeFalsy(); + expect(isHappy(624)).toBeFalsy(); + expect(isHappy(834)).toBeFalsy(); + expect(isHappy(328)).toBeFalsy(); + expect(isHappy(819)).toBeFalsy(); + + }); + + + it('should test of numbers are happy with constant space correctly', () => { + expect(isHappyConstantSpace(1)).toBeTruthy(); + expect(isHappyConstantSpace(2)).toBeFalsy(); + expect(isHappyConstantSpace(3)).toBeFalsy(); + expect(isHappyConstantSpace(4)).toBeFalsy(); + expect(isHappy(19)).toBeTruthy(); + expect(isHappy(310)).toBeTruthy(); + expect(isHappy(622)).toBeTruthy(); + expect(isHappy(881)).toBeTruthy(); + expect(isHappy(44)).toBeTruthy(); + expect(isHappy(236)).toBeTruthy(); + expect(isHappy(314)).toBeFalsy(); + expect(isHappy(624)).toBeFalsy(); + expect(isHappy(834)).toBeFalsy(); + expect(isHappy(328)).toBeFalsy(); + expect(isHappy(819)).toBeFalsy(); + + }); + + +}); diff --git a/solutions/algorithms/numbers/lexicographical/Lex.js b/solutions/algorithms/numbers/lexicographical/Lex.js new file mode 100644 index 0000000..ef7a690 --- /dev/null +++ b/solutions/algorithms/numbers/lexicographical/Lex.js @@ -0,0 +1,20 @@ + +import Stack from '../../../data-structures/stack/Stack'; + +export function lexicalOrder( n) +{ + var res = new Array(); + var s = new Stack(); + s.push(1); + while(!s.isEmpty()) + { + var cur = s.peek(); + res.push(cur); + s.pop(); + if(cur + 1 <= n && 9 > cur%10) + s.push(cur + 1); + if(cur * 10 <= n) + s.push(cur * 10); + } + return res; +} diff --git a/solutions/algorithms/numbers/lexicographical/README.md b/solutions/algorithms/numbers/lexicographical/README.md new file mode 100644 index 0000000..7cae54c --- /dev/null +++ b/solutions/algorithms/numbers/lexicographical/README.md @@ -0,0 +1,12 @@ +# Lexicographical Numbers + +Given an integer n, return 1 - n in lexicographical order. + +We can use a stack to get the order right. The challenge each time is to find the next number. If we have a number like 83 the next number is 830 if that is small enough, or 84 if 830>=n. + + + + + + + diff --git a/solutions/algorithms/numbers/lexicographical/__test__/Lex.test.js b/solutions/algorithms/numbers/lexicographical/__test__/Lex.test.js new file mode 100644 index 0000000..250ff55 --- /dev/null +++ b/solutions/algorithms/numbers/lexicographical/__test__/Lex.test.js @@ -0,0 +1,15 @@ +import { lexicalOrder } from '../Lex'; + + + + +describe('lexical-order', () => { + it('should calculate lexical order correctly', () => { + expect( lexicalOrder(11) ).toEqual([1,10,11,2,3,4,5,6,7,8,9]); + expect( lexicalOrder(101) ).toEqual([1, 10, 100, 101, 11, 12, 13, 14, 15, 16, 17, 18, 19, 2, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 3, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 4, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 5, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 6, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 7, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 8, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 9, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99]); + + }); + + + +}); diff --git a/solutions/algorithms/numbers/prime/FermatPrimality.js b/solutions/algorithms/numbers/prime/FermatPrimality.js new file mode 100644 index 0000000..d140424 --- /dev/null +++ b/solutions/algorithms/numbers/prime/FermatPrimality.js @@ -0,0 +1,16 @@ +export function fermatPrimality(n){ + if (n === 1) return false; + if (n === 2) return true; + for(var i = 0; i < 10; i++){ + // check 10 times + var a = 1 + Math.floor( (n-1) * Math.random()); // a is a number in the range 0..p-1 + var ap = 1; + for (var j = 0; j < n-1; j++){ + ap = (ap * a) % n; + } + if (ap !== 1) return false; + // we know for sure that p is composite + } + return true; + // we have checked k times and each time the test succeeded +} diff --git a/solutions/algorithms/numbers/prime/FermatTest.js b/solutions/algorithms/numbers/prime/FermatTest.js new file mode 100644 index 0000000..a08ffc9 --- /dev/null +++ b/solutions/algorithms/numbers/prime/FermatTest.js @@ -0,0 +1,21 @@ +/** + * @param {number} number + * @return {boolean} + */ + + +export function fermatTest(n){ + if (n === 1 ) return false; + if (n === 2) return true; + var a = Math.ceil(Math.sqrt(n)); + var b = a*a - n; + var squareRootb = Math.sqrt(b); + while(a <= (n+1)/2 && b !== squareRootb * squareRootb ){ + a++; + b = a*a - n; + squareRootb = Math.floor( Math.sqrt(b)); + } + // console.log([n, squareRootb, a, (n+1)/2]); + if (a === (n+1)/2) return true; + return false; +} diff --git a/solutions/algorithms/numbers/prime/README.md b/solutions/algorithms/numbers/prime/README.md index 8a9852d..6d697f7 100644 --- a/solutions/algorithms/numbers/prime/README.md +++ b/solutions/algorithms/numbers/prime/README.md @@ -19,6 +19,44 @@ a computationally difficult problem, whereas primality testing is comparatively easy (its running time is polynomial in the size of the input). + + + +## Implementation + + +##Trial Division + +[Trial Division](https://en.wikipedia.org/wiki/Trial_division) is the oldest primarlity algortihm. It works by successively dividing the number, n, by larger and larger numbers until a divisor is found. If no divisor is found by the time you reach the square or n, then n is prime. + +Write `function trialDivision(number)`. + +##Fermat's Method + +Every number odd number n can be written as the difference of two squares, so there are a and b, such that n = a^2 - b^2. If n = c*d, n can be written as ((c+d)/2)^2 * ((c-d)/2)^2. This can be checked by simple algebra. The idea of Fermat's method is to guess a value for a, and try to work out what b is. The usual starting place is the first number larger than the square root of n. If we check all numbers up to to (n+1)/2 without finding a divisor, the number is prime. + +Write `fermatTest(n)`. + + + +This function returns false if the nunber is prime, otherwise it returns a divisor. This method is slower than Trial Division, but a combination of both methods is faster. This method can be extended in various ways to make better algorithms, like the quadratic sieve, which are too complicated for us. + + +#Primality Tests + +There are algorithms that test whether a nunber if prime with high probablility, but which are sometimes wrong. These are much faster than Trial Division. These are what is used to find the large prime numbers that are used in cryptography. + +## Fermat's Primality Test + +Fermat's little theorem tells us that if p is prime and a is not divisible by p, then a^{p-1} mod p = 1. To check a number is prime we can choose an a, and check whether a^{p-1} mod p is equal to 1. If this is true for several different numbers, we can be fairly confident that p is prime. + +Write `fermatPrimality(n)`. + + + + + + ## References - [Prime Numbers on Wikipedia](https://en.wikipedia.org/wiki/Prime_number) diff --git a/solutions/algorithms/numbers/prime/__test__/FermatPrimality.test.js b/solutions/algorithms/numbers/prime/__test__/FermatPrimality.test.js new file mode 100644 index 0000000..2b4daef --- /dev/null +++ b/solutions/algorithms/numbers/prime/__test__/FermatPrimality.test.js @@ -0,0 +1,29 @@ +import { fermatPrimality } from '../FermatPrimality'; + + + +describe('Fermat Test', () => { + it('should detect prime numbers', () => { + + expect(fermatPrimality(1)).toBeFalsy(); + expect(fermatPrimality(2)).toBeTruthy(); + expect(fermatPrimality(3)).toBeTruthy(); + expect(fermatPrimality(5)).toBeTruthy(); + expect(fermatPrimality(11)).toBeTruthy(); + expect(fermatPrimality(191)).toBeTruthy(); + expect(fermatPrimality(191)).toBeTruthy(); + expect(fermatPrimality(199)).toBeTruthy(); + + expect(fermatPrimality(4)).toBeFalsy(); + expect(fermatPrimality(6)).toBeFalsy(); + expect(fermatPrimality(12)).toBeFalsy(); + expect(fermatPrimality(14)).toBeFalsy(); + expect(fermatPrimality(25)).toBeFalsy(); + expect(fermatPrimality(192)).toBeFalsy(); + expect(fermatPrimality(200)).toBeFalsy(); + expect(fermatPrimality(400)).toBeFalsy(); + + + + }); +}); diff --git a/solutions/algorithms/numbers/prime/__test__/FermatTest.test.js b/solutions/algorithms/numbers/prime/__test__/FermatTest.test.js new file mode 100644 index 0000000..ee724ac --- /dev/null +++ b/solutions/algorithms/numbers/prime/__test__/FermatTest.test.js @@ -0,0 +1,29 @@ +import { fermatTest } from '../FermatTest'; + + + +describe('Fermat Test', () => { + it('should detect prime numbers', () => { + + expect(fermatTest(1)).toBeFalsy(); + expect(fermatTest(2)).toBeTruthy(); + expect(fermatTest(3)).toBeTruthy(); + expect(fermatTest(5)).toBeTruthy(); + expect(fermatTest(11)).toBeTruthy(); + expect(fermatTest(191)).toBeTruthy(); + expect(fermatTest(191)).toBeTruthy(); + expect(fermatTest(199)).toBeTruthy(); + + expect(fermatTest(4)).toBeFalsy(); + expect(fermatTest(6)).toBeFalsy(); + expect(fermatTest(12)).toBeFalsy(); + expect(fermatTest(14)).toBeFalsy(); + expect(fermatTest(25)).toBeFalsy(); + expect(fermatTest(192)).toBeFalsy(); + expect(fermatTest(200)).toBeFalsy(); + expect(fermatTest(400)).toBeFalsy(); + + + + }); +}); diff --git a/solutions/algorithms/numbers/ugly/README.md b/solutions/algorithms/numbers/ugly/README.md new file mode 100644 index 0000000..76885cd --- /dev/null +++ b/solutions/algorithms/numbers/ugly/README.md @@ -0,0 +1,11 @@ + + +#Ugly Numbers + +A number with prime factor only in {2,3,5} is called an ugly number. Write a function, `isUgly(n)` that tests that a number is ugly. + +Now find the nth ugly number. Try to do this first in n^2 time, `nthUglyNumber` then in linear time, `nthUglyNumberLinear`. This requires a trick, you cannot just try every number. + + +Generalize this to a list of prime numbers, `superUglyNumber(n, primes)`. + diff --git a/solutions/algorithms/numbers/ugly/Ugly.js b/solutions/algorithms/numbers/ugly/Ugly.js new file mode 100644 index 0000000..4931970 --- /dev/null +++ b/solutions/algorithms/numbers/ugly/Ugly.js @@ -0,0 +1,72 @@ +export function isUgly( num) { + if (num === 0) return false; + while(num%2 === 0) num/=2; + while(num%3 === 0) num/=3; + while(num%5 === 0) num/=5; + return num == 1; +} + + + export function nthUglyNumber( n) { + if(n <= 0) return false; + if(n === 1) return 1; + var uglies = new Array(n+1); + uglies[0] = 1; // first ugly is 1 + var last =1; + const ugly = [2,3,5]; + for(var i = 1; i <= n ; i ++) + { + var next = last *5; + for(var j = 0; j < uglies.length; j++){ + ugly.forEach(function(k){ + if (uglies[j]*k > last && uglies[j]*k < next){next = uglies[j]*k;}; + }); + } + uglies[i] = (last = next); + } + return uglies[n-1]; +} + + +export function nthUglyNumberLinear( n) { + if(n <= 0) return false; + if(n === 1) return 1; + var uglies = new Array(n); + uglies[0] = 1; // first ugly is 1 + var p2 =0, p3 = 0, p5 = 0; + for(var i = 1; i < n ; i ++) + { + uglies[i] = Math.min(uglies[p2]*2, + Math.min(uglies[p3]*3,uglies[p5]*5)); + if ( uglies[i]== uglies[p2]*2) p2++; + if ( uglies[i]== uglies[p3]*3) p3++; + if ( uglies[i]== uglies[p5]*5) p5++; + } + return uglies[n-1]; +} + + + + +export function superUglyNumber(n, primes) { + if(n <= 0) return 1; + var uglies = new Array(n); // initialize to n + for(var i = 0; i < n ; i ++) + uglies[i] = Number.MAX_SAFE_INTEGER; + var pl = primes.length; + var places = new Array(pl); + for ( var j = 0; j < pl ; j++) + places[j] = 0; + + uglies[0] = 1; // first ugly is 1 + for(var i = 1; i < n ; i ++) + { + + for ( var j = 0; j < pl ; j++) + uglies[i] = Math.min(uglies[i] , uglies[places[j]] * primes[j]); + for(var k = 0; k < pl ; k ++) + if (uglies[i] === primes[k] * uglies[places[k]]) + places[k]++; + } + return uglies[n-1]; +} diff --git a/solutions/algorithms/numbers/ugly/__test__/Ugly.test.js b/solutions/algorithms/numbers/ugly/__test__/Ugly.test.js new file mode 100644 index 0000000..b27b683 --- /dev/null +++ b/solutions/algorithms/numbers/ugly/__test__/Ugly.test.js @@ -0,0 +1,79 @@ +import { isUgly, nthUglyNumber, nthUglyNumberLinear, superUglyNumber } from '../Ugly'; + + +describe('ugly', () => { + it('should test ugly numbers correctly', () => { + expect(isUgly(1)).toBeTruthy(); + expect(isUgly(2)).toBeTruthy(); + expect(isUgly(3)).toBeTruthy(); + expect(isUgly(5)).toBeTruthy(); + expect(isUgly(15)).toBeTruthy(); + expect(isUgly(30)).toBeTruthy(); + expect(isUgly(120)).toBeTruthy(); + expect(isUgly(360)).toBeTruthy(); + expect(isUgly(7)).toBeFalsy(); + expect(isUgly(14)).toBeFalsy(); + expect(isUgly(33)).toBeFalsy(); + expect(isUgly(22)).toBeFalsy(); + expect(isUgly(420)).toBeFalsy(); + expect(isUgly(17)).toBeFalsy(); + expect(isUgly(34)).toBeFalsy(); + + }); + + + it('should compute nth ugly numbers correctly', () => { + expect(nthUglyNumber(0)).toBeFalsy(); //1,2,3,4,5,6,8,9,10,12,15,16,18, + + expect(nthUglyNumber(1)).toBe(1); + expect(nthUglyNumber(2)).toBe(2); + expect(nthUglyNumber(3)).toBe(3); + expect(nthUglyNumber(4)).toBe(4); + expect(nthUglyNumber(5)).toBe(5); + expect(nthUglyNumber(6)).toBe(6); + expect(nthUglyNumber(7)).toBe(8); + expect(nthUglyNumber(8)).toBe(9); + expect(nthUglyNumber(9)).toBe(10); + expect(nthUglyNumber(10)).toBe(12); + expect(nthUglyNumber(11)).toBe(15); + expect(nthUglyNumber(12)).toBe(16); + expect(nthUglyNumber(100)).toBe(1536); + + }); + + + + it('should compute nth ugly numbers in linear time correctly', () => { + expect(nthUglyNumber(0)).toBeFalsy(); //1,2,3,4,5,6,8,9,10,12,15,16,18, + + expect(nthUglyNumberLinear(1)).toBe(1); + expect(nthUglyNumberLinear(2)).toBe(2); + expect(nthUglyNumberLinear(3)).toBe(3); + expect(nthUglyNumberLinear(4)).toBe(4); + expect(nthUglyNumberLinear(5)).toBe(5); + expect(nthUglyNumberLinear(6)).toBe(6); + expect(nthUglyNumberLinear(7)).toBe(8); + expect(nthUglyNumberLinear(8)).toBe(9); + expect(nthUglyNumberLinear(9)).toBe(10); + expect(nthUglyNumberLinear(10)).toBe(12); + expect(nthUglyNumberLinear(11)).toBe(15); + expect(nthUglyNumberLinear(12)).toBe(16); + expect(nthUglyNumberLinear(100)).toBe(1536); + + }); + + + + + it('should compute nth ugly numbers in linear time correctly', () => { + + + expect(superUglyNumber(10,[2,3,5])).toBe(12); + expect(superUglyNumber(100,[2,3,5])).toBe(1536); + + + expect(superUglyNumber(10,[2,3,7])).toBe(14); //1,2,3,4,6,7,8,9,12,14,16, + }); + + +}); diff --git a/solutions/algorithms/sorting/Sort.js b/solutions/algorithms/sorting/Sort.js index e029088..0e3c26b 100644 --- a/solutions/algorithms/sorting/Sort.js +++ b/solutions/algorithms/sorting/Sort.js @@ -9,25 +9,11 @@ import Comparator from '../../utils/comparator/Comparator'; */ export default class Sort { - constructor(originalCallbacks) { - this.callbacks = Sort.initSortingCallbacks(originalCallbacks); - this.comparator = new Comparator(this.callbacks.compareCallback); - } - - /** - * @param {SorterCallbacks} originalCallbacks - * @returns {SorterCallbacks} - */ - static initSortingCallbacks(originalCallbacks) { - const callbacks = originalCallbacks || {}; - const stubCallback = () => {}; - - callbacks.compareCallback = callbacks.compareCallback || undefined; - callbacks.visitingCallback = callbacks.visitingCallback || stubCallback; - - return callbacks; + constructor(comp = null) { + this.comparator = new Comparator(comp); } + sort() { throw new Error('sort method must be implemented'); } diff --git a/solutions/algorithms/sorting/SortTester.js b/solutions/algorithms/sorting/SortTester.js index e92ea39..a5bb6c6 100644 --- a/solutions/algorithms/sorting/SortTester.js +++ b/solutions/algorithms/sorting/SortTester.js @@ -26,16 +26,16 @@ export class SortTester { } static testSortWithCustomComparator(SortingClass) { - const callbacks = { - compareCallback: (a, b) => { - if (a.length === b.length) { - return 0; - } - return a.length < b.length ? -1 : 1; - }, - }; + const callback = + (a, b) => { + if (a.length === b.length) { + return 0; + } + return a.length < b.length ? -1 : 1; + }; + - const sorter = new SortingClass(callbacks); + const sorter = new SortingClass(callback); expect(sorter.sort([''])).toEqual(['']); expect(sorter.sort(['a'])).toEqual(['a']); @@ -45,28 +45,37 @@ export class SortTester { } static testSortStability(SortingClass) { - const callbacks = { - compareCallback: (a, b) => { - if (a.length === b.length) { - return 0; - } - return a.length < b.length ? -1 : 1; - }, - }; + const callback = + (a, b) => { + if (a.length === b.length) { + return 0; + } + return a.length < b.length ? -1 : 1; + }; + - const sorter = new SortingClass(callbacks); + const sorter = new SortingClass(callback); expect(sorter.sort(['bb', 'aa', 'c'])).toEqual(['c', 'bb', 'aa']); expect(sorter.sort(['aa', 'q', 'a', 'bbbb', 'ccc'])).toEqual(['q', 'a', 'aa', 'ccc', 'bbbb']); } static testAlgorithmTimeComplexity(SortingClass, arrayToBeSorted, numberOfVisits) { - const visitingCallback = jest.fn(); - const callbacks = { visitingCallback }; - const sorter = new SortingClass(callbacks); - sorter.sort(arrayToBeSorted); - - expect(visitingCallback).toHaveBeenCalledTimes(numberOfVisits); + const countingCallback = jest.fn(); + const callback = + (a, b) => { + countingCallback(); + if (a === b ) { + return 0; + } + return a < b ? -1 : 1; + }; + + + const sorter = new SortingClass(callback ); + sorter.sort(arrayToBeSorted); + expect(countingCallback).toHaveBeenCalledTimes( numberOfVisits); + } } diff --git a/solutions/algorithms/sorting/bubble-sort/BubbleSort.js b/solutions/algorithms/sorting/bubble-sort/BubbleSort.js index 4d3f156..8d0da01 100644 --- a/solutions/algorithms/sorting/bubble-sort/BubbleSort.js +++ b/solutions/algorithms/sorting/bubble-sort/BubbleSort.js @@ -6,17 +6,12 @@ export default class BubbleSort extends Sort { let swapped = false; // Clone original array to prevent its modification. const array = [...originalArray]; - for (let i = 1; i < array.length; i += 1) { swapped = false; - // Call visiting callback. - this.callbacks.visitingCallback(array[i]); - + for (let j = 0; j < array.length - i; j += 1) { - // Call visiting callback. - this.callbacks.visitingCallback(array[j]); - + // Swap elements if they are in wrong order. if (this.comparator.lessThan(array[j + 1], array[j])) { const tmp = array[j + 1]; diff --git a/solutions/algorithms/sorting/bubble-sort/README.md b/solutions/algorithms/sorting/bubble-sort/README.md index aed7169..ce3316d 100644 --- a/solutions/algorithms/sorting/bubble-sort/README.md +++ b/solutions/algorithms/sorting/bubble-sort/README.md @@ -9,6 +9,10 @@ are needed, which indicates that the list is sorted. ![Algorithm Visualization](https://upload.wikimedia.org/wikipedia/commons/c/c8/Bubble-sort-example-300px.gif) + + + + ## Complexity | Name | Best | Average | Worst | Memory | Stable | Comments | diff --git a/solutions/algorithms/sorting/bubble-sort/__test__/BubbleSort.test.js b/solutions/algorithms/sorting/bubble-sort/__test__/BubbleSort.test.js index 7e71a0b..84d9184 100644 --- a/solutions/algorithms/sorting/bubble-sort/__test__/BubbleSort.test.js +++ b/solutions/algorithms/sorting/bubble-sort/__test__/BubbleSort.test.js @@ -8,12 +8,13 @@ import { } from '../../SortTester'; // Complexity constants. -const SORTED_ARRAY_VISITING_COUNT = 20; -const NOT_SORTED_ARRAY_VISITING_COUNT = 189; -const REVERSE_SORTED_ARRAY_VISITING_COUNT = 209; -const EQUAL_ARRAY_VISITING_COUNT = 20; +const SORTED_ARRAY_VISITING_COUNT = 19; +const NOT_SORTED_ARRAY_VISITING_COUNT = 175; +const REVERSE_SORTED_ARRAY_VISITING_COUNT = 190; +const EQUAL_ARRAY_VISITING_COUNT = 19; describe('BubbleSort', () => { + it('should sort array', () => { SortTester.testSort(BubbleSort); }); diff --git a/solutions/algorithms/sorting/insertion-sort/InsertionSort.js b/solutions/algorithms/sorting/insertion-sort/InsertionSort.js index 4726254..cf9fb09 100644 --- a/solutions/algorithms/sorting/insertion-sort/InsertionSort.js +++ b/solutions/algorithms/sorting/insertion-sort/InsertionSort.js @@ -8,18 +8,13 @@ export default class InsertionSort extends Sort { for (let i = 0; i < array.length; i += 1) { let currentIndex = i; - // Call visiting callback. - this.callbacks.visitingCallback(array[i]); - // Go and check if previous elements and greater then current one. // If this is the case then swap that elements. while ( array[currentIndex - 1] !== undefined && this.comparator.lessThan(array[currentIndex], array[currentIndex - 1]) ) { - // Call visiting callback. - this.callbacks.visitingCallback(array[currentIndex - 1]); - + // Swap the elements. const tmp = array[currentIndex - 1]; array[currentIndex - 1] = array[currentIndex]; diff --git a/solutions/algorithms/sorting/insertion-sort/README.md b/solutions/algorithms/sorting/insertion-sort/README.md index cd3a74e..0810cb5 100644 --- a/solutions/algorithms/sorting/insertion-sort/README.md +++ b/solutions/algorithms/sorting/insertion-sort/README.md @@ -10,12 +10,26 @@ sort. ![Algorithm Visualization](https://upload.wikimedia.org/wikipedia/commons/0/0f/Insertion-sort-example-300px.gif) + +Insertion sort can be implemented by looping through the input, and inserting the next element in the now sorted list of previous elements. Tghis can be done in two ways, wither by creating a new list or array of elements, or by sorting in place. If sorting is done in place, any items may need to be moved around. + +Selection sort can be implemented as repeatedly calling insert in place on a linked list, as we previously implemented in the section of doubly linked lists. + + + ## Complexity | Name | Best | Average | Worst | Memory | Stable | Comments | | --------------------- | :-------------: | :-----------------: | :-----------------: | :-------: | :-------: | :-------- | | **Insertion sort** | n | n2 | n2 | 1 | Yes | | + +## Comparison with Selection Sort + +Selection sort and Insertion sort are similar, but have a few important differences. Both insert items into a sorted list. Selction sort looks at the entire list to be sorted each time and chooses the next smallest, and so alwayd appends an element to its currently sorted list of elements. Insertion sort, rather than find the next smallest elements, just inserts whatever element is next and finds the right position for it. Selection sort does all its work in finding what element to insert next, while Insertion sort does all its work on finding where t insert the element. + +Each method has times when it is applicable. Selection sort only write to memory n times, as it always appends to its sorted list. This makes it suitable for places where writes are expenseive, like flash memory. Insertion sort is very fast for small lists, less than 16 elements, and is the sorting routine of choice for lists of this size. The threshold can vary from between 7 and 50 items in different environments. Insertion sort does move a large amount of data around, however, so if items are large, it is better to sort indices, rather than the data items, and move the data into place in a final pass. + ## References [Wikipedia](https://en.wikipedia.org/wiki/Insertion_sort) diff --git a/solutions/algorithms/sorting/insertion-sort/__test__/InsertionSort.test.js b/solutions/algorithms/sorting/insertion-sort/__test__/InsertionSort.test.js index 588472e..8a5d3df 100644 --- a/solutions/algorithms/sorting/insertion-sort/__test__/InsertionSort.test.js +++ b/solutions/algorithms/sorting/insertion-sort/__test__/InsertionSort.test.js @@ -8,10 +8,10 @@ import { } from '../../SortTester'; // Complexity constants. -const SORTED_ARRAY_VISITING_COUNT = 20; -const NOT_SORTED_ARRAY_VISITING_COUNT = 101; -const REVERSE_SORTED_ARRAY_VISITING_COUNT = 210; -const EQUAL_ARRAY_VISITING_COUNT = 20; +const SORTED_ARRAY_VISITING_COUNT = 19; +const NOT_SORTED_ARRAY_VISITING_COUNT = 97; +const REVERSE_SORTED_ARRAY_VISITING_COUNT = 190; +const EQUAL_ARRAY_VISITING_COUNT = 19; describe('InsertionSort', () => { it('should sort array', () => { diff --git a/solutions/algorithms/sorting/merge-sort/MergeSort.js b/solutions/algorithms/sorting/merge-sort/MergeSort.js index 6a2f8a8..9102574 100644 --- a/solutions/algorithms/sorting/merge-sort/MergeSort.js +++ b/solutions/algorithms/sorting/merge-sort/MergeSort.js @@ -2,9 +2,7 @@ import Sort from '../Sort'; export default class MergeSort extends Sort { sort(originalArray) { - // Call visiting callback. - this.callbacks.visitingCallback(null); - + // If array is empty or consists of one element then return this array since it is sorted. if (originalArray.length <= 1) { return originalArray; @@ -37,9 +35,7 @@ export default class MergeSort extends Sort { minimumElement = rightArray.shift(); } - // Call visiting callback. - this.callbacks.visitingCallback(minimumElement); - + // Push the minimum element of two arrays to the sorted array. sortedArray.push(minimumElement); } diff --git a/solutions/algorithms/sorting/merge-sort/__test__/MergeSort.test.js b/solutions/algorithms/sorting/merge-sort/__test__/MergeSort.test.js index 0112003..c761836 100644 --- a/solutions/algorithms/sorting/merge-sort/__test__/MergeSort.test.js +++ b/solutions/algorithms/sorting/merge-sort/__test__/MergeSort.test.js @@ -8,10 +8,10 @@ import { } from '../../SortTester'; // Complexity constants. -const SORTED_ARRAY_VISITING_COUNT = 79; -const NOT_SORTED_ARRAY_VISITING_COUNT = 102; -const REVERSE_SORTED_ARRAY_VISITING_COUNT = 87; -const EQUAL_ARRAY_VISITING_COUNT = 79; +const SORTED_ARRAY_VISITING_COUNT = 40; +const NOT_SORTED_ARRAY_VISITING_COUNT = 95; +const REVERSE_SORTED_ARRAY_VISITING_COUNT = 96; +const EQUAL_ARRAY_VISITING_COUNT = 80; describe('MergeSort', () => { it('should sort array', () => { diff --git a/solutions/algorithms/sorting/quick-sort/QuickSort.js b/solutions/algorithms/sorting/quick-sort/QuickSort.js index 1c63c86..848c63f 100644 --- a/solutions/algorithms/sorting/quick-sort/QuickSort.js +++ b/solutions/algorithms/sorting/quick-sort/QuickSort.js @@ -24,14 +24,15 @@ export default class QuickSort extends Sort { // Split all array elements between left, center and right arrays. while (array.length) { - const currentElement = array.shift(); + const currentElement = array.shift(); - // Call visiting callback. - this.callbacks.visitingCallback(currentElement); + var comp_val = this.comparator.compare(currentElement, pivotElement); + + // we record the result of the comparison so that we do not copute it twice. - if (this.comparator.equal(currentElement, pivotElement)) { + if (comp_val === 0 ) { centerArray.push(currentElement); - } else if (this.comparator.lessThan(currentElement, pivotElement)) { + } else if (comp_val < 0 ) { leftArray.push(currentElement); } else { rightArray.push(currentElement); diff --git a/solutions/algorithms/sorting/quick-sort/QuickSortInPlace.js b/solutions/algorithms/sorting/quick-sort/QuickSortInPlace.js index 0fc6cf1..efa05dd 100644 --- a/solutions/algorithms/sorting/quick-sort/QuickSortInPlace.js +++ b/solutions/algorithms/sorting/quick-sort/QuickSortInPlace.js @@ -34,8 +34,7 @@ export default class QuickSortInPlace extends Sort { }; const pivot = array[highIndex]; - this.callbacks.visitingCallback(array[pivot]); - + let firstRunner = lowIndex - 1; for (let secondRunner = lowIndex; secondRunner < highIndex; secondRunner += 1) { if (this.comparator.lessThan(array[secondRunner], pivot)) { @@ -74,3 +73,4 @@ export default class QuickSortInPlace extends Sort { return array; } } + diff --git a/solutions/algorithms/sorting/quick-sort/QuickSortInPlaceHoare.js b/solutions/algorithms/sorting/quick-sort/QuickSortInPlaceHoare.js new file mode 100644 index 0000000..51e7bec --- /dev/null +++ b/solutions/algorithms/sorting/quick-sort/QuickSortInPlaceHoare.js @@ -0,0 +1,86 @@ +import Sort from '../Sort'; + +export default class QuickSortInPlaceHoare extends Sort { + /** Sorting in place avoids unnecessary use of additional memory, but modifies input array. + * + * This uses Hoare's original in place scheme, which is more complicated, but faster than the + * popular Lomuto scheme. + * + * @param {*[]} originalArray + * @param {number} inputLowIndex + * @param {number} inputHighIndex + * @return {*[]} + */ + sort(originalArray, inputLowIndex, inputHighIndex) { + // Destructures array on initial passthrough, and then sorts in place. + const array = inputLowIndex === undefined ? [...originalArray] : originalArray; + + /** + * Partition array segment and return index of last swap + * + * @param {number} lowIndex + * @param {number} highIndex + * @return {number} + */ + const partition = (lowIndex, highIndex) => { + /** + * @param {number} leftIndex + * @param {number} rightIndex + */ + const swap = (leftIndex, rightIndex) => { + const tempVariable = array[leftIndex]; + array[leftIndex] = array[rightIndex]; + array[rightIndex] = tempVariable; + }; + + const pivot = array[lowIndex + Math.floor((highIndex -lowIndex )/2)]; + var low = lowIndex; + var high = highIndex; + + while(1) + { + while (this.comparator.lessThan(array[low] , pivot)) + { + low++; + } + while (this.comparator.lessThan(pivot, array[high] )) + { + high--; + } + if (low >= high){ + break; + } + swap(low,high) + low++; + high--; + + } + + + return high; + }; + + /* + * While we can use a default parameter to set `low` to 0, we would + * still have to set `high`'s default within the function as we + * don't have access to `array.length - 1` when declaring parameters + */ + const lowIndex = inputLowIndex === undefined ? 0 : inputLowIndex; + const highIndex = inputHighIndex === undefined ? array.length - 1 : inputHighIndex; + + // Base case is when low and high converge + if (lowIndex < highIndex) { + const partitionIndex = partition(lowIndex, highIndex); + /* + * `partition()` swaps elements of the array based on their comparison to the `hi` parameter, + * and then returns the index where swapping is no longer necessary, which can be best thought + * of as the pivot used to split an array in a non-in-place quicksort + */ + this.sort(array, lowIndex, partitionIndex ); + this.sort(array, partitionIndex + 1, highIndex); + } + + return array; + } +} + diff --git a/solutions/algorithms/sorting/quick-sort/README.md b/solutions/algorithms/sorting/quick-sort/README.md index 683a7f3..0c5a6b9 100644 --- a/solutions/algorithms/sorting/quick-sort/README.md +++ b/solutions/algorithms/sorting/quick-sort/README.md @@ -29,6 +29,23 @@ The horizontal lines are pivot values. | --------------------- | :-------------: | :-----------------: | :-----------------: | :-------: | :-------: | :-------- | | **Quick sort** | n log(n) | n log(n) | n2 | log(n) | No | | + +#InPlace QuickSort + +Quicksort can be done in place, and this fact is one of the aspects that makes quicksort the preferred sorting algorithm in many contexts. The standard way to do this is to keep track of a low index `lo` and a high index, `hi`, and to define a recursive function, `quicksort(lo,hi)` that sorts the part of the array between `lo` and `hi` (inclusive). + +One more step is needed, partitioning the array into two. This is the critical step. There are two ways to do this, the traditional Hoare way, and the simpler, but slower Lomuto way. Once we have a function that partitions the array, and returns the index `p` where it is split, we call `quicksort(lo,p)` and `quicksort(p+1, hi)`. + +## Lomuto + +We are asked to partition the sub array from `lo` to `hi`. We choose a `pivot`, the element at `hi`, and set our index `i` to `lo`. We loop upwards with another index `j` from `lo` to `hi-1` and when the element at `j` is less than the pivot, we swap the elements at `i` and `j`, and increment `i`. Finally, we swap the element at `i` with the element at `hi`. + +## Hoare + +Hoare's method is a little more complicated, but about three times faster, and it is Hoare's implementation that makes quicksort fatser than other O(nlog(n)) methods. It changes how we partition the array as follows. We choose a pivot as before, but now we choose the `pivot` to be the value of the middle element in the subarray. We then move the two end points `lo` and `hi` towards each other until they cross. If the element at `lo` is less than the pivot we repeatedly increase `lo`. If the element at `hi` is greater than the pivot we repeatedlt decrease `hi`. If `lo` and `hi` cross we are done, otherwise we swap the elements at `lo` and `hi` increment and decrement, and continue. + +Lomuto's method is inferior to Hoare's scheme because it does three times more swaps on average and degrades to O(n^2) runtime when all elements are equal. +^ ## References - [Wikipedia](https://en.wikipedia.org/wiki/Quicksort) diff --git a/solutions/algorithms/sorting/quick-sort/__test__/QuickSortInPlace.test.js b/solutions/algorithms/sorting/quick-sort/__test__/QuickSortInPlace.test.js index 0e103f2..973e3d6 100644 --- a/solutions/algorithms/sorting/quick-sort/__test__/QuickSortInPlace.test.js +++ b/solutions/algorithms/sorting/quick-sort/__test__/QuickSortInPlace.test.js @@ -1,4 +1,4 @@ -import QuickSortInPlace from '../QuickSortInPlace'; +import QuickSortInPlace from '../QuickSortInPlace'; import { equalArr, notSortedArr, @@ -8,10 +8,10 @@ import { } from '../../SortTester'; // Complexity constants. -const SORTED_ARRAY_VISITING_COUNT = 19; -const NOT_SORTED_ARRAY_VISITING_COUNT = 12; -const REVERSE_SORTED_ARRAY_VISITING_COUNT = 19; -const EQUAL_ARRAY_VISITING_COUNT = 19; +const SORTED_ARRAY_VISITING_COUNT = 209; +const NOT_SORTED_ARRAY_VISITING_COUNT = 92; +const REVERSE_SORTED_ARRAY_VISITING_COUNT = 209; +const EQUAL_ARRAY_VISITING_COUNT = 209; describe('QuickSortInPlace', () => { it('should sort array', () => { @@ -58,3 +58,4 @@ describe('QuickSortInPlace', () => { ); }); }); + diff --git a/solutions/algorithms/sorting/quick-sort/__test__/QuickSortInPlaceHoare.test.js b/solutions/algorithms/sorting/quick-sort/__test__/QuickSortInPlaceHoare.test.js new file mode 100644 index 0000000..e8812e0 --- /dev/null +++ b/solutions/algorithms/sorting/quick-sort/__test__/QuickSortInPlaceHoare.test.js @@ -0,0 +1,66 @@ + + +import QuickSortInPlaceHoare from '../QuickSortInPlaceHoare'; +import { + equalArr, + notSortedArr, + reverseArr, + sortedArr, + SortTester, +} from '../../SortTester'; + +// Complexity constants. +const SORTED_ARRAY_VISITING_COUNT = 107; +const NOT_SORTED_ARRAY_VISITING_COUNT = 141; +const REVERSE_SORTED_ARRAY_VISITING_COUNT = 108; +const EQUAL_ARRAY_VISITING_COUNT = 118; + + + +describe('QuickSortInPlaceHoare', () => { + it('should sort array', () => { + SortTester.testSort(QuickSortInPlaceHoare); + }); + + it('should sort array with custom comparator', () => { + SortTester.testSortWithCustomComparator(QuickSortInPlaceHoare); + }); + + it('should sort negative numbers', () => { + SortTester.testNegativeNumbersSort(QuickSortInPlaceHoare); + }); + + it('should visit EQUAL array element specified number of times', () => { + SortTester.testAlgorithmTimeComplexity( + QuickSortInPlaceHoare, + equalArr, + EQUAL_ARRAY_VISITING_COUNT, + ); + }); + + it('should visit SORTED array element specified number of times', () => { + SortTester.testAlgorithmTimeComplexity( + QuickSortInPlaceHoare, + sortedArr, + SORTED_ARRAY_VISITING_COUNT, + ); + }); + + it('should visit NOT SORTED array element specified number of times', () => { + SortTester.testAlgorithmTimeComplexity( + QuickSortInPlaceHoare, + notSortedArr, + NOT_SORTED_ARRAY_VISITING_COUNT, + ); + }); + + it('should visit REVERSE SORTED array element specified number of times', () => { + SortTester.testAlgorithmTimeComplexity( + QuickSortInPlaceHoare, + reverseArr, + REVERSE_SORTED_ARRAY_VISITING_COUNT, + ); + }); + + +}); diff --git a/solutions/algorithms/sorting/selection-sort/README.md b/solutions/algorithms/sorting/selection-sort/README.md index 6a708d2..63fa2ac 100644 --- a/solutions/algorithms/sorting/selection-sort/README.md +++ b/solutions/algorithms/sorting/selection-sort/README.md @@ -9,6 +9,12 @@ performance advantages over more complicated algorithms in certain situations, particularly where auxiliary memory is limited. + +Selection sort can be implemented by repeatedly finding the smallest +element of the list, and removing the chosen element each time. If a +heap is used instead of a list, this algortihm of sorting by removing +repeatdely the least element, is called heap sort. + ![Algorithm Visualization](https://upload.wikimedia.org/wikipedia/commons/b/b0/Selection_sort_animation.gif) ![Algorithm Visualization](https://upload.wikimedia.org/wikipedia/commons/9/94/Selection-Sort-Animation.gif) diff --git a/solutions/algorithms/sorting/selection-sort/SelectionSort.js b/solutions/algorithms/sorting/selection-sort/SelectionSort.js index 1b066af..5d620c9 100644 --- a/solutions/algorithms/sorting/selection-sort/SelectionSort.js +++ b/solutions/algorithms/sorting/selection-sort/SelectionSort.js @@ -8,18 +8,14 @@ export default class SelectionSort extends Sort { for (let i = 0; i < array.length - 1; i += 1) { let minIndex = i; - // Call visiting callback. - this.callbacks.visitingCallback(array[i]); - + // Find minimum element in the rest of array. - for (let j = i + 1; j < array.length; j += 1) { - // Call visiting callback. - this.callbacks.visitingCallback(array[j]); - - if (this.comparator.lessThan(array[j], array[minIndex])) { - minIndex = j; - } - } + for (let j = i + 1; j < array.length; j += 1) { + + if (this.comparator.lessThan(array[j], array[minIndex])) { + minIndex = j; + } + } // If new minimum element has been found then swap it with current i-th element. if (minIndex !== i) { diff --git a/solutions/algorithms/sorting/selection-sort/__test__/SelectionSort.test.js b/solutions/algorithms/sorting/selection-sort/__test__/SelectionSort.test.js index d52385d..12a6c23 100644 --- a/solutions/algorithms/sorting/selection-sort/__test__/SelectionSort.test.js +++ b/solutions/algorithms/sorting/selection-sort/__test__/SelectionSort.test.js @@ -8,10 +8,10 @@ import { } from '../../SortTester'; // Complexity constants. -const SORTED_ARRAY_VISITING_COUNT = 209; -const NOT_SORTED_ARRAY_VISITING_COUNT = 209; -const REVERSE_SORTED_ARRAY_VISITING_COUNT = 209; -const EQUAL_ARRAY_VISITING_COUNT = 209; +const SORTED_ARRAY_VISITING_COUNT = 190; +const NOT_SORTED_ARRAY_VISITING_COUNT = 190; +const REVERSE_SORTED_ARRAY_VISITING_COUNT = 190; +const EQUAL_ARRAY_VISITING_COUNT = 190; describe('SelectionSort', () => { it('should sort array', () => { diff --git a/solutions/data-structures/binary-tree/BinaryTree.js b/solutions/data-structures/binary-tree/BinaryTree.js new file mode 100644 index 0000000..4392f4d --- /dev/null +++ b/solutions/data-structures/binary-tree/BinaryTree.js @@ -0,0 +1,96 @@ + + +import BinaryTreeNode from './BinaryTreeNode'; + +export default class BinaryTree { + constructor() { + this.store = new BinaryTreeNode(); + } + + + + +//Traverse the tree in-order and call callback on the value of each node. + + inOrder(callback){ + var node = this.store; + node.inOrder(callback); + return; + +} + + + postOrder(callback){ + + var node = this.store; + node.postOrder(callback); + return; + +} + + + + preOrder(callback){ + + var node = this.store; + node.preOrder(callback); + return; + +} + + +numberOfNodes(){ + var count = 0; + this.inOrder( a => { count++;}) + return count; +} + +numberOfLeaves(){ + var count = 0; + this.inOrder( a => { if (!a.left && !a.right) count++;}) + return count; +} + +// use a call back that + +height(){ + return this.store.height(); +} + + numberOfLeaves(){ + return this.store.numberOfLeaves() +} + +numberOfNodes(){ + return this.store.numberOfNodes() +} + + +balanced(){ + return this.store.balanced() +} + + +degenerate(){ + return this.store.degenerate() +} + +perfect(){ + return this.store.perfect() +} + +complete(){ + return this.store.complete(); +} + + + + toString(){ + // traverse in order + var res = []; + this.inOrder(a => { res.push(a.value);} ); + return res.toString(); + } + +} + diff --git a/solutions/data-structures/binary-tree/BinaryTreeNode.js b/solutions/data-structures/binary-tree/BinaryTreeNode.js new file mode 100644 index 0000000..446fcf4 --- /dev/null +++ b/solutions/data-structures/binary-tree/BinaryTreeNode.js @@ -0,0 +1,133 @@ + +export default class BinaryTreeNode { + constructor(value, left = null, right = null) { + this.value = value; + this.left = left; + this.right = right; + } + + +//Traverse the nodes in-order and call callback on the value of each node. + inOrder(callback){ + if (this.left) this.left.inOrder(callback); + callback(this); + if(this.right) this.right.inOrder(callback); + return; +} + + + +preOrder(callback){ + callback(this); + if(this.left) this.left.preOrder(callback); + if(this.right) this.right.preOrder(callback); + return; +} + +postOrder(callback){ + if(this.left) this.left.postOrder(callback); + if(this.right) this.right.postOrder(callback); + callback(this); + return; +} + + +height(){ + if (!this.left && !this.right) return 1; + if (!this.right) return 1 + this.left.height(); + if (!this.left) return 1 + this.right.height(); + return 1 + Math.max(this.left.height() , this.right.height() ) ; + +} + +numberOfLeaves(){ + if (!this.left || !this.right) return 1; + if (!this.left) return this.right.numberOfLeaves(); + if (!this.right) return this.left.numberOfLeaves(); + return this.left.numberOfLeaves() + this.right.numberOfLeaves(); +} + +numberOfNodes(){ + if (!this.left && !this.right) return 1; + if (!this.left) return 1 + this.right.numberOfNodes(); + if (!this.right) return 1 + this.left.numberOfNodes(); + return 1+ this.left.numberOfNodes() + this.right.numberOfNodes(); +} + + +balanced(){ + +// A binary tree is balanced if the heights of subtrees never differ by more than one. + if (this.balancedHeight()) return true; + return false; + +} + + +balancedHeight(){ + + // we return the hieght of the tree if it is balanced, otherwise we return false; + if (!this.left && !this.right) return 1; + if (!this.left) return 1 === this.right.height(); + if (!this.right) return 1 === this.left.height(); + + const h1 = this.left.balancedHeight(); + const h2 = this.right.balancedHeight(); + + if (h1 && h2 && h1 -h2 <= 1 && h2 -h1 <=1) return 1 + Math.max(h1,h2); + return false; +} + + +degenerate(){ + if (this.numberOfNodes() === this.height()) return true; + return false; +} + +perfect(){ + // a binary tree is perfect if its sub trees are perfect, and the same height + // the empty subtree is balanced. + if (!this.left && !this.right) return true; + if ( !this.left || !this.right) return false; + if (this.left.height() == this.right.height() && + this.left.perfect() && this.right.perfect() ) return true; + + return false; +} + + +complete(){ + + // A Binary Tree is complete Binary Tree if all levels are completely filled except possibly the last level and the last level has all keys as left as possible + + // We can check this by seeing if the height is greater than the 1+ log(n) where n is the number of nodes, and recursively checking that the right is never set when thre left is not. + + // if there are 7 nodes or less, the height should be 3 or less. + // if there are 8 nodes, the height can be 4. + // + + const h = this.height(); + const numNodes = this.numberOfNodes(); + if (h > Math.log2(numNodes) +1 ){ + return false ; + } + var incomplete = false; + this.inOrder(a => { if (a.right && !a.left){ + incomplete = true;} } ); + return !incomplete; +} + + toString(){ + // traverse in order + var res = []; + this.inOrder(a => { + res.push(a.value);} ); + return res.toString(); + + } + + +// toString(callback) { + // return callback ? callback(this.value) : `${this.value}`; + // } +} diff --git a/solutions/data-structures/binary-tree/InOrderIterative.js b/solutions/data-structures/binary-tree/InOrderIterative.js new file mode 100644 index 0000000..ff6d957 --- /dev/null +++ b/solutions/data-structures/binary-tree/InOrderIterative.js @@ -0,0 +1,45 @@ + + +import BinaryTreeNode from './BinaryTreeNode'; +import Stack from './../stack/Stack'; + + +export function inOrderIterative(tree,callback){ + + + var node = tree.store; + var st = new Stack(); + + while (node || !st.isEmpty() ) + { + /* Reach the leftmost node of the + curr */ + while (node !== null) + { + /* place tree node on + the stack before traversing + the node's left subtree */ + st.push(node); + node = node.left; + } + + /* Node must be NULL at this point */ + node = st.pop(); + + callback(node); + + /* we have visited the node and its + left subtree. Now, it is the right + subtree's turn */ + node = node.right; + + } +} + + + + + + + + diff --git a/solutions/data-structures/binary-tree/PostOrderIterative.js b/solutions/data-structures/binary-tree/PostOrderIterative.js new file mode 100644 index 0000000..0ffd9a4 --- /dev/null +++ b/solutions/data-structures/binary-tree/PostOrderIterative.js @@ -0,0 +1,52 @@ + + +import BinaryTreeNode from './BinaryTreeNode'; +import Stack from './../stack/Stack'; + + +export function postOrderIterative(tree,callback){ + + + var node = tree.store; + var st = new Stack(); + + while (node || !st.isEmpty() ) + { + while (node) + { + // Push node's right child and then node to stack. + if (node.right) + st.push(node.right); + st.push(node); + + // Set node as node's left child + node = node.left; + } + + // Pop an item from stack and set it as node + node = st.pop(); + + // If the popped item has a right child and the right child is not + // processed yet, then make sure right child is processed before node + if (node.right && st.peek() === node.right) + { + st.pop(st); // remove right child from stack + st.push(node); // push node back to stack + node = node.right; // change node so that the right + // child is processed next + } + else + { + callback(node); + node = null; + } + } +} + + + + + + + + diff --git a/solutions/data-structures/binary-tree/PreOrderIterative.js b/solutions/data-structures/binary-tree/PreOrderIterative.js new file mode 100644 index 0000000..6db1b68 --- /dev/null +++ b/solutions/data-structures/binary-tree/PreOrderIterative.js @@ -0,0 +1,46 @@ +f + +import BinaryTreeNode from './BinaryTreeNode'; +import Stack from './../stack/Stack'; + + +export function preOrderIterative(tree,callback){ + + + var node = tree.store; + var st = new Stack(); + if (!node) + return; + + + st.push(node); + + /* Pop all items one by one. Do following for every popped item + a) callback + b) push its right child + c) push its left child + Note that right child is pushed first so that left is processed first */ + while (!st.isEmpty() ) + { + // Pop the top item from stack + node = st.pop(); + // callback + callback(node); + + + // Push right and left children of the popped node to stack + if (node.right) + st.push(node.right); + if (node.left) + st.push(node.left); + } + +} + + + + + + + + diff --git a/solutions/data-structures/binary-tree/README.md b/solutions/data-structures/binary-tree/README.md new file mode 100644 index 0000000..759e1b4 --- /dev/null +++ b/solutions/data-structures/binary-tree/README.md @@ -0,0 +1,48 @@ +# Binary Tree + + +## Description + +Just as a list is a data structure with nodes, where each node has one child, the next node, a binart tree is a data structure made up of nodex where each node has two children, a left child and a right child. The first node is called the root, and in Computer Science, trees are drawn downwards, for some reason. + +![Binary Tree](../../../assets/Binary_tree.svg) + +As well as a left and right child, binary tree nodes usually have a value. In the aboe picture, the number displayed is the value of the node. Sometimes nodes have a parent pointer, and sometimes they even have a pointer to their siblings or uncles. + +We say a node's height is one greater than the max height of its children. A node without children has height one. A node without children is called a leaf. A node with children is called an interior node. Sometimes nodes will kepp track of their height. The depth of a node is how far it is from the root. The root has depth 1, its children have depth 2, etc. + + Serveral kinds of binary tree are distinguised. A degenerate binary tree is one that have only one or zero children at each node. This means it is similar to a list. + A full binary tree has 0 or 2 nodes at every level. A complete binary tree has two nodes at every level, save the lowest level. At the lowest level, all nodes are as far left as possible. This becomes important later when we discuss heaps. + + A perfect binary tree of height h, has all interior nodes with two children, and all leaves are at depth n. A balanced binary tree is one where the left and right subtrees of every node differ in height by no more than 1. + + + +## Implementation + +The usual minimal implementation of a binary tree node has a constructor that sets its value, left and right. Helper functions can copute the `height` of a node recursively. The `numberOfLeaves` and `numberOfNodes` are also easily computed recursively. + +Test functions that determines where a binary node is `balanced`, `degenerate`, `perfect` or `complete` can be written. + +A binary tree is a data structre that stores the root binary tree node. + +## Binary Tree Problems + +A binary tree can be traversed in several ways. In order traversal visits the left tree, then the right tree, and then visits the root. Pre-order visits the root, then the left and then the right subtree. Post-order visits the left subtree, then the right sibtree, then the root. + +Write a method that takes a callback, and calls it on each node in pre-order, in-order, and post-order. + +These methods are most easily written recursively. It is worthwhile to write these iteratively using a stack. Write + +`inOrderIterative(tree,callback)` + + `preOrderIterative(tree,callback)` + +`postOrderIterative(tree,callback)` + + + + + + + diff --git a/solutions/data-structures/binary-tree/__test__/BinaryTree.test.js b/solutions/data-structures/binary-tree/__test__/BinaryTree.test.js new file mode 100644 index 0000000..c5c079e --- /dev/null +++ b/solutions/data-structures/binary-tree/__test__/BinaryTree.test.js @@ -0,0 +1,227 @@ + +import BinaryTreeNode from '../BinaryTreeNode'; +import BinaryTree from '../BinaryTree'; + +describe('BinaryTreeNode', () => { + it('should create empty BinaryTreeNode', () => { + const node = new BinaryTree(); + expect( node).not.toBeUndefined(); + expect( node ).not.toBeUndefined(); + }); + + + + it('should traverse a BinaryTree in Order.', () => { + + const node1 = new BinaryTreeNode(1); + const node3 = new BinaryTreeNode(3); + const node2 = new BinaryTreeNode(2,node1,node3); + const node5= new BinaryTreeNode(5); + const node4 = new BinaryTreeNode(4,node2,node5); + + const tree = new BinaryTree(); + tree.store = node4; + + expect( tree.toString()).toBe('1,2,3,4,5'); +}); + + + + it('should traverse a BinaryTree in post Order.', () => { + + const node1 = new BinaryTreeNode(1); + const node3 = new BinaryTreeNode(3); + const node2 = new BinaryTreeNode(2,node1,node3); + const node5= new BinaryTreeNode(5); + const node4 = new BinaryTreeNode(4,node2,node5); + + + const tree = new BinaryTree(); + tree.store = node4; + + var res = []; + tree.postOrder(a => { + res.push(a.value);} ); + + + + expect( res.toString()).toBe('1,3,2,5,4'); +}); + + + it('should traverse a BinaryTree in preOrder.', () => { + + const node1 = new BinaryTreeNode(1); + const node3 = new BinaryTreeNode(3); + const node2 = new BinaryTreeNode(2,node1,node3); + const node5= new BinaryTreeNode(5); + const node4 = new BinaryTreeNode(4,node2,node5); + const tree = new BinaryTree(); + tree.store = node4; + + + + var res = []; + tree.preOrder(a => { + res.push(a.value);} ); + + expect(res.toString()).toBe('4,2,1,3,5'); +}); + + + + it('should count the numbers of nodes of BinaryTree' , () => { + + const node1 = new BinaryTreeNode(1); + const node3 = new BinaryTreeNode(3); + const node2 = new BinaryTreeNode(2,node1,node3); + const node6= new BinaryTreeNode(6); + const node5= new BinaryTreeNode(5, null, node6); + const node4 = new BinaryTreeNode(4,node2,node5); + + const tree = new BinaryTree(); + tree.store = node4; + + + expect(tree.numberOfNodes()).toBe(6); + + +}); + + + + + it('should count the numbers of leaves of BinaryTree', () => { + + const node1 = new BinaryTreeNode(1); + const node3 = new BinaryTreeNode(3); + const node2 = new BinaryTreeNode(2,node1,node3); + + const node6= new BinaryTreeNode(6); + const node5= new BinaryTreeNode(5, null, node6); + const node4 = new BinaryTreeNode(4,node2,node5); + + const tree = new BinaryTree(); + tree.store = node4; + + + expect(tree.numberOfLeaves()).toBe(3); + + +}); + + + + it('should compute the height of a BinaryTree ', () => { + + const node1 = new BinaryTreeNode(1); + const node3 = new BinaryTreeNode(3); + const node2 = new BinaryTreeNode(2,node1,node3); + + const node6= new BinaryTreeNode(6); + const node5= new BinaryTreeNode(5, null, node6); + const node4 = new BinaryTreeNode(4,node2,node5); + + const tree = new BinaryTree(); + tree.store = node4; + + expect(tree.height()).toBe(3); + + +}); + + + + + it('should check is a BinaryTree is balanced', () => { + + const node1 = new BinaryTreeNode(1); + const node3 = new BinaryTreeNode(3); + const node2 = new BinaryTreeNode(2,node1,node3); + const node7= new BinaryTreeNode(7); + const node6= new BinaryTreeNode(6,null, node7); + const node5= new BinaryTreeNode(5, null, node6); + const node4 = new BinaryTreeNode(4,node2,node5); + const tree = new BinaryTree(); + tree.store = node4; + + expect(node4.balanced()).toBeFalsy(); + +}); + + +it('should check is a BinaryTree is degenerate', () => { + + const node1 = new BinaryTreeNode(1); + const node3 = new BinaryTreeNode(3); + const node2 = new BinaryTreeNode(2,node1,node3); + + const node6= new BinaryTreeNode(6); + const node5= new BinaryTreeNode(5, null, node6); + const node4 = new BinaryTreeNode(4,node2,node5); + const tree = new BinaryTree(); + tree.store = node4; + const tree1 = new BinaryTree(); + tree1.store = node5; + + expect(tree.degenerate()).toBeFalsy(); + expect(tree1.degenerate()).toBeTruthy(); + +}); + + +it('should check is a BinaryTree is perfect', () => { + + const node1 = new BinaryTreeNode(1); + const node3 = new BinaryTreeNode(3); + const node2 = new BinaryTreeNode(2,node1,node3); + + const node6= new BinaryTreeNode(6); + const node5= new BinaryTreeNode(5, null, node6); + const node4 = new BinaryTreeNode(4,node2,node5); + + const tree = new BinaryTree(); + tree.store = node4; + const tree1 = new BinaryTree(); + tree1.store = node3; + + expect(tree.perfect()).toBeFalsy(); + expect(tree1.perfect()).toBeTruthy(); + + +}); + + + +it('should check is a BinaryTree is complete', () => { + + const node1 = new BinaryTreeNode(1); + const node3 = new BinaryTreeNode(3); + const node2 = new BinaryTreeNode(2,node1,node3); + + const node6= new BinaryTreeNode(6); + var node5= new BinaryTreeNode(5, null, node6); + var node4 = new BinaryTreeNode(4,node2,node5); + + var tree = new BinaryTree(); + tree.store = node4; + const tree1 = new BinaryTree(); + tree1.store = node3; + + expect(tree.complete()).toBeFalsy(); + expect(tree1.complete()).toBeTruthy(); + + + node5 = new BinaryTreeNode(5, node6,null); + node4 = new BinaryTreeNode(4,node2,node5); + tree.store = node4; + + + expect(tree.complete()).toBeTruthy(); + + +}); + + + +}); diff --git a/solutions/data-structures/binary-tree/__test__/BinaryTreeNode.test.js b/solutions/data-structures/binary-tree/__test__/BinaryTreeNode.test.js new file mode 100644 index 0000000..5a294aa --- /dev/null +++ b/solutions/data-structures/binary-tree/__test__/BinaryTreeNode.test.js @@ -0,0 +1,232 @@ + +import BinaryTreeNode from '../BinaryTreeNode'; + +describe('BinaryTreeNode', () => { + it('should create empty BinaryTreeNode', () => { + const node = new BinaryTreeNode(); + expect( node).not.toBeUndefined(); + expect( node ).not.toBeUndefined(); + }); + + + it('should create BinaryTreeNode with a value', () => { + + const node = new BinaryTreeNode(1); + expect( node ).not.toBeUndefined(); + expect( node.value ).toBe(1); + expect( node.left ).toBeNull(); + expect( node.right ).toBeNull(); +}); + + + it('should traverse a BinaryTreeNode in Order.', () => { + + const node1 = new BinaryTreeNode(1); + const node3 = new BinaryTreeNode(3); + const node2 = new BinaryTreeNode(2,node1,node3); + const node5= new BinaryTreeNode(5); + const node4 = new BinaryTreeNode(4,node2,node5); + + expect( node1 ).not.toBeUndefined(); + expect( node1.value ).toBe(1); + expect( node4.toString()).toBe('1,2,3,4,5'); +}); + + + + it('should traverse a BinaryTreeNode in post Order.', () => { + + const node1 = new BinaryTreeNode(1); + const node3 = new BinaryTreeNode(3); + const node2 = new BinaryTreeNode(2,node1,node3); + const node5= new BinaryTreeNode(5); + const node4 = new BinaryTreeNode(4,node2,node5); + + expect( node1 ).not.toBeUndefined(); + expect( node1.value ).toBe(1); + + var res = []; + node4.postOrder(a => { + res.push(a.value);} ); + + + expect( res.toString()).toBe('1,3,2,5,4'); +}); + + + it('should traverse a BinaryTreeNode in preOrder.', () => { + + const node1 = new BinaryTreeNode(1); + const node3 = new BinaryTreeNode(3); + const node2 = new BinaryTreeNode(2,node1,node3); + const node5= new BinaryTreeNode(5); + const node4 = new BinaryTreeNode(4,node2,node5); + + expect( node1 ).not.toBeUndefined(); + expect( node1.value ).toBe(1); + + var res = []; + node4.preOrder(a => { + res.push(a.value);} ); + + expect(res.toString()).toBe('4,2,1,3,5'); +}); + + + + it('should count the numbers of nodes of BinaryTreeNodes', () => { + + const node1 = new BinaryTreeNode(1); + const node3 = new BinaryTreeNode(3); + const node2 = new BinaryTreeNode(2,node1,node3); + const node6= new BinaryTreeNode(6); + const node5= new BinaryTreeNode(5, null, node6); + const node4 = new BinaryTreeNode(4,node2,node5); + + expect(node4.numberOfNodes()).toBe(6); + expect(node3.numberOfNodes()).toBe(1); + expect(node2.numberOfNodes()).toBe(3); + expect(node1.numberOfNodes()).toBe(1); + expect(node5.numberOfNodes()).toBe(2); + expect(node6.numberOfNodes()).toBe(1); + +}); + + + + + it('should count the numbers of leaves of BinaryTreeNodes', () => { + + const node1 = new BinaryTreeNode(1); + const node3 = new BinaryTreeNode(3); + const node2 = new BinaryTreeNode(2,node1,node3); + + const node6= new BinaryTreeNode(6); + const node5= new BinaryTreeNode(5, null, node6); + const node4 = new BinaryTreeNode(4,node2,node5); + + expect(node4.numberOfLeaves()).toBe(3); + expect(node3.numberOfLeaves()).toBe(1); + expect(node2.numberOfLeaves()).toBe(2); + expect(node1.numberOfLeaves()).toBe(1); + expect(node5.numberOfLeaves()).toBe(1); + expect(node6.numberOfLeaves()).toBe(1); + +}); + + + + it('should compute the height BinaryTreeNodes', () => { + + const node1 = new BinaryTreeNode(1); + const node3 = new BinaryTreeNode(3); + const node2 = new BinaryTreeNode(2,node1,node3); + + const node6= new BinaryTreeNode(6); + const node5= new BinaryTreeNode(5, null, node6); + const node4 = new BinaryTreeNode(4,node2,node5); + + expect(node4.height()).toBe(3); + expect(node3.height()).toBe(1); + expect(node2.height()).toBe(2); + expect(node1.height()).toBe(1); + expect(node5.height()).toBe(2); + expect(node6.height()).toBe(1); + +}); + + + + + it('should check is a BinaryTreeNode is balanced', () => { + + const node1 = new BinaryTreeNode(1); + const node3 = new BinaryTreeNode(3); + const node2 = new BinaryTreeNode(2,node1,node3); + const node7= new BinaryTreeNode(7); + const node6= new BinaryTreeNode(6,null, node7); + const node5= new BinaryTreeNode(5, null, node6); + const node4 = new BinaryTreeNode(4,node2,node5); + + expect(node4.balanced()).toBeFalsy(); + expect(node3.balanced()).toBeTruthy(); + expect(node2.balanced()).toBeTruthy(); + expect(node1.balanced()).toBeTruthy(); + expect(node5.balanced()).toBeFalsy(); + expect(node6.balanced()).toBeTruthy(); + expect(node7.balanced()).toBeTruthy(); + +}); + + +it('should check is a BinaryTreeNode is degenerate', () => { + + const node1 = new BinaryTreeNode(1); + const node3 = new BinaryTreeNode(3); + const node2 = new BinaryTreeNode(2,node1,node3); + + const node6= new BinaryTreeNode(6); + const node5= new BinaryTreeNode(5, null, node6); + const node4 = new BinaryTreeNode(4,node2,node5); + + expect(node4.degenerate()).toBeFalsy(); + expect(node3.degenerate()).toBeTruthy(); + expect(node2.degenerate()).toBeFalsy(); + expect(node1.degenerate()).toBeTruthy(); + expect(node5.degenerate()).toBeTruthy(); + expect(node6.degenerate()).toBeTruthy(); + +}); + + +it('should check is a BinaryTreeNode is perfect', () => { + + const node1 = new BinaryTreeNode(1); + const node3 = new BinaryTreeNode(3); + const node2 = new BinaryTreeNode(2,node1,node3); + + const node6= new BinaryTreeNode(6); + const node5= new BinaryTreeNode(5, null, node6); + const node4 = new BinaryTreeNode(4,node2,node5); + + expect(node4.perfect()).toBeFalsy(); + expect(node3.perfect()).toBeTruthy(); + expect(node2.perfect()).toBeTruthy(); + expect(node1.perfect()).toBeTruthy(); + expect(node5.perfect()).toBeFalsy(); + expect(node6.perfect()).toBeTruthy(); + +}); + + + +it('should check is a BinaryTreeNode is complete', () => { + + const node1 = new BinaryTreeNode(1); + const node3 = new BinaryTreeNode(3); + const node2 = new BinaryTreeNode(2,node1,node3); + + const node6= new BinaryTreeNode(6); + var node5= new BinaryTreeNode(5, null, node6); + var node4 = new BinaryTreeNode(4,node2,node5); + + expect(node4.complete()).toBeFalsy(); + expect(node3.complete()).toBeTruthy(); + expect(node2.complete()).toBeTruthy(); + expect(node1.complete()).toBeTruthy(); + expect(node5.complete()).toBeFalsy(); + expect(node6.complete()).toBeTruthy(); + + node5 = new BinaryTreeNode(5, node6,null); + node4 = new BinaryTreeNode(4,node2,node5); + + expect(node4.complete()).toBeTruthy(); + console.log("Trying 5"); + + expect(node5.complete()).toBeTruthy(); + +}); + + + +}); diff --git a/solutions/data-structures/binary-tree/__test__/InOrderIterative.test.js b/solutions/data-structures/binary-tree/__test__/InOrderIterative.test.js new file mode 100644 index 0000000..b6371eb --- /dev/null +++ b/solutions/data-structures/binary-tree/__test__/InOrderIterative.test.js @@ -0,0 +1,30 @@ + +import BinaryTreeNode from '../BinaryTreeNode'; +import BinaryTree from '../BinaryTree'; + +import { inOrderIterative } from '../InOrderIterative'; + + + +describe(' inOrderIterative ', () => { + + + it('should traverse a BinaryTree in Order.', () => { + + const node1 = new BinaryTreeNode(1); + const node3 = new BinaryTreeNode(3); + const node2 = new BinaryTreeNode(2,node1,node3); + const node5= new BinaryTreeNode(5); + const node4 = new BinaryTreeNode(4,node2,node5); + + const tree = new BinaryTree(); + tree.store = node4; + + var res = []; + + inOrderIterative( tree, a => { res.push(a.value);}); + + expect( res.toString()).toBe('1,2,3,4,5'); +}); + +}); diff --git a/solutions/data-structures/binary-tree/__test__/PostOrderIterative.test.js b/solutions/data-structures/binary-tree/__test__/PostOrderIterative.test.js new file mode 100644 index 0000000..c55787a --- /dev/null +++ b/solutions/data-structures/binary-tree/__test__/PostOrderIterative.test.js @@ -0,0 +1,30 @@ + +import BinaryTreeNode from '../BinaryTreeNode'; +import BinaryTree from '../BinaryTree'; + +import { postOrderIterative } from '../PostOrderIterative'; + + + +describe(' PostOrderIterative ', () => { + + + it('should traverse a BinaryTree in Order.', () => { + + const node1 = new BinaryTreeNode(1); + const node3 = new BinaryTreeNode(3); + const node2 = new BinaryTreeNode(2,node1,node3); + const node5= new BinaryTreeNode(5); + const node4 = new BinaryTreeNode(4,node2,node5); + + const tree = new BinaryTree(); + tree.store = node4; + + var res = []; + + postOrderIterative( tree, a => { res.push(a.value);}); + + expect( res.toString()).toBe('1,3,2,5,4'); +}); + +}); diff --git a/solutions/data-structures/binary-tree/__test__/PreOrderIterative.test.js b/solutions/data-structures/binary-tree/__test__/PreOrderIterative.test.js new file mode 100644 index 0000000..4ec2d95 --- /dev/null +++ b/solutions/data-structures/binary-tree/__test__/PreOrderIterative.test.js @@ -0,0 +1,30 @@ + +import BinaryTreeNode from '../BinaryTreeNode'; +import BinaryTree from '../BinaryTree'; + +import { preOrderIterative } from '../PreOrderIterative'; + + + +describe(' PreOrderIterative ', () => { + + + it('should traverse a BinaryTree in Order.', () => { + + const node1 = new BinaryTreeNode(1); + const node3 = new BinaryTreeNode(3); + const node2 = new BinaryTreeNode(2,node1,node3); + const node5= new BinaryTreeNode(5); + const node4 = new BinaryTreeNode(4,node2,node5); + + const tree = new BinaryTree(); + tree.store = node4; + + var res = []; + + preOrderIterative( tree, a => { res.push(a.value);}); + + expect( res.toString()).toBe('4,2,1,3,5'); +}); + +}); diff --git a/solutions/data-structures/doubly-linked-list/ArrayToList.js b/solutions/data-structures/doubly-linked-list/ArrayToList.js new file mode 100644 index 0000000..4217a63 --- /dev/null +++ b/solutions/data-structures/doubly-linked-list/ArrayToList.js @@ -0,0 +1,17 @@ +import DoublyLinkedList from './DoublyLinkedList'; + + + + +export function arrayToList(arr){ + + var dll = new DoublyLinkedList(); + + for( var i = 0; i < arr.length;i++){ + dll.append(arr[i]); + + } + return dll; + + +} diff --git a/solutions/data-structures/doubly-linked-list/DoublyLinkedListNode.js b/solutions/data-structures/doubly-linked-list/DoublyLinkedListNode.js index 1c97bd2..f436824 100644 --- a/solutions/data-structures/doubly-linked-list/DoublyLinkedListNode.js +++ b/solutions/data-structures/doubly-linked-list/DoublyLinkedListNode.js @@ -2,7 +2,7 @@ export default class DoublyLinkedListNode { constructor(value, next = null, previous = null) { this.value = value; this.next = next; - this.previous = previous; + this.previous = previous } toString(callback) { diff --git a/solutions/data-structures/doubly-linked-list/Insert.js b/solutions/data-structures/doubly-linked-list/Insert.js new file mode 100644 index 0000000..0db24ad --- /dev/null +++ b/solutions/data-structures/doubly-linked-list/Insert.js @@ -0,0 +1,35 @@ +import DoublyLinkedList from './DoublyLinkedList'; +import DoublyLinkedListNode from './DoublyLinkedListNode'; + +export function insert(dll, el,comp){ + + var node = dll.head; + + var newNode = new DoublyLinkedListNode(el); + + while(node && comp(node, newNode)){ + node = node.next; + } + +// either node is the node after where newNode should be, or is null +// If it is null, we put newNode on the end of the list, append will fix up the tail, so we do not need to set it. + + if (!node){ + dll.append(el); + return dll; + } + if (node.previous){ + // if there is an earlier node, have it point at the current node. + node.previous.next = newNode; + } else + { + // the inserted node is the first, so set head to be newNode. + dll.head = newNode; + } + // fix the forward and backward links + newNode.next = node; + newNode.previous = node.previous + node.previous = newNode; + return dll; + +} diff --git a/solutions/data-structures/doubly-linked-list/MergeDLL.js b/solutions/data-structures/doubly-linked-list/MergeDLL.js new file mode 100644 index 0000000..4773d4e --- /dev/null +++ b/solutions/data-structures/doubly-linked-list/MergeDLL.js @@ -0,0 +1,37 @@ +import DoublyLinkedList from './DoublyLinkedList'; + + +export function merge(dll1,dll2,comp){ + + + var res = new DoublyLinkedList(); + + + var node1 = dll1.head; + var node2 = dll2.head; + + while (node1 && node2){ + if ( comp(node1,node2)){ + res.append(node1); + node1 = node1.next; + continue; + } + if (!comp(node1,node2)){ + res.append(node2); + node2 = node2.next; + } + } + while(node1) { + res.append(node1); + node1 = node1.next; + } + + + while(node2) { + res.append(node2); + node2 = node2.next; + } + +return res; + +} diff --git a/solutions/data-structures/doubly-linked-list/PriorityQueue.js b/solutions/data-structures/doubly-linked-list/PriorityQueue.js new file mode 100644 index 0000000..b86b992 --- /dev/null +++ b/solutions/data-structures/doubly-linked-list/PriorityQueue.js @@ -0,0 +1,45 @@ +import DoublyLinkedList from './DoublyLinkedList'; +import { insert } from './Insert'; + + +export default class PriorityQueue { + constructor(comp = (a,b) => { return a.value < b.value } ) { + this.store = new DoublyLinkedList(); + this.comp = comp; + } + + + isEmpty() { + return this.store.head === null; + } + + peek() { + if (this.isEmpty()) { + return null; + } + + return this.store.head.value; + } + + enqueue(el) { + // we need to insert in the correct place + this.store = insert(this.store, el, this.comp); + return this.store; + + } + + getHighest() { + if (this.isEmpty()) { + return null; + } + + var res = this.store.head.value; + this.store.deleteHead(); + + return res; + } + + toString() { + return this.store.toString(); + } +} diff --git a/solutions/data-structures/doubly-linked-list/README.md b/solutions/data-structures/doubly-linked-list/README.md index fc51be7..778c053 100644 --- a/solutions/data-structures/doubly-linked-list/README.md +++ b/solutions/data-structures/doubly-linked-list/README.md @@ -77,3 +77,87 @@ To start, build `DoublyLinkedListNode`. A doubly linked list node keeps a refere - The `delete` method removes the first node with the specified value. - The delete operation for a doubly linked list is significantly simpler due to having a reference to the previous node. - Utilize `find` and `deleteHead/Tail` methods written above to write the method. + + +#Problems + +**Create a list from an Array** + +Given an array, create a list with the same elements in the same order. + + + +```const arrayToList = arr => { + +} +``` + + +**Find the Smallest Value in a List** + +Given a list of numbers, find the smallest value. This just requires traversing the list, keeping track of the smallest number seen for far. You can start at either end, but most people will start at the head. Finding the smallest element is the basis for selection sort, which is about twice as slow as insertion sort, which is mentioned below. The basic idea of finding the smallest element can be greatly improved by using a heap, another data structure. + + + +```const smallestValue = dll => { + +} + +console.log( smallestValue( [13, 7, 6, 12 ].arrayToList() ) ) == 6 ); + +``` + + + + +**Reverse a doubly linked list** + +Sadly, just switching head and tail will not reverse a list, as the next and previous pointers will still be messed up. To revere a list it is necessary to go through each element and to swap the next and previous values in the right way. We can either create a new doubly linked list that is the reverse of this one, or we can modify the existing list. Try to do both. + + +```const reverseDLL = dll => { + +} + +console.log( reverseDLL( [13, 7, 6, 12 ].arrayToList() ) ) == [12, 6, 7, 13 ].arrayToList() ); +console.log( reverseInPlace( [13, 7, 6, 12 ].arrayToList() ) ) == [12, 6, 7, 13 ].arrayToList() ); + + +``` + + +**Merge two sorted Doubly Linked Lists** + +Given two sorted lists, merge the two lists to make a larger sorted list. Your function should take a comparator, a function that compares two elements. This function, mergring two sorted lists, is the basis for merge sort, one of the very first sorting algorithms. + +```const merge = ( dll1, dll2, comp) => { + +} + +console.log( merge( [7, 13].arrayToList(), merge( [ 6, 12 ].arrayToList(), (a,b) => { return a < b;} ) )) == [6, 7, 12, 13 ].arrayToList() ); + +``` + + +**Insert a Value in a Sorted List** + +Gien a list and an element, insert the element in sorted order. This function is the basis for insertion sort, the best sorting agorithm for very small arrays. + + +```const insert = ( dll, el, comp) => { + +} + +console.log( merge( [7, 13].arrayToList(), 6, (a,b) => { return a.value < b.value;} ) )) == [6, 7, 13 ].arrayToList() ); + +``` + + + +**Priority Queue using DLL** + +A priority queue is a data structure that acts like a queue where you can 'enqueue' and 'dequeue' elements, but instead of returning the earliest element still in the queue, it returns the element with the highest priority. It is normal to have a comparison function, called a comparator, to tell whether one item has higher priority than another. If two elements have the same priority, you should return the earlier element. You should implement 'enqueue', `getHighest`, `peek`, and `isEmpty`. + +The implementation is a mixture of the ideas of a queue, and inserting a value in a sorted list. This pattern of problems being solved with combinations of previous solutions is very common. + + diff --git a/solutions/data-structures/doubly-linked-list/Reverse.js b/solutions/data-structures/doubly-linked-list/Reverse.js new file mode 100644 index 0000000..deb146c --- /dev/null +++ b/solutions/data-structures/doubly-linked-list/Reverse.js @@ -0,0 +1,46 @@ +import DoublyLinkedList from './DoublyLinkedList'; + + + + +export function reverseDLL(dll){ + + var currentNode = dll.tail; + if (!currentNode) return dll; + // the empty list is its own reverse. + + var res = new DoublyLinkedList(); + + while(currentNode.previous){ + res.append(currentNode.value); + currentNode = currentNode.previous; + } +// append the last value. + res.append(currentNode.value); + return res; +} + + +export function reverseInPlace(dll){ + + var currentNode = dll.head; + if (!currentNode) return dll; + // the empty list is its own reverse. + + var nextNode = currentNode.next; + + while( currentNode && nextNode){ + var thirdNode = nextNode.next; + currentNode.prev = nextNode; + nextNode.next = currentNode; + currentNode = nextNode; + nextNode = thirdNode; + } + currentNode.prev = null; + dll.head.next = null; + + var temp = dll.head; + dll.head = dll.tail; + dll.tail = temp; + return dll; +} diff --git a/solutions/data-structures/doubly-linked-list/SmallestValue.js b/solutions/data-structures/doubly-linked-list/SmallestValue.js new file mode 100644 index 0000000..a1cdc76 --- /dev/null +++ b/solutions/data-structures/doubly-linked-list/SmallestValue.js @@ -0,0 +1,23 @@ +import DoublyLinkedList from './DoublyLinkedList'; + + +export function smallestValue(dll){ + + + + + var currentNode = dll.head; + + if (currentNode === null) return null; +// if the list is empty we return null; + + var smallestSoFar = currentNode.value; + + while(currentNode.next){ + currentNode = currentNode.next; + if (currentNode.value < smallestSoFar) + smallestSoFar = currentNode.value; + + } + return smallestSoFar; +} diff --git a/solutions/data-structures/doubly-linked-list/__test__/ArrayToList.test.js b/solutions/data-structures/doubly-linked-list/__test__/ArrayToList.test.js new file mode 100644 index 0000000..c0e51a1 --- /dev/null +++ b/solutions/data-structures/doubly-linked-list/__test__/ArrayToList.test.js @@ -0,0 +1,19 @@ + +import { arrayToList } from '../ArrayToList'; + + +describe('ArrayToList', () => { + it('should turn arrays to lists ', () => { + + + expect( arrayToList( [ 1, 2,3] ).toString()).toBe('1,2,3'); + + expect( arrayToList( [ ] ).toString()).toBe(''); + + expect( arrayToList( [1 ] ).toString()).toBe('1'); + + expect( arrayToList( [1 ,1] ).toString()).toBe('1,1'); +}); + +}); + diff --git a/solutions/data-structures/doubly-linked-list/__test__/ArrayToList.text.js b/solutions/data-structures/doubly-linked-list/__test__/ArrayToList.text.js new file mode 100644 index 0000000..6bea75f --- /dev/null +++ b/solutions/data-structures/doubly-linked-list/__test__/ArrayToList.text.js @@ -0,0 +1,14 @@ + +import { arrayToList } from '../ArrayToList'; + + +describe('ArrayToList', () => { + it('should turn arrays to lists ', () => { + + + expect( arrayToList( [ 1, 2,3] ).toString()).toBe('3'); + +}); + +}); + diff --git a/solutions/data-structures/doubly-linked-list/__test__/DoublyLinkedList.test.js b/solutions/data-structures/doubly-linked-list/__test__/DoublyLinkedList.test.js index dc6a971..93e5fcf 100644 --- a/solutions/data-structures/doubly-linked-list/__test__/DoublyLinkedList.test.js +++ b/solutions/data-structures/doubly-linked-list/__test__/DoublyLinkedList.test.js @@ -14,13 +14,27 @@ describe('DoublyLinkedList', () => { linkedList.append(1); linkedList.append(2); + linkedList.append(3); + expect(linkedList.head.next.value).toBe(2); + expect(linkedList.tail.previous.value).toBe(2); + expect(linkedList.toString()).toBe('1,2,3'); + }); + + it('should prepend node to linked list', () => { + const linkedList = new DoublyLinkedList(); + + expect(linkedList.head).toBeNull(); + expect(linkedList.tail).toBeNull(); + linkedList.prepend(1); + linkedList.prepend(2); + linkedList.prepend(3); expect(linkedList.head.next.value).toBe(2); - expect(linkedList.tail.previous.value).toBe(1); - expect(linkedList.toString()).toBe('1,2'); + expect(linkedList.tail.previous.value).toBe(2); + expect(linkedList.toString()).toBe('3,2,1'); }); - it('should prepend node to linked list', () => { + it('should append and prepend node to linked list', () => { const linkedList = new DoublyLinkedList(); linkedList.prepend(2); diff --git a/solutions/data-structures/doubly-linked-list/__test__/Insert.test.js b/solutions/data-structures/doubly-linked-list/__test__/Insert.test.js new file mode 100644 index 0000000..7ffb9c5 --- /dev/null +++ b/solutions/data-structures/doubly-linked-list/__test__/Insert.test.js @@ -0,0 +1,28 @@ + + +import { insert } from '../Insert'; +import { arrayToList } from '../ArrayToList'; + +describe('Insert', () => { + it('should insert into a sorted list ', () => { + + + expect( insert( arrayToList([ 1, 3,5 ]) , 4 , function(a, b){return a.value < b.value;} ).toString()).toBe('1,3,4,5'); + + expect( insert( arrayToList([ 1, 3,4 ]) , 5 , (a, b) => {return a.value < b.value;} ).toString()).toBe('1,3,4,5'); + + + expect( insert( arrayToList([ 1, 3]) , 5 , function(a, b){return a.value < b.value;} ).toString()).toBe('1,3,5'); + expect( insert( arrayToList([ 3,5,6 ]) , 1 , function(a, b){return a.value < b.value;} ).toString()).toBe('1,3,5,6'); + +}); + it('should insert into an empty list ', () => { + expect( insert( arrayToList([ ]) , 1, function(a, b){return a.value < b.value;} ).toString()).toBe('1'); + +}); + + + +}); + + diff --git a/solutions/data-structures/doubly-linked-list/__test__/MergeDLL.test.js b/solutions/data-structures/doubly-linked-list/__test__/MergeDLL.test.js new file mode 100644 index 0000000..3586689 --- /dev/null +++ b/solutions/data-structures/doubly-linked-list/__test__/MergeDLL.test.js @@ -0,0 +1,25 @@ + + +import { merge } from '../MergeDLL'; +import { arrayToList } from '../ArrayToList'; + +describe('Merge', () => { + it('should merge into a sorted list ', () => { + + + expect( merge( arrayToList([ 1, 3,5 ]) , arrayToList([ 2, 4,6 ]) , function(a, b){return a.value < b.value;} ).toString()).toBe('1,2,3,4,5,6'); + + expect( merge( arrayToList([ 1, 3]) , arrayToList([ ]) , function(a, b){return a.value < b.value;} ).toString()).toBe('1,3'); + expect( merge( arrayToList([ ]), arrayToList([ 3,5,6 ]) , function(a, b){return a.value < b.value;} ).toString()).toBe('3,5,6'); + +}); + it('should merge into an empty list ', () => { + expect( merge( arrayToList([ ]) , arrayToList([ 3,5,6 ]) , function(a, b){return a.value < b.value;} ).toString()).toBe('3,5,6'); + +}); + + + +}); + + diff --git a/solutions/data-structures/doubly-linked-list/__test__/PriorityQueue.test.js b/solutions/data-structures/doubly-linked-list/__test__/PriorityQueue.test.js new file mode 100644 index 0000000..73f1fe6 --- /dev/null +++ b/solutions/data-structures/doubly-linked-list/__test__/PriorityQueue.test.js @@ -0,0 +1,70 @@ +import PriorityQueue from '../PriorityQueue'; + + + +describe('PriorityQueue', () => { + + it('should create empty priority queue', () => { + const queue = new PriorityQueue(); + expect(queue).not.toBeNull(); + expect(queue.store).not.toBeNull(); + }); + + it('should enqueue data to priority queue', () => { + const queue = new PriorityQueue( (a,b) => { return a.value < b.value } ); + + + queue.enqueue(4); + queue.enqueue(3); + queue.enqueue(1); + queue.enqueue(2); + + expect(queue.toString()).toBe('1,2,3,4'); + }); + + it('should be possible to enqueue/dequeue in order', () => { + const queue = new PriorityQueue(); + + queue.enqueue(2); + + queue.enqueue(1); + + expect(queue.getHighest()).toBe(1); + expect(queue.getHighest()).toBe(2); + }); + + it('should peek data from priority queue', () => { + const queue = new PriorityQueue(); + + expect(queue.peek()).toBeNull(); + + queue.enqueue(1); + queue.enqueue(2); + + expect(queue.peek()).toBe(1); + expect(queue.peek()).toBe(1); + }); + + it('should check if queue is empty', () => { + const queue = new PriorityQueue(); + + expect(queue.isEmpty()).toBeTruthy(); + + queue.enqueue(1); + + expect(queue.isEmpty()).toBeFalsy(); + }); + + it('should dequeue from queue in given order', () => { + const queue = new PriorityQueue(); + + queue.enqueue(2); + queue.enqueue(1); + + expect(queue.getHighest()).toBe(1); + expect(queue.getHighest()).toBe(2); + expect(queue.getHighest()).toBeNull(); + expect(queue.isEmpty()).toBeTruthy(); + }); +}); + diff --git a/solutions/data-structures/doubly-linked-list/__test__/Reverse.test.js b/solutions/data-structures/doubly-linked-list/__test__/Reverse.test.js new file mode 100644 index 0000000..8eb5c8a --- /dev/null +++ b/solutions/data-structures/doubly-linked-list/__test__/Reverse.test.js @@ -0,0 +1,31 @@ + +import { reverseDLL , reverseInPlace } from '../Reverse'; +import { arrayToList } from '../ArrayToList'; + +describe('Reverse', () => { + it('should reverse list ', () => { + + + expect( reverseDLL( arrayToList([ 1, 2,3]) ).toString()).toBe('3,2,1'); + + expect( reverseDLL( arrayToList( [ ] )).toString()).toBe(''); + + expect( reverseDLL( arrayToList( [1 ] )).toString()).toBe('1'); + + expect( reverseDLL( arrayToList([1 ,1]) ).toString()).toBe('1,1'); + +}); + + it('should reverse list in place ', () => { + + expect( reverseInPlace( arrayToList([ 1, 2,3]) ).toString()).toBe('3,2,1'); + + expect( reverseInPlace( arrayToList( [ ] )).toString()).toBe(''); + + expect( reverseInPlace( arrayToList( [1 ] )).toString()).toBe('1'); + + expect( reverseInPlace( arrayToList([1 ,1]) ).toString()).toBe('1,1'); +}); + +}); + diff --git a/solutions/data-structures/doubly-linked-list/__test__/SmallestValue.test.js b/solutions/data-structures/doubly-linked-list/__test__/SmallestValue.test.js new file mode 100644 index 0000000..0e1dd57 --- /dev/null +++ b/solutions/data-structures/doubly-linked-list/__test__/SmallestValue.test.js @@ -0,0 +1,18 @@ +import { smallestValue } from '../SmallestValue'; +import { arrayToList } from '../ArrayToList'; + + +describe('SmallestValue', () => { + it('should find smallets value in a list ', () => { + + + expect( smallestValue( arrayToList( [ 1, 2]) ).toString()).toBe('1'); + expect( smallestValue( arrayToList( [ ]) )).toBeNull(); + expect( smallestValue( arrayToList( [ 1, 1]) ).toString()).toBe('1'); + expect( smallestValue( arrayToList( [ -1,1, 2]) ).toString()).toBe('-1'); + expect( smallestValue( arrayToList( [ -1, -2]) ).toString()).toBe('-2'); + +}); + +}); + diff --git a/solutions/data-structures/hash-table/FindDuplicates.js b/solutions/data-structures/hash-table/FindDuplicates.js new file mode 100644 index 0000000..fbbeae3 --- /dev/null +++ b/solutions/data-structures/hash-table/FindDuplicates.js @@ -0,0 +1,34 @@ + +import HashTable from './HashTable'; + + +export function findDuplicates( arr){ + + + var len = arr.length; + + var h = new HashTable(len); + + var res = []; + + for (var i = 0; i < len; i++){ + + if (h.get(arr[i]) === 0){ + // if the hash table contains 0, we have not added this value to the output. + res.push(arr[i]); + + h.set(arr[i],1); + // we set it to be 1, so that it will not be added again + } + if (!h.get(arr[i])) + h.set(arr[i],0); + // we set each item to be 0 initially, it is it not set to anything else + } + return res; + } + + + +export function countDuplicates( arr){ + return findDuplicates(arr).length; + } diff --git a/solutions/data-structures/hash-table/FindPairSum.js b/solutions/data-structures/hash-table/FindPairSum.js new file mode 100644 index 0000000..138c465 --- /dev/null +++ b/solutions/data-structures/hash-table/FindPairSum.js @@ -0,0 +1,43 @@ + + +import HashTable from './HashTable'; + + +export function findPairSum( arr, sum ){ + + + var len = arr.length; + + var h = new HashTable(len); + + + for (var i = 0; i < len; i++){ + + if (h.get(sum-arr[i])){ + // we have found a pair + return [sum-arr[i], arr[i] ]; + } + h.set(arr[i],true); + } + return null; + } + +export function countPairSum( arr, sum ){ + + + var len = arr.length; + + var h = new HashTable(len); + + var count = 0; + + for (var i = 0; i < len; i++){ + + if (h.get(sum-arr[i])){ + // we have found a pair + count++; + } + h.set(arr[i],true); + } + return count; + } diff --git a/solutions/data-structures/hash-table/HashTable.js b/solutions/data-structures/hash-table/HashTable.js index 51575f9..049f481 100644 --- a/solutions/data-structures/hash-table/HashTable.js +++ b/solutions/data-structures/hash-table/HashTable.js @@ -24,6 +24,8 @@ export default class HashTable { * @param {string} key * @return {number} */ + + hash(key) { const hash = Array.from(key).reduce( (hashAccumulator, keySymbol) => (hashAccumulator + keySymbol.charCodeAt(0)), @@ -95,4 +97,23 @@ export default class HashTable { getKeys() { return Object.keys(this.keys); } + + hash1(key){ + // http://www.cse.yorku.ca/~oz/hash.html + // This is a simple hash function that works well. + + const arr = Array.from(key); + const len = arr.length; + + var hash = 5381; + + + for(var i = 0; i < len; i++){ + hash = (33 * hash) + arr[i].charCodeAt(0); /* hash * 33 + c */ + } + return hash % this.buckets.length; + } + + + } diff --git a/solutions/data-structures/hash-table/HashTableOpen.js b/solutions/data-structures/hash-table/HashTableOpen.js new file mode 100644 index 0000000..b7c1338 --- /dev/null +++ b/solutions/data-structures/hash-table/HashTableOpen.js @@ -0,0 +1,184 @@ + +// Hash table size directly affects on the number of collisions. +// The bigger the hash table size the less collisions you'll get. +// For demonstrating purposes hash table size is small to show how collisions +// are being handled. +const defaultHashTableSize = 32; + +export default class HashTableOpen { + /** + * @param {number} hashTableSize + */ + constructor(hashTableSize = defaultHashTableSize) { + // Create hash table of certain size and fill each bucket with null; + this.buckets = Array(hashTableSize).fill(null); + this.size = 0; + // Just to keep track of all actual keys in a fast way. + this.keys = {}; + } + + /** + * Converts key string to hash number. + * + * @param {string} key + * @return {number} + */ + + + hash(key) { + const hash = Array.from(key).reduce( + (hashAccumulator, keySymbol) => (hashAccumulator + keySymbol.charCodeAt(0)), + 0, + ); + + // Reduce hash number so it would fit hash table size. + return hash % this.buckets.length; + } + + /** + * @param {string} key + * @param {*} value + */ + set(key, value) { + + if (this.size +1 >= this.buckets.length){ + console.log("too full"); + // we do not try to fill the hash table if there are only 10 slot left. + return null; + } + const keyHash = this.hash(key); + this.keys[key] = keyHash; // for debugging + + var pos = keyHash; + var c = 0; + while(this.buckets[pos]){ + if (this.buckets[pos].key === key){ + this.buckets[pos].value = value; + return; + } + pos = (pos+1) % this.buckets.length; + if (c++ > 100) break; + } + this.buckets[pos] = {key: key,value: value}; + + } + + + + /** + * @param {string} key + * @return {*} + */ + delete(key) { + const keyHash = this.hash(key); + delete this.keys[key]; // for debugging + + var pos = keyHash; + var c= 0; + while(this.buckets[pos]){ + + if (this.buckets[pos].key === key){ + this.buckets[pos].key = "deleted"; + return; + } + pos = (pos+1) % this.buckets.length; + + if (c++ > 100) break; + } + return null; + } + + + /** + * @param {string} key + * @return {*} + */ + deleteG(key) { + const keyHash = this.hash(key); + delete this.keys[key]; // for debugging + + var pos = keyHash; + var c= 0; + while(this.buckets[pos]){ + + if (this.buckets[pos].key === key){ + // we have found the spot to be deleted. + // we mark it to null + this.buckets[pos] = null; + var p = pos+1; + while(this.buckets[p]){ + var pair = this.buckets[p]; + this.buckets[p] = null; + this.set(pair.key,pair.value); + p = (p+1) % this.buckets.length; + } + // now p is the first empty slot + + + + } + pos = (pos+1) % this.buckets.length; + + if (c++ > 100) break; + } + return null; + } + + + + + /** + * @param {string} key + * @return {*} + */ + get(key) { + const keyHash = this.hash(key); + + var pos = keyHash; + var c = 0; + while(this.buckets[pos]){ + + if (this.buckets[pos].key === key){ + return this.buckets[pos].value; + + } + pos = (pos+1) % this.buckets.length; + if (c++ > 100) break; + } + return; + } + + /** + * @param {string} key + * @return {boolean} + */ + has(key) { + return Object.hasOwnProperty.call(this.keys, key); + } + + /** + * @return {string[]} + */ + getKeys() { + return Object.keys(this.keys); + } + + hash1(key){ + // http://www.cse.yorku.ca/~oz/hash.html + // This is a simple hash function that works well. + + const arr = Array.from(key); + const len = arr.length; + + var hash = 5381; + + + for(var i = 0; i < len; i++){ + hash = (33 * hash) + arr[i].charCodeAt(0); /* hash * 33 + c */ + } + return hash % this.buckets.length; + } + + + +} diff --git a/solutions/data-structures/hash-table/LargestConsecutiveSequence.js b/solutions/data-structures/hash-table/LargestConsecutiveSequence.js new file mode 100644 index 0000000..fc8beef --- /dev/null +++ b/solutions/data-structures/hash-table/LargestConsecutiveSequence.js @@ -0,0 +1,43 @@ + + + + +import HashTable from './HashTable'; + + +export function largestConsecutiveSubsequence( arr){ + + + var len = arr.length; + + var h = new HashTable(len); + + var ans = 0; + + // Hash all the array elements + for (var i = 0; i < len; i++) + h.set(arr[i],true); + + // check each possible sequence from the start + // then update optimal length + for (var i=0; i bestCount){ + best = arr[i]; + bestCount = tc+1; + } + + + } + } + return best; +} diff --git a/solutions/data-structures/hash-table/README.md b/solutions/data-structures/hash-table/README.md index 59b5194..8607429 100644 --- a/solutions/data-structures/hash-table/README.md +++ b/solutions/data-structures/hash-table/README.md @@ -14,12 +14,109 @@ collisions where the hash function generates the same index for more than one key. Such collisions must be accommodated in some way. + + ![Hash Table](https://upload.wikimedia.org/wikipedia/commons/7/7d/Hash_table_3_1_1_0_1_0_0_SP.svg) + +We will consider two kinds of hash table, one where the buckets each contain a linked list, and one woth so-called open-adressing, where we put the hash object somewhere wlese in the table if the first position we look in is full. + + + + Hash collision resolved by separate chaining. +We start by creating a hash table. This contains an array of size `hashTableSize` filled with linked lists. We want to be able to add and delete from these lists when we add or delete from the hash table. We could use doubly linked lists, but that would make things no faster, as singly linked lists can add and delete just as fast (or faster) than doubly linked lists. We also keep the keys that we add to the table in a seperate objects `keys`. Actually, all javascript objects act as hash tables, so this object will be a hash table that maps each key to is hash value. We could use a list, or an array, of keys, but for simplicity, we use this object. We only use this for testing, so we do not rely on this to implement our actual table. + + + +The next important function is the hash function itself. This converts a string into a number between 0 and `hashTableSize`, and so tells us which bucket an string should go in. There are various ways to make hash function. We consider two simple ways. The first function `hash` adds the ascii character codes for each letter together. This gives an number h. We return the remainder when h is divided by `hashTableSize`. This function is serves our purpose, but has some weaknesses. For example, is hashes all strings with the same characters to the same value, so "act", "cat", and "tac" all return the same value. + +We can make a better function with a little math. We use dbj2, a well known hash function. This works by starting with 5381. For each character, it multiplies by 33 and adds the next character code. Finally we mod with `hashTableSize` to get a value in range. Any prime number instead of 33 gives a hash function, but 33 is know to be particularly good. A hash function like this is called a Polynomial hash code. + + +To `set` a key value pair in our hash table, we compute which bucket to use, using our hash function. We then look in the linked list for our key. If we find it, we update the value. If we do not, we just append the key value pair to our list. Each list is composed of objects, key value pairs. The lists are not in sored order, though if we did keep them in sorted orded we could speed up our retrivial by a factor of 2 on average. + +To `delete` a key is similar. We find the right bucket, and then find the node with that key in the corresponding list. We then delete the node. This is linear in the length of the list, as deleting from a list requires traversing the entire list again. If we wrote a single loop, that remember the previous nodes value, we could do this in a single traverse. + +To `get` a key, we again find in the linked list of the correct bucket. + +We add two functions helpful for testings. `has` tells whether a key is in the hash table, by querying keys. `getKeys` retuns the list of keys by using the javascript function `Object.keys`. + + ![Hash Collision](https://upload.wikimedia.org/wikipedia/commons/d/d0/Hash_table_5_0_1_1_1_1_1_LL.svg) +Another way of writing hash table is to use open-addressing. Rather than have a set of lists that contain the key value pairs we are storing, we just use an array of key value pairs. When we want to insert or get or set, we compute the hash code, and look at that point in the array. This is much simpler and faster. There is one catch however. If another key hashes to the same place, the our array might have another key value pair in the position we were going to use. We need a way to find another place for our pair. There are many ways to find another position, but we wil just consider the simplest for now. We look forward in our array (looping back to zero once we get to the end). If we are inserting, we put out key value pair in the next empty place. If we are looking for a particular key, we know that it is not in the table if we every come across an empty slot. One function is diffiuult to implement, deletion, as we can not just make position where the key value pair was null again, as if another key hashed to the same position, it would have been put in a later position. We can fix this in two ways. We can marked places as deleted, so we know to keep looking, or we can write special code to fix up all the key value pairs that are affected. + + + The `constructor` is easy. It just creates a `store` of the correct size. + +Our `hash` function is eactly as before. + +To `set` a key to a value, we compute the hash code. We start at that positioin in the array, and loop forward, incrementing the position by one, until we find an empty position or the key we seek. If we find an empty position we insert the key value pair, otherwise we set the value of the pair to the new value. + +To `get` a key's value is much the same as `set`. + +A simple way to implement `delete` is to set the key value pair to a new deleted value, that can not be confused with another key value pair. The string "deleted" will do. There is a trickier way, which we now explain. + +To `deleteG` a key value, we first find the corresponding position, p. We then find the next empty position after p. We loop over these positions, set their positions to null, and re-insert each key value pair. + + +An analysis of the complexity of linear probing is difficult, and beyond what we can treat here. So long as the hash table is not too full (70%), then operations will look at less than 2 locations on average. Once the table is more than 70% full, the number of locations that need to be scanned goes up very quickly. Open addressing is faster than chaining when tables are not too full, but chaining has better characteristics when the table is almost full. + + +#Problems# + +Most of these problems are ones that could be solved without a hash table, but using a hash table makes the solution faster. For example, to find a pair of elements of an array that sum to a given value can be solved by looking at all pairs. If the array has length n, this looks at n^2 pairs, and thus has complexity O(n^2). With as hash table, we can do this in O(n), looking at each object once, and storing n objects in out table, and doing only n requests. Hash tables are mostly used to improve the running time of algorithms. The functions they implment, get, set, and delete, can be implemented by a list, though a list is slower. + + +**Find/Count Pair that sums to a value** + +Given an array of integers, find a pair that sums to a value, or alternately, count how many pairs sum to a value. + +`findPairSum([1,2,3,4],6)` should return [2,4]. + +'countPairSum([1,2,3,4],5)` should return 2, as [1,4] and [2, 3] sum to 5. + + +**Find duplicates in an array** + +Given an array of integers, find the duplicates in the array, and return an array of the duplicates, each occuring just once. Also, count the number of different duplicates. + +`findDuplicates([1,2,3,4,3,2,2])` should return [2,3]. + +`countDuplicates([1,2,3,4,3,2,2])` should return 2. + +**Most frequent element in array** + +Given an array of integers, find the integer that occurs the most often. If two integers occur the same number of times, and more than any other, return the first. + +`mostFrequent([1,2,3,4,3,2,2])` should return 2. + +##Intermediate## + +**Largest subarray with zero sum** + +Given an array of integers, find the length of the longest subarray with sum equals to 0. + +The easy solution is try all sub arrays and see which ones sum to 0. Keep track of the length of the longest one, and return that. This is O(n^2). With a hash table, we can do better. + +`largestZeroSubArray( [15, -2, 2, -8, 1, 7, 10, 23])` should return 0, as [15, -2, 2, -8, 1, 7] sums to 0. + +**Longest consecutive subsequence** + + +Given an array of integers, find the length of the longest sub-sequence, not sub-array, such that elements in the sequence are consecutive integers, that is, numbers in order. The consecutive numbers can be in any order, they just need to be consective as a set. + +For example, [1, 9, 3, 10, 4, 20, 2] has the sub-sequence 1,3,4,2. This is consecutive. + + + +`largestConsecutiveSubsequence( [1, 9, 3, 10, 4, 20, 2])` should return 4, as 1,3,4,2 is a subsequence, with all the numbers between 1 and 4. + + + + ## References - [Wikipedia](https://en.wikipedia.org/wiki/Hash_table) diff --git a/solutions/data-structures/hash-table/__test__/FindDuplicates.test.js b/solutions/data-structures/hash-table/__test__/FindDuplicates.test.js new file mode 100644 index 0000000..c4ab951 --- /dev/null +++ b/solutions/data-structures/hash-table/__test__/FindDuplicates.test.js @@ -0,0 +1,70 @@ +import { findDuplicates, countDuplicates } from '../FindDuplicates' + + +describe('findDuplicates', () => { + it('should find duplictes of example ', () => { + + expect( findDuplicates( [1,2,3,4,3,2,2]).toString()).toBe('3,2'); + + }); + + it('should return [] for empty array ', () => { + + expect( findDuplicates( []).toString()).toBe(''); + + }); + + it('should return [] for singleton array ', () => { + + expect( findDuplicates( [1]).toString()).toBe(''); + + }); + + it('should return [] for array without duplicates ', () => { + + expect( findDuplicates( [1,2,3,4]).toString()).toBe(''); + + }); + + it('should return [1] for array with many 1s ', () => { + + expect( findDuplicates( [1,1,1,1,1,1,1,1]).toString()).toBe('1'); + + }); + +}); + + +describe('countDuplicates', () => { + it('should count duplictes of example ', () => { + + expect( countDuplicates( [1,2,3,4,3,2,2])).toBe(2); + + }); + + it('should return 0 for empty array ', () => { + + expect( countDuplicates( [])).toBe(0); + + }); + + it('should return 0 for singleton array ', () => { + + expect( countDuplicates( [1])).toBe(0); + + }); + + it('should return 0 for array without duplicates ', () => { + + expect( countDuplicates( [1,2,3,4])).toBe(0); + + }); + + it('should return 1 for array with many 1s ', () => { + + expect( countDuplicates( [1,1,1,1,1,1,1,1])).toBe(1); + + }); + +}); + diff --git a/solutions/data-structures/hash-table/__test__/FindPairSum.test.js b/solutions/data-structures/hash-table/__test__/FindPairSum.test.js new file mode 100644 index 0000000..d06d5cc --- /dev/null +++ b/solutions/data-structures/hash-table/__test__/FindPairSum.test.js @@ -0,0 +1,52 @@ +import { findPairSum, countPairSum } from '../FindPairSum' + + +describe('findPairSum', () => { + it('should find pair ', () => { + + expect( findPairSum( [1,2,3,4],6).toString()).toBe('2,4'); + + }); + + it('should return null when no pair ', () => { + + expect( findPairSum( [1,2,3,4],19)).toBeNull; + + }); + + it('should return null on empty and singleton ', () => { + + expect( findPairSum( [],1)).toBeNull; + + expect( findPairSum( [1],1)).toBeNull; + }); + + + +}); + + +describe('countPairSum', () => { + it('should count a pair ', () => { + + expect( countPairSum( [1,2,3,4],6)).toBe(1); + + }); + + it('should count more than one pair ', () => { + + expect( countPairSum( [1,2,3,4],5)).toBe(2); + + }); + + it('should return n0 on empty and singleton ', () => { + + expect( countPairSum( [],1)).toBe(0); + + expect( countPairSum( [1],1)).toBe(0); + }); + + + +}); + diff --git a/solutions/data-structures/hash-table/__test__/HashTable.test.js b/solutions/data-structures/hash-table/__test__/HashTable.test.js index cc32215..55c758b 100644 --- a/solutions/data-structures/hash-table/__test__/HashTable.test.js +++ b/solutions/data-structures/hash-table/__test__/HashTable.test.js @@ -17,6 +17,15 @@ describe('HashTable', () => { expect(hashTable.hash('abc')).toBe(6); }); + it('should generate dbj hash for specified keys', () => { + const hashTable = new HashTable(); + + expect(hashTable.hash1('a')).toBe(6); + expect(hashTable.hash1('b')).toBe(7); + expect(hashTable.hash1('abc')).toBe(11); + }); + + it('should set, read and delete data with collisions', () => { const hashTable = new HashTable(3); diff --git a/solutions/data-structures/hash-table/__test__/HashTableOpen.test.js b/solutions/data-structures/hash-table/__test__/HashTableOpen.test.js new file mode 100644 index 0000000..4cd1157 --- /dev/null +++ b/solutions/data-structures/hash-table/__test__/HashTableOpen.test.js @@ -0,0 +1,177 @@ +import HashTableOpen from '../HashTableOpen'; + +describe('HashTable', () => { + it('should create hash table of certain size', () => { + const defaultHashTable = new HashTableOpen(); + expect(defaultHashTable.buckets.length).toBe(32); + + const biggerHashTable = new HashTableOpen(64); + expect(biggerHashTable.buckets.length).toBe(64); + }); + + it('should generate proper hash for specified keys', () => { + const hashTable = new HashTableOpen(); + + expect(hashTable.hash('a')).toBe(1); + expect(hashTable.hash('b')).toBe(2); + expect(hashTable.hash('abc')).toBe(6); + }); + + it('should generate dbj hash for specified keys', () => { + const hashTable = new HashTableOpen(); + + expect(hashTable.hash1('a')).toBe(6); + expect(hashTable.hash1('b')).toBe(7); + expect(hashTable.hash1('abc')).toBe(11); + }); + + + it('should set, read and delete data with collisions', () => { + const hashTable = new HashTableOpen(10); + + expect(hashTable.hash('a')).toBe(7); + expect(hashTable.hash('b')).toBe(8); + expect(hashTable.hash('c')).toBe(9); + expect(hashTable.hash('d')).toBe(0); + + hashTable.set('a', 'sky-old'); + hashTable.set('a', 'sky'); + hashTable.set('ab', 'sky'); + hashTable.set('b', 'sea'); + hashTable.set('c', 'earth'); + hashTable.set('ba', 'ocean'); // this hashes to the same spot as 'ab' + + expect(hashTable.has('x')).toBeFalsy(); + expect(hashTable.has('b')).toBeTruthy(); + expect(hashTable.has('c')).toBeTruthy(); + + expect(hashTable.hash('ab')).toBe(5); + expect(hashTable.hash('ba')).toBe(5); + + const stringifier = value => `${value.key}:${value.value}`; + + expect(hashTable.buckets[9].key.toString(stringifier)).toBe('c'); + expect(hashTable.buckets[9].value.toString(stringifier)).toBe('earth'); + + expect(hashTable.buckets[7].key.toString(stringifier)).toBe('a'); + expect(hashTable.buckets[7].value.toString(stringifier)).toBe('sky'); + + expect(hashTable.buckets[5].key.toString(stringifier)).toBe('ab'); + expect(hashTable.buckets[5].value.toString(stringifier)).toBe('sky'); + + expect(hashTable.buckets[6].key.toString(stringifier)).toBe('ba'); + expect(hashTable.buckets[6].value.toString(stringifier)).toBe('ocean'); + + + expect(hashTable.get('a')).toBe('sky'); + expect(hashTable.get('ba')).toBe('ocean'); + expect(hashTable.get('x')).not.toBeDefined(); + + hashTable.delete('a'); + + expect(hashTable.delete('not-existing')).toBeNull(); + + expect(hashTable.get('a')).not.toBeDefined(); + expect(hashTable.get('ba')).toBe('ocean'); + + hashTable.set('ba', 'ocean-new'); + expect(hashTable.get('ba')).toBe('ocean-new'); + }); + + it('should be possible to add objects to hash table', () => { + const hashTable = new HashTableOpen(); + + hashTable.set('objectKey', { prop1: 'a', prop2: 'b' }); + + const object = hashTable.get('objectKey'); + expect(object).toBeDefined(); + expect(object.prop1).toBe('a'); + expect(object.prop2).toBe('b'); + }); + + it('should track actual keys', () => { + const hashTable = new HashTableOpen(10); + + hashTable.set('a', 'sky-old'); + hashTable.set('a', 'sky'); + hashTable.set('b', 'sea'); + hashTable.set('c', 'earth'); + hashTable.set('d', 'ocean'); + + expect(hashTable.getKeys()).toEqual(['a', 'b', 'c', 'd']); + expect(hashTable.has('a')).toBeTruthy(); + expect(hashTable.has('x')).toBeFalsy(); + + hashTable.delete('a'); + + expect(hashTable.has('a')).toBeFalsy(); + expect(hashTable.has('b')).toBeTruthy(); + expect(hashTable.has('x')).toBeFalsy(); + }); + + it('should set, read and deleteG data with collisions', () => { + const hashTable = new HashTableOpen(10); + + expect(hashTable.hash('a')).toBe(7); + expect(hashTable.hash('b')).toBe(8); + expect(hashTable.hash('c')).toBe(9); + expect(hashTable.hash('d')).toBe(0); + + hashTable.set('a', 'sky-old'); + hashTable.set('a', 'sky'); + hashTable.set('abd', 'sky'); + hashTable.set('b', 'sea'); + hashTable.set('c', 'earth'); + hashTable.set('bad', 'ocean'); // this hashes to the same spot as 'ab' + + expect(hashTable.has('x')).toBeFalsy(); + expect(hashTable.has('b')).toBeTruthy(); + expect(hashTable.has('c')).toBeTruthy(); + + expect(hashTable.hash('abd')).toBe(5); + expect(hashTable.hash('bad')).toBe(5); + expect(hashTable.hash('bda')).toBe(5); + hashTable.set('bda', 'desert'); + + const stringifier = value => `${value.key}:${value.value}`; + + expect(hashTable.buckets[9].key.toString(stringifier)).toBe('c'); + expect(hashTable.buckets[9].value.toString(stringifier)).toBe('earth'); + + expect(hashTable.buckets[7].key.toString(stringifier)).toBe('a'); + expect(hashTable.buckets[7].value.toString(stringifier)).toBe('sky'); + + expect(hashTable.buckets[5].key.toString(stringifier)).toBe('abd'); + expect(hashTable.buckets[5].value.toString(stringifier)).toBe('sky'); + + expect(hashTable.buckets[6].key.toString(stringifier)).toBe('bad'); + expect(hashTable.buckets[6].value.toString(stringifier)).toBe('ocean'); + + expect(hashTable.buckets[0].key.toString(stringifier)).toBe('bda'); + expect(hashTable.buckets[0].value.toString(stringifier)).toBe('desert'); + + + expect(hashTable.get('a')).toBe('sky'); + expect(hashTable.get('bad')).toBe('ocean'); + expect(hashTable.get('x')).not.toBeDefined(); + + hashTable.deleteG('abd'); + + expect(hashTable.buckets[5].key.toString(stringifier)).toBe('bad'); + expect(hashTable.buckets[5].value.toString(stringifier)).toBe('ocean'); + + expect(hashTable.buckets[6].key.toString(stringifier)).toBe('bda'); + expect(hashTable.buckets[6].value.toString(stringifier)).toBe('desert'); + + + expect(hashTable.delete('not-existing')).toBeNull(); + + expect(hashTable.get('abd')).not.toBeDefined(); + expect(hashTable.get('bad')).toBe('ocean'); + + hashTable.set('bad', 'ocean-new'); + expect(hashTable.get('bad')).toBe('ocean-new'); + }); + + +}); diff --git a/solutions/data-structures/hash-table/__test__/LargestConsecutiveSubsequence.test.js b/solutions/data-structures/hash-table/__test__/LargestConsecutiveSubsequence.test.js new file mode 100644 index 0000000..26bb18b --- /dev/null +++ b/solutions/data-structures/hash-table/__test__/LargestConsecutiveSubsequence.test.js @@ -0,0 +1,41 @@ + + +import { largestConsecutiveSubsequence } from '../largestConsecutiveSequence' + + +describe(' largestConsecutiveSubsequence' , () => { + it('should find example ', () => { + + expect( largestConsecutiveSubsequence( [1, 9, 3, 10, 4, 20, 2])).toBe(4); + + }); + + + + it('should find examples with 0 an an element ', () => { + + expect( largestConsecutiveSubsequence( [1, 9, 3, 10, 4, 20, 2,0,-1,-3 ])).toBe(6); + + }); + + + + + it('should return 0 for empty array ', () => { + + expect( largestConsecutiveSubsequence( [ ])).toBe(0); + + }); + + + + + it('should return 1 for singleton array ', () => { + + expect( largestConsecutiveSubsequence( [ 12])).toBe(1); + + }); + + +}); + diff --git a/solutions/data-structures/hash-table/__test__/LargestZeroSubArray.test.js b/solutions/data-structures/hash-table/__test__/LargestZeroSubArray.test.js new file mode 100644 index 0000000..6e4c710 --- /dev/null +++ b/solutions/data-structures/hash-table/__test__/LargestZeroSubArray.test.js @@ -0,0 +1,25 @@ +import { largestZeroSubArray } from '../LargestZeroSubArray' + + +describe('largestZeroSubArray', () => { + it('should compute example ', () => { + + expect( largestZeroSubArray( [15, -2, 2, -8, 1, 7, 10, 23])).toBe(5); + + }); + + it('should return null for empty array ', () => { + + expect( largestZeroSubArray( [])).toBeNull(); + + }); + + it('should return 0 for stricly positive elements in array ', () => { + + expect( largestZeroSubArray( [1,2,3,4,5,6,7])).toBe(0); + + }); + + +}); + diff --git a/solutions/data-structures/hash-table/__test__/MostFrequent.test.js b/solutions/data-structures/hash-table/__test__/MostFrequent.test.js new file mode 100644 index 0000000..b3ff611 --- /dev/null +++ b/solutions/data-structures/hash-table/__test__/MostFrequent.test.js @@ -0,0 +1,34 @@ +import { mostFrequent } from '../MostFrequent' + + +describe('mostFrequent', () => { + it('should find most frequent of example ', () => { + + expect( mostFrequent( [1,2,3,4,3,2,2])).toBe(2); + + }); + + it('should return undefined for empty array ', () => { + + expect( mostFrequent( [])).toBeUndefined; + + }); + + it('should return value for singleton array ', () => { + + expect( mostFrequent( [1])).toBe(1); + + }); + + it('should return first element for array without duplicates ', () => { + + expect( mostFrequent( [1,2,3,4])).toBe(1); + + }); + + it('should return 1 for array with many 1s ', () => { + + expect( mostFrequent( [1,1,1,1,1,1,1,1])).toBe(1); + + }); +}); diff --git a/solutions/data-structures/heap/FloydsHeapAlg.js b/solutions/data-structures/heap/FloydsHeapAlg.js new file mode 100644 index 0000000..6912b3f --- /dev/null +++ b/solutions/data-structures/heap/FloydsHeapAlg.js @@ -0,0 +1,18 @@ +import Heap from './Heap'; + + + +export function makeHeap(arr){ + + var h = new Heap( arr.length+1, (a,b)=> { return a < b} ); + h.store = arr; + h.size = arr.length; + + var half = Math.floor(arr.length/2); + + + for (var i = half; i >= 0; i--){ + h.heapify(i); + } + return h; +} diff --git a/solutions/data-structures/heap/Heap.js b/solutions/data-structures/heap/Heap.js new file mode 100644 index 0000000..70b875b --- /dev/null +++ b/solutions/data-structures/heap/Heap.js @@ -0,0 +1,141 @@ + +function parent(i){ + if (i === 0) return 0; + return Math.floor((i-1)/2); +} + +function left(i){ + return 2*i +1; +} + +function right(i){ + return 2*i+2; +} + + + +export default class Heap { + constructor( capacity = 100, comp = (a,b) => {return a < b} ) { + this.store = new Array(capacity);; + this.capacity = capacity; + this.size = 0; + this.comp = comp; + } + + + swap(i,j){ + + var tmp = this.store[i]; + this.store[i] = this.store[j]; + this.store[j] = tmp; + } + + + isEmpty(){ + return this.size === 0; + } + + isHeap(){ + + for(var i =0; 2 * i < this.size ;i++){ + + if ( left(i) < this.size && this.comp( this.store[left(i)] , this.store[i])) return false; + if ( right(i) < this.size && this.comp( this.store[right(i)] , this.store[i])) return false; + + } + return true; + + } + + extractMin(){ + // the top item is the smallest one + var res = this.store[0]; + // we need to fix up the rest of the heap + + if (this.size <= 0) + return null; + if (this.size == 1) + { + this.size--; + return this.store[0]; + } + + // Store the minimum value, and remove it from heap + + this.store[0] = this.store[this.size-1]; + this.size--; + this.heapify(0); + return res; + } + + getMin(){ + if ( this.size === 0) return null; + // return the top element + return this.store[0]; + } + + insertKey(el){ + // To insert a key, we look down the heap for a place to put it. + // do we go left or right each time? + if (this.size == this.capacity) return null; + + this.size++; + var i = this.size -1; + this.store[i] = el; + while (i !== 0 && !this.comp( this.store[parent(i)], this.store[i])) + { + + + this.swap(i, parent(i) ); + i = parent(i); + } + } + + decreaseKey(i,new_val){ + // if we decrease a key, it may need to go upwards + this.store[i] = new_val; + while (i != 0 && !this.comp( this.store[parent(i)] , this.store[i]) ) + { + this.swap(i,parent(i)); + i = parent(i); + } + + } + + + deleteKey(i){ + // if we delete a key we need to pull up the smaller of the two + // children, and insert one of its children in the other. + this.decreaseKey(i, Number.MIN_SAFE_INTEGER); + var out = this.extractMin(); + } + + + heapify(i){ + var l = left(i); + var r = right(i); + var smallest = i; + if (l < this.size && this.comp( this.store[l] , this.store[i]) ) + smallest = l; + if (r < this.size && this.comp( this.store[r] , this.store[smallest]) ) + smallest = r; + if (smallest != i) + { + this.swap( i, smallest); + this.heapify(smallest); + } + } + + + + +toString(){ + + var res = this.store.slice(0,this.size); + + return res.map(node => node.toString()).toString(); + + +} + +} diff --git a/solutions/data-structures/heap/MedianOfStream.js b/solutions/data-structures/heap/MedianOfStream.js new file mode 100644 index 0000000..35a892c --- /dev/null +++ b/solutions/data-structures/heap/MedianOfStream.js @@ -0,0 +1,54 @@ +import Heap from './Heap' + + +export function median(arr){ + + + + var hmin = new Heap(arr.length+2, (a,b) => { return a < b}); + + var hmax = new Heap(arr.length+2, (a,b) => { return a > b}); + + + var res = []; + + for (var i = 0; i < arr.length; i++){ + + // first we insert the next value in the heap that it belongs. + // If it is greater or equal to the smallest thing in the min heap, it goes in there, else it goes in the other. + + if ( arr[i] >= hmin.getMin()) + hmax.insertKey(arr[i]); + else + hmin.insertKey(arr[i]); + + + // We keep the min heap the same size, or one larger than the max heap. + + + while (hmax.size >= hmin.size ){ + hmin.insertKey(hmax.extractMin()); + } + // now the max heap is smaller than the min heap + while (hmin.size -1 > hmax.size){ + hmax.insertKey(hmin.extractMin()); + } + + + //now the min heap is equal to, or one greater than the max heap. + if (hmax.size === 0){ + + res.push(hmin.getMin()); + } else { + if (hmin.size === hmax.size){ + res.push( (hmin.getMin() +hmax.getMin())/2 ); + } else + res.push(hmin.getMin()); + + } + + } + + return res; + +} diff --git a/solutions/data-structures/heap/MergeNSortedArrays.js b/solutions/data-structures/heap/MergeNSortedArrays.js new file mode 100644 index 0000000..c344979 --- /dev/null +++ b/solutions/data-structures/heap/MergeNSortedArrays.js @@ -0,0 +1,35 @@ + +import Heap from './Heap'; + + +export function mergeNSortedArrays( arrs){ + + // we get n sorted arrays in arrs. + + var n = arrs.length; + + // we make a heap of size n + + var h = new Heap(n+2, (a,b) => { return a.v < b.v}); + + for (var i =0; i < n; i++){ + h.insertKey({x: i,y: 0,v: arrs[i][0]}); + } + + var res = []; + + var top = h.extractMin(); + + while(!h.isEmpty() || top ){ + res.push(top.v); + if (arrs[top.x] && arrs[top.x].length > top.y + 1 ){ + h.insertKey({ x: top.x, + y:top.y + 1, + v: arrs[top.x][top.y +1 ]}); + } + top = h.extractMin(); + } + + return res; + +} diff --git a/solutions/data-structures/heap/PriorityQueue.js b/solutions/data-structures/heap/PriorityQueue.js new file mode 100644 index 0000000..48716d2 --- /dev/null +++ b/solutions/data-structures/heap/PriorityQueue.js @@ -0,0 +1,34 @@ +import Heap from './Heap'; + + +export default class PriorityQueueHeap { + constructor(comp = (a,b) => { return a < b } ) { + this.store = new Heap(100, comp); + this.comp = comp; + } + + + isEmpty() { + return this.store.size === 0; + } + + peek() { + return this.store.getMin(); + + + } + + enqueue(el) { + this.store.insertKey(el); + + } + + getHighest() { + return this.store.extractMin(); + + } + + toString() { + return this.store.toString(); + } +} diff --git a/solutions/data-structures/heap/README.md b/solutions/data-structures/heap/README.md new file mode 100644 index 0000000..0de4f74 --- /dev/null +++ b/solutions/data-structures/heap/README.md @@ -0,0 +1,122 @@ +# Heap + +## Description + +A binary heap is a data structure in the form of a binary tree. +Binary heaps are a common way of implementing priority queues, which +we have earlier impleented using linked lists. The binary heap was +,invented for a sorting algorithm, heapsort. + +A binary heap is defined as a binary tree with two constraints: + +**The Shape property**: a binary heap is a complete binary tree; that is, +all levels of the tree, except possibly the last one (deepest) are +fully filled, and, if the last level of the tree is not complete, the +nodes of that level are filled from left to right. We tested this +property in the section on binary trees. + +**The Heap property**: the key stored in each node is either greater than or equal to (≥) or less than or equal to (≤) the keys in the node's children, according to some comparator. +Heaps where the parent key is greater than or equal to (≥) the child keys are called max-heaps; those where it is less than or equal to (≤) are called min-heaps. + + + +## Implementation + +Binary heaps are usually implemented using an array, rather than a +binary tree. This seems strange, as a heap is defined a kind of +binary tree, but in practice, the array implementaiton is much faster. + +A complete binary tree can be stored as an array, in the followiung +way. The root is the element in position 0. Its left child is in +position 1, and it right in position 2. In general, the left child of +a node in position i is at 2i + 1, and the right child is at 2i+2. +The parent of a node can be found from the node's position by +subtracting one and dividing by 2. + +To make an empty head, we need its capacity, and the comparator that it +will use. We set its size to be 0. An empty head has size zero, and +this is tested by `isEmpty`. + +We can test if a heap data structure has the heap property, that is, +if every node is smaller than its children, by checking that the nodes +with children, those less than size/2, are smaller than their leaft +and right children. This is the `isHeap` function. + +`extractMin` returns the top element, that is, the element in position +0. It also removes this element, so needs to fix up the rest of the +heap. We do this by putting the last element in the heap in position +0, and calling `heapify`. `heapify` is a function that converts a +data structure into a heap, so long as the only element out of +position is the top element. + +`heapify` checks which of the top, left and right elements are +smallest. It puts the smallest one in the root position. If this +swaps the left or right node with the root, it calls `heapify` on the +subtree that it has modified. As the height of the heap with n +elements, is log(n), `heapify` only takes log(n) time. + +`insertKey` adds an element at the end of the array, and swaps that +element with its parents until the element is larger than its parent. + +`getMin`, the heap equivalent of `peek`, returns the element in position 0. + +Heaps were invented for Heapsort. This algorithm takes an an array, +and sorts it. The simplest way to do this is to insert each element +in a heap, and then extractMin until the heap is empty. This takes +O(nlog(n)) time, as `insertKey` and `extractMin` takes log(n) time. + + +## Heap Problems + +**Priority Queue** + +Earlier we inmplemented a Priority Queue using a linked list. Now implement it using a heap. + +A priority queue is a data structure that acts like a queue where you +can 'enqueue' and 'dequeue' elements, but instead of returning the +earliest element still in the queue, it returns the element with the +highest priority. It is normal to have a comparison function, called +a comparator, to tell whether one item has higher priority than +another. If two elements have the same priority, you should return +the earlier element. You should implement 'enqueue', `getHighest`, +`peek`, and `isEmpty`. `enqueue` and `getHighest` should take at most +O(log(n)) time, and the other operations should take O(1). + + +**makeHeap** + + +Rather than turn an array into a heap by calling insertMin *n* times, +Floyd invented a way of turning an array in to a heap in linear time. +This works by calling heapify, starting with element size/2, and +continuing down to element 0. This calls heapify size/2 times, so +naively it seems to take O( n * log(n)). However, due to math which +is more complicated than we deal with here, this actually takes linear +time. This is because almost all the work is done at the low levels, +and `heapify` is cheaper the lower in a tree you are. + +Implement Floyd's makeHeap algorithm. + +`makeHeap([5,4,3,2,1]).toString() = '1,2,3,4,5'` + + + + +**Merge k Sorted Arrays** + +Given k sorted arrays of size n each, merge them. + +The fast way to do this is to push a triple, the array number, the +index and value, to a heap. We push one triple from each array. We +`extractMin` the smallest, comparing on just the value. We insert the +next element from the array whose triple was returned by `extractMin`. + + + +**Median of a Stream** + +Return the running median of a stream in an efficient way. This can +be done by keeping all the elements greater than the current median in +a min heap, and all the elements less than the current median in a max +heap. + diff --git a/solutions/data-structures/heap/__test__/FloydsHeapAlg.test.js b/solutions/data-structures/heap/__test__/FloydsHeapAlg.test.js new file mode 100644 index 0000000..c74a9fd --- /dev/null +++ b/solutions/data-structures/heap/__test__/FloydsHeapAlg.test.js @@ -0,0 +1,32 @@ +import { makeHeap } from '../FloydsHeapAlg'; + + + + +describe(' Heap ', () => { + + + it('should create a Heap from an array.', () => { + + + var h = makeHeap([5,4,3,2,1]); + + expect(h.isHeap()).toBeTruthy(); +}); + + + it('should extract in order.', () => { + + var h = makeHeap([5,4,3,2,1]); + + expect(h.extractMin()).toBe(1); + expect(h.extractMin()).toBe(2); + expect(h.extractMin()).toBe(3); + expect(h.extractMin()).toBe(4); + expect(h.extractMin()).toBe(5); + expect(h.extractMin()).toBeNull(); + + +}); + +}); diff --git a/solutions/data-structures/heap/__test__/Heap.test.js b/solutions/data-structures/heap/__test__/Heap.test.js new file mode 100644 index 0000000..db071d4 --- /dev/null +++ b/solutions/data-structures/heap/__test__/Heap.test.js @@ -0,0 +1,166 @@ +import Heap from '../Heap'; + + + + +describe(' Heap ', () => { + + + it('should create an empty Heap.', () => { + + var h = new Heap(); + + expect(h.store).not.toBeNull(); + + expect(h.capacity).toBe(100); + expect(h.size).toBe(0); + + h = new Heap(100); + + expect(h.capacity).toBe(100); +}); + + + it('should add values to heap.', () => { + + var h = new Heap(); + + h.insertKey(3); + h.insertKey(2); + expect(h.toString()).toBe('2,3'); + + + +}); + + + + it('should delete values .', () => { + + var h = new Heap(); + + h.insertKey(3); + h.insertKey(2); + + h.deleteKey(1); + expect(h.toString()).toBe('2'); + h.insertKey(15); + h.insertKey(5); + h.insertKey(4); + h.insertKey(45); + expect(h.toString()).toBe('2,4,5,15,45'); + + + +}); + + + + it('should extract the min.', () => { + + var h = new Heap(); + + h.insertKey(3); + h.insertKey(2); + expect(h.toString()).toBe('2,3'); + + h.deleteKey(1); + expect(h.toString()).toBe('2'); + h.insertKey(15); + h.insertKey(5); + h.insertKey(4); + h.insertKey(45); + expect(h.toString()).toBe('2,4,5,15,45'); + + + expect(h.extractMin()).toBe(2); + + +}); + + it('should extractget the min.', () => { + + var h = new Heap(); + + h.insertKey(3); + h.insertKey(2); + expect(h.toString()).toBe('2,3'); + + h.deleteKey(1); + expect(h.toString()).toBe('2'); + h.insertKey(15); + h.insertKey(5); + h.insertKey(4); + h.insertKey(45); + + + expect(h.extractMin()).toBe(2); + + + expect(h.getMin()).toBe(4); + +}); + + + it('should decrease keys .', () => { + + var h = new Heap(); + + h.insertKey(3); + h.insertKey(2); + expect(h.toString()).toBe('2,3'); + + h.deleteKey(1); + expect(h.toString()).toBe('2'); + h.insertKey(15); + h.insertKey(5); + h.insertKey(4); + h.insertKey(45); + expect(h.toString()).toBe('2,4,5,15,45'); + + + expect(h.extractMin()).toBe(2); + + expect(h.toString()).toBe('4,15,5,45'); + + expect(h.getMin()).toBe(4); + + h.decreaseKey(2, 1); + expect( h.getMin()).toBe(1); + + + +}); + + + it('should test the heap property', () => { + + var h = new Heap(); + + h.insertKey(3); + h.insertKey(2); + + expect(h.isHeap()).toBeTruthy(); + + h.deleteKey(1); + expect(h.isHeap()).toBeTruthy(); + h.insertKey(15); + h.insertKey(5); + h.insertKey(4); + h.insertKey(45); + expect(h.isHeap()).toBeTruthy(); + + h.extractMin(); + + expect(h.isHeap()).toBeTruthy(); + + + h.decreaseKey(2, 1); + + expect(h.isHeap()).toBeTruthy(); + +}); + + + +}); diff --git a/solutions/data-structures/heap/__test__/MedianOfStream.test.js b/solutions/data-structures/heap/__test__/MedianOfStream.test.js new file mode 100644 index 0000000..fac58d1 --- /dev/null +++ b/solutions/data-structures/heap/__test__/MedianOfStream.test.js @@ -0,0 +1,31 @@ +import { median } from '../MedianOfStream'; + + + + +describe(' Median ', () => { + + + it('should track the median.', () => { + + expect(median([1,2,3,4,5,6,7,8,9]).toString() ).toBe('1,1.5,2,2.5,3,3.5,4,4.5,5'); + + }); + + + it('should track the median when items are out of order.', () => { + + expect(median([1,10,3,4,10,6,10,8,9]).toString() ).toBe('1,5.5,3,3.5,4,5,6,7,8'); + +}); + +it('should track the median when many items are equal.', () => { + + + expect(median([1,1,1,1,1,1,1,2,2,2,2,2,2,2,2,3,3,3,3,3,3,3,3,3,3,3,3]).toString() ).toBe('1,1,1,1,1,1,1,1,1,1,1,1,1,1.5,2,2,2,2,2,2,2,2,2,2,2,2,2'); + + +}); + +}); + diff --git a/solutions/data-structures/heap/__test__/MergeNSortedArrrays.test.js b/solutions/data-structures/heap/__test__/MergeNSortedArrrays.test.js new file mode 100644 index 0000000..2ca7338 --- /dev/null +++ b/solutions/data-structures/heap/__test__/MergeNSortedArrrays.test.js @@ -0,0 +1,44 @@ + +import { mergeNSortedArrays } from '../MergeNSortedArrays' + +describe(' MergeNSortedArrays ', () => { + + + it('should merge n arrays no interleaving ', () => { + + expect( mergeNSortedArrays( [ [1,2,3], [4,5,6]]).toString()).toBe('1,2,3,4,5,6'); + + + + }); + + + it('should merge n arrays nterleaving ', () => { + + expect( mergeNSortedArrays( [ [1,3,5], [2,4,6]]).toString()).toBe('1,2,3,4,5,6'); + + + + }); + + + + it('should merge n arrays n > 3 ', () => { + + expect( mergeNSortedArrays( [ [1,5,9], [2,6,10],[3,7,11],[4,8,12] ]).toString()).toBe('1,2,3,4,5,6,7,8,9,10,11,12'); + + + + }); + + + it('should merge n arrays different lengths ', () => { + + expect( mergeNSortedArrays( [ [1,5,9,15,18], [2,6,10],[3,7,11,13],[4,8,12] ]).toString()).toBe('1,2,3,4,5,6,7,8,9,10,11,12,13,15,18'); + + + + }); + + +}); diff --git a/solutions/data-structures/heap/__test__/PriorityQueue.test.js b/solutions/data-structures/heap/__test__/PriorityQueue.test.js new file mode 100644 index 0000000..160b36c --- /dev/null +++ b/solutions/data-structures/heap/__test__/PriorityQueue.test.js @@ -0,0 +1,72 @@ +import PriorityQueueHeap from '../PriorityQueue'; + + + +describe('PriorityQueueHeap', () => { + + it('should create empty priority queue', () => { + const queue = new PriorityQueueHeap(); + expect(queue).not.toBeNull(); + expect(queue.store).not.toBeNull(); + }); + + it('should enqueue data to priority queue', () => { + const queue = new PriorityQueueHeap( (a,b) => { return a < b } ); + + console.log("Starting PQ"); + + queue.enqueue(4); + queue.enqueue(3); + queue.enqueue(1); + queue.enqueue(2); + + expect(queue.toString()).toBe('1,2,3,4'); + }); + + it('should be possible to enqueue/dequeue in order', () => { + const queue = new PriorityQueueHeap(); + + queue.enqueue(2); + + queue.enqueue(1); + expect(queue.toString()).toBe('1,2'); + + expect(queue.getHighest()).toBe(1); + expect(queue.getHighest()).toBe(2); + }); + + it('should peek data from priority queue', () => { + const queue = new PriorityQueueHeap(); + + expect(queue.peek()).toBeNull(); + + queue.enqueue(1); + queue.enqueue(2); + + expect(queue.peek()).toBe(1); + expect(queue.peek()).toBe(1); + }); + + it('should check if queue is empty', () => { + const queue = new PriorityQueueHeap(); + + expect(queue.isEmpty()).toBeTruthy(); + + queue.enqueue(1); + + expect(queue.isEmpty()).toBeFalsy(); + }); + + it('should dequeue from queue in given order', () => { + const queue = new PriorityQueueHeap(); + + queue.enqueue(2); + queue.enqueue(1); + + expect(queue.getHighest()).toBe(1); + expect(queue.getHighest()).toBe(2); + expect(queue.getHighest()).toBeNull(); + expect(queue.isEmpty()).toBeTruthy(); + }); +}); + diff --git a/solutions/data-structures/linked-list/ArrayToLinkedList.js b/solutions/data-structures/linked-list/ArrayToLinkedList.js new file mode 100644 index 0000000..07366da --- /dev/null +++ b/solutions/data-structures/linked-list/ArrayToLinkedList.js @@ -0,0 +1,14 @@ +import LinkedList from './LinkedList'; + + +export function arrayToLinkedList(arr){ + + var ll = new LinkedList(); + + for( var i = 0; i < arr.length;i++){ + ll.append(arr[i]); + } + return ll; + + +} diff --git a/solutions/data-structures/linked-list/FindLoop.js b/solutions/data-structures/linked-list/FindLoop.js new file mode 100644 index 0000000..a79db38 --- /dev/null +++ b/solutions/data-structures/linked-list/FindLoop.js @@ -0,0 +1,18 @@ +import LinkedList from './LinkedList'; + + +export function findLoop( ll){ + + var slow = ll.head; + var fast = ll.head; + + + while (fast && fast.next && slow ** fast !== slow){ + fast = fast.next.next; + slow = slow.next; + } + + if (fast && fast.next && fast === slow ) return true; + return false; + +} diff --git a/solutions/data-structures/linked-list/LinkedList.js b/solutions/data-structures/linked-list/LinkedList.js index 855feee..f0d5a3f 100644 --- a/solutions/data-structures/linked-list/LinkedList.js +++ b/solutions/data-structures/linked-list/LinkedList.js @@ -128,6 +128,9 @@ export default class LinkedList { const newNode = new LinkedListNode(value, targetNode.next); targetNode.next = newNode; + if (this.tail === targetNode){ + this.tail = newNode; + } return newNode; } diff --git a/solutions/data-structures/linked-list/Palindrome.js b/solutions/data-structures/linked-list/Palindrome.js new file mode 100644 index 0000000..10dff14 --- /dev/null +++ b/solutions/data-structures/linked-list/Palindrome.js @@ -0,0 +1,67 @@ +import LinkedList from './LinkedList'; + +import { reverse, reverseInPlace } from "./ReverseInPlace"; + +export function palindrome(ll){ + + + // The simplest solution is to reverse the list, and check the reversed an original list are equal. + + var rev = reverse(ll); + + var node2 = rev.head; + var node1 = ll.head; + + while (node1 && node2){ + + if (node1.value !== node2.value) + return false; + node1 = node1.next; + node2 = node2.next; + } + + return node1 === node2; +} +export function palindromeInPlace(ll){ + if (!ll) return ll; + var len = 0; + var node1 = ll.head; + while (node1){ + len++; + node1 = node1.next; + } + if (len < 2) return true; + + // Now find the middle point. + + var half = len/2; + node1 = ll.head; + for(var i =0; i < half; i++){ + node1 = node1.next; + } + var middle = node1; + var n = middle; + var nl = new LinkedList(); + nl.head = middle; + nl.tail = n; + var rev = reverseInPlace(nl); + + n = rev.head; + var node2 = rev.head; + node1 = ll.head; + i = 0; + while (i < half && node1 && node2){ + if (node1.value !== node2.value) + return false; + node1 = node1.next; + node2 = node2.next; + i++; + } + + + var mid = reverseInPlace(rev); + middle.next = mid; + ll.tail = mid.tail; + + return true; +} diff --git a/solutions/data-structures/linked-list/README.md b/solutions/data-structures/linked-list/README.md index fe204ed..b4a6728 100644 --- a/solutions/data-structures/linked-list/README.md +++ b/solutions/data-structures/linked-list/README.md @@ -6,7 +6,7 @@ A linked list is a linear colletion of elements _in sequential order_. Imagine a ![Linked List](../../../assets/linked-list-overview.png) -A linked list is composed of a smaller data structure usually called a `Node`. The node object contains the `value` of the element itself, and also a pointer to the `next` node in the chain. By utilizing `node.next`, you can traverse the list. The beginning of a linked list is denoted with `head` and the last element is `tail`. +A linked list is composed of a smaller data structure usually called a `Node`. The node object contains the `value` of the element itself, and also a pointer to the `next` node in the chain. By utilizing `node.next`, you can traverse the list. The beginning of a linked list is denoted with `head` and the last element is `tail`. The 'tail' of a list may refer either to the rest of the list after the head, or to the last node in the list. We will refer to the last node as the tail, but it is worth remembering that some people, especially those fro mthe functional programming community, consider the rest of the list the tail. In a singly-linked list, the link is established in _one direction_ only, where the node only keeps track of the next node in the chain. This means that with a singly-linked list, you can only traverse in the direction of the tail. On the other hand, a node in a doubly-linked list keeps track of both `next` and `previous` which allows you to traverse in both directions (towards `head` and `tail`). @@ -27,9 +27,9 @@ In this exercise, implement the following functions for the `LinkedListNode` and - `append(value)` - Write a method that inserts the `value` at the end of the linked list. - `delete(value)` - - Write a method that deletes the `value` in the linked list. + - Write a method that deletes the first node that contains the `value` in the linked list. - `find(value)` - - Write a method that returns the `node` that contains the `value`. + - Write a method that returns the first `node` that contains the `value`. - `insertAfter(value, targetValue)` - Write a method that inserts the `node` after the `node` that contains the `targetValue`. - `deleteHead()` @@ -106,7 +106,7 @@ Now, let's open up `LinkedList.js` in our editor. - Create a new `Node`. - Set the tail's `next` to be the new node. - Update `tail` to point at the new node. -- Again, take into consideration when this is the first item stored in the list. +- Again, take into consideration when this is the first/last item stored in the list. ### `find(value)` @@ -125,6 +125,9 @@ Now, let's open up `LinkedList.js` in our editor. - Think about how the above concept can be applied to find the target node. +A simple implementation of `find` would take a single argument, the value to be found. However, sometimes we want to find an object, and know only the value of ones of its slots. We might have a list of employee objects with slots name, address, and employee-id. Our `find` method should be able to return an employee object just by taking the id. We enable this by making the single argument to find and object with two slots, `value:` and `callback:`. If the `value:` slot is set, we use it, as normal. If the `callback:` slot if set, it must be a function that can be applied to the nodes of the list, and which returns an object. It should return true for the object that should be returned. This use of callbacks is very common in javascript, so is worth practising. + + ### `insertAfter(value, insertValue)` - The `insertAfter` method stores the `insertValue` right after the node with the `value`. @@ -134,6 +137,7 @@ Now, let's open up `LinkedList.js` in our editor. - Find the node with the target value. - Set the found node's `next` to the new node. - Set new node's `next` to the found node's `next. + - If the node is inserted at the end, the tail needs to be set correctly. - Utilize the previously written `find` method to find the node. ### `delete(value)` @@ -149,11 +153,31 @@ Now, let's open up `LinkedList.js` in our editor. - This means that we must locate the node right before the target node. Think about how you would be able to tell when the next node will be the target node. - Also, take into consideration if the `head` or `tail` node is the node to be deleted. - This is the most complex operation in a linked list. Drawing the operation on paper can help you visualize the problem. Don't hesitate to reach out to #basic-algorithms channel on Slack if you are stuck. + - You might try to use `find` to get to the node you want to delete. This does not work. Can you see why? ### `deleteHead()` / `deleteTail()` -- The `deleteHead/Tail` methods are utility methods for common operations. +- The `deleteHead/Tail` methods are utility methods for common operations. The `deleteTail()` function is slow. It needs to traverse the entire list. ## Time and Space Complexity Analysis -This section will be released in the future when the Big O notation tutorials are written. Please join #basic-algorithms channel for the update notifications. +Some of the operations on linked lists take a fixed, constant amount of instructions, no matter the length of the list. `prepend`, `append`, `inserAfter`, and `deleteHead` obviously just touch at most two nodes. Other functions may need to look at every node in the data structure. `find` and `delete` needs to look at every node if they are asked to find or delete a vlaue that is not there. The last function `deleteTail` needs to traverse the entire list, as it needs to set the tail to the second last node. As we can only go forwards, we need to travel down the entire list. + +## Problems + +**Find a loop in a list** + +Given a list, we can ask whether there is a loop in it. One obvious way to check is to remember each node that we visit in a hash table, and use this to see if there is a loop. There is another way that only requires a constant amount of extra storage (in fact, it just needs to remember two nodes). Floyd's cycle finding algorithm, as this is called, is not obvious, but it is a trick that many interviewers expect you to know. The idea is simple. You travel through the list at two speeds. One traversal goes two steps for each step the other takes. The fast traversal will eventually overtake the slow traversal if there is a loop. If there is no loop, the fast traversal will hit the end of the list. + + +**Reverse a list in place** + +A trick that people expect you to know is how to reverse a list in place. When you traverse a list, from a given node, you can find the next node. Once you have two nodes in a row, you can find the third node, and make the second node point to the first. You can continue on, flipping the pointers backwards, so they point to the previous node, rather than the next. This is unwise in any production system, especially if there is concurrency, and is very bad coding practice anywhere. Despite this, interviewers love asking about it. + + + +**Palidromes** + +A palidrome is a word that reads the same backwards as forwards. "redivider" is a palidrome. If we store each letter in a different node in a singly linked list, the problem is, how can we tell if a list represents a palindrome. We can reverse the list, but there are ways to do this without making a new reversed copy of the list. First try to check for a palindrome with an extra reversed copy. + +Now try to find the length of a list, then find the middle element, then reverse the second half of the list. You now have two lists, the original first half, and the reversed second half. If these two lists are equal, the list is a palindrome. You should re-reverse the second half to return the list to its original form. diff --git a/solutions/data-structures/linked-list/ReverseInPlace.js b/solutions/data-structures/linked-list/ReverseInPlace.js new file mode 100644 index 0000000..504f982 --- /dev/null +++ b/solutions/data-structures/linked-list/ReverseInPlace.js @@ -0,0 +1,43 @@ +import LinkedList from './LinkedList'; + + +export function reverse(ll){ + + var res = new LinkedList(); + + var node = ll.head; + + while(node){ + res.prepend(node.value); + node = node.next; + } + + return res; + +} + +export function reverseInPlace(ll){ + + if (!ll) return ll; + var node1 = ll.head; + + if (!node1 || !node1.next) return ll; + + // zero and one element lists are already reversed + + var node2 = node1.next; + + while(node2.next){ + + var tmp = node2.next; + node2.next = node1; + node1 = node2; + node2 = tmp; + } + + node2.next = node1; + ll.head.next = null; + ll.tail = ll.head; + ll.head = node2; + return ll; +} diff --git a/solutions/data-structures/linked-list/__test__/ArrayToLinkedList.test.js b/solutions/data-structures/linked-list/__test__/ArrayToLinkedList.test.js new file mode 100644 index 0000000..8c6f3a0 --- /dev/null +++ b/solutions/data-structures/linked-list/__test__/ArrayToLinkedList.test.js @@ -0,0 +1,25 @@ +import { arrayToLinkedList } from '../ArrayToLinkedList'; +import LinkedList from '../LinkedList'; + + +describe('ArrayToLinkedList', () => { + it('Linked Lists should work ', () => { + const linkedList = new LinkedList(); + + linkedList.append(1); + linkedList.append(2); + expect(linkedList.toString()).toBe('1,2'); + +}); + + it('should turn arrays to lists ', () => { + expect( arrayToLinkedList( [ 1, 2,3] ).toString()).toBe('1,2,3'); + + expect( arrayToLinkedList( [ ] ).toString()).toBe(''); + + expect( arrayToLinkedList( [1 ] ).toString()).toBe('1'); + + expect( arrayToLinkedList( [1 ,1] ).toString()).toBe('1,1'); +}); + +}); diff --git a/solutions/data-structures/linked-list/__test__/FindLoop.test.js b/solutions/data-structures/linked-list/__test__/FindLoop.test.js new file mode 100644 index 0000000..f8f0d04 --- /dev/null +++ b/solutions/data-structures/linked-list/__test__/FindLoop.test.js @@ -0,0 +1,40 @@ +import { arrayToLinkedList } from '../ArrayToLinkedList'; +import LinkedList from '../LinkedList'; +import { findLoop } from '../FindLoop'; + + + +describe('FindLoop', () => { + it('non loops should return false ', () => { + + + expect( findLoop( arrayToLinkedList( [ 1, 2,3] ) )).toBeFalsy(); + + expect( findLoop( arrayToLinkedList( [ 1] ) )).toBeFalsy(); + + expect( findLoop( arrayToLinkedList( [ ] ) )).toBeFalsy(); + + +}); + + + it(' loops should return true ', () => { + var ll = new LinkedList(); + ll.append(1); + ll.append(2); + ll.append(3); + ll.append(4); + + /* + ll.tail.next = ll.head; + expect( arrayToLinkedList(ll )).toBeTruthy(); + + ll.head.next = ll.head; + expect( arrayToLinkedList(ll )).toBeTruthy(); +*/ + + + +}); + +}); diff --git a/solutions/data-structures/linked-list/__test__/LinkedList.test.js b/solutions/data-structures/linked-list/__test__/LinkedList.test.js index e7a7d01..94730bf 100644 --- a/solutions/data-structures/linked-list/__test__/LinkedList.test.js +++ b/solutions/data-structures/linked-list/__test__/LinkedList.test.js @@ -139,6 +139,13 @@ describe('LinkedList', () => { .insertAfter(2, 1); expect(linkedList.toString()).toBe('1,2,3'); + const linkedList1 = new LinkedList(); + linkedList1 + .append(1) + .append(2) + .insertAfter(3, 2); + + expect(linkedList1.tail.toString()).toBe('3'); }); it('should delete linked list tail', () => { diff --git a/solutions/data-structures/linked-list/__test__/Palindrome.test.js b/solutions/data-structures/linked-list/__test__/Palindrome.test.js new file mode 100644 index 0000000..9b843a0 --- /dev/null +++ b/solutions/data-structures/linked-list/__test__/Palindrome.test.js @@ -0,0 +1,48 @@ +import { arrayToLinkedList } from '../ArrayToLinkedList'; +import LinkedList from '../LinkedList'; +import { palindrome, palindromeInPlace } from '../Palindrome'; + + + +describe('ReverseInPlace', () => { + it(' Check Palindromes ', () => { + + + expect( palindrome( arrayToLinkedList( [ 1, 2,1] ) )).toBeTruthy(); + + expect( palindrome( arrayToLinkedList( [ 1, 2,2,1] ) )).toBeTruthy(); + + + expect( palindrome( arrayToLinkedList( [ 1, 2,3 ] ) )).toBeFalsy(); + + +}); + + it(' Check single and empty Palindromes ', () => { + expect( palindrome( arrayToLinkedList( [ 1] ) )).toBeTruthy(); + + expect( palindrome( arrayToLinkedList( [ ] ))).toBeTruthy(); + }); + + + it(' Check Palindromes ', () => { + + + expect( palindromeInPlace( arrayToLinkedList( [ 1, 2,1] ) )).toBeTruthy(); + + expect( palindromeInPlace( arrayToLinkedList( [ 1, 2,2,1] ) )).toBeTruthy(); + + + expect( palindromeInPlace( arrayToLinkedList( [ 1, 2,3 ] ) )).toBeFalsy(); + + +}); + + it(' Check single and empty Palindromes ', () => { + expect( palindromeInPlace( arrayToLinkedList( [ 1] ) )).toBeTruthy(); + + expect( palindromeInPlace( arrayToLinkedList( [ ] ))).toBeTruthy(); + }); + + +}); diff --git a/solutions/data-structures/linked-list/__test__/ReverseInPlace.test.js b/solutions/data-structures/linked-list/__test__/ReverseInPlace.test.js new file mode 100644 index 0000000..85faf29 --- /dev/null +++ b/solutions/data-structures/linked-list/__test__/ReverseInPlace.test.js @@ -0,0 +1,26 @@ +import { arrayToLinkedList } from '../ArrayToLinkedList'; +import LinkedList from '../LinkedList'; +import { reverseInPlace, reverse } from '../ReverseInPlace'; + + + +describe('ReverseInPlace', () => { + it(' lists should reverse in place ', () => { + expect( reverseInPlace( arrayToLinkedList( [ 1, 2,3] ) ).toString() ).toBe('3,2,1'); + + expect( reverseInPlace( arrayToLinkedList( [ 1] ) ).toString() ).toBe('1'); + + expect( reverseInPlace( arrayToLinkedList( [ ] ) ).toString() ).toBe(''); + + }); + + it(' lists should reverse ', () => { + expect( reverse( arrayToLinkedList( [ 1, 2,3] ) ).toString() ).toBe('3,2,1'); + + expect( reverse( arrayToLinkedList( [ 1] ) ).toString() ).toBe('1'); + + expect( reverse( arrayToLinkedList( [ ] ) ).toString() ).toBe(''); + + }); + +}); diff --git a/solutions/data-structures/queue/Queue.js b/solutions/data-structures/queue/Queue.js index b5656a0..5b361c9 100644 --- a/solutions/data-structures/queue/Queue.js +++ b/solutions/data-structures/queue/Queue.js @@ -3,8 +3,13 @@ export default class Queue { this.store = []; } - isEmpty() { - return !this.store.length; + + length(){ + return this.store.length; + } + + isEmpty() { + return this.store.length === 0; } peek() { @@ -12,7 +17,7 @@ export default class Queue { return null; } - return this.store[0]; + return this.store[this.store.length-1]; } enqueue(el) { diff --git a/solutions/data-structures/queue/QueueUsingStack.js b/solutions/data-structures/queue/QueueUsingStack.js new file mode 100644 index 0000000..089c3aa --- /dev/null +++ b/solutions/data-structures/queue/QueueUsingStack.js @@ -0,0 +1,36 @@ +import Stack from '../stack/Stack' + + +export default class QueueS { + constructor() { + this.store = new Stack(); + this.otherStack = new Stack(); + } + + isEmpty() { + return this.store.isEmpty(); + } + + peek() { + return this.store.peek(); + } + + enqueue(el) { + while (!this.store.isEmpty()){ + this.otherStack.push(this.store.pop()); + } + this.store.push(el); + while (!this.otherStack.isEmpty()){ + this.store.push(this.otherStack.pop()); + } + } + + dequeue() { + return this.store.pop() + } + + toString() { + return this.store.toString(); + } +} + diff --git a/solutions/data-structures/queue/QueueUsingStackR.js b/solutions/data-structures/queue/QueueUsingStackR.js new file mode 100644 index 0000000..b260374 --- /dev/null +++ b/solutions/data-structures/queue/QueueUsingStackR.js @@ -0,0 +1,54 @@ +import Stack from '../stack/Stack' + + +export default class QueueR { + constructor() { + this.store = new Stack(); + this.otherStack = new Stack(); + } + + isEmpty() { + return this.store.isEmpty(); + } + + peek() { + while (!this.store.isEmpty()){ + this.otherStack.push(this.store.pop()); + } + var ans = this.otherStack.peek(); + while (!this.otherStack.isEmpty()){ + this.store.push(this.otherStack.pop()); + } + + return ans; + } + + enqueue(el) { + return this.store.push(el); + } + + dequeue() { + while (!this.store.isEmpty()){ + this.otherStack.push(this.store.pop()); + } + var ans = this.otherStack.pop(); + while (!this.otherStack.isEmpty()){ + this.store.push(this.otherStack.pop()); + } + + return ans; + } + + toString() { + while (!this.store.isEmpty()){ + this.otherStack.push(this.store.pop()); + } + var ans = this.otherStack.toString(); + while (!this.otherStack.isEmpty()){ + this.store.push(this.otherStack.pop()); + } + + return ans; + } +} + diff --git a/solutions/data-structures/queue/README.md b/solutions/data-structures/queue/README.md index 33766c8..74cd1a0 100644 --- a/solutions/data-structures/queue/README.md +++ b/solutions/data-structures/queue/README.md @@ -18,19 +18,19 @@ Queue implements FIFO through two operations; `enqueue` and `dequeue`, which can In this exercise, implement the following functions for the `Queue` class - `isEmpty()` - - Write a method that returns `true` if the queue is currently empty. + - Write a method that returns `true` if the queue is currently empty, and false otherwise. - `peek()` - - Write a method that returns the element at the front of the queue. + - Write a method that returns the element at the front of the queue, or returns null if the queue is empty. - `enqueue(el)` - - Write a method that stores an element(`el`) into the queue. + - Write a method that adds an element(`el`) to the back of the queue. - `dequeue()` - - Write a method that retrieves an element from the queue. + - Write a method that removes an element from the front of the queue, and returns it. It should return null if the queue is empty. - `toString()` - The stringify method is provided for you. `toString()` is a useful method to implement into data structures for easier debugging. - For example, you could use it for logging: ``` const queue = new Queue(); - constole.log(queue.toString()); + console.log(queue.toString()); ``` - A queue is simple enough to be logged without actually needing `toString()`, but with more complex data structures, this is an invaluable method. @@ -42,23 +42,45 @@ In this exercise, implement the following functions for the `Queue` class Implement a queue using stacks. Instead of using an array as your store, use a stack: ``` -const Stack = require('../stack/Stack'); +import Stack from '../stack/Stack' -export default class Stack { + +export default class QueueS { constructor() { this.store = new Stack(); - } + this.otherStack = new Stack(); + } + +etc. +``` + + +There are (at least) two ways to implement a queue. One way makes inserting very cheap, but makes popping and peeking expensive. The other makes popping and peeking very cheap, but makes inserting harder. Try to implement both ways. In both implementations, some operations require touching every objects that is stored. We say an algorithm that touches every object in the data structure is linear (or worse) or, in big O notation, O(n). An operation that can be completed in the same amount of steps, no matter how many objects are in the data struturem is said to be constant, or O(1). + +Ideally, in a Queue, the operations isEmpty, peek, enqueue, and dequeue should be constant or O(1). Whether or not the sample implementation is constant depends on the complexity of the underlying operations. An implementation that uses C arrays underneath should be O(1) almost all the time, save when it needs to re-allocate memory (as the array has grown or n elements have been unshifted). Re-allocating memory and copying is O(n), as it needs to touch every piece of data. + + +**Queue Stack** - isEmpty() {} +Now implement a stack using just one queue. - peek() {} +peek, pop, and isEmpty are easy. The trick is to find a way to implement push so that the other functions work. As a hint, consider what would need to happen for the last element added to be the next element to be dequeued. All the other elements would need to be enqueued after it. This is another example of one function being linear, in this case enqueue, and the others being O(1). - push(value) {} - pop() {} - toString() { - return this.store.toString(); +```import Queue from "./Queue"; +import Stack from "../stack/Stack"; + +export default class StackQ { + constructor() { + this.store = new Queue(); } -} + +etc. + ``` + + + + + diff --git a/solutions/data-structures/queue/StackUsingQueue.js b/solutions/data-structures/queue/StackUsingQueue.js new file mode 100644 index 0000000..8257509 --- /dev/null +++ b/solutions/data-structures/queue/StackUsingQueue.js @@ -0,0 +1,45 @@ +import Queue from "./Queue"; +import Stack from "../stack/Stack"; + +export default class StackQ { + constructor() { + this.store = new Queue(); + } + + +// The idea here is to make insertion slow and peeking and popping fast. +// We keep the items in the Queue in the reverse of the order the usually would be in. +// + + isEmpty() { + return this.store.isEmpty(); + } + + peek() { + return this.store.peek(); + } + + push(el) { + var s = this.store.length(); + + this.store.enqueue(el); + +// we want the most recently added object to be next item that is popped. We need to remove all the other items and +// queue them up so they are newer than the current item. + + for (var i = 0; i < s; i++) { + this.store.enqueue(this.store.peek()); + this.store.dequeue(); + } + + return this.store.peek(); + } + + pop() { + return this.store.dequeue(); + } + + toString() { + return this.store.toString(); + } +} diff --git a/solutions/data-structures/queue/__test__/Queue.test.js b/solutions/data-structures/queue/__test__/Queue.test.js index 7bd3ba8..a1de34d 100644 --- a/solutions/data-structures/queue/__test__/Queue.test.js +++ b/solutions/data-structures/queue/__test__/Queue.test.js @@ -13,7 +13,7 @@ describe('Queue', () => { queue.enqueue(1); queue.enqueue(2); - expect(queue.toString()).toBe('1,2'); + expect(queue.toString()).toBe('2,1'); }); it('should be possible to enqueue/dequeue in order', () => { diff --git a/solutions/data-structures/queue/__test__/QueueUsingStack.test.js b/solutions/data-structures/queue/__test__/QueueUsingStack.test.js new file mode 100644 index 0000000..02b0b69 --- /dev/null +++ b/solutions/data-structures/queue/__test__/QueueUsingStack.test.js @@ -0,0 +1,62 @@ +import QueueS from '../QueueUsingStack'; + +describe('QueueA', () => { + it('should create empty queue', () => { + const queue = new QueueS(); + expect(queue).not.toBeNull(); + expect(queue.store).not.toBeNull(); + }); + + it('should enqueue data to queue', () => { + const queue = new QueueS(); + + queue.enqueue(1); + queue.enqueue(2); + + expect(queue.toString()).toBe('2,1'); + }); + + it('should be possible to enqueue/dequeue in order', () => { + const queue = new QueueS(); + + queue.enqueue(1); + queue.enqueue(2); + + expect(queue.dequeue()).toBe(1); + expect(queue.dequeue()).toBe(2); + }); + + it('should peek data from queue', () => { + const queue = new QueueS(); + + expect(queue.peek()).toBeNull(); + + queue.enqueue(1); + queue.enqueue(2); + + expect(queue.peek()).toBe(1); + expect(queue.peek()).toBe(1); + }); + + it('should check if queue is empty', () => { + const queue = new QueueS(); + + expect(queue.isEmpty()).toBeTruthy(); + + queue.enqueue(1); + + expect(queue.isEmpty()).toBeFalsy(); + }); + + it('should dequeue from queue in FIFO order', () => { + const queue = new QueueS(); + + queue.enqueue(1); + queue.enqueue(2); + + expect(queue.dequeue()).toBe(1); + expect(queue.dequeue()).toBe(2); + expect(queue.dequeue()).toBeNull(); + expect(queue.isEmpty()).toBeTruthy(); + }); +}); diff --git a/solutions/data-structures/queue/__test__/QueueUsingStackR.test.js b/solutions/data-structures/queue/__test__/QueueUsingStackR.test.js new file mode 100644 index 0000000..9e3d4fb --- /dev/null +++ b/solutions/data-structures/queue/__test__/QueueUsingStackR.test.js @@ -0,0 +1,62 @@ +import QueueR from '../QueueUsingStackR'; + +describe('QueueR', () => { + it('should create empty queue', () => { + const queue = new QueueR(); + expect(queue).not.toBeNull(); + expect(queue.store).not.toBeNull(); + }); + + it('should enqueue data to queue', () => { + const queue = new QueueR(); + + queue.enqueue(1); + queue.enqueue(2); + + expect(queue.toString()).toBe('2,1'); + }); + + it('should be possible to enqueue/dequeue in order', () => { + const queue = new QueueR(); + + queue.enqueue(1); + queue.enqueue(2); + + expect(queue.dequeue()).toBe(1); + expect(queue.dequeue()).toBe(2); + }); + + it('should peek data from queue', () => { + const queue = new QueueR(); + + expect(queue.peek()).toBeNull(); + + queue.enqueue(1); + queue.enqueue(2); + + expect(queue.peek()).toBe(1); + expect(queue.peek()).toBe(1); + }); + + it('should check if queue is empty', () => { + const queue = new QueueR(); + + expect(queue.isEmpty()).toBeTruthy(); + + queue.enqueue(1); + + expect(queue.isEmpty()).toBeFalsy(); + }); + + it('should dequeue from queue in FIFO order', () => { + const queue = new QueueR(); + + queue.enqueue(1); + queue.enqueue(2); + + expect(queue.dequeue()).toBe(1); + expect(queue.dequeue()).toBe(2); + expect(queue.dequeue()).toBeNull(); + expect(queue.isEmpty()).toBeTruthy(); + }); +}); diff --git a/solutions/data-structures/queue/__test__/StackUsingQueue.test.js b/solutions/data-structures/queue/__test__/StackUsingQueue.test.js new file mode 100644 index 0000000..cf85672 --- /dev/null +++ b/solutions/data-structures/queue/__test__/StackUsingQueue.test.js @@ -0,0 +1,73 @@ +import StackQ from '../StackUsingQueue'; + +describe('StackQ', () => { + it('should create empty stack', () => { + const stack = new StackQ(); + expect(stack).not.toBeUndefined(); + expect(stack.store).not.toBeUndefined(); + }); + + it('should stack data to stack', () => { + const stack = new StackQ(); + + stack.push(1); + expect(stack.toString()).toBe('1'); + stack.push(2); + expect(stack.toString()).toBe('1,2'); + stack.push(3); + expect(stack.toString()).toBe('1,2,3'); + stack.push(4); + + expect(stack.toString()).toBe('1,2,3,4'); + expect(stack.toString()).toBe('1,2,3,4'); + }); + + it('should peek data from stack', () => { + const stack = new StackQ(); + + expect(stack.peek()).not.toBeUndefined(); + + stack.push(1); + expect(stack.peek()).toBe(1); + stack.push(2); + + expect(stack.peek()).toBe(2); + expect(stack.peek()).toBe(2); + }); + + it('should check if stack is empty', () => { + const stack = new StackQ(); + + expect(stack.isEmpty()).toBeTruthy(); + + stack.push(1); + + expect(stack.isEmpty()).toBeFalsy(); + }); + + it('should pop data from stack', () => { + const stack = new StackQ(); + + stack.push(1); + stack.push(2); + + expect(stack.pop()).toBe(2); + expect(stack.pop()).toBe(1); + expect(stack.pop()).not.toBeUndefined(); + expect(stack.isEmpty()).toBeTruthy(); + }); + + it('should be possible to push/pop', () => { + const stack = new StackQ(); + + stack.push(1); + stack.push(2); + stack.push(3); + + expect(stack.toString()).toBe('1,2,3'); + expect(stack.toString()).toBe('1,2,3'); + expect(stack.pop()).toBe(3); + expect(stack.pop()).toBe(2); + expect(stack.pop()).toBe(1); + }); +}); diff --git a/solutions/data-structures/stack/BracketMatching.js b/solutions/data-structures/stack/BracketMatching.js new file mode 100644 index 0000000..63092be --- /dev/null +++ b/solutions/data-structures/stack/BracketMatching.js @@ -0,0 +1,22 @@ +import Stack from './Stack'; + + +export const bracketMatch = str => { + + let stack = new Stack(); + + for (var i = 0; i < str.length; i++) { + + let chr = str.charAt(i); + if (chr === '(' || chr === '[' || chr === '{' ) stack.push(chr); + if (chr === ')' || chr === ']' || chr === '}'){ + if ( + (chr === ')' && stack.peek() === '(') || + (chr === ']' && stack.peek() === '[') || + (chr === '}' && stack.peek() === '{') ) + stack.pop(); + else return false; + } + } + return stack.isEmpty(); +} diff --git a/solutions/data-structures/stack/NextGreaterElement.js b/solutions/data-structures/stack/NextGreaterElement.js new file mode 100644 index 0000000..7d3b833 --- /dev/null +++ b/solutions/data-structures/stack/NextGreaterElement.js @@ -0,0 +1,42 @@ +import Stack from './Stack'; + + + +export const nextGreaterElement = arr => { + let stack = new Stack(); + var res = new Array(arr.length); + + stack.push(0); + for (var i = 1; i < arr.length; i++) { + + if (stack.isEmpty()) { + stack.push(i); + continue; + } + + /* if stack is not empty, then + pop an index from stack. + If the corresponding element is smaller + than next element, then + the NGE of that index if the next element + Keep popping while elements are + smaller and stack is not empty */ + while ( !stack.isEmpty() && arr[stack.peek()] < arr[i]) + { + res[stack.pop()] = arr[i]; + } + + /* push next index onto stack so that we can find + next greater for it */ + stack.push(i); + } + + /* After iterating over the loop, the remaining + elements in stack do not have a next greater + element, so set them to null */ + while (!stack.isEmpty() ) { + res[stack.pop()] = null; + } + +return res; +} diff --git a/solutions/data-structures/stack/PostFixEval.js b/solutions/data-structures/stack/PostFixEval.js new file mode 100644 index 0000000..74c19e1 --- /dev/null +++ b/solutions/data-structures/stack/PostFixEval.js @@ -0,0 +1,40 @@ +import Stack from './Stack'; + + + +export const evalPostFix = arr => { + let stack = new Stack(); + + for (var i = 0; i < arr.length; i++) { + if (typeof arr[i] == 'number'){ + stack.push(arr[i]); + } + if (arr[i] === '-'){ + let arg1 = stack.pop(); + let arg2 = stack.pop(); + if (arg1 && arg2)stack.push(arg1-arg2); else return null; + } + if (arr[i] === '+'){ + let arg1 = stack.pop(); + let arg2 = stack.pop(); + if (arg1 && arg2)stack.push(arg1+arg2); else return null; + } + if (arr[i] === '*'){ + let arg1 = stack.pop(); + let arg2 = stack.pop(); + if (arg1 && arg2)stack.push(arg1*arg2); else return null; + } + if (arr[i] === '/'){ + let arg1 = stack.pop(); + let arg2 = stack.pop(); + if (arg1 && arg2)stack.push(arg1/arg2); else return null; + } + } + let res = stack.pop(); + + if (stack.isEmpty() && res ) return res; + else return null; + +} + + diff --git a/solutions/data-structures/stack/README.md b/solutions/data-structures/stack/README.md index 0ca72e3..9c88257 100644 --- a/solutions/data-structures/stack/README.md +++ b/solutions/data-structures/stack/README.md @@ -17,13 +17,13 @@ Stack implements LIFO through two operations: `push` and `pop`, which can be vis In this exercise, implement the following functions for the `Stack` class - `isEmpty()` - - Write a method that returns `true` if the stack is currently empty. + - Write a method that returns `true` if the stack is currently empty, and false otherwise. - `peek()` - - Write a method that returns the element from the top of the stack. + - Write a method that returns the element from the top of the stack, and returns null if the stack is empty. - `push(el)` - Write a method that stores an element(`el`) into the stack. - `pop()` - - Write a method that retrieves an element from the stack. + - Write a method that removes the top element from the stack and returns it, and returns null if the stack is empty.. - `toString()` - The stringify method has been provided for you. @@ -33,6 +33,8 @@ In this exercise, implement the following functions for the `Stack` class Write an algorithm to determine if all of the delimiters in an expression are matched and closed. + + - Delimiters are `(` `{` `[` - Closed is `()` and `(()` would not be closed - Matched is `{}`, and `{]` would not be matched. @@ -50,3 +52,59 @@ console.log(bracketMatch('{df][d}') === false); console.log(bracketMatch('([)]') === false); ``` +**PostFix** + +Write an algorithm to evaluate postfix. + +Postfix is a way of representing algebraic expressions without needing brackets, and it is faster to evaluate than regulat infix expressions. Where we normally would write '1 + 2' in infix, or normal mathematical notation, in postfix we write '1 2 +'. The operations =,*,-, and / take the previous two arguments. The natural way of evaluating postifx is to push each number on a stack. When you get to an operation, you pop the two numbers needed off the stack, and push the result back on. When the expression is finished, there should be exactly one number on the stack. If we ever have too few arguments on the stack, or too mant arguments at the end, we return null; + +```const evalPostFix = arr => { + +} + +console.log( evalPostFix( [ 1, 2,'+'] ) ) === 3); +console.log( evalPostFix( [ 1, 2, 3,'+','+'] ) == 6); +console.log( evalPostFix( [ 0, 1, 2,'+'] ) == null ); + + +``` + +**Stock Span Problem** + +The stock span problem is a problem often posed in interviews, where we have an array of n price quotes for a stock and we want to determine the span of stock’s price for all n days. +The span, Si, of the stock on day, i, is how many you have to go back before the stock price was higher than now. This computes the largest interval when you would have made a profit from buying the stock, as its price is lower than now in all that span. + +For example, if an array of 7 days prices is given as [100, 80, 60, 70, 60, 75, 85], then the span values for corresponding 7 days are [1, 1, 1, 2, 1, 4, 6] + +Many interviewers like to ask questions about stock prices. The simplest way to do this problem is to start from each price, and look backwards until you find a larger price. If the prices happened to increase over the time period, each time you looked at a stock, you would need to look at all previous prices. This algorithm thus can look at n^2/n prices, so the algorrithm is called O(n^2). Using a stack, the problem can be solved in O(n), that is, using at most only a linear number of operations c*n, where c is some constant. + +It is not immediately clear from the solution that the algorithm is linear, as there are nested loops. How do we know that the algorithm is linear? The outside loop goes through the array once, while the inside loop pops items off the stack. We only push n items onto the stack, so we can at most pop n items off the stack. This when we add up the amount of work all the inner loops do, the total is n pops or less. The usual sign of an n^2 algorithm is nested loops, but in cases like this, where the inner loop pops a stack, or other data structure, we can prove that the inner loop only does a linear amount of work. + + +```const stockSpan = arr => { + +} + +console.log( stockSpan( [100, 80, 60, 70, 60, 75, 85] ) ) == [1, 1, 1, 2, 1, 4, 6] ); + +``` + +**Next Greater Element** + + +This problem is similar to the stock span problem, but looks forward rather than backwards. A huge part of solving programming challenges is recognizing when you are being asked about a problem almost identical to a well known problem. + +Given an array of numbers, for each number, find the next greater element (NGE), that is, the next number that is bigger than the current number. If no number is bigger, we set the answer to null. For every array, the next greatest number of the last element is always null, as there are no numbers to the right. For the array [13, 7, 6, 12], the NGEs are [null, 12,12, null]. + +Using a stack, in a similar way to the previous problem, we can solve this in linear time. + + +```const nextGreatestElement = arr => { + +} + +console.log( stockSpan( [13, 7, 6, 12 ] ) ) == [null, 12, 12, null] ); + +``` + + diff --git a/solutions/data-structures/stack/Stack.js b/solutions/data-structures/stack/Stack.js index 44873b2..a4b00d3 100644 --- a/solutions/data-structures/stack/Stack.js +++ b/solutions/data-structures/stack/Stack.js @@ -9,7 +9,7 @@ export default class Stack { peek() { if (this.isEmpty()) { - return; + return null; } return this.store[this.store.length - 1]; @@ -20,6 +20,7 @@ export default class Stack { } pop() { + if (this.store.length == 0) return null; return this.store.pop(); } diff --git a/solutions/data-structures/stack/StockSpan.js b/solutions/data-structures/stack/StockSpan.js new file mode 100644 index 0000000..a7614c6 --- /dev/null +++ b/solutions/data-structures/stack/StockSpan.js @@ -0,0 +1,31 @@ +import Stack from './Stack'; + + + +export const stockSpan = price => { + let stack = new Stack(); + var res = new Array(price.length); + + res[0] = 1; + stack.push(0); + + for (var i = 1; i < price.length; i++) { + // Pop elements from stack while stack is not + // empty and top of stack is smaller than + // proce[i] + while (!stack.isEmpty() && price[stack.peek()] <= price[i]) + stack.pop(); + + // If stack becomes empty, then price[i] is + // greater than all elements on left of it, + // i.e., price[0], price[1], ..price[i-1]. Else + // price[i] is greater than elements after + // top of stack + res[i] = (stack.isEmpty()) ? (i + 1) : (i - stack.peek()); + + // Push this element to stack + stack.push(i); + } +return res; +} + diff --git a/solutions/data-structures/stack/__test__/Bracket.test.js b/solutions/data-structures/stack/__test__/Bracket.test.js new file mode 100644 index 0000000..a726a6c --- /dev/null +++ b/solutions/data-structures/stack/__test__/Bracket.test.js @@ -0,0 +1,26 @@ +import Stack from '../Stack'; + +import { bracketMatch } from '../BracketMatching'; + + +describe('Bracket', () => { + it('should match simple patterns', () => { + + expect( bracketMatch("(())") ).toBeTruthy(); + expect( bracketMatch("(()") ).toBeFalsy(); + expect( bracketMatch("())") ).toBeFalsy(); + expect( bracketMatch("((a)a") ).toBeFalsy(); + expect( bracketMatch("1(1(2)3)1") ).toBeTruthy(); + expect( bracketMatch("a(b)c(d)e") ).toBeTruthy(); + + expect( bracketMatch('{ac[bb]}')).toBeTruthy(); + expect (bracketMatch('[dklf(df(kl))d]{}') ).toBeTruthy(); + expect( bracketMatch('{[[[]]]}') ).toBeTruthy(); + expect( bracketMatch('{3234[fd') ).toBeFalsy(); + expect( bracketMatch('{df][d}') ).toBeFalsy(); + expect( bracketMatch('([)]') ).toBeFalsy(); + + }); + +}); + diff --git a/solutions/data-structures/stack/__test__/NextGreaterElement.test.js b/solutions/data-structures/stack/__test__/NextGreaterElement.test.js new file mode 100644 index 0000000..f304175 --- /dev/null +++ b/solutions/data-structures/stack/__test__/NextGreaterElement.test.js @@ -0,0 +1,12 @@ +import { nextGreaterElement } from '../NextGreaterElement' + + +describe('NextGreaterElement', () => { + it('should should compute NGE', () => { + + expect( nextGreaterElement( [ 11,13,21,3] ).toString()).toBe( [13,21,null,null].toString() ); + +}); + +}); + diff --git a/solutions/data-structures/stack/__test__/PostFixEval.test.js b/solutions/data-structures/stack/__test__/PostFixEval.test.js new file mode 100644 index 0000000..f7292c9 --- /dev/null +++ b/solutions/data-structures/stack/__test__/PostFixEval.test.js @@ -0,0 +1,18 @@ +import Stack from '../Stack'; + +import { evalPostFix } from '../PostFixEval'; + + +describe('PostFixEval', () => { + it('should eval postfix ', () => { + + + expect( evalPostFix( [ 1, 2,'+'] ).toString()).toBe('3'); + expect( evalPostFix( [ 1, 2, 3,'+','+'] ).toString()).toBe('6'); + expect( evalPostFix( [ 0, 1, 2,'+'] ) ).toBeNull(); + expect( evalPostFix( [ 1, 2 ] ) ).toBeNull(); +}); + +}); + + diff --git a/solutions/data-structures/stack/__test__/Stack.test.js b/solutions/data-structures/stack/__test__/Stack.test.js index ff0cbb4..112c55a 100644 --- a/solutions/data-structures/stack/__test__/Stack.test.js +++ b/solutions/data-structures/stack/__test__/Stack.test.js @@ -19,7 +19,7 @@ describe('Stack', () => { it('should peek data from stack', () => { const stack = new Stack(); - expect(stack.peek()).toBeUndefined(); + expect(stack.peek()).not.toBeUndefined(); stack.push(1); stack.push(2); @@ -46,7 +46,7 @@ describe('Stack', () => { expect(stack.pop()).toBe(2); expect(stack.pop()).toBe(1); - expect(stack.pop()).toBeUndefined(); + expect(stack.pop()).not.toBeUndefined(); expect(stack.isEmpty()).toBeTruthy(); }); diff --git a/solutions/data-structures/stack/__test__/StockSpan.test.js b/solutions/data-structures/stack/__test__/StockSpan.test.js new file mode 100644 index 0000000..1b8699c --- /dev/null +++ b/solutions/data-structures/stack/__test__/StockSpan.test.js @@ -0,0 +1,13 @@ +import { stockSpan } from '../StockSpan' + + +describe('StockSpan', () => { + it('should should compute spans', () => { + + expect( stockSpan( [100, 80, 60, 70, 60, 75, 85] ).toString()).toBe( [1, 1, 1, 2, 1, 4, 6].toString() ); + +}); + +}); + + diff --git a/src/algorithms/numbers/fibonacci/NumberDecodings.js b/src/algorithms/numbers/fibonacci/NumberDecodings.js new file mode 100644 index 0000000..78b1bd3 --- /dev/null +++ b/src/algorithms/numbers/fibonacci/NumberDecodings.js @@ -0,0 +1,89 @@ +export function nwd( s, p){ + var l = s.length; + if ( l <= p) return 1; // There is only one way to decode a single digit + if ( l-1 == p) return 1; + if ( s[p+1] === '0') return nwd(s,p+2); + // If the second digit is 0, we can only parse this as a single digit. + if ( (s[p] == '1' || + ( s[p] == '2' && s[p+1] > '0' && s[p+1] <= '6' ) ) + ){ // if the string is "[1][1-9]..." or"[2][1-6]..." then we can decode this + // using just one digit, or using 2 digits. There are nwd(s, p+1) + // ways of decoding the rest of the string if we use one digit, + // and nwd(s, p+2) ways if we use 2. + return ( nwd(s, p+1) + nwd(s, p+2)); + + } + // The string starts with a 3 or higher (or 27 or higher), so we can only parse this as a single digit + return nwd(s,p+1); +} + +export function nwdMem(s,p){ + var mem = new Array(1+ s.length); + return nwdM(s,p,mem); +} + +export function nwdM( s,p, mem){ + var l = s.length; + if ( l <= p ) return 1; // There is only one way to decode a single digit + if ( l-1 === p ) return 1; + if (mem[p]){return mem[p];} + if ( s[p+1] === '0') + { + mem[p] = nwdM(s,p+2,mem); + return mem[p]; } + // If the second digit is 0, we can only parse this as a single digit. + if ((s[p] == '1' || + ( s[p] == '2' && s[p+1] > '0' && s[p+1] <= '6' ) ) + ) // if the string is "[1][1-9]..." or"[2][1-6]..." then we can decode this + // using just one digit, or using 2 digits. There are nwd(s, p+1) + // ways of decoding the rest of the string if we use one digit, + // and nwd(s, p+2) ways if we use 2. + { + mem[p] = nwdM(s, p+1,mem) + nwdM(s, p+2,mem); + + return mem[p]; + } + // The string starts with a 3 or higher (or 27 or higher), so we can only parse this as a single digit + mem[p] = nwdM(s,p+1,mem); + return mem[p]; + +} + + +export function nwd_dp( s){ + var l = s.length; + var dp = new Array(l); + dp[l] = 1; + for (var i = l-1; i>=0; i--){ + if ( l-1==i) { dp[i] = 1; + continue; } + if (s[i+1] === '0') { dp[i] = dp[ i+2]; + continue;} + if ( s[i] === '1' || + (s[i] === '2' && s[i+1] >= '0' && s[i+1] <= '6' )) + { + dp[i] = ( dp[ i+2] + dp[ i+1] ); } + else + dp[i] = dp[i+1] ; + } + return dp[0]; +} + +export function nwd_c( s){ + var l = s.length; + var c = 1; + var c1 = 1; + for (var i = l-1; i>=0;i--){ + if ( l-1===i) { c = 1; continue;} + if (s[i+1] === '0'){ c1 = c; + continue;} + if (s[i] === '1' || + ( s[i] == '2' && s[i+1] > '0' && s[i+1] <= '6' ) ) + { + var tmp = c; + c = ( c1 + c); + c1 = tmp; + } else c1 = c; + } + return c; +} diff --git a/src/algorithms/numbers/fibonacci/__test__/NumberDecodings.test.js b/src/algorithms/numbers/fibonacci/__test__/NumberDecodings.test.js new file mode 100644 index 0000000..7990a5b --- /dev/null +++ b/src/algorithms/numbers/fibonacci/__test__/NumberDecodings.test.js @@ -0,0 +1,67 @@ +import { nwd, nwdMem, nwd_dp, nwd_c } from '../NumberDecodings'; + + +describe('NumberDecodings', () => { + it('should calculatenumber of decodings correctly', () => { + expect(nwd("11",0)).toEqual(2); + expect(nwd("1212",0)).toEqual(5); + expect(nwd("122",0)).toEqual(3); + expect(nwd("1227",0)).toEqual(3); + expect(nwd("10122",0)).toEqual(3); + expect(nwd("101227",0)).toEqual(3); + expect(nwd("101010",0)).toEqual(1); + expect(nwd("123123123",0)).toEqual(27); + expect(nwd("1230123010",0)).toEqual(9); + expect(nwd("123242526",0)).toEqual(24); + + }); + + + + it('should calculatenumber of decodings correctly with memoization', () => { + expect(nwdMem("11",0)).toEqual(2); + expect(nwdMem("1212",0)).toEqual(5); + expect(nwdMem("122",0)).toEqual(3); + expect(nwdMem("1227",0)).toEqual(3); + expect(nwdMem("10122",0)).toEqual(3); + expect(nwdMem("101227",0)).toEqual(3); + expect(nwdMem("101010",0)).toEqual(1); + expect(nwdMem("123123123",0)).toEqual(27); + expect(nwdMem("1230123010",0)).toEqual(9); + expect(nwdMem("123242526",0)).toEqual(24); + }); + + + + it('should calculate number of decodings correctly iteratively', () => { + expect(nwd_dp("11",0)).toEqual(2); + expect(nwd_dp("1212",0)).toEqual(5); + expect(nwd_dp("122",0)).toEqual(3); + expect(nwd_dp("1227",0)).toEqual(3); + expect(nwd_dp("10122",0)).toEqual(3); + expect(nwd_dp("101227",0)).toEqual(3); + expect(nwd_dp("101010",0)).toEqual(1); + expect(nwd_dp("123123123",0)).toEqual(27); + expect(nwd_dp("1230123010",0)).toEqual(9); + expect(nwd_dp("123242526",0)).toEqual(24); + }); + + + + + it('should calculate number of decodings correctly with O(1) memory', () => { + expect(nwd_c("11",0)).toEqual(2); + expect(nwd_c("1212",0)).toEqual(5); + expect(nwd_c("122",0)).toEqual(3); + expect(nwd_c("1227",0)).toEqual(3); + expect(nwd_c("10122",0)).toEqual(3); + expect(nwd_c("101227",0)).toEqual(3); + expect(nwd_c("101010",0)).toEqual(1); + expect(nwd_c("123123123",0)).toEqual(27); + expect(nwd_c("1230123010",0)).toEqual(9); + expect(nwd_c("123242526",0)).toEqual(24); + }); + + + +}); diff --git a/src/algorithms/numbers/fibonacci/__test__/fibonacci.test.js b/src/algorithms/numbers/fibonacci/__test__/fibonacci.test.js new file mode 100644 index 0000000..499db86 --- /dev/null +++ b/src/algorithms/numbers/fibonacci/__test__/fibonacci.test.js @@ -0,0 +1,57 @@ +import fibonacci from '../fibonacci'; + +import fibonacciR from '../fibonacciR'; + +import fibonacciM from '../fibonacciM'; + + +describe('fibonacci', () => { + it('should calculate fibonacci correctly', () => { + expect(fibonacci(1)).toEqual([1]); + expect(fibonacci(2)).toEqual([1, 1]); + expect(fibonacci(3)).toEqual([1, 1, 2]); + expect(fibonacci(4)).toEqual([1, 1, 2, 3]); + expect(fibonacci(5)).toEqual([1, 1, 2, 3, 5]); + expect(fibonacci(6)).toEqual([1, 1, 2, 3, 5, 8]); + expect(fibonacci(7)).toEqual([1, 1, 2, 3, 5, 8, 13]); + expect(fibonacci(8)).toEqual([1, 1, 2, 3, 5, 8, 13, 21]); + }); + + it('should calculate fibonacci correctly', () => { + expect(fibonacciR(1)).toEqual(1); + expect(fibonacciR(2)).toEqual(1); + expect(fibonacciR(3)).toEqual(2); + expect(fibonacciR(4)).toEqual(3); + expect(fibonacciR(5)).toEqual(5); + expect(fibonacciR(6)).toEqual(8); + expect(fibonacciR(7)).toEqual(13); + expect(fibonacciR(8)).toEqual(21); + }); + + + + it('should calculate fibonacci correctly', () => { + expect(fibonacciM(1)).toEqual(1); + expect(fibonacciM(2)).toEqual(1); + expect(fibonacciM(3)).toEqual(2); + expect(fibonacciM(4)).toEqual(3); + expect(fibonacciM(5)).toEqual(5); + expect(fibonacciM(6)).toEqual(8); + expect(fibonacciM(7)).toEqual(13); + expect(fibonacciM(8)).toEqual(21); + }); + + + it('should calculate fibonacci correctly', () => { + expect(fibonacciM(1)).toEqual(1); + expect(fibonacciM(2)).toEqual(1); + expect(fibonacciM(3)).toEqual(2); + expect(fibonacciM(4)).toEqual(3); + expect(fibonacciM(5)).toEqual(5); + expect(fibonacciM(6)).toEqual(8); + expect(fibonacciM(7)).toEqual(13); + expect(fibonacciM(8)).toEqual(21); + }); + + +}); diff --git a/src/algorithms/numbers/fibonacci/__test__/fibonacciNth.test.js b/src/algorithms/numbers/fibonacci/__test__/fibonacciNth.test.js new file mode 100644 index 0000000..8e702a5 --- /dev/null +++ b/src/algorithms/numbers/fibonacci/__test__/fibonacciNth.test.js @@ -0,0 +1,15 @@ +import fibonacciNth from '../fibonacciNth'; + +describe('fibonacciNth', () => { + it('should calculate fibonacci correctly', () => { + expect(fibonacciNth(1)).toBe(1); + expect(fibonacciNth(2)).toBe(1); + expect(fibonacciNth(3)).toBe(2); + expect(fibonacciNth(4)).toBe(3); + expect(fibonacciNth(5)).toBe(5); + expect(fibonacciNth(6)).toBe(8); + expect(fibonacciNth(7)).toBe(13); + expect(fibonacciNth(8)).toBe(21); + expect(fibonacciNth(20)).toBe(6765); + }); +}); diff --git a/src/algorithms/numbers/fibonacci/fibonacci.js b/src/algorithms/numbers/fibonacci/fibonacci.js new file mode 100644 index 0000000..4caa1d9 --- /dev/null +++ b/src/algorithms/numbers/fibonacci/fibonacci.js @@ -0,0 +1,29 @@ +/** + * Return a fibonacci sequence as an array. + * + * @param n + * @return {number[]} + */ +export default function fibonacci(n) { + const fibSequence = [1]; + + let currentValue = 1; + let previousValue = 0; + + if (n === 1) { + return fibSequence; + } + + let iterationsCounter = n - 1; + + while (iterationsCounter) { + currentValue += previousValue; + previousValue = currentValue - previousValue; + + fibSequence.push(currentValue); + + iterationsCounter -= 1; + } + + return fibSequence; +} diff --git a/src/algorithms/numbers/fibonacci/fibonacciM.js b/src/algorithms/numbers/fibonacci/fibonacciM.js new file mode 100644 index 0000000..b1ccfe7 --- /dev/null +++ b/src/algorithms/numbers/fibonacci/fibonacciM.js @@ -0,0 +1,21 @@ + +/** + * Return a fibonacci sequence as an array. + * + * @param n + * @return {number[]} + */ +export default function fibonacciM(n) { +var mem = new Array(n); +return fibonacciMem(n, mem); +} + +function fibonacciMem(n,mem){ + if (n ===0) return 0; + if (n ===1) return 1; + if (mem[n]) return mem[n]; + mem[n] = fibonacciMem(n-1,mem) + fibonacciMem(n-2,mem); + return mem[n]; +} + + diff --git a/src/algorithms/numbers/fibonacci/fibonacciNth.js b/src/algorithms/numbers/fibonacci/fibonacciNth.js new file mode 100644 index 0000000..fb28fdd --- /dev/null +++ b/src/algorithms/numbers/fibonacci/fibonacciNth.js @@ -0,0 +1,25 @@ +/** + * Calculate fibonacci number at specific position using Dynamic Programming approach. + * + * @param n + * @return {number} + */ +export default function fibonacciNth(n) { + let currentValue = 1; + let previousValue = 0; + + if (n === 1) { + return 1; + } + + let iterationsCounter = n - 1; + + while (iterationsCounter) { + currentValue += previousValue; + previousValue = curre ntValue - previousValue; + + iterationsCounter -= 1; + } + + return currentValue; +} diff --git a/src/algorithms/numbers/fibonacci/fibonacciR.js b/src/algorithms/numbers/fibonacci/fibonacciR.js new file mode 100644 index 0000000..dd85099 --- /dev/null +++ b/src/algorithms/numbers/fibonacci/fibonacciR.js @@ -0,0 +1,12 @@ +/** + * Return a fibonacci sequence as an array. + * + * @param n + * @return {number[]} + */ +export default function fibonacciR(n) { + if (n ===0) return 0; + if (n ===1) return 1; + + return fibonacciR(n-1) + fibonacciR(n-2); +} diff --git a/src/algorithms/numbers/prime/FermatPrimality.js b/src/algorithms/numbers/prime/FermatPrimality.js new file mode 100644 index 0000000..d140424 --- /dev/null +++ b/src/algorithms/numbers/prime/FermatPrimality.js @@ -0,0 +1,16 @@ +export function fermatPrimality(n){ + if (n === 1) return false; + if (n === 2) return true; + for(var i = 0; i < 10; i++){ + // check 10 times + var a = 1 + Math.floor( (n-1) * Math.random()); // a is a number in the range 0..p-1 + var ap = 1; + for (var j = 0; j < n-1; j++){ + ap = (ap * a) % n; + } + if (ap !== 1) return false; + // we know for sure that p is composite + } + return true; + // we have checked k times and each time the test succeeded +} diff --git a/src/algorithms/numbers/prime/FermatTest.js b/src/algorithms/numbers/prime/FermatTest.js new file mode 100644 index 0000000..a08ffc9 --- /dev/null +++ b/src/algorithms/numbers/prime/FermatTest.js @@ -0,0 +1,21 @@ +/** + * @param {number} number + * @return {boolean} + */ + + +export function fermatTest(n){ + if (n === 1 ) return false; + if (n === 2) return true; + var a = Math.ceil(Math.sqrt(n)); + var b = a*a - n; + var squareRootb = Math.sqrt(b); + while(a <= (n+1)/2 && b !== squareRootb * squareRootb ){ + a++; + b = a*a - n; + squareRootb = Math.floor( Math.sqrt(b)); + } + // console.log([n, squareRootb, a, (n+1)/2]); + if (a === (n+1)/2) return true; + return false; +} diff --git a/src/algorithms/numbers/prime/__test__/FermatTest.test.js b/src/algorithms/numbers/prime/__test__/FermatTest.test.js new file mode 100644 index 0000000..2b4daef --- /dev/null +++ b/src/algorithms/numbers/prime/__test__/FermatTest.test.js @@ -0,0 +1,29 @@ +import { fermatPrimality } from '../FermatPrimality'; + + + +describe('Fermat Test', () => { + it('should detect prime numbers', () => { + + expect(fermatPrimality(1)).toBeFalsy(); + expect(fermatPrimality(2)).toBeTruthy(); + expect(fermatPrimality(3)).toBeTruthy(); + expect(fermatPrimality(5)).toBeTruthy(); + expect(fermatPrimality(11)).toBeTruthy(); + expect(fermatPrimality(191)).toBeTruthy(); + expect(fermatPrimality(191)).toBeTruthy(); + expect(fermatPrimality(199)).toBeTruthy(); + + expect(fermatPrimality(4)).toBeFalsy(); + expect(fermatPrimality(6)).toBeFalsy(); + expect(fermatPrimality(12)).toBeFalsy(); + expect(fermatPrimality(14)).toBeFalsy(); + expect(fermatPrimality(25)).toBeFalsy(); + expect(fermatPrimality(192)).toBeFalsy(); + expect(fermatPrimality(200)).toBeFalsy(); + expect(fermatPrimality(400)).toBeFalsy(); + + + + }); +}); diff --git a/src/algorithms/numbers/prime/__test__/trialDivision.test.js b/src/algorithms/numbers/prime/__test__/trialDivision.test.js new file mode 100644 index 0000000..2608085 --- /dev/null +++ b/src/algorithms/numbers/prime/__test__/trialDivision.test.js @@ -0,0 +1,37 @@ +import trialDivision from '../trialDivision'; + +/** + * @param {function(n: number)} testFunction + */ +function primalityTest(testFunction) { + expect(testFunction(1)).toBeFalsy(); + expect(testFunction(2)).toBeTruthy(); + expect(testFunction(3)).toBeTruthy(); + expect(testFunction(5)).toBeTruthy(); + expect(testFunction(11)).toBeTruthy(); + expect(testFunction(191)).toBeTruthy(); + expect(testFunction(191)).toBeTruthy(); + expect(testFunction(199)).toBeTruthy(); + + expect(testFunction(-1)).toBeFalsy(); + expect(testFunction(0)).toBeFalsy(); + expect(testFunction(4)).toBeFalsy(); + expect(testFunction(6)).toBeFalsy(); + expect(testFunction(12)).toBeFalsy(); + expect(testFunction(14)).toBeFalsy(); + expect(testFunction(25)).toBeFalsy(); + expect(testFunction(192)).toBeFalsy(); + expect(testFunction(200)).toBeFalsy(); + expect(testFunction(400)).toBeFalsy(); + + // It should also deal with floats. + expect(testFunction(0.5)).toBeFalsy(); + expect(testFunction(1.3)).toBeFalsy(); + expect(testFunction(10.5)).toBeFalsy(); +} + +describe('trialDivision', () => { + it('should detect prime numbers', () => { + primalityTest(trialDivision); + }); +}); diff --git a/src/algorithms/numbers/prime/trialDivision.js b/src/algorithms/numbers/prime/trialDivision.js new file mode 100644 index 0000000..c347e05 --- /dev/null +++ b/src/algorithms/numbers/prime/trialDivision.js @@ -0,0 +1,35 @@ +/** + * @param {number} number + * @return {boolean} + */ +export default function trialDivision(number) { + // Check if number is integer. + if (number % 1 !== 0) { + return false; + } + + if (number <= 1) { + // If number is less than one then it isn't prime by definition. + return false; + } + + if (number <= 3) { + // All numbers from 2 to 3 are prime. + return true; + } + + // If the number is not divided by 2 then we may eliminate all further even dividers. + if (number % 2 === 0) { + return false; + } + + // If there is no dividers up to square root of n then there is no higher dividers as well. + const dividerLimit = Math.sqrt(number); + for (let divider = 3; divider <= dividerLimit; divider += 2) { + if (number % divider === 0) { + return false; + } + } + + return true; +} diff --git a/src/data-structures/linked-list/__test__/LinkedList.test.js b/src/data-structures/linked-list/__test__/LinkedList.test.js index e7a7d01..94730bf 100644 --- a/src/data-structures/linked-list/__test__/LinkedList.test.js +++ b/src/data-structures/linked-list/__test__/LinkedList.test.js @@ -139,6 +139,13 @@ describe('LinkedList', () => { .insertAfter(2, 1); expect(linkedList.toString()).toBe('1,2,3'); + const linkedList1 = new LinkedList(); + linkedList1 + .append(1) + .append(2) + .insertAfter(3, 2); + + expect(linkedList1.tail.toString()).toBe('3'); }); it('should delete linked list tail', () => { diff --git a/src/data-structures/queue/__test__/Queue.test.js b/src/data-structures/queue/__test__/Queue.test.js index 7bd3ba8..a1de34d 100644 --- a/src/data-structures/queue/__test__/Queue.test.js +++ b/src/data-structures/queue/__test__/Queue.test.js @@ -13,7 +13,7 @@ describe('Queue', () => { queue.enqueue(1); queue.enqueue(2); - expect(queue.toString()).toBe('1,2'); + expect(queue.toString()).toBe('2,1'); }); it('should be possible to enqueue/dequeue in order', () => { diff --git a/src/data-structures/stack/__test__/Stack.test.js b/src/data-structures/stack/__test__/Stack.test.js index ff0cbb4..112c55a 100644 --- a/src/data-structures/stack/__test__/Stack.test.js +++ b/src/data-structures/stack/__test__/Stack.test.js @@ -19,7 +19,7 @@ describe('Stack', () => { it('should peek data from stack', () => { const stack = new Stack(); - expect(stack.peek()).toBeUndefined(); + expect(stack.peek()).not.toBeUndefined(); stack.push(1); stack.push(2); @@ -46,7 +46,7 @@ describe('Stack', () => { expect(stack.pop()).toBe(2); expect(stack.pop()).toBe(1); - expect(stack.pop()).toBeUndefined(); + expect(stack.pop()).not.toBeUndefined(); expect(stack.isEmpty()).toBeTruthy(); });