├── .babelrc ├── .gitignore ├── README.md ├── assets ├── doubly-linked-list.png ├── github-clone.png ├── github-fork.png ├── linked-list-delete.png ├── linked-list-insert-after.png ├── linked-list-overview.png ├── linked-list-tdd-console.png ├── linked-list-tdd.png ├── linkedlist.png ├── queue.png └── stack.png ├── jest.config.js ├── package-lock.json ├── package.json ├── skeletons ├── algorithms │ ├── numbers │ │ ├── factorial │ │ │ ├── README.md │ │ │ ├── __test__ │ │ │ │ ├── factorial.test.js │ │ │ │ └── factorialRecursive.test.js │ │ │ ├── factorial.js │ │ │ └── factorialRecursive.js │ │ ├── fibonacci │ │ │ ├── README.md │ │ │ ├── __test__ │ │ │ │ ├── fibonacci.test.js │ │ │ │ └── fibonacciNth.test.js │ │ │ ├── fibonacci.js │ │ │ └── fibonacciNth.js │ │ └── prime │ │ │ ├── README.md │ │ │ ├── __test__ │ │ │ └── trialDivision.test.js │ │ │ └── trialDivision.js │ ├── search │ │ ├── binary-search │ │ │ ├── README.md │ │ │ ├── __test__ │ │ │ │ └── binarySearch.test.js │ │ │ └── binarySearch.js │ │ ├── breadth-first-search │ │ │ ├── README.md │ │ │ ├── __test__ │ │ │ │ └── breadthFirstSearch.test.js │ │ │ └── breadthFirstSearch.js │ │ ├── depth-first-search │ │ │ ├── README.md │ │ │ ├── __test__ │ │ │ │ └── depthFirstSearch.test.js │ │ │ └── depthFirstSearch.js │ │ └── linear-search │ │ │ ├── README.md │ │ │ ├── __test__ │ │ │ └── linearSearch.test.js │ │ │ └── linearSearch.js │ └── sorting │ │ ├── Sort.js │ │ ├── SortTester.js │ │ ├── __test__ │ │ └── Sort.test.js │ │ ├── bubble-sort │ │ ├── BubbleSort.js │ │ ├── README.md │ │ └── __test__ │ │ │ └── BubbleSort.test.js │ │ ├── insertion-sort │ │ ├── InsertionSort.js │ │ ├── README.md │ │ └── __test__ │ │ │ └── InsertionSort.test.js │ │ ├── merge-sort │ │ ├── MergeSort.js │ │ ├── README.md │ │ └── __test__ │ │ │ └── MergeSort.test.js │ │ ├── quick-sort │ │ ├── QuickSort.js │ │ ├── QuickSortInPlace.js │ │ ├── README.md │ │ └── __test__ │ │ │ ├── QuickSort.test.js │ │ │ └── QuickSortInPlace.test.js │ │ └── selection-sort │ │ ├── README.md │ │ ├── SelectionSort.js │ │ └── __test__ │ │ └── SelectionSort.test.js ├── data-structures │ ├── binary-search-tree │ │ ├── BinarySearchTree.js │ │ ├── BinarySearchTreeNode.js │ │ ├── BinaryTreeNode.js │ │ ├── README.md │ │ └── __test__ │ │ │ ├── BinarySearchTree.test.js │ │ │ └── BinarySearchTreeNode.test.js │ ├── doubly-linked-list │ │ ├── DoublyLinkedList.js │ │ ├── DoublyLinkedListNode.js │ │ ├── README.md │ │ └── __test__ │ │ │ ├── DoublyLinkedList.test.js │ │ │ └── DoublyLinkedListNode.test.js │ ├── hash-table │ │ ├── HashTable.js │ │ ├── README.md │ │ └── __test__ │ │ │ └── HashTable.test.js │ ├── linked-list │ │ ├── LinkedList.js │ │ ├── LinkedListNode.js │ │ ├── README.md │ │ └── __test__ │ │ │ ├── LinkedList.test.js │ │ │ └── LinkedListNode.test.js │ ├── queue │ │ ├── Queue.js │ │ ├── README.md │ │ └── __test__ │ │ │ └── Queue.test.js │ └── stack │ │ ├── README.md │ │ ├── Stack.js │ │ └── __test__ │ │ └── Stack.test.js └── utils │ └── comparator │ ├── Comparator.js │ └── __test__ │ └── Comparator.test.js ├── solutions ├── algorithms │ ├── numbers │ │ ├── factorial │ │ │ ├── README.md │ │ │ ├── __test__ │ │ │ │ ├── factorial.test.js │ │ │ │ └── factorialRecursive.test.js │ │ │ ├── factorial.js │ │ │ └── factorialRecursive.js │ │ ├── fibonacci │ │ │ ├── README.md │ │ │ ├── __test__ │ │ │ │ ├── fibonacci.test.js │ │ │ │ └── fibonacciNth.test.js │ │ │ ├── fibonacci.js │ │ │ └── fibonacciNth.js │ │ └── prime │ │ │ ├── README.md │ │ │ ├── __test__ │ │ │ └── trialDivision.test.js │ │ │ └── trialDivision.js │ ├── search │ │ ├── binary-search │ │ │ ├── README.md │ │ │ ├── __test__ │ │ │ │ └── binarySearch.test.js │ │ │ └── binarySearch.js │ │ ├── breadth-first-search │ │ │ ├── README.md │ │ │ ├── __test__ │ │ │ │ └── breadthFirstSearch.test.js │ │ │ └── breadthFirstSearch.js │ │ ├── depth-first-search │ │ │ ├── README.md │ │ │ ├── __test__ │ │ │ │ └── depthFirstSearch.test.js │ │ │ └── depthFirstSearch.js │ │ └── linear-search │ │ │ ├── README.md │ │ │ ├── __test__ │ │ │ └── linearSearch.test.js │ │ │ └── linearSearch.js │ └── sorting │ │ ├── Sort.js │ │ ├── SortTester.js │ │ ├── __test__ │ │ └── Sort.test.js │ │ ├── bubble-sort │ │ ├── BubbleSort.js │ │ ├── README.md │ │ └── __test__ │ │ │ └── BubbleSort.test.js │ │ ├── insertion-sort │ │ ├── InsertionSort.js │ │ ├── README.md │ │ └── __test__ │ │ │ └── InsertionSort.test.js │ │ ├── merge-sort │ │ ├── MergeSort.js │ │ ├── README.md │ │ └── __test__ │ │ │ └── MergeSort.test.js │ │ ├── quick-sort │ │ ├── QuickSort.js │ │ ├── QuickSortInPlace.js │ │ ├── README.md │ │ └── __test__ │ │ │ ├── QuickSort.test.js │ │ │ └── QuickSortInPlace.test.js │ │ └── selection-sort │ │ ├── README.md │ │ ├── SelectionSort.js │ │ └── __test__ │ │ └── SelectionSort.test.js ├── data-structures │ ├── binary-search-tree │ │ ├── BinarySearchTree.js │ │ ├── BinarySearchTreeNode.js │ │ ├── BinaryTreeNode.js │ │ ├── README.md │ │ └── __test__ │ │ │ ├── BinarySearchTree.test.js │ │ │ └── BinarySearchTreeNode.test.js │ ├── doubly-linked-list │ │ ├── DoublyLinkedList.js │ │ ├── DoublyLinkedListNode.js │ │ ├── README.md │ │ └── __test__ │ │ │ ├── DoublyLinkedList.test.js │ │ │ └── DoublyLinkedListNode.test.js │ ├── hash-table │ │ ├── HashTable.js │ │ ├── README.md │ │ └── __test__ │ │ │ └── HashTable.test.js │ ├── linked-list │ │ ├── LinkedList.js │ │ ├── LinkedListNode.js │ │ ├── README.md │ │ └── __test__ │ │ │ ├── LinkedList.test.js │ │ │ └── LinkedListNode.test.js │ ├── queue │ │ ├── Queue.js │ │ ├── README.md │ │ └── __test__ │ │ │ └── Queue.test.js │ └── stack │ │ ├── README.md │ │ ├── Stack.js │ │ └── __test__ │ │ └── Stack.test.js └── utils │ └── comparator │ ├── Comparator.js │ └── __test__ │ └── Comparator.test.js └── src └── data-structures ├── doubly-linked-list ├── DoublyLinkedList.js ├── DoublyLinkedListNode.js ├── README.md └── __test__ │ ├── DoublyLinkedList.test.js │ └── DoublyLinkedListNode.test.js ├── linked-list ├── LinkedList.js ├── LinkedListNode.js ├── README.md └── __test__ │ ├── LinkedList.test.js │ └── LinkedListNode.test.js ├── queue ├── Queue.js ├── README.md └── __test__ │ └── Queue.test.js └── stack ├── README.md ├── Stack.js └── __test__ └── Stack.test.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["env"] 3 | } 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .vscode 3 | -------------------------------------------------------------------------------- /assets/doubly-linked-list.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OperationCode/algorithms/81754b71d9e14dcc4e340999bc3029df1f824443/assets/doubly-linked-list.png -------------------------------------------------------------------------------- /assets/github-clone.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OperationCode/algorithms/81754b71d9e14dcc4e340999bc3029df1f824443/assets/github-clone.png -------------------------------------------------------------------------------- /assets/github-fork.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OperationCode/algorithms/81754b71d9e14dcc4e340999bc3029df1f824443/assets/github-fork.png -------------------------------------------------------------------------------- /assets/linked-list-delete.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OperationCode/algorithms/81754b71d9e14dcc4e340999bc3029df1f824443/assets/linked-list-delete.png -------------------------------------------------------------------------------- /assets/linked-list-insert-after.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OperationCode/algorithms/81754b71d9e14dcc4e340999bc3029df1f824443/assets/linked-list-insert-after.png -------------------------------------------------------------------------------- /assets/linked-list-overview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OperationCode/algorithms/81754b71d9e14dcc4e340999bc3029df1f824443/assets/linked-list-overview.png -------------------------------------------------------------------------------- /assets/linked-list-tdd-console.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OperationCode/algorithms/81754b71d9e14dcc4e340999bc3029df1f824443/assets/linked-list-tdd-console.png -------------------------------------------------------------------------------- /assets/linked-list-tdd.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OperationCode/algorithms/81754b71d9e14dcc4e340999bc3029df1f824443/assets/linked-list-tdd.png -------------------------------------------------------------------------------- /assets/linkedlist.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OperationCode/algorithms/81754b71d9e14dcc4e340999bc3029df1f824443/assets/linkedlist.png -------------------------------------------------------------------------------- /assets/queue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OperationCode/algorithms/81754b71d9e14dcc4e340999bc3029df1f824443/assets/queue.png -------------------------------------------------------------------------------- /assets/stack.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OperationCode/algorithms/81754b71d9e14dcc4e340999bc3029df1f824443/assets/stack.png -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | // The bail config option can be used here to have Jest stop running tests after 3 | // the first failure. 4 | bail: false, 5 | 6 | // Indicates whether each individual test should be reported during the run. 7 | verbose: true, 8 | 9 | // Indicates whether the coverage information should be collected while executing the test 10 | collectCoverage: false, 11 | 12 | // The directory where Jest should output its coverage files. 13 | coverageDirectory: './coverage/', 14 | 15 | // If the test path matches any of the patterns, it will be skipped. 16 | testPathIgnorePatterns: ['/node_modules/', '/solutions', '/skeletons'], 17 | 18 | // If the file path matches any of the patterns, coverage information will be skipped. 19 | coveragePathIgnorePatterns: ['/node_modules/'], 20 | 21 | // The pattern Jest uses to detect test files. 22 | testRegex: '(/__tests__/.*|(\\.|/)(test|spec))\\.jsx?$', 23 | }; 24 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "operation-code-algorithms", 3 | "version": "0.0.1", 4 | "description": "Operation Code Algorithms Curriculum", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "jest", 8 | "watch": "jest --watch" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "git+https://github.com/trekhleb/javascript-algorithms.git" 13 | }, 14 | "keywords": [ 15 | "operation-code", 16 | "computer-science", 17 | "cs", 18 | "algorithms", 19 | "data-structures", 20 | "javascript", 21 | "algorithm", 22 | "javascript-algorithms", 23 | "sorting-algorithms", 24 | "graph", 25 | "tree" 26 | ], 27 | "author": "Kevin K. Lee (https://www.linkedin.com/in/kevinkiklee/)", 28 | "license": "MIT", 29 | "bugs": { 30 | "url": "https://github.com/kevinkiklee/opcode-algorithms/issues" 31 | }, 32 | "homepage": "https://github.com/kevinkiklee/opcode-algorithms#readme", 33 | "devDependencies": { 34 | "@types/jest": "^23.1.4", 35 | "babel-cli": "^6.26.0", 36 | "babel-preset-env": "^1.7.0", 37 | "eslint": "^4.19.1", 38 | "eslint-config-airbnb": "^17.0.0", 39 | "eslint-plugin-import": "^2.13.0", 40 | "eslint-plugin-jest": "^21.17.0", 41 | "eslint-plugin-jsx-a11y": "^6.1.0", 42 | "eslint-plugin-react": "^7.10.0", 43 | "jest": "^23.3.0" 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /skeletons/algorithms/numbers/factorial/README.md: -------------------------------------------------------------------------------- 1 | # Factorial 2 | 3 | In mathematics, the factorial of a non-negative integer `n`, 4 | denoted by `n!`, is the product of all positive integers less 5 | than or equal to `n`. For example: 6 | 7 | ``` 8 | 5! = 5 * 4 * 3 * 2 * 1 = 120 9 | ``` 10 | 11 | | n | n! | 12 | | ----- | --------------------------: | 13 | | 0 | 1 | 14 | | 1 | 1 | 15 | | 2 | 2 | 16 | | 3 | 6 | 17 | | 4 | 24 | 18 | | 5 | 120 | 19 | | 6 | 720 | 20 | | 7 | 5 040 | 21 | | 8 | 40 320 | 22 | | 9 | 362 880 | 23 | | 10 | 3 628 800 | 24 | | 11 | 39 916 800 | 25 | | 12 | 479 001 600 | 26 | | 13 | 6 227 020 800 | 27 | | 14 | 87 178 291 200 | 28 | | 15 | 1 307 674 368 000 | 29 | 30 | ## References 31 | 32 | [Wikipedia](https://en.wikipedia.org/wiki/Factorial) 33 | -------------------------------------------------------------------------------- /skeletons/algorithms/numbers/factorial/__test__/factorial.test.js: -------------------------------------------------------------------------------- 1 | import factorial from '../factorial'; 2 | 3 | describe('factorial', () => { 4 | it('should calculate factorial', () => { 5 | expect(factorial(0)).toBe(1); 6 | expect(factorial(1)).toBe(1); 7 | expect(factorial(5)).toBe(120); 8 | expect(factorial(8)).toBe(40320); 9 | expect(factorial(10)).toBe(3628800); 10 | }); 11 | }); 12 | -------------------------------------------------------------------------------- /skeletons/algorithms/numbers/factorial/__test__/factorialRecursive.test.js: -------------------------------------------------------------------------------- 1 | import factorialRecursive from '../factorialRecursive'; 2 | 3 | describe('factorialRecursive', () => { 4 | it('should calculate factorial', () => { 5 | expect(factorialRecursive(0)).toBe(1); 6 | expect(factorialRecursive(1)).toBe(1); 7 | expect(factorialRecursive(5)).toBe(120); 8 | expect(factorialRecursive(8)).toBe(40320); 9 | expect(factorialRecursive(10)).toBe(3628800); 10 | }); 11 | }); 12 | -------------------------------------------------------------------------------- /skeletons/algorithms/numbers/factorial/factorial.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {number} number 3 | * @return {number} 4 | */ 5 | export default function factorial(number) { 6 | // WRITE YOUR CODE HERE 7 | } 8 | -------------------------------------------------------------------------------- /skeletons/algorithms/numbers/factorial/factorialRecursive.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {number} number 3 | * @return {number} 4 | */ 5 | export default function factorialRecursive(number) { 6 | // WRITE YOUR CODE HERE 7 | } 8 | -------------------------------------------------------------------------------- /skeletons/algorithms/numbers/fibonacci/README.md: -------------------------------------------------------------------------------- 1 | # Fibonacci Number 2 | 3 | In mathematics, the Fibonacci numbers are the numbers in the following 4 | integer sequence, called the Fibonacci sequence, and characterized by 5 | the fact that every number after the first two is the sum of the two 6 | preceding ones: 7 | 8 | `0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, ...` 9 | 10 | A tiling with squares whose side lengths are successive Fibonacci numbers 11 | 12 | ![Fibonacci](https://upload.wikimedia.org/wikipedia/commons/d/db/34%2A21-FibonacciBlocks.png) 13 | 14 | The Fibonacci spiral: an approximation of the golden spiral created by drawing circular arcs connecting the opposite corners of squares in the Fibonacci tiling;[4] this one uses squares of sizes 1, 1, 2, 3, 5, 8, 13 and 21. 15 | 16 | ![Fibonacci Spiral](https://upload.wikimedia.org/wikipedia/commons/2/2e/FibonacciSpiral.svg) 17 | 18 | ## References 19 | 20 | [Wikipedia](https://en.wikipedia.org/wiki/Fibonacci_number) 21 | -------------------------------------------------------------------------------- /skeletons/algorithms/numbers/fibonacci/__test__/fibonacci.test.js: -------------------------------------------------------------------------------- 1 | import fibonacci from '../fibonacci'; 2 | 3 | describe('fibonacci', () => { 4 | it('should calculate fibonacci correctly', () => { 5 | expect(fibonacci(1)).toEqual([1]); 6 | expect(fibonacci(2)).toEqual([1, 1]); 7 | expect(fibonacci(3)).toEqual([1, 1, 2]); 8 | expect(fibonacci(4)).toEqual([1, 1, 2, 3]); 9 | expect(fibonacci(5)).toEqual([1, 1, 2, 3, 5]); 10 | expect(fibonacci(6)).toEqual([1, 1, 2, 3, 5, 8]); 11 | expect(fibonacci(7)).toEqual([1, 1, 2, 3, 5, 8, 13]); 12 | expect(fibonacci(8)).toEqual([1, 1, 2, 3, 5, 8, 13, 21]); 13 | }); 14 | }); 15 | -------------------------------------------------------------------------------- /skeletons/algorithms/numbers/fibonacci/__test__/fibonacciNth.test.js: -------------------------------------------------------------------------------- 1 | import fibonacciNth from '../fibonacciNth'; 2 | 3 | describe('fibonacciNth', () => { 4 | it('should calculate fibonacci correctly', () => { 5 | expect(fibonacciNth(1)).toBe(1); 6 | expect(fibonacciNth(2)).toBe(1); 7 | expect(fibonacciNth(3)).toBe(2); 8 | expect(fibonacciNth(4)).toBe(3); 9 | expect(fibonacciNth(5)).toBe(5); 10 | expect(fibonacciNth(6)).toBe(8); 11 | expect(fibonacciNth(7)).toBe(13); 12 | expect(fibonacciNth(8)).toBe(21); 13 | expect(fibonacciNth(20)).toBe(6765); 14 | }); 15 | }); 16 | -------------------------------------------------------------------------------- /skeletons/algorithms/numbers/fibonacci/fibonacci.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Return a fibonacci sequence as an array. 3 | * 4 | * @param n 5 | * @return {number[]} 6 | */ 7 | export default function fibonacci(n) { 8 | // WRITE YOUR CODE HERE 9 | } 10 | -------------------------------------------------------------------------------- /skeletons/algorithms/numbers/fibonacci/fibonacciNth.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Calculate fibonacci number at specific position using Dynamic Programming approach. 3 | * 4 | * @param n 5 | * @return {number} 6 | */ 7 | export default function fibonacciNth(n) { 8 | // WRITE YOUR CODE HERE 9 | } 10 | -------------------------------------------------------------------------------- /skeletons/algorithms/numbers/prime/README.md: -------------------------------------------------------------------------------- 1 | # Primality Test 2 | 3 | A **prime number** (or a **prime**) is a natural number greater than `1` that 4 | cannot be formed by multiplying two smaller natural numbers. A natural number 5 | greater than `1` that is not prime is called a composite number. For 6 | example, `5` is prime because the only ways of writing it as a 7 | product, `1 × 5` or `5 × 1`, involve `5` itself. However, `6` is 8 | composite because it is the product of two numbers `(2 × 3)` that are 9 | both smaller than `6`. 10 | 11 | ![Prime Numbers](https://upload.wikimedia.org/wikipedia/commons/f/f0/Primes-vs-composites.svg) 12 | 13 | A **primality test** is an algorithm for determining whether an input 14 | number is prime. Among other fields of mathematics, it is used 15 | for cryptography. Unlike integer factorization, primality tests 16 | do not generally give prime factors, only stating whether the 17 | input number is prime or not. Factorization is thought to be 18 | a computationally difficult problem, whereas primality testing 19 | is comparatively easy (its running time is polynomial in the 20 | size of the input). 21 | 22 | ## References 23 | 24 | - [Prime Numbers on Wikipedia](https://en.wikipedia.org/wiki/Prime_number) 25 | - [Primality Test on Wikipedia](https://en.wikipedia.org/wiki/Primality_test) 26 | -------------------------------------------------------------------------------- /skeletons/algorithms/numbers/prime/__test__/trialDivision.test.js: -------------------------------------------------------------------------------- 1 | import trialDivision from '../trialDivision'; 2 | 3 | /** 4 | * @param {function(n: number)} testFunction 5 | */ 6 | function primalityTest(testFunction) { 7 | expect(testFunction(1)).toBeFalsy(); 8 | expect(testFunction(2)).toBeTruthy(); 9 | expect(testFunction(3)).toBeTruthy(); 10 | expect(testFunction(5)).toBeTruthy(); 11 | expect(testFunction(11)).toBeTruthy(); 12 | expect(testFunction(191)).toBeTruthy(); 13 | expect(testFunction(191)).toBeTruthy(); 14 | expect(testFunction(199)).toBeTruthy(); 15 | 16 | expect(testFunction(-1)).toBeFalsy(); 17 | expect(testFunction(0)).toBeFalsy(); 18 | expect(testFunction(4)).toBeFalsy(); 19 | expect(testFunction(6)).toBeFalsy(); 20 | expect(testFunction(12)).toBeFalsy(); 21 | expect(testFunction(14)).toBeFalsy(); 22 | expect(testFunction(25)).toBeFalsy(); 23 | expect(testFunction(192)).toBeFalsy(); 24 | expect(testFunction(200)).toBeFalsy(); 25 | expect(testFunction(400)).toBeFalsy(); 26 | 27 | // It should also deal with floats. 28 | expect(testFunction(0.5)).toBeFalsy(); 29 | expect(testFunction(1.3)).toBeFalsy(); 30 | expect(testFunction(10.5)).toBeFalsy(); 31 | } 32 | 33 | describe('trialDivision', () => { 34 | it('should detect prime numbers', () => { 35 | primalityTest(trialDivision); 36 | }); 37 | }); 38 | -------------------------------------------------------------------------------- /skeletons/algorithms/numbers/prime/trialDivision.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {number} number 3 | * @return {boolean} 4 | */ 5 | export default function trialDivision(number) { 6 | // WRITE YOUR CODE HERE 7 | } 8 | -------------------------------------------------------------------------------- /skeletons/algorithms/search/binary-search/README.md: -------------------------------------------------------------------------------- 1 | # Binary Search 2 | 3 | In computer science, binary search, also known as half-interval 4 | search, logarithmic search, or binary chop, is a search algorithm 5 | that finds the position of a target value within a sorted 6 | array. Binary search compares the target value to the middle 7 | element of the array; if they are unequal, the half in which 8 | the target cannot lie is eliminated and the search continues 9 | on the remaining half until it is successful. If the search 10 | ends with the remaining half being empty, the target is not 11 | in the array. 12 | 13 | ![Binary Search](https://upload.wikimedia.org/wikipedia/commons/8/83/Binary_Search_Depiction.svg) 14 | 15 | ## References 16 | 17 | - [Wikipedia](https://en.wikipedia.org/wiki/Binary_search_algorithm) 18 | - [YouTube](https://www.youtube.com/watch?v=P3YID7liBug&index=29&list=PLLXdhg_r2hKA7DPDsunoDZ-Z769jWn4R8) 19 | -------------------------------------------------------------------------------- /skeletons/algorithms/search/binary-search/__test__/binarySearch.test.js: -------------------------------------------------------------------------------- 1 | import binarySearch from '../binarySearch'; 2 | 3 | describe('binarySearch', () => { 4 | it('should search number in sorted array', () => { 5 | expect(binarySearch([], 1)).toBe(-1); 6 | expect(binarySearch([1], 1)).toBe(0); 7 | expect(binarySearch([1, 2], 1)).toBe(0); 8 | expect(binarySearch([1, 2], 2)).toBe(1); 9 | expect(binarySearch([1, 5, 10, 12], 1)).toBe(0); 10 | expect(binarySearch([1, 5, 10, 12, 14, 17, 22, 100], 17)).toBe(5); 11 | expect(binarySearch([1, 5, 10, 12, 14, 17, 22, 100], 1)).toBe(0); 12 | expect(binarySearch([1, 5, 10, 12, 14, 17, 22, 100], 100)).toBe(7); 13 | expect(binarySearch([1, 5, 10, 12, 14, 17, 22, 100], 0)).toBe(-1); 14 | }); 15 | 16 | it('should search object in sorted array', () => { 17 | const sortedArrayOfObjects = [ 18 | { key: 1, value: 'value1' }, 19 | { key: 2, value: 'value2' }, 20 | { key: 3, value: 'value3' }, 21 | ]; 22 | 23 | const comparator = (a, b) => { 24 | if (a.key === b.key) return 0; 25 | return a.key < b.key ? -1 : 1; 26 | }; 27 | 28 | expect(binarySearch([], { key: 1 }, comparator)).toBe(-1); 29 | expect(binarySearch(sortedArrayOfObjects, { key: 4 }, comparator)).toBe(-1); 30 | expect(binarySearch(sortedArrayOfObjects, { key: 1 }, comparator)).toBe(0); 31 | expect(binarySearch(sortedArrayOfObjects, { key: 2 }, comparator)).toBe(1); 32 | expect(binarySearch(sortedArrayOfObjects, { key: 3 }, comparator)).toBe(2); 33 | }); 34 | }); 35 | -------------------------------------------------------------------------------- /skeletons/algorithms/search/binary-search/binarySearch.js: -------------------------------------------------------------------------------- 1 | import Comparator from '../../../utils/comparator/Comparator'; 2 | 3 | /** 4 | * @param {*[]} sortedArray 5 | * @param {*} seekElement 6 | * @param {function(a, b)} [comparatorCallback] 7 | * @return {number} 8 | */ 9 | 10 | export default function binarySearch(sortedArray, seekElement, comparatorCallback) { 11 | // WRITE YOUR CODE HERE 12 | } 13 | -------------------------------------------------------------------------------- /skeletons/algorithms/search/breadth-first-search/README.md: -------------------------------------------------------------------------------- 1 | # Breadth-First Search (BFS) 2 | 3 | Breadth-first search (BFS) is an algorithm for traversing 4 | or searching tree or graph data structures. It starts at 5 | the tree root (or some arbitrary node of a graph, sometimes 6 | referred to as a 'search key') and explores the neighbor 7 | nodes first, before moving to the next level neighbors. 8 | 9 | ![Algorithm Visualization](https://upload.wikimedia.org/wikipedia/commons/5/5d/Breadth-First-Search-Algorithm.gif) 10 | 11 | ## References 12 | 13 | - [Wikipedia](https://en.wikipedia.org/wiki/Breadth-first_search) 14 | - [Tree Traversals (Inorder, Preorder and Postorder)](https://www.geeksforgeeks.org/tree-traversals-inorder-preorder-and-postorder/) 15 | - [BFS vs DFS](https://www.geeksforgeeks.org/bfs-vs-dfs-binary-tree/) 16 | -------------------------------------------------------------------------------- /skeletons/algorithms/search/breadth-first-search/__test__/breadthFirstSearch.test.js: -------------------------------------------------------------------------------- 1 | import BinaryTreeNode from '../../../../data-structures/binary-search-tree/BinaryTreeNode'; 2 | import breadthFirstSearch from '../breadthFirstSearch'; 3 | 4 | describe('breadthFirstSearch', () => { 5 | it('should perform DFS operation on tree', () => { 6 | const nodeA = new BinaryTreeNode('A'); 7 | const nodeB = new BinaryTreeNode('B'); 8 | const nodeC = new BinaryTreeNode('C'); 9 | const nodeD = new BinaryTreeNode('D'); 10 | const nodeE = new BinaryTreeNode('E'); 11 | const nodeF = new BinaryTreeNode('F'); 12 | const nodeG = new BinaryTreeNode('G'); 13 | 14 | nodeA.setLeft(nodeB).setRight(nodeC); 15 | nodeB.setLeft(nodeD).setRight(nodeE); 16 | nodeC.setLeft(nodeF).setRight(nodeG); 17 | 18 | // In-order traversing. 19 | expect(nodeA.toString()).toBe('D,B,E,A,F,C,G'); 20 | 21 | const enterNodeCallback = jest.fn(); 22 | const leaveNodeCallback = jest.fn(); 23 | 24 | // Traverse tree without callbacks first to check default ones. 25 | breadthFirstSearch(nodeA); 26 | 27 | // Traverse tree with callbacks. 28 | breadthFirstSearch(nodeA, { 29 | enterNode: enterNodeCallback, 30 | leaveNode: leaveNodeCallback, 31 | }); 32 | 33 | expect(enterNodeCallback).toHaveBeenCalledTimes(7); 34 | expect(leaveNodeCallback).toHaveBeenCalledTimes(7); 35 | 36 | // Check node entering. 37 | expect(enterNodeCallback.mock.calls[0][0].value).toEqual('A'); 38 | expect(enterNodeCallback.mock.calls[1][0].value).toEqual('B'); 39 | expect(enterNodeCallback.mock.calls[2][0].value).toEqual('C'); 40 | expect(enterNodeCallback.mock.calls[3][0].value).toEqual('D'); 41 | expect(enterNodeCallback.mock.calls[4][0].value).toEqual('E'); 42 | expect(enterNodeCallback.mock.calls[5][0].value).toEqual('F'); 43 | expect(enterNodeCallback.mock.calls[6][0].value).toEqual('G'); 44 | 45 | // Check node leaving. 46 | expect(leaveNodeCallback.mock.calls[0][0].value).toEqual('A'); 47 | expect(leaveNodeCallback.mock.calls[1][0].value).toEqual('B'); 48 | expect(leaveNodeCallback.mock.calls[2][0].value).toEqual('C'); 49 | expect(leaveNodeCallback.mock.calls[3][0].value).toEqual('D'); 50 | expect(leaveNodeCallback.mock.calls[4][0].value).toEqual('E'); 51 | expect(leaveNodeCallback.mock.calls[5][0].value).toEqual('F'); 52 | expect(leaveNodeCallback.mock.calls[6][0].value).toEqual('G'); 53 | }); 54 | 55 | it('allow users to redefine node visiting logic', () => { 56 | const nodeA = new BinaryTreeNode('A'); 57 | const nodeB = new BinaryTreeNode('B'); 58 | const nodeC = new BinaryTreeNode('C'); 59 | const nodeD = new BinaryTreeNode('D'); 60 | const nodeE = new BinaryTreeNode('E'); 61 | const nodeF = new BinaryTreeNode('F'); 62 | const nodeG = new BinaryTreeNode('G'); 63 | 64 | nodeA.setLeft(nodeB).setRight(nodeC); 65 | nodeB.setLeft(nodeD).setRight(nodeE); 66 | nodeC.setLeft(nodeF).setRight(nodeG); 67 | 68 | // In-order traversing. 69 | expect(nodeA.toString()).toBe('D,B,E,A,F,C,G'); 70 | 71 | const enterNodeCallback = jest.fn(); 72 | const leaveNodeCallback = jest.fn(); 73 | 74 | // Traverse tree without callbacks first to check default ones. 75 | breadthFirstSearch(nodeA); 76 | 77 | // Traverse tree with callbacks. 78 | breadthFirstSearch(nodeA, { 79 | allowTraversal: (node, child) => { 80 | // Forbid traversing left half of the tree. 81 | return child.value !== 'B'; 82 | }, 83 | enterNode: enterNodeCallback, 84 | leaveNode: leaveNodeCallback, 85 | }); 86 | 87 | expect(enterNodeCallback).toHaveBeenCalledTimes(4); 88 | expect(leaveNodeCallback).toHaveBeenCalledTimes(4); 89 | 90 | // Check node entering. 91 | expect(enterNodeCallback.mock.calls[0][0].value).toEqual('A'); 92 | expect(enterNodeCallback.mock.calls[1][0].value).toEqual('C'); 93 | expect(enterNodeCallback.mock.calls[2][0].value).toEqual('F'); 94 | expect(enterNodeCallback.mock.calls[3][0].value).toEqual('G'); 95 | 96 | // Check node leaving. 97 | expect(leaveNodeCallback.mock.calls[0][0].value).toEqual('A'); 98 | expect(leaveNodeCallback.mock.calls[1][0].value).toEqual('C'); 99 | expect(leaveNodeCallback.mock.calls[2][0].value).toEqual('F'); 100 | expect(leaveNodeCallback.mock.calls[3][0].value).toEqual('G'); 101 | }); 102 | }); 103 | -------------------------------------------------------------------------------- /skeletons/algorithms/search/breadth-first-search/breadthFirstSearch.js: -------------------------------------------------------------------------------- 1 | import Queue from '../../../data-structures/queue/Queue'; 2 | 3 | /** 4 | * @typedef {Object} Callbacks 5 | * @property {function(node: BinaryTreeNode, child: BinaryTreeNode): boolean} allowTraversal - 6 | * Determines whether DFS should traverse from the node to its child. 7 | * @property {function(node: BinaryTreeNode)} enterNode - Called when DFS enters the node. 8 | * @property {function(node: BinaryTreeNode)} leaveNode - Called when DFS leaves the node. 9 | */ 10 | 11 | /** 12 | * @param {Callbacks} [callbacks] 13 | * @returns {Callbacks} 14 | */ 15 | function initCallbacks(callbacks = {}) { 16 | const initiatedCallback = callbacks; 17 | 18 | const stubCallback = () => {}; 19 | const defaultAllowTraversal = () => true; 20 | 21 | initiatedCallback.allowTraversal = callbacks.allowTraversal || defaultAllowTraversal; 22 | initiatedCallback.enterNode = callbacks.enterNode || stubCallback; 23 | initiatedCallback.leaveNode = callbacks.leaveNode || stubCallback; 24 | 25 | return initiatedCallback; 26 | } 27 | 28 | /** 29 | * @param {BinaryTreeNode} rootNode 30 | * @param {Callbacks} [originalCallbacks] 31 | */ 32 | export default function breadthFirstSearch(rootNode, originalCallbacks) { 33 | const callbacks = initCallbacks(originalCallbacks); 34 | 35 | // WRITE YOUR CODE HERE 36 | } 37 | -------------------------------------------------------------------------------- /skeletons/algorithms/search/depth-first-search/README.md: -------------------------------------------------------------------------------- 1 | # Depth-First Search (DFS) 2 | 3 | Depth-first search (DFS) is an algorithm for traversing or 4 | searching tree or graph data structures. One starts at 5 | the root (selecting some arbitrary node as the root in 6 | the case of a graph) and explores as far as possible 7 | along each branch before backtracking. 8 | 9 | ![Algorithm Visualization](https://upload.wikimedia.org/wikipedia/commons/7/7f/Depth-First-Search.gif) 10 | 11 | ## References 12 | 13 | - [Wikipedia](https://en.wikipedia.org/wiki/Depth-first_search) 14 | - [Tree Traversals (Inorder, Preorder and Postorder)](https://www.geeksforgeeks.org/tree-traversals-inorder-preorder-and-postorder/) 15 | - [BFS vs DFS](https://www.geeksforgeeks.org/bfs-vs-dfs-binary-tree/) 16 | -------------------------------------------------------------------------------- /skeletons/algorithms/search/depth-first-search/__test__/depthFirstSearch.test.js: -------------------------------------------------------------------------------- 1 | import BinaryTreeNode from '../../../../data-structures/tree/BinaryTreeNode'; 2 | import depthFirstSearch from '../depthFirstSearch'; 3 | 4 | describe('depthFirstSearch', () => { 5 | it('should perform DFS operation on tree', () => { 6 | const nodeA = new BinaryTreeNode('A'); 7 | const nodeB = new BinaryTreeNode('B'); 8 | const nodeC = new BinaryTreeNode('C'); 9 | const nodeD = new BinaryTreeNode('D'); 10 | const nodeE = new BinaryTreeNode('E'); 11 | const nodeF = new BinaryTreeNode('F'); 12 | const nodeG = new BinaryTreeNode('G'); 13 | 14 | nodeA.setLeft(nodeB).setRight(nodeC); 15 | nodeB.setLeft(nodeD).setRight(nodeE); 16 | nodeC.setLeft(nodeF).setRight(nodeG); 17 | 18 | // In-order traversing. 19 | expect(nodeA.toString()).toBe('D,B,E,A,F,C,G'); 20 | 21 | const enterNodeCallback = jest.fn(); 22 | const leaveNodeCallback = jest.fn(); 23 | 24 | // Traverse tree without callbacks first to check default ones. 25 | depthFirstSearch(nodeA); 26 | 27 | // Traverse tree with callbacks. 28 | depthFirstSearch(nodeA, { 29 | enterNode: enterNodeCallback, 30 | leaveNode: leaveNodeCallback, 31 | }); 32 | 33 | expect(enterNodeCallback).toHaveBeenCalledTimes(7); 34 | expect(leaveNodeCallback).toHaveBeenCalledTimes(7); 35 | 36 | // Check node entering. 37 | expect(enterNodeCallback.mock.calls[0][0].value).toEqual('A'); 38 | expect(enterNodeCallback.mock.calls[1][0].value).toEqual('B'); 39 | expect(enterNodeCallback.mock.calls[2][0].value).toEqual('D'); 40 | expect(enterNodeCallback.mock.calls[3][0].value).toEqual('E'); 41 | expect(enterNodeCallback.mock.calls[4][0].value).toEqual('C'); 42 | expect(enterNodeCallback.mock.calls[5][0].value).toEqual('F'); 43 | expect(enterNodeCallback.mock.calls[6][0].value).toEqual('G'); 44 | 45 | // Check node leaving. 46 | expect(leaveNodeCallback.mock.calls[0][0].value).toEqual('D'); 47 | expect(leaveNodeCallback.mock.calls[1][0].value).toEqual('E'); 48 | expect(leaveNodeCallback.mock.calls[2][0].value).toEqual('B'); 49 | expect(leaveNodeCallback.mock.calls[3][0].value).toEqual('F'); 50 | expect(leaveNodeCallback.mock.calls[4][0].value).toEqual('G'); 51 | expect(leaveNodeCallback.mock.calls[5][0].value).toEqual('C'); 52 | expect(leaveNodeCallback.mock.calls[6][0].value).toEqual('A'); 53 | }); 54 | 55 | it('allow users to redefine node visiting logic', () => { 56 | const nodeA = new BinaryTreeNode('A'); 57 | const nodeB = new BinaryTreeNode('B'); 58 | const nodeC = new BinaryTreeNode('C'); 59 | const nodeD = new BinaryTreeNode('D'); 60 | const nodeE = new BinaryTreeNode('E'); 61 | const nodeF = new BinaryTreeNode('F'); 62 | const nodeG = new BinaryTreeNode('G'); 63 | 64 | nodeA.setLeft(nodeB).setRight(nodeC); 65 | nodeB.setLeft(nodeD).setRight(nodeE); 66 | nodeC.setLeft(nodeF).setRight(nodeG); 67 | 68 | // In-order traversing. 69 | expect(nodeA.toString()).toBe('D,B,E,A,F,C,G'); 70 | 71 | const enterNodeCallback = jest.fn(); 72 | const leaveNodeCallback = jest.fn(); 73 | 74 | // Traverse tree without callbacks first to check default ones. 75 | depthFirstSearch(nodeA); 76 | 77 | // Traverse tree with callbacks. 78 | depthFirstSearch(nodeA, { 79 | allowTraversal: (node, child) => { 80 | // Forbid traversing left part of the tree. 81 | return child.value !== 'B'; 82 | }, 83 | enterNode: enterNodeCallback, 84 | leaveNode: leaveNodeCallback, 85 | }); 86 | 87 | expect(enterNodeCallback).toHaveBeenCalledTimes(4); 88 | expect(leaveNodeCallback).toHaveBeenCalledTimes(4); 89 | 90 | // Check node entering. 91 | expect(enterNodeCallback.mock.calls[0][0].value).toEqual('A'); 92 | expect(enterNodeCallback.mock.calls[1][0].value).toEqual('C'); 93 | expect(enterNodeCallback.mock.calls[2][0].value).toEqual('F'); 94 | expect(enterNodeCallback.mock.calls[3][0].value).toEqual('G'); 95 | 96 | // Check node leaving. 97 | expect(leaveNodeCallback.mock.calls[0][0].value).toEqual('F'); 98 | expect(leaveNodeCallback.mock.calls[1][0].value).toEqual('G'); 99 | expect(leaveNodeCallback.mock.calls[2][0].value).toEqual('C'); 100 | expect(leaveNodeCallback.mock.calls[3][0].value).toEqual('A'); 101 | }); 102 | }); 103 | -------------------------------------------------------------------------------- /skeletons/algorithms/search/depth-first-search/depthFirstSearch.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @typedef {Object} Callbacks 3 | * @property {function(node: BinaryTreeNode, child: BinaryTreeNode): boolean} allowTraversal - 4 | * Determines whether DFS should traverse from the node to its child. 5 | * @property {function(node: BinaryTreeNode)} enterNode - Called when DFS enters the node. 6 | * @property {function(node: BinaryTreeNode)} leaveNode - Called when DFS leaves the node. 7 | */ 8 | 9 | /** 10 | * @param {Callbacks} [callbacks] 11 | * @returns {Callbacks} 12 | */ 13 | function initCallbacks(callbacks = {}) { 14 | const initiatedCallback = callbacks; 15 | 16 | const stubCallback = () => {}; 17 | const defaultAllowTraversal = () => true; 18 | 19 | initiatedCallback.allowTraversal = callbacks.allowTraversal || defaultAllowTraversal; 20 | initiatedCallback.enterNode = callbacks.enterNode || stubCallback; 21 | initiatedCallback.leaveNode = callbacks.leaveNode || stubCallback; 22 | 23 | return initiatedCallback; 24 | } 25 | 26 | /** 27 | * @param {BinaryTreeNode} node 28 | * @param {Callbacks} callbacks 29 | */ 30 | export function depthFirstSearchRecursive(node, callbacks) { 31 | // WRITE YOUR CODE HERE 32 | } 33 | 34 | /** 35 | * @param {BinaryTreeNode} rootNode 36 | * @param {Callbacks} [callbacks] 37 | */ 38 | export default function depthFirstSearch(rootNode, callbacks) { 39 | depthFirstSearchRecursive(rootNode, initCallbacks(callbacks)); 40 | } 41 | -------------------------------------------------------------------------------- /skeletons/algorithms/search/linear-search/README.md: -------------------------------------------------------------------------------- 1 | # Linear Search 2 | In computer science, linear search or sequential search is a 3 | method for finding a target value within a list. It sequentially 4 | checks each element of the list for the target value until a 5 | match is found or until all the elements have been searched. 6 | Linear search runs in at worst linear time and makes at most `n` 7 | comparisons, where `n` is the length of the list. 8 | 9 | ![Linear Search](https://www.tutorialspoint.com/data_structures_algorithms/images/linear_search.gif) 10 | 11 | ## References 12 | - [Wikipedia](https://en.wikipedia.org/wiki/Linear_search) 13 | - [TutorialsPoint](https://www.tutorialspoint.com/data_structures_algorithms/linear_search_algorithm.htm) 14 | - [Youtube](https://www.youtube.com/watch?v=SGU9duLE30w) 15 | -------------------------------------------------------------------------------- /skeletons/algorithms/search/linear-search/__test__/linearSearch.test.js: -------------------------------------------------------------------------------- 1 | import linearSearch from '../linearSearch'; 2 | 3 | describe('linearSearch', () => { 4 | it('should search all numbers in array', () => { 5 | const array = [1, 2, 4, 6, 2]; 6 | 7 | expect(linearSearch(array, 10)).toEqual([]); 8 | expect(linearSearch(array, 1)).toEqual([0]); 9 | expect(linearSearch(array, 2)).toEqual([1, 4]); 10 | }); 11 | 12 | it('should search all strings in array', () => { 13 | const array = ['a', 'b', 'a']; 14 | 15 | expect(linearSearch(array, 'c')).toEqual([]); 16 | expect(linearSearch(array, 'b')).toEqual([1]); 17 | expect(linearSearch(array, 'a')).toEqual([0, 2]); 18 | }); 19 | 20 | it('should search through objects as well', () => { 21 | const comparatorCallback = (a, b) => { 22 | if (a.key === b.key) { 23 | return 0; 24 | } 25 | 26 | return a.key <= b.key ? -1 : 1; 27 | }; 28 | 29 | const array = [ 30 | { key: 5 }, 31 | { key: 6 }, 32 | { key: 7 }, 33 | { key: 6 }, 34 | ]; 35 | 36 | expect(linearSearch(array, { key: 10 }, comparatorCallback)).toEqual([]); 37 | expect(linearSearch(array, { key: 5 }, comparatorCallback)).toEqual([0]); 38 | expect(linearSearch(array, { key: 6 }, comparatorCallback)).toEqual([1, 3]); 39 | }); 40 | }); 41 | -------------------------------------------------------------------------------- /skeletons/algorithms/search/linear-search/linearSearch.js: -------------------------------------------------------------------------------- 1 | import Comparator from '../../../utils/comparator/Comparator'; 2 | 3 | /** 4 | * @param {*[]} array 5 | * @param {*} seekElement 6 | * @param {function(a, b)} [comparatorCallback] 7 | * @return {number[]} 8 | */ 9 | export default function linearSearch(array, seekElement, comparatorCallback) { 10 | const comparator = new Comparator(comparatorCallback); 11 | const foundIndices = []; 12 | 13 | array.forEach((element, index) => { 14 | if (comparator.equal(element, seekElement)) { 15 | foundIndices.push(index); 16 | } 17 | }); 18 | 19 | return foundIndices; 20 | } 21 | -------------------------------------------------------------------------------- /skeletons/algorithms/sorting/Sort.js: -------------------------------------------------------------------------------- 1 | import Comparator from '../../utils/comparator/Comparator'; 2 | 3 | /** 4 | * @typedef {Object} SorterCallbacks 5 | * @property {function(a: *, b: *)} compareCallback - If provided then all elements comparisons 6 | * will be done through this callback. 7 | * @property {function(a: *)} visitingCallback - If provided it will be called each time the sorting 8 | * function is visiting the next element. 9 | */ 10 | 11 | export default class Sort { 12 | constructor(originalCallbacks) { 13 | this.callbacks = Sort.initSortingCallbacks(originalCallbacks); 14 | this.comparator = new Comparator(this.callbacks.compareCallback); 15 | } 16 | 17 | /** 18 | * @param {SorterCallbacks} originalCallbacks 19 | * @returns {SorterCallbacks} 20 | */ 21 | static initSortingCallbacks(originalCallbacks) { 22 | const callbacks = originalCallbacks || {}; 23 | const stubCallback = () => {}; 24 | 25 | callbacks.compareCallback = callbacks.compareCallback || undefined; 26 | callbacks.visitingCallback = callbacks.visitingCallback || stubCallback; 27 | 28 | return callbacks; 29 | } 30 | 31 | sort() { 32 | throw new Error('sort method must be implemented'); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /skeletons/algorithms/sorting/SortTester.js: -------------------------------------------------------------------------------- 1 | export const sortedArr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20]; 2 | export const reverseArr = [20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1]; 3 | export const notSortedArr = [15, 8, 5, 12, 10, 1, 16, 9, 11, 7, 20, 3, 2, 6, 17, 18, 4, 13, 14, 19]; 4 | export const equalArr = [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]; 5 | export const negativeArr = [-1, 0, 5, -10, 20, 13, -7, 3, 2, -3]; 6 | export const negativeArrSorted = [-10, -7, -3, -1, 0, 2, 3, 5, 13, 20]; 7 | 8 | export class SortTester { 9 | static testSort(SortingClass) { 10 | const sorter = new SortingClass(); 11 | 12 | expect(sorter.sort([])).toEqual([]); 13 | expect(sorter.sort([1])).toEqual([1]); 14 | expect(sorter.sort([1, 2])).toEqual([1, 2]); 15 | expect(sorter.sort([2, 1])).toEqual([1, 2]); 16 | expect(sorter.sort([3, 4, 2, 1, 0, 0, 4, 3, 4, 2])).toEqual([0, 0, 1, 2, 2, 3, 3, 4, 4, 4]); 17 | expect(sorter.sort(sortedArr)).toEqual(sortedArr); 18 | expect(sorter.sort(reverseArr)).toEqual(sortedArr); 19 | expect(sorter.sort(notSortedArr)).toEqual(sortedArr); 20 | expect(sorter.sort(equalArr)).toEqual(equalArr); 21 | } 22 | 23 | static testNegativeNumbersSort(SortingClass) { 24 | const sorter = new SortingClass(); 25 | expect(sorter.sort(negativeArr)).toEqual(negativeArrSorted); 26 | } 27 | 28 | static testSortWithCustomComparator(SortingClass) { 29 | const callbacks = { 30 | compareCallback: (a, b) => { 31 | if (a.length === b.length) { 32 | return 0; 33 | } 34 | return a.length < b.length ? -1 : 1; 35 | }, 36 | }; 37 | 38 | const sorter = new SortingClass(callbacks); 39 | 40 | expect(sorter.sort([''])).toEqual(['']); 41 | expect(sorter.sort(['a'])).toEqual(['a']); 42 | expect(sorter.sort(['aa', 'a'])).toEqual(['a', 'aa']); 43 | expect(sorter.sort(['aa', 'q', 'bbbb', 'ccc'])).toEqual(['q', 'aa', 'ccc', 'bbbb']); 44 | expect(sorter.sort(['aa', 'aa'])).toEqual(['aa', 'aa']); 45 | } 46 | 47 | static testSortStability(SortingClass) { 48 | const callbacks = { 49 | compareCallback: (a, b) => { 50 | if (a.length === b.length) { 51 | return 0; 52 | } 53 | return a.length < b.length ? -1 : 1; 54 | }, 55 | }; 56 | 57 | const sorter = new SortingClass(callbacks); 58 | 59 | expect(sorter.sort(['bb', 'aa', 'c'])).toEqual(['c', 'bb', 'aa']); 60 | expect(sorter.sort(['aa', 'q', 'a', 'bbbb', 'ccc'])).toEqual(['q', 'a', 'aa', 'ccc', 'bbbb']); 61 | } 62 | 63 | static testAlgorithmTimeComplexity(SortingClass, arrayToBeSorted, numberOfVisits) { 64 | const visitingCallback = jest.fn(); 65 | const callbacks = { visitingCallback }; 66 | const sorter = new SortingClass(callbacks); 67 | 68 | sorter.sort(arrayToBeSorted); 69 | 70 | expect(visitingCallback).toHaveBeenCalledTimes(numberOfVisits); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /skeletons/algorithms/sorting/__test__/Sort.test.js: -------------------------------------------------------------------------------- 1 | import Sort from '../Sort'; 2 | 3 | describe('Sort', () => { 4 | it('should throw an error when trying to call Sort.sort() method directly', () => { 5 | function doForbiddenSort() { 6 | const sorter = new Sort(); 7 | sorter.sort(); 8 | } 9 | 10 | expect(doForbiddenSort).toThrow(); 11 | }); 12 | }); 13 | -------------------------------------------------------------------------------- /skeletons/algorithms/sorting/bubble-sort/BubbleSort.js: -------------------------------------------------------------------------------- 1 | import Sort from '../Sort'; 2 | 3 | export default class BubbleSort extends Sort { 4 | sort(originalArray) { 5 | // WRITE YOUR CODE HERE 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /skeletons/algorithms/sorting/bubble-sort/README.md: -------------------------------------------------------------------------------- 1 | # Bubble Sort 2 | 3 | Bubble sort, sometimes referred to as sinking sort, is a 4 | simple sorting algorithm that repeatedly steps through 5 | the list to be sorted, compares each pair of adjacent 6 | items and swaps them if they are in the wrong order. 7 | The pass through the list is repeated until no swaps 8 | are needed, which indicates that the list is sorted. 9 | 10 | ![Algorithm Visualization](https://upload.wikimedia.org/wikipedia/commons/c/c8/Bubble-sort-example-300px.gif) 11 | 12 | ## Complexity 13 | 14 | | Name | Best | Average | Worst | Memory | Stable | Comments | 15 | | --------------------- | :-------------: | :-----------------: | :-----------------: | :-------: | :-------: | :-------- | 16 | | **Bubble sort** | n | n2 | n2 | 1 | Yes | | 17 | 18 | ## References 19 | 20 | - [Wikipedia](https://en.wikipedia.org/wiki/Bubble_sort) 21 | - [YouTube](https://www.youtube.com/watch?v=6Gv8vg0kcHc&index=27&t=0s&list=PLLXdhg_r2hKA7DPDsunoDZ-Z769jWn4R8) 22 | -------------------------------------------------------------------------------- /skeletons/algorithms/sorting/bubble-sort/__test__/BubbleSort.test.js: -------------------------------------------------------------------------------- 1 | import BubbleSort from '../BubbleSort'; 2 | import { 3 | equalArr, 4 | notSortedArr, 5 | reverseArr, 6 | sortedArr, 7 | SortTester, 8 | } from '../../SortTester'; 9 | 10 | // Complexity constants. 11 | const SORTED_ARRAY_VISITING_COUNT = 20; 12 | const NOT_SORTED_ARRAY_VISITING_COUNT = 189; 13 | const REVERSE_SORTED_ARRAY_VISITING_COUNT = 209; 14 | const EQUAL_ARRAY_VISITING_COUNT = 20; 15 | 16 | describe('BubbleSort', () => { 17 | it('should sort array', () => { 18 | SortTester.testSort(BubbleSort); 19 | }); 20 | 21 | it('should sort array with custom comparator', () => { 22 | SortTester.testSortWithCustomComparator(BubbleSort); 23 | }); 24 | 25 | it('should do stable sorting', () => { 26 | SortTester.testSortStability(BubbleSort); 27 | }); 28 | 29 | it('should sort negative numbers', () => { 30 | SortTester.testNegativeNumbersSort(BubbleSort); 31 | }); 32 | 33 | it('should visit EQUAL array element specified number of times', () => { 34 | SortTester.testAlgorithmTimeComplexity( 35 | BubbleSort, 36 | equalArr, 37 | EQUAL_ARRAY_VISITING_COUNT, 38 | ); 39 | }); 40 | 41 | it('should visit SORTED array element specified number of times', () => { 42 | SortTester.testAlgorithmTimeComplexity( 43 | BubbleSort, 44 | sortedArr, 45 | SORTED_ARRAY_VISITING_COUNT, 46 | ); 47 | }); 48 | 49 | it('should visit NOT SORTED array element specified number of times', () => { 50 | SortTester.testAlgorithmTimeComplexity( 51 | BubbleSort, 52 | notSortedArr, 53 | NOT_SORTED_ARRAY_VISITING_COUNT, 54 | ); 55 | }); 56 | 57 | it('should visit REVERSE SORTED array element specified number of times', () => { 58 | SortTester.testAlgorithmTimeComplexity( 59 | BubbleSort, 60 | reverseArr, 61 | REVERSE_SORTED_ARRAY_VISITING_COUNT, 62 | ); 63 | }); 64 | }); 65 | -------------------------------------------------------------------------------- /skeletons/algorithms/sorting/insertion-sort/InsertionSort.js: -------------------------------------------------------------------------------- 1 | import Sort from '../Sort'; 2 | 3 | export default class InsertionSort extends Sort { 4 | sort(originalArray) { 5 | // WRITE YOUR CODE HERE 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /skeletons/algorithms/sorting/insertion-sort/README.md: -------------------------------------------------------------------------------- 1 | # Insertion Sort 2 | 3 | Insertion sort is a simple sorting algorithm that builds 4 | the final sorted array (or list) one item at a time. 5 | It is much less efficient on large lists than more 6 | advanced algorithms such as quicksort, heapsort, or merge 7 | sort. 8 | 9 | ![Algorithm Visualization](https://upload.wikimedia.org/wikipedia/commons/4/42/Insertion_sort.gif) 10 | 11 | ![Algorithm Visualization](https://upload.wikimedia.org/wikipedia/commons/0/0f/Insertion-sort-example-300px.gif) 12 | 13 | ## Complexity 14 | 15 | | Name | Best | Average | Worst | Memory | Stable | Comments | 16 | | --------------------- | :-------------: | :-----------------: | :-----------------: | :-------: | :-------: | :-------- | 17 | | **Insertion sort** | n | n2 | n2 | 1 | Yes | | 18 | 19 | ## References 20 | 21 | [Wikipedia](https://en.wikipedia.org/wiki/Insertion_sort) 22 | -------------------------------------------------------------------------------- /skeletons/algorithms/sorting/insertion-sort/__test__/InsertionSort.test.js: -------------------------------------------------------------------------------- 1 | import InsertionSort from '../InsertionSort'; 2 | import { 3 | equalArr, 4 | notSortedArr, 5 | reverseArr, 6 | sortedArr, 7 | SortTester, 8 | } from '../../SortTester'; 9 | 10 | // Complexity constants. 11 | const SORTED_ARRAY_VISITING_COUNT = 20; 12 | const NOT_SORTED_ARRAY_VISITING_COUNT = 101; 13 | const REVERSE_SORTED_ARRAY_VISITING_COUNT = 210; 14 | const EQUAL_ARRAY_VISITING_COUNT = 20; 15 | 16 | describe('InsertionSort', () => { 17 | it('should sort array', () => { 18 | SortTester.testSort(InsertionSort); 19 | }); 20 | 21 | it('should sort array with custom comparator', () => { 22 | SortTester.testSortWithCustomComparator(InsertionSort); 23 | }); 24 | 25 | it('should do stable sorting', () => { 26 | SortTester.testSortStability(InsertionSort); 27 | }); 28 | 29 | it('should sort negative numbers', () => { 30 | SortTester.testNegativeNumbersSort(InsertionSort); 31 | }); 32 | 33 | it('should visit EQUAL array element specified number of times', () => { 34 | SortTester.testAlgorithmTimeComplexity( 35 | InsertionSort, 36 | equalArr, 37 | EQUAL_ARRAY_VISITING_COUNT, 38 | ); 39 | }); 40 | 41 | it('should visit SORTED array element specified number of times', () => { 42 | SortTester.testAlgorithmTimeComplexity( 43 | InsertionSort, 44 | sortedArr, 45 | SORTED_ARRAY_VISITING_COUNT, 46 | ); 47 | }); 48 | 49 | it('should visit NOT SORTED array element specified number of times', () => { 50 | SortTester.testAlgorithmTimeComplexity( 51 | InsertionSort, 52 | notSortedArr, 53 | NOT_SORTED_ARRAY_VISITING_COUNT, 54 | ); 55 | }); 56 | 57 | it('should visit REVERSE SORTED array element specified number of times', () => { 58 | SortTester.testAlgorithmTimeComplexity( 59 | InsertionSort, 60 | reverseArr, 61 | REVERSE_SORTED_ARRAY_VISITING_COUNT, 62 | ); 63 | }); 64 | }); 65 | -------------------------------------------------------------------------------- /skeletons/algorithms/sorting/merge-sort/MergeSort.js: -------------------------------------------------------------------------------- 1 | import Sort from '../Sort'; 2 | 3 | export default class MergeSort extends Sort { 4 | sort(originalArray) { 5 | // WRITE YOUR CODE HERE 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /skeletons/algorithms/sorting/merge-sort/README.md: -------------------------------------------------------------------------------- 1 | # Merge Sort 2 | 3 | In computer science, merge sort (also commonly spelled 4 | mergesort) is an efficient, general-purpose, 5 | comparison-based sorting algorithm. Most implementations 6 | produce a stable sort, which means that the implementation 7 | preserves the input order of equal elements in the sorted 8 | output. Mergesort is a divide and conquer algorithm that 9 | was invented by John von Neumann in 1945. 10 | 11 | An example of merge sort. First divide the list into 12 | the smallest unit (1 element), then compare each 13 | element with the adjacent list to sort and merge the 14 | two adjacent lists. Finally all the elements are sorted 15 | and merged. 16 | 17 | ![Merge Sort](https://upload.wikimedia.org/wikipedia/commons/c/cc/Merge-sort-example-300px.gif) 18 | 19 | A recursive merge sort algorithm used to sort an array of 7 20 | integer values. These are the steps a human would take to 21 | emulate merge sort (top-down). 22 | 23 | ![Merge Sort](https://upload.wikimedia.org/wikipedia/commons/e/e6/Merge_sort_algorithm_diagram.svg) 24 | 25 | ## Complexity 26 | 27 | | Name | Best | Average | Worst | Memory | Stable | Comments | 28 | | --------------------- | :-------------: | :-----------------: | :-----------------: | :-------: | :-------: | :-------- | 29 | | **Merge sort** | n log(n) | n log(n) | n log(n) | n | Yes | | 30 | 31 | ## References 32 | 33 | - [Wikipedia](https://en.wikipedia.org/wiki/Merge_sort) 34 | - [YouTube](https://www.youtube.com/watch?v=KF2j-9iSf4Q&index=27&list=PLLXdhg_r2hKA7DPDsunoDZ-Z769jWn4R8) 35 | -------------------------------------------------------------------------------- /skeletons/algorithms/sorting/merge-sort/__test__/MergeSort.test.js: -------------------------------------------------------------------------------- 1 | import MergeSort from '../MergeSort'; 2 | import { 3 | equalArr, 4 | notSortedArr, 5 | reverseArr, 6 | sortedArr, 7 | SortTester, 8 | } from '../../SortTester'; 9 | 10 | // Complexity constants. 11 | const SORTED_ARRAY_VISITING_COUNT = 79; 12 | const NOT_SORTED_ARRAY_VISITING_COUNT = 102; 13 | const REVERSE_SORTED_ARRAY_VISITING_COUNT = 87; 14 | const EQUAL_ARRAY_VISITING_COUNT = 79; 15 | 16 | describe('MergeSort', () => { 17 | it('should sort array', () => { 18 | SortTester.testSort(MergeSort); 19 | }); 20 | 21 | it('should sort array with custom comparator', () => { 22 | SortTester.testSortWithCustomComparator(MergeSort); 23 | }); 24 | 25 | it('should do stable sorting', () => { 26 | SortTester.testSortStability(MergeSort); 27 | }); 28 | 29 | it('should sort negative numbers', () => { 30 | SortTester.testNegativeNumbersSort(MergeSort); 31 | }); 32 | 33 | it('should visit EQUAL array element specified number of times', () => { 34 | SortTester.testAlgorithmTimeComplexity( 35 | MergeSort, 36 | equalArr, 37 | EQUAL_ARRAY_VISITING_COUNT, 38 | ); 39 | }); 40 | 41 | it('should visit SORTED array element specified number of times', () => { 42 | SortTester.testAlgorithmTimeComplexity( 43 | MergeSort, 44 | sortedArr, 45 | SORTED_ARRAY_VISITING_COUNT, 46 | ); 47 | }); 48 | 49 | it('should visit NOT SORTED array element specified number of times', () => { 50 | SortTester.testAlgorithmTimeComplexity( 51 | MergeSort, 52 | notSortedArr, 53 | NOT_SORTED_ARRAY_VISITING_COUNT, 54 | ); 55 | }); 56 | 57 | it('should visit REVERSE SORTED array element specified number of times', () => { 58 | SortTester.testAlgorithmTimeComplexity( 59 | MergeSort, 60 | reverseArr, 61 | REVERSE_SORTED_ARRAY_VISITING_COUNT, 62 | ); 63 | }); 64 | }); 65 | -------------------------------------------------------------------------------- /skeletons/algorithms/sorting/quick-sort/QuickSort.js: -------------------------------------------------------------------------------- 1 | import Sort from '../Sort'; 2 | 3 | export default class QuickSort extends Sort { 4 | /** 5 | * @param {*[]} originalArray 6 | * @return {*[]} 7 | */ 8 | sort(originalArray) { 9 | // WRITE YOUR CODE HERE 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /skeletons/algorithms/sorting/quick-sort/QuickSortInPlace.js: -------------------------------------------------------------------------------- 1 | import Sort from '../Sort'; 2 | 3 | export default class QuickSortInPlace extends Sort { 4 | /** Sorting in place avoids unnecessary use of additional memory, but modifies input array. 5 | * 6 | * This process is difficult to describe, but much clearer with a visualization: 7 | * @see: http://www.algomation.com/algorithm/quick-sort-visualization 8 | * 9 | * @param {*[]} originalArray 10 | * @param {number} inputLowIndex 11 | * @param {number} inputHighIndex 12 | * @return {*[]} 13 | */ 14 | sort(originalArray, inputLowIndex, inputHighIndex) { 15 | // WRITE YOUR CODE HERE 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /skeletons/algorithms/sorting/quick-sort/README.md: -------------------------------------------------------------------------------- 1 | # Quicksort 2 | 3 | Quicksort is a divide and conquer algorithm. 4 | Quicksort first divides a large array into two smaller 5 | sub-arrays: the low elements and the high elements. 6 | Quicksort can then recursively sort the sub-arrays 7 | 8 | The steps are: 9 | 10 | 1. Pick an element, called a pivot, from the array. 11 | 2. Partitioning: reorder the array so that all elements with 12 | values less than the pivot come before the pivot, while all 13 | elements with values greater than the pivot come after it 14 | (equal values can go either way). After this partitioning, 15 | the pivot is in its final position. This is called the 16 | partition operation. 17 | 3. Recursively apply the above steps to the sub-array of 18 | elements with smaller values and separately to the 19 | sub-array of elements with greater values. 20 | 21 | Animated visualization of the quicksort algorithm. 22 | The horizontal lines are pivot values. 23 | 24 | ![Quicksort](https://upload.wikimedia.org/wikipedia/commons/6/6a/Sorting_quicksort_anim.gif) 25 | 26 | ## Complexity 27 | 28 | | Name | Best | Average | Worst | Memory | Stable | Comments | 29 | | --------------------- | :-------------: | :-----------------: | :-----------------: | :-------: | :-------: | :-------- | 30 | | **Quick sort** | n log(n) | n log(n) | n2 | log(n) | No | | 31 | 32 | ## References 33 | 34 | - [Wikipedia](https://en.wikipedia.org/wiki/Quicksort) 35 | - [YouTube](https://www.youtube.com/watch?v=SLauY6PpjW4&index=28&list=PLLXdhg_r2hKA7DPDsunoDZ-Z769jWn4R8) 36 | -------------------------------------------------------------------------------- /skeletons/algorithms/sorting/quick-sort/__test__/QuickSort.test.js: -------------------------------------------------------------------------------- 1 | import QuickSort from '../QuickSort'; 2 | import { 3 | equalArr, 4 | notSortedArr, 5 | reverseArr, 6 | sortedArr, 7 | SortTester, 8 | } from '../../SortTester'; 9 | 10 | // Complexity constants. 11 | const SORTED_ARRAY_VISITING_COUNT = 190; 12 | const NOT_SORTED_ARRAY_VISITING_COUNT = 62; 13 | const REVERSE_SORTED_ARRAY_VISITING_COUNT = 190; 14 | const EQUAL_ARRAY_VISITING_COUNT = 19; 15 | 16 | describe('QuickSort', () => { 17 | it('should sort array', () => { 18 | SortTester.testSort(QuickSort); 19 | }); 20 | 21 | it('should sort array with custom comparator', () => { 22 | SortTester.testSortWithCustomComparator(QuickSort); 23 | }); 24 | 25 | it('should do stable sorting', () => { 26 | SortTester.testSortStability(QuickSort); 27 | }); 28 | 29 | it('should sort negative numbers', () => { 30 | SortTester.testNegativeNumbersSort(QuickSort); 31 | }); 32 | 33 | it('should visit EQUAL array element specified number of times', () => { 34 | SortTester.testAlgorithmTimeComplexity( 35 | QuickSort, 36 | equalArr, 37 | EQUAL_ARRAY_VISITING_COUNT, 38 | ); 39 | }); 40 | 41 | it('should visit SORTED array element specified number of times', () => { 42 | SortTester.testAlgorithmTimeComplexity( 43 | QuickSort, 44 | sortedArr, 45 | SORTED_ARRAY_VISITING_COUNT, 46 | ); 47 | }); 48 | 49 | it('should visit NOT SORTED array element specified number of times', () => { 50 | SortTester.testAlgorithmTimeComplexity( 51 | QuickSort, 52 | notSortedArr, 53 | NOT_SORTED_ARRAY_VISITING_COUNT, 54 | ); 55 | }); 56 | 57 | it('should visit REVERSE SORTED array element specified number of times', () => { 58 | SortTester.testAlgorithmTimeComplexity( 59 | QuickSort, 60 | reverseArr, 61 | REVERSE_SORTED_ARRAY_VISITING_COUNT, 62 | ); 63 | }); 64 | }); 65 | -------------------------------------------------------------------------------- /skeletons/algorithms/sorting/quick-sort/__test__/QuickSortInPlace.test.js: -------------------------------------------------------------------------------- 1 | import QuickSortInPlace from '../QuickSortInPlace'; 2 | import { 3 | equalArr, 4 | notSortedArr, 5 | reverseArr, 6 | sortedArr, 7 | SortTester, 8 | } from '../../SortTester'; 9 | 10 | // Complexity constants. 11 | const SORTED_ARRAY_VISITING_COUNT = 19; 12 | const NOT_SORTED_ARRAY_VISITING_COUNT = 12; 13 | const REVERSE_SORTED_ARRAY_VISITING_COUNT = 19; 14 | const EQUAL_ARRAY_VISITING_COUNT = 19; 15 | 16 | describe('QuickSortInPlace', () => { 17 | it('should sort array', () => { 18 | SortTester.testSort(QuickSortInPlace); 19 | }); 20 | 21 | it('should sort array with custom comparator', () => { 22 | SortTester.testSortWithCustomComparator(QuickSortInPlace); 23 | }); 24 | 25 | it('should sort negative numbers', () => { 26 | SortTester.testNegativeNumbersSort(QuickSortInPlace); 27 | }); 28 | 29 | it('should visit EQUAL array element specified number of times', () => { 30 | SortTester.testAlgorithmTimeComplexity( 31 | QuickSortInPlace, 32 | equalArr, 33 | EQUAL_ARRAY_VISITING_COUNT, 34 | ); 35 | }); 36 | 37 | it('should visit SORTED array element specified number of times', () => { 38 | SortTester.testAlgorithmTimeComplexity( 39 | QuickSortInPlace, 40 | sortedArr, 41 | SORTED_ARRAY_VISITING_COUNT, 42 | ); 43 | }); 44 | 45 | it('should visit NOT SORTED array element specified number of times', () => { 46 | SortTester.testAlgorithmTimeComplexity( 47 | QuickSortInPlace, 48 | notSortedArr, 49 | NOT_SORTED_ARRAY_VISITING_COUNT, 50 | ); 51 | }); 52 | 53 | it('should visit REVERSE SORTED array element specified number of times', () => { 54 | SortTester.testAlgorithmTimeComplexity( 55 | QuickSortInPlace, 56 | reverseArr, 57 | REVERSE_SORTED_ARRAY_VISITING_COUNT, 58 | ); 59 | }); 60 | }); 61 | -------------------------------------------------------------------------------- /skeletons/algorithms/sorting/selection-sort/README.md: -------------------------------------------------------------------------------- 1 | # Selection Sort 2 | 3 | Selection sort is a sorting algorithm, specifically an 4 | in-place comparison sort. It has O(n2) time complexity, 5 | making it inefficient on large lists, and generally 6 | performs worse than the similar insertion sort. 7 | Selection sort is noted for its simplicity, and it has 8 | performance advantages over more complicated algorithms 9 | in certain situations, particularly where auxiliary 10 | memory is limited. 11 | 12 | ![Algorithm Visualization](https://upload.wikimedia.org/wikipedia/commons/b/b0/Selection_sort_animation.gif) 13 | 14 | ![Algorithm Visualization](https://upload.wikimedia.org/wikipedia/commons/9/94/Selection-Sort-Animation.gif) 15 | 16 | ## Complexity 17 | 18 | | Name | Best | Average | Worst | Memory | Stable | Comments | 19 | | --------------------- | :-------------: | :-----------------: | :-----------------: | :-------: | :-------: | :-------- | 20 | | **Selection sort** | n2 | n2 | n2 | 1 | No | | 21 | 22 | ## References 23 | 24 | [Wikipedia](https://en.wikipedia.org/wiki/Selection_sort) 25 | -------------------------------------------------------------------------------- /skeletons/algorithms/sorting/selection-sort/SelectionSort.js: -------------------------------------------------------------------------------- 1 | import Sort from '../Sort'; 2 | 3 | export default class SelectionSort extends Sort { 4 | sort(originalArray) { 5 | // WRITE YOUR CODE HERE 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /skeletons/algorithms/sorting/selection-sort/__test__/SelectionSort.test.js: -------------------------------------------------------------------------------- 1 | import SelectionSort from '../SelectionSort'; 2 | import { 3 | equalArr, 4 | notSortedArr, 5 | reverseArr, 6 | sortedArr, 7 | SortTester, 8 | } from '../../SortTester'; 9 | 10 | // Complexity constants. 11 | const SORTED_ARRAY_VISITING_COUNT = 209; 12 | const NOT_SORTED_ARRAY_VISITING_COUNT = 209; 13 | const REVERSE_SORTED_ARRAY_VISITING_COUNT = 209; 14 | const EQUAL_ARRAY_VISITING_COUNT = 209; 15 | 16 | describe('SelectionSort', () => { 17 | it('should sort array', () => { 18 | SortTester.testSort(SelectionSort); 19 | }); 20 | 21 | it('should sort array with custom comparator', () => { 22 | SortTester.testSortWithCustomComparator(SelectionSort); 23 | }); 24 | 25 | it('should sort negative numbers', () => { 26 | SortTester.testNegativeNumbersSort(SelectionSort); 27 | }); 28 | 29 | it('should visit EQUAL array element specified number of times', () => { 30 | SortTester.testAlgorithmTimeComplexity( 31 | SelectionSort, 32 | equalArr, 33 | EQUAL_ARRAY_VISITING_COUNT, 34 | ); 35 | }); 36 | 37 | it('should visit SORTED array element specified number of times', () => { 38 | SortTester.testAlgorithmTimeComplexity( 39 | SelectionSort, 40 | sortedArr, 41 | SORTED_ARRAY_VISITING_COUNT, 42 | ); 43 | }); 44 | 45 | it('should visit NOT SORTED array element specified number of times', () => { 46 | SortTester.testAlgorithmTimeComplexity( 47 | SelectionSort, 48 | notSortedArr, 49 | NOT_SORTED_ARRAY_VISITING_COUNT, 50 | ); 51 | }); 52 | 53 | it('should visit REVERSE SORTED array element specified number of times', () => { 54 | SortTester.testAlgorithmTimeComplexity( 55 | SelectionSort, 56 | reverseArr, 57 | REVERSE_SORTED_ARRAY_VISITING_COUNT, 58 | ); 59 | }); 60 | }); 61 | -------------------------------------------------------------------------------- /skeletons/data-structures/binary-search-tree/BinarySearchTree.js: -------------------------------------------------------------------------------- 1 | import BinarySearchTreeNode from './BinarySearchTreeNode'; 2 | 3 | export default class BinarySearchTree { 4 | /** 5 | * @param {function} [nodeValueCompareFunction] 6 | */ 7 | constructor(nodeValueCompareFunction) {} 8 | 9 | /** 10 | * @param {*} value 11 | * @return {BinarySearchTreeNode} 12 | */ 13 | insert(value) {} 14 | 15 | /** 16 | * @param {*} value 17 | * @return {boolean} 18 | */ 19 | contains(value) {} 20 | 21 | /** 22 | * @param {*} value 23 | * @return {boolean} 24 | */ 25 | remove(value) {} 26 | 27 | /** 28 | * @return {string} 29 | */ 30 | toString() { 31 | return this.root.toString(); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /skeletons/data-structures/binary-search-tree/BinarySearchTreeNode.js: -------------------------------------------------------------------------------- 1 | import BinaryTreeNode from './BinaryTreeNode'; 2 | import Comparator from '../../../utils/comparator/Comparator'; 3 | 4 | export default class BinarySearchTreeNode extends BinaryTreeNode { 5 | /** 6 | * @param {*} [value] - node value. 7 | * @param {function} [compareFunction] - comparator function for node values. 8 | */ 9 | constructor(value = null, compareFunction = undefined) {} 10 | 11 | /** 12 | * @param {*} value 13 | * @return {BinarySearchTreeNode} 14 | */ 15 | insert(value) {} 16 | 17 | /** 18 | * @param {*} value 19 | * @return {BinarySearchTreeNode} 20 | */ 21 | find(value) {} 22 | 23 | /** 24 | * @param {*} value 25 | * @return {boolean} 26 | */ 27 | contains(value) {} 28 | 29 | /** 30 | * @param {*} value 31 | * @return {boolean} 32 | */ 33 | remove(value) {} 34 | 35 | /** 36 | * @return {BinarySearchTreeNode} 37 | */ 38 | findMin() {} 39 | } 40 | -------------------------------------------------------------------------------- /skeletons/data-structures/binary-search-tree/BinaryTreeNode.js: -------------------------------------------------------------------------------- 1 | import Comparator from '../../utils/comparator/Comparator'; 2 | import HashTable from '../hash-table/HashTable'; 3 | 4 | export default class BinaryTreeNode { 5 | /** 6 | * @param {*} [value] - node value. 7 | */ 8 | constructor(value = null) {} 9 | 10 | /** 11 | * @return {number} 12 | */ 13 | get leftHeight() {} 14 | 15 | /** 16 | * @return {number} 17 | */ 18 | get rightHeight() {} 19 | 20 | /** 21 | * @return {number} 22 | */ 23 | get height() {} 24 | 25 | /** 26 | * @return {number} 27 | */ 28 | get balanceFactor() {} 29 | 30 | /** 31 | * Get parent's sibling if it exists. 32 | * @return {BinaryTreeNode} 33 | */ 34 | get uncle() {} 35 | 36 | /** 37 | * @param {*} value 38 | * @return {BinaryTreeNode} 39 | */ 40 | setValue(value) {} 41 | 42 | /** 43 | * @param {BinaryTreeNode} node 44 | * @return {BinaryTreeNode} 45 | */ 46 | setLeft(node) {} 47 | 48 | /** 49 | * @param {BinaryTreeNode} node 50 | * @return {BinaryTreeNode} 51 | */ 52 | setRight(node) {} 53 | 54 | /** 55 | * @param {BinaryTreeNode} nodeToRemove 56 | * @return {boolean} 57 | */ 58 | removeChild(nodeToRemove) {} 59 | 60 | /** 61 | * @param {BinaryTreeNode} nodeToReplace 62 | * @param {BinaryTreeNode} replacementNode 63 | * @return {boolean} 64 | */ 65 | replaceChild(nodeToReplace, replacementNode) {} 66 | 67 | /** 68 | * @param {BinaryTreeNode} sourceNode 69 | * @param {BinaryTreeNode} targetNode 70 | */ 71 | static copyNode(sourceNode, targetNode) {} 72 | 73 | /** 74 | * @return {*[]} 75 | */ 76 | traverseInOrder() {} 77 | 78 | /** 79 | * @return {string} 80 | */ 81 | toString() { 82 | return this.traverseInOrder().toString(); 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /skeletons/data-structures/binary-search-tree/README.md: -------------------------------------------------------------------------------- 1 | # Binary Search Tree 2 | 3 | In computer science, binary search trees (BST), sometimes called 4 | ordered or sorted binary trees, are a particular type of container: 5 | data structures that store "items" (such as numbers, names etc.) 6 | in memory. They allow fast lookup, addition and removal of 7 | items, and can be used to implement either dynamic sets of 8 | items, or lookup tables that allow finding an item by its key 9 | (e.g., finding the phone number of a person by name). 10 | 11 | Binary search trees keep their keys in sorted order, so that lookup 12 | and other operations can use the principle of binary search: 13 | when looking for a key in a tree (or a place to insert a new key), 14 | they traverse the tree from root to leaf, making comparisons to 15 | keys stored in the nodes of the tree and deciding, on the basis 16 | of the comparison, to continue searching in the left or right 17 | subtrees. On average, this means that each comparison allows 18 | the operations to skip about half of the tree, so that each 19 | lookup, insertion or deletion takes time proportional to the 20 | logarithm of the number of items stored in the tree. This is 21 | much better than the linear time required to find items by key 22 | in an (unsorted) array, but slower than the corresponding 23 | operations on hash tables. 24 | 25 | A binary search tree of size 9 and depth 3, with 8 at the root. 26 | The leaves are not drawn. 27 | 28 | ![Binary Search Tree](https://upload.wikimedia.org/wikipedia/commons/d/da/Binary_search_tree.svg) 29 | 30 | ## References 31 | 32 | - [Wikipedia](https://en.wikipedia.org/wiki/Binary_search_tree) 33 | - [Inserting to BST on YouTube](https://www.youtube.com/watch?v=wcIRPqTR3Kc&list=PLLXdhg_r2hKA7DPDsunoDZ-Z769jWn4R8&index=9&t=0s) 34 | - [BST Interactive Visualisations](https://www.cs.usfca.edu/~galles/visualization/BST.html) 35 | -------------------------------------------------------------------------------- /skeletons/data-structures/binary-search-tree/__test__/BinarySearchTree.test.js: -------------------------------------------------------------------------------- 1 | import BinarySearchTree from '../BinarySearchTree'; 2 | 3 | describe('BinarySearchTree', () => { 4 | it('should create binary search tree', () => { 5 | const bst = new BinarySearchTree(); 6 | 7 | expect(bst).toBeDefined(); 8 | expect(bst.root).toBeDefined(); 9 | expect(bst.root.value).toBeNull(); 10 | expect(bst.root.left).toBeNull(); 11 | expect(bst.root.right).toBeNull(); 12 | }); 13 | 14 | it('should insert values', () => { 15 | const bst = new BinarySearchTree(); 16 | 17 | const insertedNode1 = bst.insert(10); 18 | const insertedNode2 = bst.insert(20); 19 | bst.insert(5); 20 | 21 | expect(bst.toString()).toBe('5,10,20'); 22 | expect(insertedNode1.value).toBe(10); 23 | expect(insertedNode2.value).toBe(20); 24 | }); 25 | 26 | it('should check if value exists', () => { 27 | const bst = new BinarySearchTree(); 28 | 29 | bst.insert(10); 30 | bst.insert(20); 31 | bst.insert(5); 32 | 33 | expect(bst.contains(20)).toBeTruthy(); 34 | expect(bst.contains(40)).toBeFalsy(); 35 | }); 36 | 37 | it('should remove nodes', () => { 38 | const bst = new BinarySearchTree(); 39 | 40 | bst.insert(10); 41 | bst.insert(20); 42 | bst.insert(5); 43 | 44 | expect(bst.toString()).toBe('5,10,20'); 45 | 46 | const removed1 = bst.remove(5); 47 | expect(bst.toString()).toBe('10,20'); 48 | expect(removed1).toBeTruthy(); 49 | 50 | const removed2 = bst.remove(20); 51 | expect(bst.toString()).toBe('10'); 52 | expect(removed2).toBeTruthy(); 53 | }); 54 | 55 | it('should insert object values', () => { 56 | const nodeValueCompareFunction = (a, b) => { 57 | const normalizedA = a || { value: null }; 58 | const normalizedB = b || { value: null }; 59 | 60 | if (normalizedA.value === normalizedB.value) { 61 | return 0; 62 | } 63 | 64 | return normalizedA.value < normalizedB.value ? -1 : 1; 65 | }; 66 | 67 | const obj1 = { key: 'obj1', value: 1, toString: () => 'obj1' }; 68 | const obj2 = { key: 'obj2', value: 2, toString: () => 'obj2' }; 69 | const obj3 = { key: 'obj3', value: 3, toString: () => 'obj3' }; 70 | 71 | const bst = new BinarySearchTree(nodeValueCompareFunction); 72 | 73 | bst.insert(obj2); 74 | bst.insert(obj3); 75 | bst.insert(obj1); 76 | 77 | expect(bst.toString()).toBe('obj1,obj2,obj3'); 78 | }); 79 | 80 | it('should be traversed to sorted array', () => { 81 | const bst = new BinarySearchTree(); 82 | 83 | bst.insert(10); 84 | bst.insert(-10); 85 | bst.insert(20); 86 | bst.insert(-20); 87 | bst.insert(25); 88 | bst.insert(6); 89 | 90 | expect(bst.toString()).toBe('-20,-10,6,10,20,25'); 91 | expect(bst.root.height).toBe(2); 92 | 93 | bst.insert(4); 94 | 95 | expect(bst.toString()).toBe('-20,-10,4,6,10,20,25'); 96 | expect(bst.root.height).toBe(3); 97 | }); 98 | }); 99 | -------------------------------------------------------------------------------- /skeletons/data-structures/doubly-linked-list/DoublyLinkedList.js: -------------------------------------------------------------------------------- 1 | import DoublyLinkedListNode from './DoublyLinkedListNode'; 2 | 3 | export default class DoublyLinkedList { 4 | constructor() {} 5 | 6 | /** 7 | * @param {*} value 8 | * @return {DoublyLinkedList} 9 | */ 10 | prepend(value) {} 11 | 12 | /** 13 | * @param {*} value 14 | * @return {DoublyLinkedList} 15 | */ 16 | append(value) {} 17 | 18 | /** 19 | * @param {Object} findParams 20 | * @param {*} findParams.value 21 | * @param {function} [findParams.callback] 22 | * @return {DoublyLinkedListNode} 23 | */ 24 | find({ 25 | value, 26 | callback 27 | }) {} 28 | 29 | /** 30 | * @return {DoublyLinkedListNode} 31 | */ 32 | deleteHead() {} 33 | 34 | /** 35 | * @return {DoublyLinkedListNode} 36 | */ 37 | deleteTail() {} 38 | 39 | /** 40 | * @param {*} value 41 | * @return {DoublyLinkedListNode} 42 | */ 43 | delete(value) {} 44 | 45 | /** 46 | * @return {DoublyLinkedListNode[]} 47 | */ 48 | toArray() { 49 | const nodes = []; 50 | let node = this.head; 51 | 52 | while (node) { 53 | nodes.push(node); 54 | node = node.next; 55 | } 56 | 57 | return nodes; 58 | } 59 | 60 | /** 61 | * @param {function} [callback] 62 | * @return {string} 63 | */ 64 | toString(callback) { 65 | return this.toArray().map(node => node.toString(callback)).toString(); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /skeletons/data-structures/doubly-linked-list/DoublyLinkedListNode.js: -------------------------------------------------------------------------------- 1 | export default class DoublyLinkedListNode { 2 | constructor(value, next = null, previous = null) { 3 | this.value = value; 4 | } 5 | 6 | toString(callback) { 7 | return callback ? callback(this.value) : `${this.value}`; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /skeletons/data-structures/doubly-linked-list/README.md: -------------------------------------------------------------------------------- 1 | # Doubly Linked List 2 | 3 | ## Description 4 | 5 | Unlike a singly-linked list, a doubly-linked list node keeps a reference to the previous node in addition to the next node. This allows traversal in both directions of the list, towards the head and the tail. 6 | 7 | ![Linked List](../../../assets/doubly-linked-list.png) 8 | 9 | The operations for the doubly linked list is the same a singly linked list. 10 | 11 | ## Implementation 12 | 13 | In this exercise, implement the following functions for the `DoublyLinkedListNode` and `DoublyLinkedList` classes: 14 | 15 | - `DoublyLinkedListNode` 16 | - `constructor()` 17 | - Write a method that instantiates the node. 18 | - The node takes `value`, `previous` and `next`. 19 | - `LinkedList` 20 | - `constructor()` 21 | - Write a method that instantiates the list. 22 | - The instance variables `head` and `tail` should be set to `null`. 23 | - `prepend(value)` 24 | - Write a method that inserts the `value` at the beginning of the linked list. 25 | - `append(value)` 26 | - Write a method that inserts the `value` at the end of the linked list. 27 | - `find(value)` 28 | - Write a method that returns the `node` that contains the `value`. 29 | - `deleteHead()` 30 | - Write a method that deletes the first element in the linked list. 31 | - `deleteTail()` 32 | - Write a method that deletes the last element in the linked list. 33 | - `delete(value)` 34 | - Write a method that deletes the `value` in the linked list. 35 | 36 | The most important operations are `prepend/append` for adding data, `delete` for removing data, and `find` for retrieving data. 37 | 38 | ## Detailed Walkthrough 39 | 40 | To start, build `DoublyLinkedListNode`. A doubly linked list node keeps a reference to the previous node in addition to the next node. Then, build the constructor the same way as the singly linked list. 41 | 42 | > Jest Tip: If you need to filter by the exercise name, press `p`. 43 | > After pressing `p`, enter the string "Doubly" to filter by doubly linked list tests. 44 | 45 | ### `prepend()` 46 | 47 | - The `prepend` method inserts the item at the beginning of the list. 48 | - Operations: 49 | - Create a new `Node`. 50 | - Set the current head's previous reference to the new node. 51 | - Set the new node's next to the current `head`. 52 | - Update `head` to point at the new node. 53 | - Take into consideration where this is the first value in the linked list. 54 | 55 | ### `append()` 56 | 57 | - The `append` method inserts the item at the end of the list. 58 | - Operations: 59 | - Create a new `Node`. 60 | - Set the current tail's `next` to be the new node. 61 | - Set the new node's previous to the current `tail`. 62 | - Update `tail` to point at the new node. 63 | - Take into consideration where this is the first value in the linked list. 64 | 65 | ### `find(value)` 66 | 67 | - The `find` method returns the node with the target value. 68 | - Traverse the array in the same way as a singly linked list. 69 | 70 | ### `deleteHead()` / `deleteTail()` 71 | 72 | - The `deleteHead/Tail` methods are useful utilities methods. 73 | - Take into consideration where there is only one node in the linked list. 74 | 75 | ### `delete(value)` 76 | 77 | - The `delete` method removes the first node with the specified value. 78 | - The delete operation for a doubly linked list is significantly simpler due to having a reference to the previous node. 79 | - Utilize `find` and `deleteHead/Tail` methods written above to write the method. 80 | -------------------------------------------------------------------------------- /skeletons/data-structures/doubly-linked-list/__test__/DoublyLinkedListNode.test.js: -------------------------------------------------------------------------------- 1 | import DoublyLinkedListNode from '../DoublyLinkedListNode'; 2 | 3 | describe('DoublyLinkedListNode', () => { 4 | it('should create list node with value', () => { 5 | const node = new DoublyLinkedListNode(1); 6 | 7 | expect(node.value).toBe(1); 8 | expect(node.next).toBeNull(); 9 | expect(node.previous).toBeNull(); 10 | }); 11 | 12 | it('should create list node with object as a value', () => { 13 | const nodeValue = { 14 | value: 1, 15 | key: 'test' 16 | }; 17 | const node = new DoublyLinkedListNode(nodeValue); 18 | 19 | expect(node.value.value).toBe(1); 20 | expect(node.value.key).toBe('test'); 21 | expect(node.next).toBeNull(); 22 | expect(node.previous).toBeNull(); 23 | }); 24 | 25 | it('should link nodes together', () => { 26 | const node2 = new DoublyLinkedListNode(2); 27 | const node1 = new DoublyLinkedListNode(1, node2); 28 | const node3 = new DoublyLinkedListNode(10, node1, node2); 29 | 30 | expect(node1.next).toBeDefined(); 31 | expect(node1.previous).toBeNull(); 32 | expect(node2.next).toBeNull(); 33 | expect(node2.previous).toBeNull(); 34 | expect(node3.next).toBeDefined(); 35 | expect(node3.previous).toBeDefined(); 36 | expect(node1.value).toBe(1); 37 | expect(node1.next.value).toBe(2); 38 | expect(node3.next.value).toBe(1); 39 | expect(node3.previous.value).toBe(2); 40 | }); 41 | 42 | it('should convert node to string', () => { 43 | const node = new DoublyLinkedListNode(1); 44 | 45 | expect(node.toString()).toBe('1'); 46 | 47 | node.value = 'string value'; 48 | expect(node.toString()).toBe('string value'); 49 | }); 50 | 51 | it('should convert node to string with custom stringifier', () => { 52 | const nodeValue = { 53 | value: 1, 54 | key: 'test' 55 | }; 56 | 57 | const node = new DoublyLinkedListNode(nodeValue); 58 | const toStringCallback = value => `value: ${value.value}, key: ${value.key}`; 59 | 60 | expect(node.toString(toStringCallback)).toBe('value: 1, key: test'); 61 | }); 62 | }); 63 | -------------------------------------------------------------------------------- /skeletons/data-structures/hash-table/HashTable.js: -------------------------------------------------------------------------------- 1 | import LinkedList from '../linked-list/LinkedList'; 2 | 3 | // Hash table size directly affects on the number of collisions. 4 | // The bigger the hash table size the less collisions you'll get. 5 | // For demonstrating purposes hash table size is small to show how collisions 6 | // are being handled. 7 | const defaultHashTableSize = 32; 8 | 9 | export default class HashTable { 10 | /** 11 | * @param {number} hashTableSize 12 | */ 13 | constructor(hashTableSize = defaultHashTableSize) {} 14 | 15 | /** 16 | * Converts key string to hash number. 17 | *asadsd 18 | * @param {string} key 19 | * @return {number} 20 | */ 21 | hash(key) {} 22 | 23 | /** 24 | * @param {string} key 25 | * @param {*} value 26 | */ 27 | set(key, value) {} 28 | 29 | /** 30 | * @param {string} key 31 | * @return {*} 32 | */ 33 | delete(key) {} 34 | 35 | /** 36 | * @param {string} key 37 | * @return {*} 38 | */ 39 | get(key) {} 40 | 41 | /** 42 | * @param {string} key 43 | * @return {boolean} 44 | */ 45 | has(key) {} 46 | 47 | /** 48 | * @return {string[]} 49 | */ 50 | getKeys() {} 51 | } 52 | -------------------------------------------------------------------------------- /skeletons/data-structures/hash-table/README.md: -------------------------------------------------------------------------------- 1 | # Hash Table 2 | 3 | In computing, a hash table (hash map) is a data 4 | structure which implements an associative array 5 | abstract data type, a structure that can map keys 6 | to values. A hash table uses a hash function to 7 | compute an index into an array of buckets or slots, 8 | from which the desired value can be found 9 | 10 | Ideally, the hash function will assign each key to a 11 | unique bucket, but most hash table designs employ an 12 | imperfect hash function, which might cause hash 13 | collisions where the hash function generates the same 14 | index for more than one key. Such collisions must be 15 | accommodated in some way. 16 | 17 | ![Hash Table](https://upload.wikimedia.org/wikipedia/commons/7/7d/Hash_table_3_1_1_0_1_0_0_SP.svg) 18 | 19 | Hash collision resolved by separate chaining. 20 | 21 | ![Hash Collision](https://upload.wikimedia.org/wikipedia/commons/d/d0/Hash_table_5_0_1_1_1_1_1_LL.svg) 22 | 23 | ## References 24 | 25 | - [Wikipedia](https://en.wikipedia.org/wiki/Hash_table) 26 | - [YouTube](https://www.youtube.com/watch?v=shs0KM3wKv8&index=4&list=PLLXdhg_r2hKA7DPDsunoDZ-Z769jWn4R8) 27 | -------------------------------------------------------------------------------- /skeletons/data-structures/hash-table/__test__/HashTable.test.js: -------------------------------------------------------------------------------- 1 | import HashTable from '../HashTable'; 2 | 3 | describe('HashTable', () => { 4 | it('should create hash table of certain size', () => { 5 | const defaultHashTable = new HashTable(); 6 | expect(defaultHashTable.buckets.length).toBe(32); 7 | 8 | const biggerHashTable = new HashTable(64); 9 | expect(biggerHashTable.buckets.length).toBe(64); 10 | }); 11 | 12 | it('should generate proper hash for specified keys', () => { 13 | const hashTable = new HashTable(); 14 | 15 | expect(hashTable.hash('a')).toBe(1); 16 | expect(hashTable.hash('b')).toBe(2); 17 | expect(hashTable.hash('abc')).toBe(6); 18 | }); 19 | 20 | it('should set, read and delete data with collisions', () => { 21 | const hashTable = new HashTable(3); 22 | 23 | expect(hashTable.hash('a')).toBe(1); 24 | expect(hashTable.hash('b')).toBe(2); 25 | expect(hashTable.hash('c')).toBe(0); 26 | expect(hashTable.hash('d')).toBe(1); 27 | 28 | hashTable.set('a', 'sky-old'); 29 | hashTable.set('a', 'sky'); 30 | hashTable.set('b', 'sea'); 31 | hashTable.set('c', 'earth'); 32 | hashTable.set('d', 'ocean'); 33 | 34 | expect(hashTable.has('x')).toBeFalsy(); 35 | expect(hashTable.has('b')).toBeTruthy(); 36 | expect(hashTable.has('c')).toBeTruthy(); 37 | 38 | const stringifier = value => `${value.key}:${value.value}`; 39 | 40 | expect(hashTable.buckets[0].toString(stringifier)).toBe('c:earth'); 41 | expect(hashTable.buckets[1].toString(stringifier)).toBe('a:sky,d:ocean'); 42 | expect(hashTable.buckets[2].toString(stringifier)).toBe('b:sea'); 43 | 44 | expect(hashTable.get('a')).toBe('sky'); 45 | expect(hashTable.get('d')).toBe('ocean'); 46 | expect(hashTable.get('x')).not.toBeDefined(); 47 | 48 | hashTable.delete('a'); 49 | 50 | expect(hashTable.delete('not-existing')).toBeNull(); 51 | 52 | expect(hashTable.get('a')).not.toBeDefined(); 53 | expect(hashTable.get('d')).toBe('ocean'); 54 | 55 | hashTable.set('d', 'ocean-new'); 56 | expect(hashTable.get('d')).toBe('ocean-new'); 57 | }); 58 | 59 | it('should be possible to add objects to hash table', () => { 60 | const hashTable = new HashTable(); 61 | 62 | hashTable.set('objectKey', { prop1: 'a', prop2: 'b' }); 63 | 64 | const object = hashTable.get('objectKey'); 65 | expect(object).toBeDefined(); 66 | expect(object.prop1).toBe('a'); 67 | expect(object.prop2).toBe('b'); 68 | }); 69 | 70 | it('should track actual keys', () => { 71 | const hashTable = new HashTable(3); 72 | 73 | hashTable.set('a', 'sky-old'); 74 | hashTable.set('a', 'sky'); 75 | hashTable.set('b', 'sea'); 76 | hashTable.set('c', 'earth'); 77 | hashTable.set('d', 'ocean'); 78 | 79 | expect(hashTable.getKeys()).toEqual(['a', 'b', 'c', 'd']); 80 | expect(hashTable.has('a')).toBeTruthy(); 81 | expect(hashTable.has('x')).toBeFalsy(); 82 | 83 | hashTable.delete('a'); 84 | 85 | expect(hashTable.has('a')).toBeFalsy(); 86 | expect(hashTable.has('b')).toBeTruthy(); 87 | expect(hashTable.has('x')).toBeFalsy(); 88 | }); 89 | }); 90 | -------------------------------------------------------------------------------- /skeletons/data-structures/linked-list/LinkedList.js: -------------------------------------------------------------------------------- 1 | import LinkedListNode from './LinkedListNode'; 2 | 3 | export default class LinkedList { 4 | constructor() {} 5 | 6 | /** 7 | * @param {*} value 8 | * @return {LinkedList} 9 | */ 10 | prepend(value) {} 11 | 12 | /** 13 | * @param {*} value 14 | * @return {LinkedList} 15 | */ 16 | append(value) {} 17 | 18 | /** 19 | * @param {*} value 20 | * @return {LinkedListNode} 21 | */ 22 | delete(value) {} 23 | 24 | /** 25 | * @param {Object} findParams 26 | * @param {*} findParams.value 27 | * @return {LinkedListNode} 28 | */ 29 | find({ 30 | value, 31 | callback 32 | }) {} 33 | 34 | /** 35 | * @param {*} value 36 | * @return {LinkedList} 37 | */ 38 | insertAfter(value, insertValue) {} 39 | 40 | /** 41 | * @return {LinkedListNode} 42 | */ 43 | deleteTail() {} 44 | 45 | /** 46 | * @return {LinkedListNode} 47 | */ 48 | deleteHead() {} 49 | 50 | /** 51 | * @return {LinkedListNode[]} 52 | */ 53 | toArray() { 54 | const nodes = []; 55 | let node = this.head; 56 | 57 | while (node) { 58 | nodes.push(node); 59 | node = node.next; 60 | } 61 | 62 | return nodes; 63 | } 64 | 65 | /** 66 | * @param {function} [callback] 67 | * @return {string} 68 | */ 69 | toString(callback) { 70 | return this.toArray().map(node => node.toString(callback)).toString(); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /skeletons/data-structures/linked-list/LinkedListNode.js: -------------------------------------------------------------------------------- 1 | export default class LinkedListNode { 2 | constructor(value, next = null) { 3 | this.value = value; 4 | } 5 | 6 | toString(callback) { 7 | return callback ? callback(this.value) : `${this.value}`; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /skeletons/data-structures/linked-list/__test__/LinkedListNode.test.js: -------------------------------------------------------------------------------- 1 | import LinkedListNode from '../LinkedListNode'; 2 | 3 | describe('LinkedListNode', () => { 4 | it('should create list node with value', () => { 5 | const node = new LinkedListNode(1); 6 | 7 | expect(node.value).toBe(1); 8 | expect(node.next).toBeNull(); 9 | }); 10 | 11 | it('should create list node with object as a value', () => { 12 | const nodeValue = { 13 | value: 1, 14 | key: 'test' 15 | }; 16 | const node = new LinkedListNode(nodeValue); 17 | 18 | expect(node.value.value).toBe(1); 19 | expect(node.value.key).toBe('test'); 20 | expect(node.next).toBeNull(); 21 | }); 22 | 23 | it('should link nodes together', () => { 24 | const node2 = new LinkedListNode(2); 25 | const node1 = new LinkedListNode(1, node2); 26 | 27 | expect(node1.next).toBeDefined(); 28 | expect(node2.next).toBeNull(); 29 | expect(node1.value).toBe(1); 30 | expect(node1.next.value).toBe(2); 31 | }); 32 | 33 | it('should convert node to string', () => { 34 | const node = new LinkedListNode(1); 35 | 36 | expect(node.toString()).toBe('1'); 37 | 38 | node.value = 'string value'; 39 | expect(node.toString()).toBe('string value'); 40 | }); 41 | 42 | it('should convert node to string with custom stringifier', () => { 43 | const nodeValue = { 44 | value: 1, 45 | key: 'test' 46 | }; 47 | 48 | const node = new LinkedListNode(nodeValue); 49 | const toStringCallback = value => `value: ${value.value}, key: ${value.key}`; 50 | 51 | expect(node.toString(toStringCallback)).toBe('value: 1, key: test'); 52 | }); 53 | }); 54 | -------------------------------------------------------------------------------- /skeletons/data-structures/queue/Queue.js: -------------------------------------------------------------------------------- 1 | export default class Queue { 2 | constructor() { 3 | this.store = []; 4 | } 5 | 6 | isEmpty() {} 7 | 8 | peek() {} 9 | 10 | enqueue(value) {} 11 | 12 | dequeue() {} 13 | 14 | toString() { 15 | return this.store.toString(); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /skeletons/data-structures/queue/README.md: -------------------------------------------------------------------------------- 1 | # Queue 2 | 3 | ## Description 4 | 5 | A queue is a data structure that can store or retrieve one item at a time, in first-in-first-out (FIFO) order. Think of FIFO as standing in the line at the grocery store. You would expect the first person that stood in the line to be the first one served by the cashier. 6 | 7 | Queue implements FIFO through two operations; `enqueue` and `dequeue`, which can be visualized in the diagram below: 8 | 9 | ![Queing operations](../../../assets/queue.png) 10 | 11 | - `enqueue` operation stores the item at the back of the line 12 | - `dequeue` operation retrieves the item from the front of the line 13 | 14 | (_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_) 15 | 16 | ## Implementation 17 | 18 | In this exercise, implement the following functions for the `Queue` class 19 | 20 | - `isEmpty()` 21 | - Write a method that returns `true` if the queue is currently empty. 22 | - `peek()` 23 | - Write a method that returns the element at the front of the queue. 24 | - `enqueue()` 25 | - Write a method that stores an element into the queue. 26 | - `dequeue()` 27 | - Write a method that retrieves an element from the queue. 28 | - `toString()` 29 | - The stringify method is provided for you. `toString()` is a useful method to implement into data structures for easier debugging. 30 | - For example, you could use it for logging: 31 | ``` 32 | const queue = new Queue(); 33 | constole.log(queue.toString()); 34 | ``` 35 | - A queue is simple enough to be logged without actually needing `toString()`, but with more complex data structures, this is an invaluable method. 36 | 37 | ## Queue Exercises 38 | 39 | Solve this exercise after writing a queue. 40 | 41 | **Bracket Matching** 42 | 43 | Write an algorithm to determine if all of the delimiters in an expression are matched and closed. 44 | 45 | - Delimiters are `(` `{` `[` 46 | - Closed is `()` and `(()` would not be closed 47 | - Matched is `{}`, and `{]` would not be matched. 48 | 49 | ``` 50 | const bracketMatch = string => { 51 | 52 | } 53 | 54 | console.log(bracketMatch('{ac[bb]}') === true); 55 | console.log(bracketMatch('[dklf(df(kl))d]{}') === true); 56 | console.log(bracketMatch('{[[[]]]}') === true); 57 | console.log(bracketMatch('{3234[fd') === false); 58 | console.log(bracketMatch('{df][d}') === false); 59 | console.log(bracketMatch('([)]') === false); 60 | ``` 61 | -------------------------------------------------------------------------------- /skeletons/data-structures/queue/__test__/Queue.test.js: -------------------------------------------------------------------------------- 1 | import Queue from '../Queue'; 2 | 3 | describe('Queue', () => { 4 | it('should create empty queue', () => { 5 | const queue = new Queue(); 6 | expect(queue).not.toBeNull(); 7 | expect(queue.store).not.toBeNull(); 8 | }); 9 | 10 | it('should enqueue data to queue', () => { 11 | const queue = new Queue(); 12 | 13 | queue.enqueue(1); 14 | queue.enqueue(2); 15 | 16 | expect(queue.toString()).toBe('1,2'); 17 | }); 18 | 19 | it('should be possible to enqueue/dequeue in order', () => { 20 | const queue = new Queue(); 21 | 22 | queue.enqueue(1); 23 | queue.enqueue(2); 24 | 25 | expect(queue.dequeue()).toBe(1); 26 | expect(queue.dequeue()).toBe(2); 27 | }); 28 | 29 | it('should peek data from queue', () => { 30 | const queue = new Queue(); 31 | 32 | expect(queue.peek()).toBeNull(); 33 | 34 | queue.enqueue(1); 35 | queue.enqueue(2); 36 | 37 | expect(queue.peek()).toBe(1); 38 | expect(queue.peek()).toBe(1); 39 | }); 40 | 41 | it('should check if queue is empty', () => { 42 | const queue = new Queue(); 43 | 44 | expect(queue.isEmpty()).toBeTruthy(); 45 | 46 | queue.enqueue(1); 47 | 48 | expect(queue.isEmpty()).toBeFalsy(); 49 | }); 50 | 51 | it('should dequeue from queue in FIFO order', () => { 52 | const queue = new Queue(); 53 | 54 | queue.enqueue(1); 55 | queue.enqueue(2); 56 | 57 | expect(queue.dequeue()).toBe(1); 58 | expect(queue.dequeue()).toBe(2); 59 | expect(queue.dequeue()).toBeNull(); 60 | expect(queue.isEmpty()).toBeTruthy(); 61 | }); 62 | }); 63 | -------------------------------------------------------------------------------- /skeletons/data-structures/stack/README.md: -------------------------------------------------------------------------------- 1 | # Stack 2 | 3 | In computer science, a stack is an abstract data type that serves 4 | as a collection of elements, with two principal operations: 5 | 6 | * **push**, which adds an element to the collection, and 7 | * **pop**, which removes the most recently added element that was not yet removed. 8 | 9 | The order in which elements come off a stack gives rise to its 10 | alternative name, LIFO (last in, first out). Additionally, a 11 | peek operation may give access to the top without modifying 12 | the stack. The name "stack" for this type of structure comes 13 | from the analogy to a set of physical items stacked on top of 14 | each other, which makes it easy to take an item off the top 15 | of the stack, while getting to an item deeper in the stack 16 | may require taking off multiple other items first 17 | 18 | Simple representation of a stack runtime with push and pop operations. 19 | 20 | ![Stack](https://upload.wikimedia.org/wikipedia/commons/b/b4/Lifo_stack.png) 21 | 22 | ## References 23 | 24 | - [Wikipedia](https://en.wikipedia.org/wiki/Stack_(abstract_data_type)) 25 | - [YouTube](https://www.youtube.com/watch?v=wjI1WNcIntg&list=PLLXdhg_r2hKA7DPDsunoDZ-Z769jWn4R8&index=3&) 26 | -------------------------------------------------------------------------------- /skeletons/data-structures/stack/Stack.js: -------------------------------------------------------------------------------- 1 | export default class Stack { 2 | constructor() { 3 | this.store = []; 4 | } 5 | 6 | isEmpty() {} 7 | 8 | peek() {} 9 | 10 | push(value) {} 11 | 12 | pop() {} 13 | 14 | toString() {} 15 | } 16 | -------------------------------------------------------------------------------- /skeletons/data-structures/stack/__test__/Stack.test.js: -------------------------------------------------------------------------------- 1 | import Stack from '../Stack'; 2 | 3 | describe('Stack', () => { 4 | it('should create empty stack', () => { 5 | const stack = new Stack(); 6 | expect(stack).not.toBeUndefined(); 7 | expect(stack.store).not.toBeUndefined(); 8 | }); 9 | 10 | it('should stack data to stack', () => { 11 | const stack = new Stack(); 12 | 13 | stack.push(1); 14 | stack.push(2); 15 | 16 | expect(stack.toString()).toBe('1,2'); 17 | }); 18 | 19 | it('should peek data from stack', () => { 20 | const stack = new Stack(); 21 | 22 | expect(stack.peek()).toBeUndefined(); 23 | 24 | stack.push(1); 25 | stack.push(2); 26 | 27 | expect(stack.peek()).toBe(2); 28 | expect(stack.peek()).toBe(2); 29 | }); 30 | 31 | it('should check if stack is empty', () => { 32 | const stack = new Stack(); 33 | 34 | expect(stack.isEmpty()).toBeTruthy(); 35 | 36 | stack.push(1); 37 | 38 | expect(stack.isEmpty()).toBeFalsy(); 39 | }); 40 | 41 | it('should pop data from stack', () => { 42 | const stack = new Stack(); 43 | 44 | stack.push(1); 45 | stack.push(2); 46 | 47 | expect(stack.pop()).toBe(2); 48 | expect(stack.pop()).toBe(1); 49 | expect(stack.pop()).toBeUndefined(); 50 | expect(stack.isEmpty()).toBeTruthy(); 51 | }); 52 | 53 | it('should be possible to push/pop', () => { 54 | const stack = new Stack(); 55 | 56 | stack.push(1); 57 | stack.push(2); 58 | 59 | expect(stack.toString()).toBe('1,2'); 60 | expect(stack.pop()).toBe(2); 61 | expect(stack.pop()).toBe(1); 62 | }); 63 | }); 64 | -------------------------------------------------------------------------------- /skeletons/utils/comparator/Comparator.js: -------------------------------------------------------------------------------- 1 | export default class Comparator { 2 | /** 3 | * @param {function(a: *, b: *)} [compareFunction] 4 | */ 5 | constructor(compareFunction) {} 6 | 7 | /** 8 | * @param {(string|number)} a 9 | * @param {(string|number)} b 10 | * @returns {number} 11 | */ 12 | static defaultCompareFunction(a, b) {} 13 | 14 | equal(a, b) {} 15 | 16 | lessThan(a, b) {} 17 | 18 | greaterThan(a, b) {} 19 | 20 | lessThanOrEqual(a, b) {} 21 | 22 | greaterThanOrEqual(a, b) {} 23 | 24 | reverse() {} 25 | } 26 | -------------------------------------------------------------------------------- /skeletons/utils/comparator/__test__/Comparator.test.js: -------------------------------------------------------------------------------- 1 | import Comparator from '../Comparator'; 2 | 3 | describe('Comparator', () => { 4 | it('should compare with default comparator function', () => { 5 | const comparator = new Comparator(); 6 | 7 | expect(comparator.equal(0, 0)).toBeTruthy(); 8 | expect(comparator.equal(0, 1)).toBeFalsy(); 9 | expect(comparator.equal('a', 'a')).toBeTruthy(); 10 | expect(comparator.lessThan(1, 2)).toBeTruthy(); 11 | expect(comparator.lessThan(-1, 2)).toBeTruthy(); 12 | expect(comparator.lessThan('a', 'b')).toBeTruthy(); 13 | expect(comparator.lessThan('a', 'ab')).toBeTruthy(); 14 | expect(comparator.lessThan(10, 2)).toBeFalsy(); 15 | expect(comparator.lessThanOrEqual(10, 2)).toBeFalsy(); 16 | expect(comparator.lessThanOrEqual(1, 1)).toBeTruthy(); 17 | expect(comparator.lessThanOrEqual(0, 0)).toBeTruthy(); 18 | expect(comparator.greaterThan(0, 0)).toBeFalsy(); 19 | expect(comparator.greaterThan(10, 0)).toBeTruthy(); 20 | expect(comparator.greaterThanOrEqual(10, 0)).toBeTruthy(); 21 | expect(comparator.greaterThanOrEqual(10, 10)).toBeTruthy(); 22 | expect(comparator.greaterThanOrEqual(0, 10)).toBeFalsy(); 23 | }); 24 | 25 | it('should compare with custom comparator function', () => { 26 | const comparator = new Comparator((a, b) => { 27 | if (a.length === b.length) { 28 | return 0; 29 | } 30 | 31 | return a.length < b.length ? -1 : 1; 32 | }); 33 | 34 | expect(comparator.equal('a', 'b')).toBeTruthy(); 35 | expect(comparator.equal('a', '')).toBeFalsy(); 36 | expect(comparator.lessThan('b', 'aa')).toBeTruthy(); 37 | expect(comparator.greaterThanOrEqual('a', 'aa')).toBeFalsy(); 38 | expect(comparator.greaterThanOrEqual('aa', 'a')).toBeTruthy(); 39 | expect(comparator.greaterThanOrEqual('a', 'a')).toBeTruthy(); 40 | 41 | comparator.reverse(); 42 | 43 | expect(comparator.equal('a', 'b')).toBeTruthy(); 44 | expect(comparator.equal('a', '')).toBeFalsy(); 45 | expect(comparator.lessThan('b', 'aa')).toBeFalsy(); 46 | expect(comparator.greaterThanOrEqual('a', 'aa')).toBeTruthy(); 47 | expect(comparator.greaterThanOrEqual('aa', 'a')).toBeFalsy(); 48 | expect(comparator.greaterThanOrEqual('a', 'a')).toBeTruthy(); 49 | }); 50 | }); 51 | -------------------------------------------------------------------------------- /solutions/algorithms/numbers/factorial/README.md: -------------------------------------------------------------------------------- 1 | # Factorial 2 | 3 | In mathematics, the factorial of a non-negative integer `n`, 4 | denoted by `n!`, is the product of all positive integers less 5 | than or equal to `n`. For example: 6 | 7 | ``` 8 | 5! = 5 * 4 * 3 * 2 * 1 = 120 9 | ``` 10 | 11 | | n | n! | 12 | | ----- | --------------------------: | 13 | | 0 | 1 | 14 | | 1 | 1 | 15 | | 2 | 2 | 16 | | 3 | 6 | 17 | | 4 | 24 | 18 | | 5 | 120 | 19 | | 6 | 720 | 20 | | 7 | 5 040 | 21 | | 8 | 40 320 | 22 | | 9 | 362 880 | 23 | | 10 | 3 628 800 | 24 | | 11 | 39 916 800 | 25 | | 12 | 479 001 600 | 26 | | 13 | 6 227 020 800 | 27 | | 14 | 87 178 291 200 | 28 | | 15 | 1 307 674 368 000 | 29 | 30 | ## References 31 | 32 | [Wikipedia](https://en.wikipedia.org/wiki/Factorial) 33 | -------------------------------------------------------------------------------- /solutions/algorithms/numbers/factorial/__test__/factorial.test.js: -------------------------------------------------------------------------------- 1 | import factorial from '../factorial'; 2 | 3 | describe('factorial', () => { 4 | it('should calculate factorial', () => { 5 | expect(factorial(0)).toBe(1); 6 | expect(factorial(1)).toBe(1); 7 | expect(factorial(5)).toBe(120); 8 | expect(factorial(8)).toBe(40320); 9 | expect(factorial(10)).toBe(3628800); 10 | }); 11 | }); 12 | -------------------------------------------------------------------------------- /solutions/algorithms/numbers/factorial/__test__/factorialRecursive.test.js: -------------------------------------------------------------------------------- 1 | import factorialRecursive from '../factorialRecursive'; 2 | 3 | describe('factorialRecursive', () => { 4 | it('should calculate factorial', () => { 5 | expect(factorialRecursive(0)).toBe(1); 6 | expect(factorialRecursive(1)).toBe(1); 7 | expect(factorialRecursive(5)).toBe(120); 8 | expect(factorialRecursive(8)).toBe(40320); 9 | expect(factorialRecursive(10)).toBe(3628800); 10 | }); 11 | }); 12 | -------------------------------------------------------------------------------- /solutions/algorithms/numbers/factorial/factorial.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {number} number 3 | * @return {number} 4 | */ 5 | export default function factorial(number) { 6 | let result = 1; 7 | 8 | for (let i = 2; i <= number; i += 1) { 9 | result *= i; 10 | } 11 | 12 | return result; 13 | } 14 | -------------------------------------------------------------------------------- /solutions/algorithms/numbers/factorial/factorialRecursive.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {number} number 3 | * @return {number} 4 | */ 5 | export default function factorialRecursive(number) { 6 | return number > 1 ? number * factorialRecursive(number - 1) : 1; 7 | } 8 | -------------------------------------------------------------------------------- /solutions/algorithms/numbers/fibonacci/README.md: -------------------------------------------------------------------------------- 1 | # Fibonacci Number 2 | 3 | In mathematics, the Fibonacci numbers are the numbers in the following 4 | integer sequence, called the Fibonacci sequence, and characterized by 5 | the fact that every number after the first two is the sum of the two 6 | preceding ones: 7 | 8 | `0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, ...` 9 | 10 | A tiling with squares whose side lengths are successive Fibonacci numbers 11 | 12 | ![Fibonacci](https://upload.wikimedia.org/wikipedia/commons/d/db/34%2A21-FibonacciBlocks.png) 13 | 14 | The Fibonacci spiral: an approximation of the golden spiral created by drawing circular arcs connecting the opposite corners of squares in the Fibonacci tiling;[4] this one uses squares of sizes 1, 1, 2, 3, 5, 8, 13 and 21. 15 | 16 | ![Fibonacci Spiral](https://upload.wikimedia.org/wikipedia/commons/2/2e/FibonacciSpiral.svg) 17 | 18 | ## References 19 | 20 | [Wikipedia](https://en.wikipedia.org/wiki/Fibonacci_number) 21 | -------------------------------------------------------------------------------- /solutions/algorithms/numbers/fibonacci/__test__/fibonacci.test.js: -------------------------------------------------------------------------------- 1 | import fibonacci from '../fibonacci'; 2 | 3 | describe('fibonacci', () => { 4 | it('should calculate fibonacci correctly', () => { 5 | expect(fibonacci(1)).toEqual([1]); 6 | expect(fibonacci(2)).toEqual([1, 1]); 7 | expect(fibonacci(3)).toEqual([1, 1, 2]); 8 | expect(fibonacci(4)).toEqual([1, 1, 2, 3]); 9 | expect(fibonacci(5)).toEqual([1, 1, 2, 3, 5]); 10 | expect(fibonacci(6)).toEqual([1, 1, 2, 3, 5, 8]); 11 | expect(fibonacci(7)).toEqual([1, 1, 2, 3, 5, 8, 13]); 12 | expect(fibonacci(8)).toEqual([1, 1, 2, 3, 5, 8, 13, 21]); 13 | }); 14 | }); 15 | -------------------------------------------------------------------------------- /solutions/algorithms/numbers/fibonacci/__test__/fibonacciNth.test.js: -------------------------------------------------------------------------------- 1 | import fibonacciNth from '../fibonacciNth'; 2 | 3 | describe('fibonacciNth', () => { 4 | it('should calculate fibonacci correctly', () => { 5 | expect(fibonacciNth(1)).toBe(1); 6 | expect(fibonacciNth(2)).toBe(1); 7 | expect(fibonacciNth(3)).toBe(2); 8 | expect(fibonacciNth(4)).toBe(3); 9 | expect(fibonacciNth(5)).toBe(5); 10 | expect(fibonacciNth(6)).toBe(8); 11 | expect(fibonacciNth(7)).toBe(13); 12 | expect(fibonacciNth(8)).toBe(21); 13 | expect(fibonacciNth(20)).toBe(6765); 14 | }); 15 | }); 16 | -------------------------------------------------------------------------------- /solutions/algorithms/numbers/fibonacci/fibonacci.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Return a fibonacci sequence as an array. 3 | * 4 | * @param n 5 | * @return {number[]} 6 | */ 7 | export default function fibonacci(n) { 8 | const fibSequence = [1]; 9 | 10 | let currentValue = 1; 11 | let previousValue = 0; 12 | 13 | if (n === 1) { 14 | return fibSequence; 15 | } 16 | 17 | let iterationsCounter = n - 1; 18 | 19 | while (iterationsCounter) { 20 | currentValue += previousValue; 21 | previousValue = currentValue - previousValue; 22 | 23 | fibSequence.push(currentValue); 24 | 25 | iterationsCounter -= 1; 26 | } 27 | 28 | return fibSequence; 29 | } 30 | -------------------------------------------------------------------------------- /solutions/algorithms/numbers/fibonacci/fibonacciNth.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Calculate fibonacci number at specific position using Dynamic Programming approach. 3 | * 4 | * @param n 5 | * @return {number} 6 | */ 7 | export default function fibonacciNth(n) { 8 | let currentValue = 1; 9 | let previousValue = 0; 10 | 11 | if (n === 1) { 12 | return 1; 13 | } 14 | 15 | let iterationsCounter = n - 1; 16 | 17 | while (iterationsCounter) { 18 | currentValue += previousValue; 19 | previousValue = currentValue - previousValue; 20 | 21 | iterationsCounter -= 1; 22 | } 23 | 24 | return currentValue; 25 | } 26 | -------------------------------------------------------------------------------- /solutions/algorithms/numbers/prime/README.md: -------------------------------------------------------------------------------- 1 | # Primality Test 2 | 3 | A **prime number** (or a **prime**) is a natural number greater than `1` that 4 | cannot be formed by multiplying two smaller natural numbers. A natural number 5 | greater than `1` that is not prime is called a composite number. For 6 | example, `5` is prime because the only ways of writing it as a 7 | product, `1 × 5` or `5 × 1`, involve `5` itself. However, `6` is 8 | composite because it is the product of two numbers `(2 × 3)` that are 9 | both smaller than `6`. 10 | 11 | ![Prime Numbers](https://upload.wikimedia.org/wikipedia/commons/f/f0/Primes-vs-composites.svg) 12 | 13 | A **primality test** is an algorithm for determining whether an input 14 | number is prime. Among other fields of mathematics, it is used 15 | for cryptography. Unlike integer factorization, primality tests 16 | do not generally give prime factors, only stating whether the 17 | input number is prime or not. Factorization is thought to be 18 | a computationally difficult problem, whereas primality testing 19 | is comparatively easy (its running time is polynomial in the 20 | size of the input). 21 | 22 | ## References 23 | 24 | - [Prime Numbers on Wikipedia](https://en.wikipedia.org/wiki/Prime_number) 25 | - [Primality Test on Wikipedia](https://en.wikipedia.org/wiki/Primality_test) 26 | -------------------------------------------------------------------------------- /solutions/algorithms/numbers/prime/__test__/trialDivision.test.js: -------------------------------------------------------------------------------- 1 | import trialDivision from '../trialDivision'; 2 | 3 | /** 4 | * @param {function(n: number)} testFunction 5 | */ 6 | function primalityTest(testFunction) { 7 | expect(testFunction(1)).toBeFalsy(); 8 | expect(testFunction(2)).toBeTruthy(); 9 | expect(testFunction(3)).toBeTruthy(); 10 | expect(testFunction(5)).toBeTruthy(); 11 | expect(testFunction(11)).toBeTruthy(); 12 | expect(testFunction(191)).toBeTruthy(); 13 | expect(testFunction(191)).toBeTruthy(); 14 | expect(testFunction(199)).toBeTruthy(); 15 | 16 | expect(testFunction(-1)).toBeFalsy(); 17 | expect(testFunction(0)).toBeFalsy(); 18 | expect(testFunction(4)).toBeFalsy(); 19 | expect(testFunction(6)).toBeFalsy(); 20 | expect(testFunction(12)).toBeFalsy(); 21 | expect(testFunction(14)).toBeFalsy(); 22 | expect(testFunction(25)).toBeFalsy(); 23 | expect(testFunction(192)).toBeFalsy(); 24 | expect(testFunction(200)).toBeFalsy(); 25 | expect(testFunction(400)).toBeFalsy(); 26 | 27 | // It should also deal with floats. 28 | expect(testFunction(0.5)).toBeFalsy(); 29 | expect(testFunction(1.3)).toBeFalsy(); 30 | expect(testFunction(10.5)).toBeFalsy(); 31 | } 32 | 33 | describe('trialDivision', () => { 34 | it('should detect prime numbers', () => { 35 | primalityTest(trialDivision); 36 | }); 37 | }); 38 | -------------------------------------------------------------------------------- /solutions/algorithms/numbers/prime/trialDivision.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {number} number 3 | * @return {boolean} 4 | */ 5 | export default function trialDivision(number) { 6 | // Check if number is integer. 7 | if (number % 1 !== 0) { 8 | return false; 9 | } 10 | 11 | if (number <= 1) { 12 | // If number is less than one then it isn't prime by definition. 13 | return false; 14 | } 15 | 16 | if (number <= 3) { 17 | // All numbers from 2 to 3 are prime. 18 | return true; 19 | } 20 | 21 | // If the number is not divided by 2 then we may eliminate all further even dividers. 22 | if (number % 2 === 0) { 23 | return false; 24 | } 25 | 26 | // If there is no dividers up to square root of n then there is no higher dividers as well. 27 | const dividerLimit = Math.sqrt(number); 28 | for (let divider = 3; divider <= dividerLimit; divider += 2) { 29 | if (number % divider === 0) { 30 | return false; 31 | } 32 | } 33 | 34 | return true; 35 | } 36 | -------------------------------------------------------------------------------- /solutions/algorithms/search/binary-search/README.md: -------------------------------------------------------------------------------- 1 | # Binary Search 2 | 3 | In computer science, binary search, also known as half-interval 4 | search, logarithmic search, or binary chop, is a search algorithm 5 | that finds the position of a target value within a sorted 6 | array. Binary search compares the target value to the middle 7 | element of the array; if they are unequal, the half in which 8 | the target cannot lie is eliminated and the search continues 9 | on the remaining half until it is successful. If the search 10 | ends with the remaining half being empty, the target is not 11 | in the array. 12 | 13 | ![Binary Search](https://upload.wikimedia.org/wikipedia/commons/8/83/Binary_Search_Depiction.svg) 14 | 15 | ## References 16 | 17 | - [Wikipedia](https://en.wikipedia.org/wiki/Binary_search_algorithm) 18 | - [YouTube](https://www.youtube.com/watch?v=P3YID7liBug&index=29&list=PLLXdhg_r2hKA7DPDsunoDZ-Z769jWn4R8) 19 | -------------------------------------------------------------------------------- /solutions/algorithms/search/binary-search/__test__/binarySearch.test.js: -------------------------------------------------------------------------------- 1 | import binarySearch from '../binarySearch'; 2 | 3 | describe('binarySearch', () => { 4 | it('should search number in sorted array', () => { 5 | expect(binarySearch([], 1)).toBe(-1); 6 | expect(binarySearch([1], 1)).toBe(0); 7 | expect(binarySearch([1, 2], 1)).toBe(0); 8 | expect(binarySearch([1, 2], 2)).toBe(1); 9 | expect(binarySearch([1, 5, 10, 12], 1)).toBe(0); 10 | expect(binarySearch([1, 5, 10, 12, 14, 17, 22, 100], 17)).toBe(5); 11 | expect(binarySearch([1, 5, 10, 12, 14, 17, 22, 100], 1)).toBe(0); 12 | expect(binarySearch([1, 5, 10, 12, 14, 17, 22, 100], 100)).toBe(7); 13 | expect(binarySearch([1, 5, 10, 12, 14, 17, 22, 100], 0)).toBe(-1); 14 | }); 15 | 16 | it('should search object in sorted array', () => { 17 | const sortedArrayOfObjects = [ 18 | { key: 1, value: 'value1' }, 19 | { key: 2, value: 'value2' }, 20 | { key: 3, value: 'value3' }, 21 | ]; 22 | 23 | const comparator = (a, b) => { 24 | if (a.key === b.key) return 0; 25 | return a.key < b.key ? -1 : 1; 26 | }; 27 | 28 | expect(binarySearch([], { key: 1 }, comparator)).toBe(-1); 29 | expect(binarySearch(sortedArrayOfObjects, { key: 4 }, comparator)).toBe(-1); 30 | expect(binarySearch(sortedArrayOfObjects, { key: 1 }, comparator)).toBe(0); 31 | expect(binarySearch(sortedArrayOfObjects, { key: 2 }, comparator)).toBe(1); 32 | expect(binarySearch(sortedArrayOfObjects, { key: 3 }, comparator)).toBe(2); 33 | }); 34 | }); 35 | -------------------------------------------------------------------------------- /solutions/algorithms/search/binary-search/binarySearch.js: -------------------------------------------------------------------------------- 1 | import Comparator from '../../../utils/comparator/Comparator'; 2 | 3 | /** 4 | * @param {*[]} sortedArray 5 | * @param {*} seekElement 6 | * @param {function(a, b)} [comparatorCallback] 7 | * @return {number} 8 | */ 9 | 10 | export default function binarySearch(sortedArray, seekElement, comparatorCallback) { 11 | const comparator = new Comparator(comparatorCallback); 12 | 13 | let startIndex = 0; 14 | let endIndex = sortedArray.length - 1; 15 | 16 | while (startIndex <= endIndex) { 17 | const middleIndex = startIndex + Math.floor((endIndex - startIndex) / 2); 18 | 19 | // If we've found the element just return its position. 20 | if (comparator.equal(sortedArray[middleIndex], seekElement)) { 21 | return middleIndex; 22 | } 23 | 24 | // Decide which half to choose for seeking next: left or right one. 25 | if (comparator.lessThan(sortedArray[middleIndex], seekElement)) { 26 | // Go to the right half of the array. 27 | startIndex = middleIndex + 1; 28 | } else { 29 | // Go to the left half of the array. 30 | endIndex = middleIndex - 1; 31 | } 32 | } 33 | 34 | return -1; 35 | } 36 | -------------------------------------------------------------------------------- /solutions/algorithms/search/breadth-first-search/README.md: -------------------------------------------------------------------------------- 1 | # Breadth-First Search (BFS) 2 | 3 | Breadth-first search (BFS) is an algorithm for traversing 4 | or searching tree or graph data structures. It starts at 5 | the tree root (or some arbitrary node of a graph, sometimes 6 | referred to as a 'search key') and explores the neighbor 7 | nodes first, before moving to the next level neighbors. 8 | 9 | ![Algorithm Visualization](https://upload.wikimedia.org/wikipedia/commons/5/5d/Breadth-First-Search-Algorithm.gif) 10 | 11 | ## References 12 | 13 | - [Wikipedia](https://en.wikipedia.org/wiki/Breadth-first_search) 14 | - [Tree Traversals (Inorder, Preorder and Postorder)](https://www.geeksforgeeks.org/tree-traversals-inorder-preorder-and-postorder/) 15 | - [BFS vs DFS](https://www.geeksforgeeks.org/bfs-vs-dfs-binary-tree/) 16 | -------------------------------------------------------------------------------- /solutions/algorithms/search/breadth-first-search/__test__/breadthFirstSearch.test.js: -------------------------------------------------------------------------------- 1 | import BinaryTreeNode from '../../../../data-structures/binary-search-tree/BinaryTreeNode'; 2 | import breadthFirstSearch from '../breadthFirstSearch'; 3 | 4 | describe('breadthFirstSearch', () => { 5 | it('should perform DFS operation on tree', () => { 6 | const nodeA = new BinaryTreeNode('A'); 7 | const nodeB = new BinaryTreeNode('B'); 8 | const nodeC = new BinaryTreeNode('C'); 9 | const nodeD = new BinaryTreeNode('D'); 10 | const nodeE = new BinaryTreeNode('E'); 11 | const nodeF = new BinaryTreeNode('F'); 12 | const nodeG = new BinaryTreeNode('G'); 13 | 14 | nodeA.setLeft(nodeB).setRight(nodeC); 15 | nodeB.setLeft(nodeD).setRight(nodeE); 16 | nodeC.setLeft(nodeF).setRight(nodeG); 17 | 18 | // In-order traversing. 19 | expect(nodeA.toString()).toBe('D,B,E,A,F,C,G'); 20 | 21 | const enterNodeCallback = jest.fn(); 22 | const leaveNodeCallback = jest.fn(); 23 | 24 | // Traverse tree without callbacks first to check default ones. 25 | breadthFirstSearch(nodeA); 26 | 27 | // Traverse tree with callbacks. 28 | breadthFirstSearch(nodeA, { 29 | enterNode: enterNodeCallback, 30 | leaveNode: leaveNodeCallback, 31 | }); 32 | 33 | expect(enterNodeCallback).toHaveBeenCalledTimes(7); 34 | expect(leaveNodeCallback).toHaveBeenCalledTimes(7); 35 | 36 | // Check node entering. 37 | expect(enterNodeCallback.mock.calls[0][0].value).toEqual('A'); 38 | expect(enterNodeCallback.mock.calls[1][0].value).toEqual('B'); 39 | expect(enterNodeCallback.mock.calls[2][0].value).toEqual('C'); 40 | expect(enterNodeCallback.mock.calls[3][0].value).toEqual('D'); 41 | expect(enterNodeCallback.mock.calls[4][0].value).toEqual('E'); 42 | expect(enterNodeCallback.mock.calls[5][0].value).toEqual('F'); 43 | expect(enterNodeCallback.mock.calls[6][0].value).toEqual('G'); 44 | 45 | // Check node leaving. 46 | expect(leaveNodeCallback.mock.calls[0][0].value).toEqual('A'); 47 | expect(leaveNodeCallback.mock.calls[1][0].value).toEqual('B'); 48 | expect(leaveNodeCallback.mock.calls[2][0].value).toEqual('C'); 49 | expect(leaveNodeCallback.mock.calls[3][0].value).toEqual('D'); 50 | expect(leaveNodeCallback.mock.calls[4][0].value).toEqual('E'); 51 | expect(leaveNodeCallback.mock.calls[5][0].value).toEqual('F'); 52 | expect(leaveNodeCallback.mock.calls[6][0].value).toEqual('G'); 53 | }); 54 | 55 | it('allow users to redefine node visiting logic', () => { 56 | const nodeA = new BinaryTreeNode('A'); 57 | const nodeB = new BinaryTreeNode('B'); 58 | const nodeC = new BinaryTreeNode('C'); 59 | const nodeD = new BinaryTreeNode('D'); 60 | const nodeE = new BinaryTreeNode('E'); 61 | const nodeF = new BinaryTreeNode('F'); 62 | const nodeG = new BinaryTreeNode('G'); 63 | 64 | nodeA.setLeft(nodeB).setRight(nodeC); 65 | nodeB.setLeft(nodeD).setRight(nodeE); 66 | nodeC.setLeft(nodeF).setRight(nodeG); 67 | 68 | // In-order traversing. 69 | expect(nodeA.toString()).toBe('D,B,E,A,F,C,G'); 70 | 71 | const enterNodeCallback = jest.fn(); 72 | const leaveNodeCallback = jest.fn(); 73 | 74 | // Traverse tree without callbacks first to check default ones. 75 | breadthFirstSearch(nodeA); 76 | 77 | // Traverse tree with callbacks. 78 | breadthFirstSearch(nodeA, { 79 | allowTraversal: (node, child) => { 80 | // Forbid traversing left half of the tree. 81 | return child.value !== 'B'; 82 | }, 83 | enterNode: enterNodeCallback, 84 | leaveNode: leaveNodeCallback, 85 | }); 86 | 87 | expect(enterNodeCallback).toHaveBeenCalledTimes(4); 88 | expect(leaveNodeCallback).toHaveBeenCalledTimes(4); 89 | 90 | // Check node entering. 91 | expect(enterNodeCallback.mock.calls[0][0].value).toEqual('A'); 92 | expect(enterNodeCallback.mock.calls[1][0].value).toEqual('C'); 93 | expect(enterNodeCallback.mock.calls[2][0].value).toEqual('F'); 94 | expect(enterNodeCallback.mock.calls[3][0].value).toEqual('G'); 95 | 96 | // Check node leaving. 97 | expect(leaveNodeCallback.mock.calls[0][0].value).toEqual('A'); 98 | expect(leaveNodeCallback.mock.calls[1][0].value).toEqual('C'); 99 | expect(leaveNodeCallback.mock.calls[2][0].value).toEqual('F'); 100 | expect(leaveNodeCallback.mock.calls[3][0].value).toEqual('G'); 101 | }); 102 | }); 103 | -------------------------------------------------------------------------------- /solutions/algorithms/search/breadth-first-search/breadthFirstSearch.js: -------------------------------------------------------------------------------- 1 | import Queue from '../../../data-structures/queue/Queue'; 2 | 3 | /** 4 | * @typedef {Object} Callbacks 5 | * @property {function(node: BinaryTreeNode, child: BinaryTreeNode): boolean} allowTraversal - 6 | * Determines whether DFS should traverse from the node to its child. 7 | * @property {function(node: BinaryTreeNode)} enterNode - Called when DFS enters the node. 8 | * @property {function(node: BinaryTreeNode)} leaveNode - Called when DFS leaves the node. 9 | */ 10 | 11 | /** 12 | * @param {Callbacks} [callbacks] 13 | * @returns {Callbacks} 14 | */ 15 | function initCallbacks(callbacks = {}) { 16 | const initiatedCallback = callbacks; 17 | 18 | const stubCallback = () => {}; 19 | const defaultAllowTraversal = () => true; 20 | 21 | initiatedCallback.allowTraversal = callbacks.allowTraversal || defaultAllowTraversal; 22 | initiatedCallback.enterNode = callbacks.enterNode || stubCallback; 23 | initiatedCallback.leaveNode = callbacks.leaveNode || stubCallback; 24 | 25 | return initiatedCallback; 26 | } 27 | 28 | /** 29 | * @param {BinaryTreeNode} rootNode 30 | * @param {Callbacks} [originalCallbacks] 31 | */ 32 | export default function breadthFirstSearch(rootNode, originalCallbacks) { 33 | const callbacks = initCallbacks(originalCallbacks); 34 | const nodeQueue = new Queue(); 35 | 36 | // Do initial queue setup. 37 | nodeQueue.enqueue(rootNode); 38 | 39 | while (!nodeQueue.isEmpty()) { 40 | const currentNode = nodeQueue.dequeue(); 41 | 42 | callbacks.enterNode(currentNode); 43 | 44 | // Add all children to the queue for future traversals. 45 | 46 | // Traverse left branch. 47 | if (currentNode.left && callbacks.allowTraversal(currentNode, currentNode.left)) { 48 | nodeQueue.enqueue(currentNode.left); 49 | } 50 | 51 | // Traverse right branch. 52 | if (currentNode.right && callbacks.allowTraversal(currentNode, currentNode.right)) { 53 | nodeQueue.enqueue(currentNode.right); 54 | } 55 | 56 | callbacks.leaveNode(currentNode); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /solutions/algorithms/search/depth-first-search/README.md: -------------------------------------------------------------------------------- 1 | # Depth-First Search (DFS) 2 | 3 | Depth-first search (DFS) is an algorithm for traversing or 4 | searching tree or graph data structures. One starts at 5 | the root (selecting some arbitrary node as the root in 6 | the case of a graph) and explores as far as possible 7 | along each branch before backtracking. 8 | 9 | ![Algorithm Visualization](https://upload.wikimedia.org/wikipedia/commons/7/7f/Depth-First-Search.gif) 10 | 11 | ## References 12 | 13 | - [Wikipedia](https://en.wikipedia.org/wiki/Depth-first_search) 14 | - [Tree Traversals (Inorder, Preorder and Postorder)](https://www.geeksforgeeks.org/tree-traversals-inorder-preorder-and-postorder/) 15 | - [BFS vs DFS](https://www.geeksforgeeks.org/bfs-vs-dfs-binary-tree/) 16 | -------------------------------------------------------------------------------- /solutions/algorithms/search/depth-first-search/__test__/depthFirstSearch.test.js: -------------------------------------------------------------------------------- 1 | import BinaryTreeNode from '../../../../data-structures/tree/BinaryTreeNode'; 2 | import depthFirstSearch from '../depthFirstSearch'; 3 | 4 | describe('depthFirstSearch', () => { 5 | it('should perform DFS operation on tree', () => { 6 | const nodeA = new BinaryTreeNode('A'); 7 | const nodeB = new BinaryTreeNode('B'); 8 | const nodeC = new BinaryTreeNode('C'); 9 | const nodeD = new BinaryTreeNode('D'); 10 | const nodeE = new BinaryTreeNode('E'); 11 | const nodeF = new BinaryTreeNode('F'); 12 | const nodeG = new BinaryTreeNode('G'); 13 | 14 | nodeA.setLeft(nodeB).setRight(nodeC); 15 | nodeB.setLeft(nodeD).setRight(nodeE); 16 | nodeC.setLeft(nodeF).setRight(nodeG); 17 | 18 | // In-order traversing. 19 | expect(nodeA.toString()).toBe('D,B,E,A,F,C,G'); 20 | 21 | const enterNodeCallback = jest.fn(); 22 | const leaveNodeCallback = jest.fn(); 23 | 24 | // Traverse tree without callbacks first to check default ones. 25 | depthFirstSearch(nodeA); 26 | 27 | // Traverse tree with callbacks. 28 | depthFirstSearch(nodeA, { 29 | enterNode: enterNodeCallback, 30 | leaveNode: leaveNodeCallback, 31 | }); 32 | 33 | expect(enterNodeCallback).toHaveBeenCalledTimes(7); 34 | expect(leaveNodeCallback).toHaveBeenCalledTimes(7); 35 | 36 | // Check node entering. 37 | expect(enterNodeCallback.mock.calls[0][0].value).toEqual('A'); 38 | expect(enterNodeCallback.mock.calls[1][0].value).toEqual('B'); 39 | expect(enterNodeCallback.mock.calls[2][0].value).toEqual('D'); 40 | expect(enterNodeCallback.mock.calls[3][0].value).toEqual('E'); 41 | expect(enterNodeCallback.mock.calls[4][0].value).toEqual('C'); 42 | expect(enterNodeCallback.mock.calls[5][0].value).toEqual('F'); 43 | expect(enterNodeCallback.mock.calls[6][0].value).toEqual('G'); 44 | 45 | // Check node leaving. 46 | expect(leaveNodeCallback.mock.calls[0][0].value).toEqual('D'); 47 | expect(leaveNodeCallback.mock.calls[1][0].value).toEqual('E'); 48 | expect(leaveNodeCallback.mock.calls[2][0].value).toEqual('B'); 49 | expect(leaveNodeCallback.mock.calls[3][0].value).toEqual('F'); 50 | expect(leaveNodeCallback.mock.calls[4][0].value).toEqual('G'); 51 | expect(leaveNodeCallback.mock.calls[5][0].value).toEqual('C'); 52 | expect(leaveNodeCallback.mock.calls[6][0].value).toEqual('A'); 53 | }); 54 | 55 | it('allow users to redefine node visiting logic', () => { 56 | const nodeA = new BinaryTreeNode('A'); 57 | const nodeB = new BinaryTreeNode('B'); 58 | const nodeC = new BinaryTreeNode('C'); 59 | const nodeD = new BinaryTreeNode('D'); 60 | const nodeE = new BinaryTreeNode('E'); 61 | const nodeF = new BinaryTreeNode('F'); 62 | const nodeG = new BinaryTreeNode('G'); 63 | 64 | nodeA.setLeft(nodeB).setRight(nodeC); 65 | nodeB.setLeft(nodeD).setRight(nodeE); 66 | nodeC.setLeft(nodeF).setRight(nodeG); 67 | 68 | // In-order traversing. 69 | expect(nodeA.toString()).toBe('D,B,E,A,F,C,G'); 70 | 71 | const enterNodeCallback = jest.fn(); 72 | const leaveNodeCallback = jest.fn(); 73 | 74 | // Traverse tree without callbacks first to check default ones. 75 | depthFirstSearch(nodeA); 76 | 77 | // Traverse tree with callbacks. 78 | depthFirstSearch(nodeA, { 79 | allowTraversal: (node, child) => { 80 | // Forbid traversing left part of the tree. 81 | return child.value !== 'B'; 82 | }, 83 | enterNode: enterNodeCallback, 84 | leaveNode: leaveNodeCallback, 85 | }); 86 | 87 | expect(enterNodeCallback).toHaveBeenCalledTimes(4); 88 | expect(leaveNodeCallback).toHaveBeenCalledTimes(4); 89 | 90 | // Check node entering. 91 | expect(enterNodeCallback.mock.calls[0][0].value).toEqual('A'); 92 | expect(enterNodeCallback.mock.calls[1][0].value).toEqual('C'); 93 | expect(enterNodeCallback.mock.calls[2][0].value).toEqual('F'); 94 | expect(enterNodeCallback.mock.calls[3][0].value).toEqual('G'); 95 | 96 | // Check node leaving. 97 | expect(leaveNodeCallback.mock.calls[0][0].value).toEqual('F'); 98 | expect(leaveNodeCallback.mock.calls[1][0].value).toEqual('G'); 99 | expect(leaveNodeCallback.mock.calls[2][0].value).toEqual('C'); 100 | expect(leaveNodeCallback.mock.calls[3][0].value).toEqual('A'); 101 | }); 102 | }); 103 | -------------------------------------------------------------------------------- /solutions/algorithms/search/depth-first-search/depthFirstSearch.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @typedef {Object} Callbacks 3 | * @property {function(node: BinaryTreeNode, child: BinaryTreeNode): boolean} allowTraversal - 4 | * Determines whether DFS should traverse from the node to its child. 5 | * @property {function(node: BinaryTreeNode)} enterNode - Called when DFS enters the node. 6 | * @property {function(node: BinaryTreeNode)} leaveNode - Called when DFS leaves the node. 7 | */ 8 | 9 | /** 10 | * @param {Callbacks} [callbacks] 11 | * @returns {Callbacks} 12 | */ 13 | function initCallbacks(callbacks = {}) { 14 | const initiatedCallback = callbacks; 15 | 16 | const stubCallback = () => {}; 17 | const defaultAllowTraversal = () => true; 18 | 19 | initiatedCallback.allowTraversal = callbacks.allowTraversal || defaultAllowTraversal; 20 | initiatedCallback.enterNode = callbacks.enterNode || stubCallback; 21 | initiatedCallback.leaveNode = callbacks.leaveNode || stubCallback; 22 | 23 | return initiatedCallback; 24 | } 25 | 26 | /** 27 | * @param {BinaryTreeNode} node 28 | * @param {Callbacks} callbacks 29 | */ 30 | export function depthFirstSearchRecursive(node, callbacks) { 31 | callbacks.enterNode(node); 32 | 33 | // Traverse left branch. 34 | if (node.left && callbacks.allowTraversal(node, node.left)) { 35 | depthFirstSearchRecursive(node.left, callbacks); 36 | } 37 | 38 | // Traverse right branch. 39 | if (node.right && callbacks.allowTraversal(node, node.right)) { 40 | depthFirstSearchRecursive(node.right, callbacks); 41 | } 42 | 43 | callbacks.leaveNode(node); 44 | } 45 | 46 | /** 47 | * @param {BinaryTreeNode} rootNode 48 | * @param {Callbacks} [callbacks] 49 | */ 50 | export default function depthFirstSearch(rootNode, callbacks) { 51 | depthFirstSearchRecursive(rootNode, initCallbacks(callbacks)); 52 | } 53 | -------------------------------------------------------------------------------- /solutions/algorithms/search/linear-search/README.md: -------------------------------------------------------------------------------- 1 | # Linear Search 2 | In computer science, linear search or sequential search is a 3 | method for finding a target value within a list. It sequentially 4 | checks each element of the list for the target value until a 5 | match is found or until all the elements have been searched. 6 | Linear search runs in at worst linear time and makes at most `n` 7 | comparisons, where `n` is the length of the list. 8 | 9 | ![Linear Search](https://www.tutorialspoint.com/data_structures_algorithms/images/linear_search.gif) 10 | 11 | ## References 12 | - [Wikipedia](https://en.wikipedia.org/wiki/Linear_search) 13 | - [TutorialsPoint](https://www.tutorialspoint.com/data_structures_algorithms/linear_search_algorithm.htm) 14 | - [Youtube](https://www.youtube.com/watch?v=SGU9duLE30w) 15 | -------------------------------------------------------------------------------- /solutions/algorithms/search/linear-search/__test__/linearSearch.test.js: -------------------------------------------------------------------------------- 1 | import linearSearch from '../linearSearch'; 2 | 3 | describe('linearSearch', () => { 4 | it('should search all numbers in array', () => { 5 | const array = [1, 2, 4, 6, 2]; 6 | 7 | expect(linearSearch(array, 10)).toEqual([]); 8 | expect(linearSearch(array, 1)).toEqual([0]); 9 | expect(linearSearch(array, 2)).toEqual([1, 4]); 10 | }); 11 | 12 | it('should search all strings in array', () => { 13 | const array = ['a', 'b', 'a']; 14 | 15 | expect(linearSearch(array, 'c')).toEqual([]); 16 | expect(linearSearch(array, 'b')).toEqual([1]); 17 | expect(linearSearch(array, 'a')).toEqual([0, 2]); 18 | }); 19 | 20 | it('should search through objects as well', () => { 21 | const comparatorCallback = (a, b) => { 22 | if (a.key === b.key) { 23 | return 0; 24 | } 25 | 26 | return a.key <= b.key ? -1 : 1; 27 | }; 28 | 29 | const array = [ 30 | { key: 5 }, 31 | { key: 6 }, 32 | { key: 7 }, 33 | { key: 6 }, 34 | ]; 35 | 36 | expect(linearSearch(array, { key: 10 }, comparatorCallback)).toEqual([]); 37 | expect(linearSearch(array, { key: 5 }, comparatorCallback)).toEqual([0]); 38 | expect(linearSearch(array, { key: 6 }, comparatorCallback)).toEqual([1, 3]); 39 | }); 40 | }); 41 | -------------------------------------------------------------------------------- /solutions/algorithms/search/linear-search/linearSearch.js: -------------------------------------------------------------------------------- 1 | import Comparator from '../../../utils/comparator/Comparator'; 2 | 3 | /** 4 | * @param {*[]} array 5 | * @param {*} seekElement 6 | * @param {function(a, b)} [comparatorCallback] 7 | * @return {number[]} 8 | */ 9 | export default function linearSearch(array, seekElement, comparatorCallback) { 10 | const comparator = new Comparator(comparatorCallback); 11 | const foundIndices = []; 12 | 13 | array.forEach((element, index) => { 14 | if (comparator.equal(element, seekElement)) { 15 | foundIndices.push(index); 16 | } 17 | }); 18 | 19 | return foundIndices; 20 | } 21 | -------------------------------------------------------------------------------- /solutions/algorithms/sorting/Sort.js: -------------------------------------------------------------------------------- 1 | import Comparator from '../../utils/comparator/Comparator'; 2 | 3 | /** 4 | * @typedef {Object} SorterCallbacks 5 | * @property {function(a: *, b: *)} compareCallback - If provided then all elements comparisons 6 | * will be done through this callback. 7 | * @property {function(a: *)} visitingCallback - If provided it will be called each time the sorting 8 | * function is visiting the next element. 9 | */ 10 | 11 | export default class Sort { 12 | constructor(originalCallbacks) { 13 | this.callbacks = Sort.initSortingCallbacks(originalCallbacks); 14 | this.comparator = new Comparator(this.callbacks.compareCallback); 15 | } 16 | 17 | /** 18 | * @param {SorterCallbacks} originalCallbacks 19 | * @returns {SorterCallbacks} 20 | */ 21 | static initSortingCallbacks(originalCallbacks) { 22 | const callbacks = originalCallbacks || {}; 23 | const stubCallback = () => {}; 24 | 25 | callbacks.compareCallback = callbacks.compareCallback || undefined; 26 | callbacks.visitingCallback = callbacks.visitingCallback || stubCallback; 27 | 28 | return callbacks; 29 | } 30 | 31 | sort() { 32 | throw new Error('sort method must be implemented'); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /solutions/algorithms/sorting/SortTester.js: -------------------------------------------------------------------------------- 1 | export const sortedArr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20]; 2 | export const reverseArr = [20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1]; 3 | export const notSortedArr = [15, 8, 5, 12, 10, 1, 16, 9, 11, 7, 20, 3, 2, 6, 17, 18, 4, 13, 14, 19]; 4 | export const equalArr = [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]; 5 | export const negativeArr = [-1, 0, 5, -10, 20, 13, -7, 3, 2, -3]; 6 | export const negativeArrSorted = [-10, -7, -3, -1, 0, 2, 3, 5, 13, 20]; 7 | 8 | export class SortTester { 9 | static testSort(SortingClass) { 10 | const sorter = new SortingClass(); 11 | 12 | expect(sorter.sort([])).toEqual([]); 13 | expect(sorter.sort([1])).toEqual([1]); 14 | expect(sorter.sort([1, 2])).toEqual([1, 2]); 15 | expect(sorter.sort([2, 1])).toEqual([1, 2]); 16 | expect(sorter.sort([3, 4, 2, 1, 0, 0, 4, 3, 4, 2])).toEqual([0, 0, 1, 2, 2, 3, 3, 4, 4, 4]); 17 | expect(sorter.sort(sortedArr)).toEqual(sortedArr); 18 | expect(sorter.sort(reverseArr)).toEqual(sortedArr); 19 | expect(sorter.sort(notSortedArr)).toEqual(sortedArr); 20 | expect(sorter.sort(equalArr)).toEqual(equalArr); 21 | } 22 | 23 | static testNegativeNumbersSort(SortingClass) { 24 | const sorter = new SortingClass(); 25 | expect(sorter.sort(negativeArr)).toEqual(negativeArrSorted); 26 | } 27 | 28 | static testSortWithCustomComparator(SortingClass) { 29 | const callbacks = { 30 | compareCallback: (a, b) => { 31 | if (a.length === b.length) { 32 | return 0; 33 | } 34 | return a.length < b.length ? -1 : 1; 35 | }, 36 | }; 37 | 38 | const sorter = new SortingClass(callbacks); 39 | 40 | expect(sorter.sort([''])).toEqual(['']); 41 | expect(sorter.sort(['a'])).toEqual(['a']); 42 | expect(sorter.sort(['aa', 'a'])).toEqual(['a', 'aa']); 43 | expect(sorter.sort(['aa', 'q', 'bbbb', 'ccc'])).toEqual(['q', 'aa', 'ccc', 'bbbb']); 44 | expect(sorter.sort(['aa', 'aa'])).toEqual(['aa', 'aa']); 45 | } 46 | 47 | static testSortStability(SortingClass) { 48 | const callbacks = { 49 | compareCallback: (a, b) => { 50 | if (a.length === b.length) { 51 | return 0; 52 | } 53 | return a.length < b.length ? -1 : 1; 54 | }, 55 | }; 56 | 57 | const sorter = new SortingClass(callbacks); 58 | 59 | expect(sorter.sort(['bb', 'aa', 'c'])).toEqual(['c', 'bb', 'aa']); 60 | expect(sorter.sort(['aa', 'q', 'a', 'bbbb', 'ccc'])).toEqual(['q', 'a', 'aa', 'ccc', 'bbbb']); 61 | } 62 | 63 | static testAlgorithmTimeComplexity(SortingClass, arrayToBeSorted, numberOfVisits) { 64 | const visitingCallback = jest.fn(); 65 | const callbacks = { visitingCallback }; 66 | const sorter = new SortingClass(callbacks); 67 | 68 | sorter.sort(arrayToBeSorted); 69 | 70 | expect(visitingCallback).toHaveBeenCalledTimes(numberOfVisits); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /solutions/algorithms/sorting/__test__/Sort.test.js: -------------------------------------------------------------------------------- 1 | import Sort from '../Sort'; 2 | 3 | describe('Sort', () => { 4 | it('should throw an error when trying to call Sort.sort() method directly', () => { 5 | function doForbiddenSort() { 6 | const sorter = new Sort(); 7 | sorter.sort(); 8 | } 9 | 10 | expect(doForbiddenSort).toThrow(); 11 | }); 12 | }); 13 | -------------------------------------------------------------------------------- /solutions/algorithms/sorting/bubble-sort/BubbleSort.js: -------------------------------------------------------------------------------- 1 | import Sort from '../Sort'; 2 | 3 | export default class BubbleSort extends Sort { 4 | sort(originalArray) { 5 | // Flag that holds info about whether the swap has occur or not. 6 | let swapped = false; 7 | // Clone original array to prevent its modification. 8 | const array = [...originalArray]; 9 | 10 | for (let i = 1; i < array.length; i += 1) { 11 | swapped = false; 12 | 13 | // Call visiting callback. 14 | this.callbacks.visitingCallback(array[i]); 15 | 16 | for (let j = 0; j < array.length - i; j += 1) { 17 | // Call visiting callback. 18 | this.callbacks.visitingCallback(array[j]); 19 | 20 | // Swap elements if they are in wrong order. 21 | if (this.comparator.lessThan(array[j + 1], array[j])) { 22 | const tmp = array[j + 1]; 23 | array[j + 1] = array[j]; 24 | array[j] = tmp; 25 | 26 | // Register the swap. 27 | swapped = true; 28 | } 29 | } 30 | 31 | // If there were no swaps then array is already sorted and there is 32 | // no need to proceed. 33 | if (!swapped) { 34 | return array; 35 | } 36 | } 37 | 38 | return array; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /solutions/algorithms/sorting/bubble-sort/README.md: -------------------------------------------------------------------------------- 1 | # Bubble Sort 2 | 3 | Bubble sort, sometimes referred to as sinking sort, is a 4 | simple sorting algorithm that repeatedly steps through 5 | the list to be sorted, compares each pair of adjacent 6 | items and swaps them if they are in the wrong order. 7 | The pass through the list is repeated until no swaps 8 | are needed, which indicates that the list is sorted. 9 | 10 | ![Algorithm Visualization](https://upload.wikimedia.org/wikipedia/commons/c/c8/Bubble-sort-example-300px.gif) 11 | 12 | ## Complexity 13 | 14 | | Name | Best | Average | Worst | Memory | Stable | Comments | 15 | | --------------------- | :-------------: | :-----------------: | :-----------------: | :-------: | :-------: | :-------- | 16 | | **Bubble sort** | n | n2 | n2 | 1 | Yes | | 17 | 18 | ## References 19 | 20 | - [Wikipedia](https://en.wikipedia.org/wiki/Bubble_sort) 21 | - [YouTube](https://www.youtube.com/watch?v=6Gv8vg0kcHc&index=27&t=0s&list=PLLXdhg_r2hKA7DPDsunoDZ-Z769jWn4R8) 22 | -------------------------------------------------------------------------------- /solutions/algorithms/sorting/bubble-sort/__test__/BubbleSort.test.js: -------------------------------------------------------------------------------- 1 | import BubbleSort from '../BubbleSort'; 2 | import { 3 | equalArr, 4 | notSortedArr, 5 | reverseArr, 6 | sortedArr, 7 | SortTester, 8 | } from '../../SortTester'; 9 | 10 | // Complexity constants. 11 | const SORTED_ARRAY_VISITING_COUNT = 20; 12 | const NOT_SORTED_ARRAY_VISITING_COUNT = 189; 13 | const REVERSE_SORTED_ARRAY_VISITING_COUNT = 209; 14 | const EQUAL_ARRAY_VISITING_COUNT = 20; 15 | 16 | describe('BubbleSort', () => { 17 | it('should sort array', () => { 18 | SortTester.testSort(BubbleSort); 19 | }); 20 | 21 | it('should sort array with custom comparator', () => { 22 | SortTester.testSortWithCustomComparator(BubbleSort); 23 | }); 24 | 25 | it('should do stable sorting', () => { 26 | SortTester.testSortStability(BubbleSort); 27 | }); 28 | 29 | it('should sort negative numbers', () => { 30 | SortTester.testNegativeNumbersSort(BubbleSort); 31 | }); 32 | 33 | it('should visit EQUAL array element specified number of times', () => { 34 | SortTester.testAlgorithmTimeComplexity( 35 | BubbleSort, 36 | equalArr, 37 | EQUAL_ARRAY_VISITING_COUNT, 38 | ); 39 | }); 40 | 41 | it('should visit SORTED array element specified number of times', () => { 42 | SortTester.testAlgorithmTimeComplexity( 43 | BubbleSort, 44 | sortedArr, 45 | SORTED_ARRAY_VISITING_COUNT, 46 | ); 47 | }); 48 | 49 | it('should visit NOT SORTED array element specified number of times', () => { 50 | SortTester.testAlgorithmTimeComplexity( 51 | BubbleSort, 52 | notSortedArr, 53 | NOT_SORTED_ARRAY_VISITING_COUNT, 54 | ); 55 | }); 56 | 57 | it('should visit REVERSE SORTED array element specified number of times', () => { 58 | SortTester.testAlgorithmTimeComplexity( 59 | BubbleSort, 60 | reverseArr, 61 | REVERSE_SORTED_ARRAY_VISITING_COUNT, 62 | ); 63 | }); 64 | }); 65 | -------------------------------------------------------------------------------- /solutions/algorithms/sorting/insertion-sort/InsertionSort.js: -------------------------------------------------------------------------------- 1 | import Sort from '../Sort'; 2 | 3 | export default class InsertionSort extends Sort { 4 | sort(originalArray) { 5 | const array = [...originalArray]; 6 | 7 | // Go through all array elements... 8 | for (let i = 0; i < array.length; i += 1) { 9 | let currentIndex = i; 10 | 11 | // Call visiting callback. 12 | this.callbacks.visitingCallback(array[i]); 13 | 14 | // Go and check if previous elements and greater then current one. 15 | // If this is the case then swap that elements. 16 | while ( 17 | array[currentIndex - 1] !== undefined 18 | && this.comparator.lessThan(array[currentIndex], array[currentIndex - 1]) 19 | ) { 20 | // Call visiting callback. 21 | this.callbacks.visitingCallback(array[currentIndex - 1]); 22 | 23 | // Swap the elements. 24 | const tmp = array[currentIndex - 1]; 25 | array[currentIndex - 1] = array[currentIndex]; 26 | array[currentIndex] = tmp; 27 | 28 | // Shift current index left. 29 | currentIndex -= 1; 30 | } 31 | } 32 | 33 | return array; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /solutions/algorithms/sorting/insertion-sort/README.md: -------------------------------------------------------------------------------- 1 | # Insertion Sort 2 | 3 | Insertion sort is a simple sorting algorithm that builds 4 | the final sorted array (or list) one item at a time. 5 | It is much less efficient on large lists than more 6 | advanced algorithms such as quicksort, heapsort, or merge 7 | sort. 8 | 9 | ![Algorithm Visualization](https://upload.wikimedia.org/wikipedia/commons/4/42/Insertion_sort.gif) 10 | 11 | ![Algorithm Visualization](https://upload.wikimedia.org/wikipedia/commons/0/0f/Insertion-sort-example-300px.gif) 12 | 13 | ## Complexity 14 | 15 | | Name | Best | Average | Worst | Memory | Stable | Comments | 16 | | --------------------- | :-------------: | :-----------------: | :-----------------: | :-------: | :-------: | :-------- | 17 | | **Insertion sort** | n | n2 | n2 | 1 | Yes | | 18 | 19 | ## References 20 | 21 | [Wikipedia](https://en.wikipedia.org/wiki/Insertion_sort) 22 | -------------------------------------------------------------------------------- /solutions/algorithms/sorting/insertion-sort/__test__/InsertionSort.test.js: -------------------------------------------------------------------------------- 1 | import InsertionSort from '../InsertionSort'; 2 | import { 3 | equalArr, 4 | notSortedArr, 5 | reverseArr, 6 | sortedArr, 7 | SortTester, 8 | } from '../../SortTester'; 9 | 10 | // Complexity constants. 11 | const SORTED_ARRAY_VISITING_COUNT = 20; 12 | const NOT_SORTED_ARRAY_VISITING_COUNT = 101; 13 | const REVERSE_SORTED_ARRAY_VISITING_COUNT = 210; 14 | const EQUAL_ARRAY_VISITING_COUNT = 20; 15 | 16 | describe('InsertionSort', () => { 17 | it('should sort array', () => { 18 | SortTester.testSort(InsertionSort); 19 | }); 20 | 21 | it('should sort array with custom comparator', () => { 22 | SortTester.testSortWithCustomComparator(InsertionSort); 23 | }); 24 | 25 | it('should do stable sorting', () => { 26 | SortTester.testSortStability(InsertionSort); 27 | }); 28 | 29 | it('should sort negative numbers', () => { 30 | SortTester.testNegativeNumbersSort(InsertionSort); 31 | }); 32 | 33 | it('should visit EQUAL array element specified number of times', () => { 34 | SortTester.testAlgorithmTimeComplexity( 35 | InsertionSort, 36 | equalArr, 37 | EQUAL_ARRAY_VISITING_COUNT, 38 | ); 39 | }); 40 | 41 | it('should visit SORTED array element specified number of times', () => { 42 | SortTester.testAlgorithmTimeComplexity( 43 | InsertionSort, 44 | sortedArr, 45 | SORTED_ARRAY_VISITING_COUNT, 46 | ); 47 | }); 48 | 49 | it('should visit NOT SORTED array element specified number of times', () => { 50 | SortTester.testAlgorithmTimeComplexity( 51 | InsertionSort, 52 | notSortedArr, 53 | NOT_SORTED_ARRAY_VISITING_COUNT, 54 | ); 55 | }); 56 | 57 | it('should visit REVERSE SORTED array element specified number of times', () => { 58 | SortTester.testAlgorithmTimeComplexity( 59 | InsertionSort, 60 | reverseArr, 61 | REVERSE_SORTED_ARRAY_VISITING_COUNT, 62 | ); 63 | }); 64 | }); 65 | -------------------------------------------------------------------------------- /solutions/algorithms/sorting/merge-sort/MergeSort.js: -------------------------------------------------------------------------------- 1 | import Sort from '../Sort'; 2 | 3 | export default class MergeSort extends Sort { 4 | sort(originalArray) { 5 | // Call visiting callback. 6 | this.callbacks.visitingCallback(null); 7 | 8 | // If array is empty or consists of one element then return this array since it is sorted. 9 | if (originalArray.length <= 1) { 10 | return originalArray; 11 | } 12 | 13 | // Split array on two halves. 14 | const middleIndex = Math.floor(originalArray.length / 2); 15 | const leftArray = originalArray.slice(0, middleIndex); 16 | const rightArray = originalArray.slice(middleIndex, originalArray.length); 17 | 18 | // Sort two halves of split array 19 | const leftSortedArray = this.sort(leftArray); 20 | const rightSortedArray = this.sort(rightArray); 21 | 22 | // Merge two sorted arrays into one. 23 | return this.mergeSortedArrays(leftSortedArray, rightSortedArray); 24 | } 25 | 26 | mergeSortedArrays(leftArray, rightArray) { 27 | let sortedArray = []; 28 | 29 | // In case if arrays are not of size 1. 30 | while (leftArray.length && rightArray.length) { 31 | let minimumElement = null; 32 | 33 | // Find minimum element of two arrays. 34 | if (this.comparator.lessThanOrEqual(leftArray[0], rightArray[0])) { 35 | minimumElement = leftArray.shift(); 36 | } else { 37 | minimumElement = rightArray.shift(); 38 | } 39 | 40 | // Call visiting callback. 41 | this.callbacks.visitingCallback(minimumElement); 42 | 43 | // Push the minimum element of two arrays to the sorted array. 44 | sortedArray.push(minimumElement); 45 | } 46 | 47 | // If one of two array still have elements we need to just concatenate 48 | // this element to the sorted array since it is already sorted. 49 | if (leftArray.length) { 50 | sortedArray = sortedArray.concat(leftArray); 51 | } 52 | 53 | if (rightArray.length) { 54 | sortedArray = sortedArray.concat(rightArray); 55 | } 56 | 57 | return sortedArray; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /solutions/algorithms/sorting/merge-sort/README.md: -------------------------------------------------------------------------------- 1 | # Merge Sort 2 | 3 | In computer science, merge sort (also commonly spelled 4 | mergesort) is an efficient, general-purpose, 5 | comparison-based sorting algorithm. Most implementations 6 | produce a stable sort, which means that the implementation 7 | preserves the input order of equal elements in the sorted 8 | output. Mergesort is a divide and conquer algorithm that 9 | was invented by John von Neumann in 1945. 10 | 11 | An example of merge sort. First divide the list into 12 | the smallest unit (1 element), then compare each 13 | element with the adjacent list to sort and merge the 14 | two adjacent lists. Finally all the elements are sorted 15 | and merged. 16 | 17 | ![Merge Sort](https://upload.wikimedia.org/wikipedia/commons/c/cc/Merge-sort-example-300px.gif) 18 | 19 | A recursive merge sort algorithm used to sort an array of 7 20 | integer values. These are the steps a human would take to 21 | emulate merge sort (top-down). 22 | 23 | ![Merge Sort](https://upload.wikimedia.org/wikipedia/commons/e/e6/Merge_sort_algorithm_diagram.svg) 24 | 25 | ## Complexity 26 | 27 | | Name | Best | Average | Worst | Memory | Stable | Comments | 28 | | --------------------- | :-------------: | :-----------------: | :-----------------: | :-------: | :-------: | :-------- | 29 | | **Merge sort** | n log(n) | n log(n) | n log(n) | n | Yes | | 30 | 31 | ## References 32 | 33 | - [Wikipedia](https://en.wikipedia.org/wiki/Merge_sort) 34 | - [YouTube](https://www.youtube.com/watch?v=KF2j-9iSf4Q&index=27&list=PLLXdhg_r2hKA7DPDsunoDZ-Z769jWn4R8) 35 | -------------------------------------------------------------------------------- /solutions/algorithms/sorting/merge-sort/__test__/MergeSort.test.js: -------------------------------------------------------------------------------- 1 | import MergeSort from '../MergeSort'; 2 | import { 3 | equalArr, 4 | notSortedArr, 5 | reverseArr, 6 | sortedArr, 7 | SortTester, 8 | } from '../../SortTester'; 9 | 10 | // Complexity constants. 11 | const SORTED_ARRAY_VISITING_COUNT = 79; 12 | const NOT_SORTED_ARRAY_VISITING_COUNT = 102; 13 | const REVERSE_SORTED_ARRAY_VISITING_COUNT = 87; 14 | const EQUAL_ARRAY_VISITING_COUNT = 79; 15 | 16 | describe('MergeSort', () => { 17 | it('should sort array', () => { 18 | SortTester.testSort(MergeSort); 19 | }); 20 | 21 | it('should sort array with custom comparator', () => { 22 | SortTester.testSortWithCustomComparator(MergeSort); 23 | }); 24 | 25 | it('should do stable sorting', () => { 26 | SortTester.testSortStability(MergeSort); 27 | }); 28 | 29 | it('should sort negative numbers', () => { 30 | SortTester.testNegativeNumbersSort(MergeSort); 31 | }); 32 | 33 | it('should visit EQUAL array element specified number of times', () => { 34 | SortTester.testAlgorithmTimeComplexity( 35 | MergeSort, 36 | equalArr, 37 | EQUAL_ARRAY_VISITING_COUNT, 38 | ); 39 | }); 40 | 41 | it('should visit SORTED array element specified number of times', () => { 42 | SortTester.testAlgorithmTimeComplexity( 43 | MergeSort, 44 | sortedArr, 45 | SORTED_ARRAY_VISITING_COUNT, 46 | ); 47 | }); 48 | 49 | it('should visit NOT SORTED array element specified number of times', () => { 50 | SortTester.testAlgorithmTimeComplexity( 51 | MergeSort, 52 | notSortedArr, 53 | NOT_SORTED_ARRAY_VISITING_COUNT, 54 | ); 55 | }); 56 | 57 | it('should visit REVERSE SORTED array element specified number of times', () => { 58 | SortTester.testAlgorithmTimeComplexity( 59 | MergeSort, 60 | reverseArr, 61 | REVERSE_SORTED_ARRAY_VISITING_COUNT, 62 | ); 63 | }); 64 | }); 65 | -------------------------------------------------------------------------------- /solutions/algorithms/sorting/quick-sort/QuickSort.js: -------------------------------------------------------------------------------- 1 | import Sort from '../Sort'; 2 | 3 | export default class QuickSort extends Sort { 4 | /** 5 | * @param {*[]} originalArray 6 | * @return {*[]} 7 | */ 8 | sort(originalArray) { 9 | // Clone original array to prevent it from modification. 10 | const array = [...originalArray]; 11 | 12 | // If array has less than or equal to one elements then it is already sorted. 13 | if (array.length <= 1) { 14 | return array; 15 | } 16 | 17 | // Init left and right arrays. 18 | const leftArray = []; 19 | const rightArray = []; 20 | 21 | // Take the first element of array as a pivot. 22 | const pivotElement = array.shift(); 23 | const centerArray = [pivotElement]; 24 | 25 | // Split all array elements between left, center and right arrays. 26 | while (array.length) { 27 | const currentElement = array.shift(); 28 | 29 | // Call visiting callback. 30 | this.callbacks.visitingCallback(currentElement); 31 | 32 | if (this.comparator.equal(currentElement, pivotElement)) { 33 | centerArray.push(currentElement); 34 | } else if (this.comparator.lessThan(currentElement, pivotElement)) { 35 | leftArray.push(currentElement); 36 | } else { 37 | rightArray.push(currentElement); 38 | } 39 | } 40 | 41 | // Sort left and right arrays. 42 | const leftArraySorted = this.sort(leftArray); 43 | const rightArraySorted = this.sort(rightArray); 44 | 45 | // Let's now join sorted left array with center array and with sorted right array. 46 | return leftArraySorted.concat(centerArray, rightArraySorted); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /solutions/algorithms/sorting/quick-sort/QuickSortInPlace.js: -------------------------------------------------------------------------------- 1 | import Sort from '../Sort'; 2 | 3 | export default class QuickSortInPlace extends Sort { 4 | /** Sorting in place avoids unnecessary use of additional memory, but modifies input array. 5 | * 6 | * This process is difficult to describe, but much clearer with a visualization: 7 | * @see: http://www.algomation.com/algorithm/quick-sort-visualization 8 | * 9 | * @param {*[]} originalArray 10 | * @param {number} inputLowIndex 11 | * @param {number} inputHighIndex 12 | * @return {*[]} 13 | */ 14 | sort(originalArray, inputLowIndex, inputHighIndex) { 15 | // Destructures array on initial passthrough, and then sorts in place. 16 | const array = inputLowIndex === undefined ? [...originalArray] : originalArray; 17 | 18 | /** 19 | * Partition array segment and return index of last swap 20 | * 21 | * @param {number} lowIndex 22 | * @param {number} highIndex 23 | * @return {number} 24 | */ 25 | const partition = (lowIndex, highIndex) => { 26 | /** 27 | * @param {number} leftIndex 28 | * @param {number} rightIndex 29 | */ 30 | const swap = (leftIndex, rightIndex) => { 31 | const tempVariable = array[leftIndex]; 32 | array[leftIndex] = array[rightIndex]; 33 | array[rightIndex] = tempVariable; 34 | }; 35 | 36 | const pivot = array[highIndex]; 37 | this.callbacks.visitingCallback(array[pivot]); 38 | 39 | let firstRunner = lowIndex - 1; 40 | for (let secondRunner = lowIndex; secondRunner < highIndex; secondRunner += 1) { 41 | if (this.comparator.lessThan(array[secondRunner], pivot)) { 42 | firstRunner += 1; 43 | swap(firstRunner, secondRunner); 44 | } 45 | } 46 | 47 | if (this.comparator.lessThan(pivot, array[firstRunner + 1])) { 48 | swap(firstRunner + 1, highIndex); 49 | } 50 | 51 | return firstRunner + 1; 52 | }; 53 | 54 | /* 55 | * While we can use a default parameter to set `low` to 0, we would 56 | * still have to set `high`'s default within the function as we 57 | * don't have access to `array.length - 1` when declaring parameters 58 | */ 59 | const lowIndex = inputLowIndex === undefined ? 0 : inputLowIndex; 60 | const highIndex = inputHighIndex === undefined ? array.length - 1 : inputHighIndex; 61 | 62 | // Base case is when low and high converge 63 | if (lowIndex < highIndex) { 64 | const partitionIndex = partition(lowIndex, highIndex); 65 | /* 66 | * `partition()` swaps elements of the array based on their comparison to the `hi` parameter, 67 | * and then returns the index where swapping is no longer necessary, which can be best thought 68 | * of as the pivot used to split an array in a non-in-place quicksort 69 | */ 70 | this.sort(array, lowIndex, partitionIndex - 1); 71 | this.sort(array, partitionIndex + 1, highIndex); 72 | } 73 | 74 | return array; 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /solutions/algorithms/sorting/quick-sort/README.md: -------------------------------------------------------------------------------- 1 | # Quicksort 2 | 3 | Quicksort is a divide and conquer algorithm. 4 | Quicksort first divides a large array into two smaller 5 | sub-arrays: the low elements and the high elements. 6 | Quicksort can then recursively sort the sub-arrays 7 | 8 | The steps are: 9 | 10 | 1. Pick an element, called a pivot, from the array. 11 | 2. Partitioning: reorder the array so that all elements with 12 | values less than the pivot come before the pivot, while all 13 | elements with values greater than the pivot come after it 14 | (equal values can go either way). After this partitioning, 15 | the pivot is in its final position. This is called the 16 | partition operation. 17 | 3. Recursively apply the above steps to the sub-array of 18 | elements with smaller values and separately to the 19 | sub-array of elements with greater values. 20 | 21 | Animated visualization of the quicksort algorithm. 22 | The horizontal lines are pivot values. 23 | 24 | ![Quicksort](https://upload.wikimedia.org/wikipedia/commons/6/6a/Sorting_quicksort_anim.gif) 25 | 26 | ## Complexity 27 | 28 | | Name | Best | Average | Worst | Memory | Stable | Comments | 29 | | --------------------- | :-------------: | :-----------------: | :-----------------: | :-------: | :-------: | :-------- | 30 | | **Quick sort** | n log(n) | n log(n) | n2 | log(n) | No | | 31 | 32 | ## References 33 | 34 | - [Wikipedia](https://en.wikipedia.org/wiki/Quicksort) 35 | - [YouTube](https://www.youtube.com/watch?v=SLauY6PpjW4&index=28&list=PLLXdhg_r2hKA7DPDsunoDZ-Z769jWn4R8) 36 | -------------------------------------------------------------------------------- /solutions/algorithms/sorting/quick-sort/__test__/QuickSort.test.js: -------------------------------------------------------------------------------- 1 | import QuickSort from '../QuickSort'; 2 | import { 3 | equalArr, 4 | notSortedArr, 5 | reverseArr, 6 | sortedArr, 7 | SortTester, 8 | } from '../../SortTester'; 9 | 10 | // Complexity constants. 11 | const SORTED_ARRAY_VISITING_COUNT = 190; 12 | const NOT_SORTED_ARRAY_VISITING_COUNT = 62; 13 | const REVERSE_SORTED_ARRAY_VISITING_COUNT = 190; 14 | const EQUAL_ARRAY_VISITING_COUNT = 19; 15 | 16 | describe('QuickSort', () => { 17 | it('should sort array', () => { 18 | SortTester.testSort(QuickSort); 19 | }); 20 | 21 | it('should sort array with custom comparator', () => { 22 | SortTester.testSortWithCustomComparator(QuickSort); 23 | }); 24 | 25 | it('should do stable sorting', () => { 26 | SortTester.testSortStability(QuickSort); 27 | }); 28 | 29 | it('should sort negative numbers', () => { 30 | SortTester.testNegativeNumbersSort(QuickSort); 31 | }); 32 | 33 | it('should visit EQUAL array element specified number of times', () => { 34 | SortTester.testAlgorithmTimeComplexity( 35 | QuickSort, 36 | equalArr, 37 | EQUAL_ARRAY_VISITING_COUNT, 38 | ); 39 | }); 40 | 41 | it('should visit SORTED array element specified number of times', () => { 42 | SortTester.testAlgorithmTimeComplexity( 43 | QuickSort, 44 | sortedArr, 45 | SORTED_ARRAY_VISITING_COUNT, 46 | ); 47 | }); 48 | 49 | it('should visit NOT SORTED array element specified number of times', () => { 50 | SortTester.testAlgorithmTimeComplexity( 51 | QuickSort, 52 | notSortedArr, 53 | NOT_SORTED_ARRAY_VISITING_COUNT, 54 | ); 55 | }); 56 | 57 | it('should visit REVERSE SORTED array element specified number of times', () => { 58 | SortTester.testAlgorithmTimeComplexity( 59 | QuickSort, 60 | reverseArr, 61 | REVERSE_SORTED_ARRAY_VISITING_COUNT, 62 | ); 63 | }); 64 | }); 65 | -------------------------------------------------------------------------------- /solutions/algorithms/sorting/quick-sort/__test__/QuickSortInPlace.test.js: -------------------------------------------------------------------------------- 1 | import QuickSortInPlace from '../QuickSortInPlace'; 2 | import { 3 | equalArr, 4 | notSortedArr, 5 | reverseArr, 6 | sortedArr, 7 | SortTester, 8 | } from '../../SortTester'; 9 | 10 | // Complexity constants. 11 | const SORTED_ARRAY_VISITING_COUNT = 19; 12 | const NOT_SORTED_ARRAY_VISITING_COUNT = 12; 13 | const REVERSE_SORTED_ARRAY_VISITING_COUNT = 19; 14 | const EQUAL_ARRAY_VISITING_COUNT = 19; 15 | 16 | describe('QuickSortInPlace', () => { 17 | it('should sort array', () => { 18 | SortTester.testSort(QuickSortInPlace); 19 | }); 20 | 21 | it('should sort array with custom comparator', () => { 22 | SortTester.testSortWithCustomComparator(QuickSortInPlace); 23 | }); 24 | 25 | it('should sort negative numbers', () => { 26 | SortTester.testNegativeNumbersSort(QuickSortInPlace); 27 | }); 28 | 29 | it('should visit EQUAL array element specified number of times', () => { 30 | SortTester.testAlgorithmTimeComplexity( 31 | QuickSortInPlace, 32 | equalArr, 33 | EQUAL_ARRAY_VISITING_COUNT, 34 | ); 35 | }); 36 | 37 | it('should visit SORTED array element specified number of times', () => { 38 | SortTester.testAlgorithmTimeComplexity( 39 | QuickSortInPlace, 40 | sortedArr, 41 | SORTED_ARRAY_VISITING_COUNT, 42 | ); 43 | }); 44 | 45 | it('should visit NOT SORTED array element specified number of times', () => { 46 | SortTester.testAlgorithmTimeComplexity( 47 | QuickSortInPlace, 48 | notSortedArr, 49 | NOT_SORTED_ARRAY_VISITING_COUNT, 50 | ); 51 | }); 52 | 53 | it('should visit REVERSE SORTED array element specified number of times', () => { 54 | SortTester.testAlgorithmTimeComplexity( 55 | QuickSortInPlace, 56 | reverseArr, 57 | REVERSE_SORTED_ARRAY_VISITING_COUNT, 58 | ); 59 | }); 60 | }); 61 | -------------------------------------------------------------------------------- /solutions/algorithms/sorting/selection-sort/README.md: -------------------------------------------------------------------------------- 1 | # Selection Sort 2 | 3 | Selection sort is a sorting algorithm, specifically an 4 | in-place comparison sort. It has O(n2) time complexity, 5 | making it inefficient on large lists, and generally 6 | performs worse than the similar insertion sort. 7 | Selection sort is noted for its simplicity, and it has 8 | performance advantages over more complicated algorithms 9 | in certain situations, particularly where auxiliary 10 | memory is limited. 11 | 12 | ![Algorithm Visualization](https://upload.wikimedia.org/wikipedia/commons/b/b0/Selection_sort_animation.gif) 13 | 14 | ![Algorithm Visualization](https://upload.wikimedia.org/wikipedia/commons/9/94/Selection-Sort-Animation.gif) 15 | 16 | ## Complexity 17 | 18 | | Name | Best | Average | Worst | Memory | Stable | Comments | 19 | | --------------------- | :-------------: | :-----------------: | :-----------------: | :-------: | :-------: | :-------- | 20 | | **Selection sort** | n2 | n2 | n2 | 1 | No | | 21 | 22 | ## References 23 | 24 | [Wikipedia](https://en.wikipedia.org/wiki/Selection_sort) 25 | -------------------------------------------------------------------------------- /solutions/algorithms/sorting/selection-sort/SelectionSort.js: -------------------------------------------------------------------------------- 1 | import Sort from '../Sort'; 2 | 3 | export default class SelectionSort extends Sort { 4 | sort(originalArray) { 5 | // Clone original array to prevent its modification. 6 | const array = [...originalArray]; 7 | 8 | for (let i = 0; i < array.length - 1; i += 1) { 9 | let minIndex = i; 10 | 11 | // Call visiting callback. 12 | this.callbacks.visitingCallback(array[i]); 13 | 14 | // Find minimum element in the rest of array. 15 | for (let j = i + 1; j < array.length; j += 1) { 16 | // Call visiting callback. 17 | this.callbacks.visitingCallback(array[j]); 18 | 19 | if (this.comparator.lessThan(array[j], array[minIndex])) { 20 | minIndex = j; 21 | } 22 | } 23 | 24 | // If new minimum element has been found then swap it with current i-th element. 25 | if (minIndex !== i) { 26 | const tmp = array[i]; 27 | array[i] = array[minIndex]; 28 | array[minIndex] = tmp; 29 | } 30 | } 31 | 32 | return array; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /solutions/algorithms/sorting/selection-sort/__test__/SelectionSort.test.js: -------------------------------------------------------------------------------- 1 | import SelectionSort from '../SelectionSort'; 2 | import { 3 | equalArr, 4 | notSortedArr, 5 | reverseArr, 6 | sortedArr, 7 | SortTester, 8 | } from '../../SortTester'; 9 | 10 | // Complexity constants. 11 | const SORTED_ARRAY_VISITING_COUNT = 209; 12 | const NOT_SORTED_ARRAY_VISITING_COUNT = 209; 13 | const REVERSE_SORTED_ARRAY_VISITING_COUNT = 209; 14 | const EQUAL_ARRAY_VISITING_COUNT = 209; 15 | 16 | describe('SelectionSort', () => { 17 | it('should sort array', () => { 18 | SortTester.testSort(SelectionSort); 19 | }); 20 | 21 | it('should sort array with custom comparator', () => { 22 | SortTester.testSortWithCustomComparator(SelectionSort); 23 | }); 24 | 25 | it('should sort negative numbers', () => { 26 | SortTester.testNegativeNumbersSort(SelectionSort); 27 | }); 28 | 29 | it('should visit EQUAL array element specified number of times', () => { 30 | SortTester.testAlgorithmTimeComplexity( 31 | SelectionSort, 32 | equalArr, 33 | EQUAL_ARRAY_VISITING_COUNT, 34 | ); 35 | }); 36 | 37 | it('should visit SORTED array element specified number of times', () => { 38 | SortTester.testAlgorithmTimeComplexity( 39 | SelectionSort, 40 | sortedArr, 41 | SORTED_ARRAY_VISITING_COUNT, 42 | ); 43 | }); 44 | 45 | it('should visit NOT SORTED array element specified number of times', () => { 46 | SortTester.testAlgorithmTimeComplexity( 47 | SelectionSort, 48 | notSortedArr, 49 | NOT_SORTED_ARRAY_VISITING_COUNT, 50 | ); 51 | }); 52 | 53 | it('should visit REVERSE SORTED array element specified number of times', () => { 54 | SortTester.testAlgorithmTimeComplexity( 55 | SelectionSort, 56 | reverseArr, 57 | REVERSE_SORTED_ARRAY_VISITING_COUNT, 58 | ); 59 | }); 60 | }); 61 | -------------------------------------------------------------------------------- /solutions/data-structures/binary-search-tree/BinarySearchTree.js: -------------------------------------------------------------------------------- 1 | import BinarySearchTreeNode from './BinarySearchTreeNode'; 2 | 3 | export default class BinarySearchTree { 4 | /** 5 | * @param {function} [nodeValueCompareFunction] 6 | */ 7 | constructor(nodeValueCompareFunction) { 8 | this.root = new BinarySearchTreeNode(null, nodeValueCompareFunction); 9 | 10 | // Steal node comparator from the root. 11 | this.nodeComparator = this.root.nodeComparator; 12 | } 13 | 14 | /** 15 | * @param {*} value 16 | * @return {BinarySearchTreeNode} 17 | */ 18 | insert(value) { 19 | return this.root.insert(value); 20 | } 21 | 22 | /** 23 | * @param {*} value 24 | * @return {boolean} 25 | */ 26 | contains(value) { 27 | return this.root.contains(value); 28 | } 29 | 30 | /** 31 | * @param {*} value 32 | * @return {boolean} 33 | */ 34 | remove(value) { 35 | return this.root.remove(value); 36 | } 37 | 38 | /** 39 | * @return {string} 40 | */ 41 | toString() { 42 | return this.root.toString(); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /solutions/data-structures/binary-search-tree/README.md: -------------------------------------------------------------------------------- 1 | # Binary Search Tree 2 | 3 | In computer science, binary search trees (BST), sometimes called 4 | ordered or sorted binary trees, are a particular type of container: 5 | data structures that store "items" (such as numbers, names etc.) 6 | in memory. They allow fast lookup, addition and removal of 7 | items, and can be used to implement either dynamic sets of 8 | items, or lookup tables that allow finding an item by its key 9 | (e.g., finding the phone number of a person by name). 10 | 11 | Binary search trees keep their keys in sorted order, so that lookup 12 | and other operations can use the principle of binary search: 13 | when looking for a key in a tree (or a place to insert a new key), 14 | they traverse the tree from root to leaf, making comparisons to 15 | keys stored in the nodes of the tree and deciding, on the basis 16 | of the comparison, to continue searching in the left or right 17 | subtrees. On average, this means that each comparison allows 18 | the operations to skip about half of the tree, so that each 19 | lookup, insertion or deletion takes time proportional to the 20 | logarithm of the number of items stored in the tree. This is 21 | much better than the linear time required to find items by key 22 | in an (unsorted) array, but slower than the corresponding 23 | operations on hash tables. 24 | 25 | A binary search tree of size 9 and depth 3, with 8 at the root. 26 | The leaves are not drawn. 27 | 28 | ![Binary Search Tree](https://upload.wikimedia.org/wikipedia/commons/d/da/Binary_search_tree.svg) 29 | 30 | ## References 31 | 32 | - [Wikipedia](https://en.wikipedia.org/wiki/Binary_search_tree) 33 | - [Inserting to BST on YouTube](https://www.youtube.com/watch?v=wcIRPqTR3Kc&list=PLLXdhg_r2hKA7DPDsunoDZ-Z769jWn4R8&index=9&t=0s) 34 | - [BST Interactive Visualisations](https://www.cs.usfca.edu/~galles/visualization/BST.html) 35 | -------------------------------------------------------------------------------- /solutions/data-structures/binary-search-tree/__test__/BinarySearchTree.test.js: -------------------------------------------------------------------------------- 1 | import BinarySearchTree from '../BinarySearchTree'; 2 | 3 | describe('BinarySearchTree', () => { 4 | it('should create binary search tree', () => { 5 | const bst = new BinarySearchTree(); 6 | 7 | expect(bst).toBeDefined(); 8 | expect(bst.root).toBeDefined(); 9 | expect(bst.root.value).toBeNull(); 10 | expect(bst.root.left).toBeNull(); 11 | expect(bst.root.right).toBeNull(); 12 | }); 13 | 14 | it('should insert values', () => { 15 | const bst = new BinarySearchTree(); 16 | 17 | const insertedNode1 = bst.insert(10); 18 | const insertedNode2 = bst.insert(20); 19 | bst.insert(5); 20 | 21 | expect(bst.toString()).toBe('5,10,20'); 22 | expect(insertedNode1.value).toBe(10); 23 | expect(insertedNode2.value).toBe(20); 24 | }); 25 | 26 | it('should check if value exists', () => { 27 | const bst = new BinarySearchTree(); 28 | 29 | bst.insert(10); 30 | bst.insert(20); 31 | bst.insert(5); 32 | 33 | expect(bst.contains(20)).toBeTruthy(); 34 | expect(bst.contains(40)).toBeFalsy(); 35 | }); 36 | 37 | it('should remove nodes', () => { 38 | const bst = new BinarySearchTree(); 39 | 40 | bst.insert(10); 41 | bst.insert(20); 42 | bst.insert(5); 43 | 44 | expect(bst.toString()).toBe('5,10,20'); 45 | 46 | const removed1 = bst.remove(5); 47 | expect(bst.toString()).toBe('10,20'); 48 | expect(removed1).toBeTruthy(); 49 | 50 | const removed2 = bst.remove(20); 51 | expect(bst.toString()).toBe('10'); 52 | expect(removed2).toBeTruthy(); 53 | }); 54 | 55 | it('should insert object values', () => { 56 | const nodeValueCompareFunction = (a, b) => { 57 | const normalizedA = a || { value: null }; 58 | const normalizedB = b || { value: null }; 59 | 60 | if (normalizedA.value === normalizedB.value) { 61 | return 0; 62 | } 63 | 64 | return normalizedA.value < normalizedB.value ? -1 : 1; 65 | }; 66 | 67 | const obj1 = { key: 'obj1', value: 1, toString: () => 'obj1' }; 68 | const obj2 = { key: 'obj2', value: 2, toString: () => 'obj2' }; 69 | const obj3 = { key: 'obj3', value: 3, toString: () => 'obj3' }; 70 | 71 | const bst = new BinarySearchTree(nodeValueCompareFunction); 72 | 73 | bst.insert(obj2); 74 | bst.insert(obj3); 75 | bst.insert(obj1); 76 | 77 | expect(bst.toString()).toBe('obj1,obj2,obj3'); 78 | }); 79 | 80 | it('should be traversed to sorted array', () => { 81 | const bst = new BinarySearchTree(); 82 | 83 | bst.insert(10); 84 | bst.insert(-10); 85 | bst.insert(20); 86 | bst.insert(-20); 87 | bst.insert(25); 88 | bst.insert(6); 89 | 90 | expect(bst.toString()).toBe('-20,-10,6,10,20,25'); 91 | expect(bst.root.height).toBe(2); 92 | 93 | bst.insert(4); 94 | 95 | expect(bst.toString()).toBe('-20,-10,4,6,10,20,25'); 96 | expect(bst.root.height).toBe(3); 97 | }); 98 | }); 99 | -------------------------------------------------------------------------------- /solutions/data-structures/doubly-linked-list/DoublyLinkedList.js: -------------------------------------------------------------------------------- 1 | import DoublyLinkedListNode from './DoublyLinkedListNode'; 2 | 3 | export default class DoublyLinkedList { 4 | constructor() { 5 | this.head = null; 6 | this.tail = null; 7 | } 8 | 9 | /** 10 | * @param {*} value 11 | * @return {DoublyLinkedList} 12 | */ 13 | prepend(value) { 14 | const node = new DoublyLinkedListNode(value); 15 | 16 | if (!this.head) { 17 | this.head = node; 18 | this.tail = node; 19 | } else { 20 | this.head.previous = node; 21 | node.next = this.head; 22 | this.head = node; 23 | } 24 | 25 | return this; 26 | } 27 | 28 | /** 29 | * @param {*} value 30 | * @return {DoublyLinkedList} 31 | */ 32 | append(value) { 33 | const node = new DoublyLinkedListNode(value); 34 | 35 | if (!this.head) { 36 | this.head = node; 37 | this.tail = node; 38 | } else { 39 | this.tail.next = node; 40 | node.previous = this.tail; 41 | this.tail = node; 42 | } 43 | 44 | return this; 45 | } 46 | 47 | /** 48 | * @param {Object} findParams 49 | * @param {*} findParams.value 50 | * @param {function} [findParams.callback] 51 | * @return {DoublyLinkedListNode} 52 | */ 53 | find({ 54 | value, 55 | callback 56 | }) { 57 | let node = this.head; 58 | 59 | while (node) { 60 | const callbackResult = callback && callback(node.value); 61 | 62 | if (callbackResult) { 63 | return node; 64 | } 65 | 66 | if (node.value === value) { 67 | return node; 68 | } 69 | 70 | node = node.next; 71 | } 72 | 73 | return null; 74 | } 75 | 76 | /** 77 | * @return {DoublyLinkedListNode} 78 | */ 79 | deleteHead() { 80 | if (!this.head) { 81 | return null; 82 | } 83 | 84 | const node = this.head; 85 | 86 | if (this.head === this.tail) { 87 | this.head = null; 88 | this.tail = null; 89 | } else { 90 | this.head = this.head.next; 91 | this.head.previous = null; 92 | } 93 | 94 | return node; 95 | } 96 | 97 | /** 98 | * @return {DoublyLinkedListNode} 99 | */ 100 | deleteTail() { 101 | if (!this.tail) { 102 | return null; 103 | } 104 | 105 | const node = this.tail; 106 | 107 | if (this.head === this.tail) { 108 | this.head = null; 109 | this.tail = null; 110 | } else { 111 | this.tail = this.tail.previous; 112 | this.tail.next = null; 113 | } 114 | 115 | return node; 116 | } 117 | 118 | /** 119 | * @param {*} value 120 | * @return {DoublyLinkedListNode} 121 | */ 122 | delete(value) { 123 | const node = this.find({ 124 | value 125 | }); 126 | 127 | if (!node) { 128 | return null; 129 | } 130 | 131 | if (this.head === node) { 132 | return this.deleteHead(); 133 | } 134 | 135 | if (this.tail === node) { 136 | return this.deleteTail(); 137 | } 138 | 139 | node.previous.next = node.next; 140 | node.next.previous = node.previous; 141 | 142 | return node; 143 | } 144 | 145 | /** 146 | * @return {DoublyLinkedListNode[]} 147 | */ 148 | toArray() { 149 | const nodes = []; 150 | let node = this.head; 151 | 152 | while (node) { 153 | nodes.push(node); 154 | node = node.next; 155 | } 156 | 157 | return nodes; 158 | } 159 | 160 | /** 161 | * @param {function} [callback] 162 | * @return {string} 163 | */ 164 | toString(callback) { 165 | return this.toArray().map(node => node.toString(callback)).toString(); 166 | } 167 | } 168 | -------------------------------------------------------------------------------- /solutions/data-structures/doubly-linked-list/DoublyLinkedListNode.js: -------------------------------------------------------------------------------- 1 | export default class DoublyLinkedListNode { 2 | constructor(value, next = null, previous = null) { 3 | this.value = value; 4 | this.next = next; 5 | this.previous = previous; 6 | } 7 | 8 | toString(callback) { 9 | return callback ? callback(this.value) : `${this.value}`; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /solutions/data-structures/doubly-linked-list/README.md: -------------------------------------------------------------------------------- 1 | # Doubly Linked List 2 | 3 | ## Description 4 | 5 | Unlike a singly-linked list, a doubly-linked list node keeps a reference to the previous node in addition to the next node. This allows traversal in both directions of the list, towards the head and the tail. 6 | 7 | ![Linked List](../../../assets/doubly-linked-list.png) 8 | 9 | The operations for the doubly linked list is the same a singly linked list. 10 | 11 | ## Implementation 12 | 13 | In this exercise, implement the following functions for the `DoublyLinkedListNode` and `DoublyLinkedList` classes: 14 | 15 | - `DoublyLinkedListNode` 16 | - `constructor()` 17 | - Write a method that instantiates the node. 18 | - The node takes `value`, `previous` and `next`. 19 | - `LinkedList` 20 | - `constructor()` 21 | - Write a method that instantiates the list. 22 | - The instance variables `head` and `tail` should be set to `null`. 23 | - `prepend(value)` 24 | - Write a method that inserts the `value` at the beginning of the linked list. 25 | - `append(value)` 26 | - Write a method that inserts the `value` at the end of the linked list. 27 | - `find(value)` 28 | - Write a method that returns the `node` that contains the `value`. 29 | - `deleteHead()` 30 | - Write a method that deletes the first element in the linked list. 31 | - `deleteTail()` 32 | - Write a method that deletes the last element in the linked list. 33 | - `delete(value)` 34 | - Write a method that deletes the `value` in the linked list. 35 | 36 | The most important operations are `prepend/append` for adding data, `delete` for removing data, and `find` for retrieving data. 37 | 38 | ## Detailed Walkthrough 39 | 40 | To start, build `DoublyLinkedListNode`. A doubly linked list node keeps a reference to the previous node in addition to the next node. Then, build the constructor the same way as the singly linked list. 41 | 42 | > Jest Tip: If you need to filter by the exercise name, press `p`. 43 | > After pressing `p`, enter the string "Doubly" to filter by doubly linked list tests. 44 | 45 | ### `prepend()` 46 | 47 | - The `prepend` method inserts the item at the beginning of the list. 48 | - Operations: 49 | - Create a new `Node`. 50 | - Set the current head's previous reference to the new node. 51 | - Set the new node's next to the current `head`. 52 | - Update `head` to point at the new node. 53 | - Take into consideration where this is the first value in the linked list. 54 | 55 | ### `append()` 56 | 57 | - The `append` method inserts the item at the end of the list. 58 | - Operations: 59 | - Create a new `Node`. 60 | - Set the current tail's `next` to be the new node. 61 | - Set the new node's previous to the current `tail`. 62 | - Update `tail` to point at the new node. 63 | - Take into consideration where this is the first value in the linked list. 64 | 65 | ### `find(value)` 66 | 67 | - The `find` method returns the node with the target value. 68 | - Traverse the array in the same way as a singly linked list. 69 | 70 | ### `deleteHead()` / `deleteTail()` 71 | 72 | - The `deleteHead/Tail` methods are useful utilities methods. 73 | - Take into consideration where there is only one node in the linked list. 74 | 75 | ### `delete(value)` 76 | 77 | - The `delete` method removes the first node with the specified value. 78 | - The delete operation for a doubly linked list is significantly simpler due to having a reference to the previous node. 79 | - Utilize `find` and `deleteHead/Tail` methods written above to write the method. 80 | -------------------------------------------------------------------------------- /solutions/data-structures/doubly-linked-list/__test__/DoublyLinkedListNode.test.js: -------------------------------------------------------------------------------- 1 | import DoublyLinkedListNode from '../DoublyLinkedListNode'; 2 | 3 | describe('DoublyLinkedListNode', () => { 4 | it('should create list node with value', () => { 5 | const node = new DoublyLinkedListNode(1); 6 | 7 | expect(node.value).toBe(1); 8 | expect(node.next).toBeNull(); 9 | expect(node.previous).toBeNull(); 10 | }); 11 | 12 | it('should create list node with object as a value', () => { 13 | const nodeValue = { 14 | value: 1, 15 | key: 'test' 16 | }; 17 | const node = new DoublyLinkedListNode(nodeValue); 18 | 19 | expect(node.value.value).toBe(1); 20 | expect(node.value.key).toBe('test'); 21 | expect(node.next).toBeNull(); 22 | expect(node.previous).toBeNull(); 23 | }); 24 | 25 | it('should link nodes together', () => { 26 | const node2 = new DoublyLinkedListNode(2); 27 | const node1 = new DoublyLinkedListNode(1, node2); 28 | const node3 = new DoublyLinkedListNode(10, node1, node2); 29 | 30 | expect(node1.next).toBeDefined(); 31 | expect(node1.previous).toBeNull(); 32 | expect(node2.next).toBeNull(); 33 | expect(node2.previous).toBeNull(); 34 | expect(node3.next).toBeDefined(); 35 | expect(node3.previous).toBeDefined(); 36 | expect(node1.value).toBe(1); 37 | expect(node1.next.value).toBe(2); 38 | expect(node3.next.value).toBe(1); 39 | expect(node3.previous.value).toBe(2); 40 | }); 41 | 42 | it('should convert node to string', () => { 43 | const node = new DoublyLinkedListNode(1); 44 | 45 | expect(node.toString()).toBe('1'); 46 | 47 | node.value = 'string value'; 48 | expect(node.toString()).toBe('string value'); 49 | }); 50 | 51 | it('should convert node to string with custom stringifier', () => { 52 | const nodeValue = { 53 | value: 1, 54 | key: 'test' 55 | }; 56 | 57 | const node = new DoublyLinkedListNode(nodeValue); 58 | const toStringCallback = value => `value: ${value.value}, key: ${value.key}`; 59 | 60 | expect(node.toString(toStringCallback)).toBe('value: 1, key: test'); 61 | }); 62 | }); 63 | -------------------------------------------------------------------------------- /solutions/data-structures/hash-table/HashTable.js: -------------------------------------------------------------------------------- 1 | import LinkedList from '../linked-list/LinkedList'; 2 | 3 | // Hash table size directly affects on the number of collisions. 4 | // The bigger the hash table size the less collisions you'll get. 5 | // For demonstrating purposes hash table size is small to show how collisions 6 | // are being handled. 7 | const defaultHashTableSize = 32; 8 | 9 | export default class HashTable { 10 | /** 11 | * @param {number} hashTableSize 12 | */ 13 | constructor(hashTableSize = defaultHashTableSize) { 14 | // Create hash table of certain size and fill each bucket with empty linked list. 15 | this.buckets = Array(hashTableSize).fill(null).map(() => new LinkedList()); 16 | 17 | // Just to keep track of all actual keys in a fast way. 18 | this.keys = {}; 19 | } 20 | 21 | /** 22 | * Converts key string to hash number. 23 | * 24 | * @param {string} key 25 | * @return {number} 26 | */ 27 | hash(key) { 28 | const hash = Array.from(key).reduce( 29 | (hashAccumulator, keySymbol) => (hashAccumulator + keySymbol.charCodeAt(0)), 30 | 0, 31 | ); 32 | 33 | // Reduce hash number so it would fit hash table size. 34 | return hash % this.buckets.length; 35 | } 36 | 37 | /** 38 | * @param {string} key 39 | * @param {*} value 40 | */ 41 | set(key, value) { 42 | const keyHash = this.hash(key); 43 | this.keys[key] = keyHash; 44 | const bucketLinkedList = this.buckets[keyHash]; 45 | const node = bucketLinkedList.find({ callback: nodeValue => nodeValue.key === key }); 46 | 47 | if (!node) { 48 | // Insert new node. 49 | bucketLinkedList.append({ key, value }); 50 | } else { 51 | // Update value of existing node. 52 | node.value.value = value; 53 | } 54 | } 55 | 56 | /** 57 | * @param {string} key 58 | * @return {*} 59 | */ 60 | delete(key) { 61 | const keyHash = this.hash(key); 62 | delete this.keys[key]; 63 | const bucketLinkedList = this.buckets[keyHash]; 64 | const node = bucketLinkedList.find({ callback: nodeValue => nodeValue.key === key }); 65 | 66 | if (node) { 67 | return bucketLinkedList.delete(node.value); 68 | } 69 | 70 | return null; 71 | } 72 | 73 | /** 74 | * @param {string} key 75 | * @return {*} 76 | */ 77 | get(key) { 78 | const bucketLinkedList = this.buckets[this.hash(key)]; 79 | const node = bucketLinkedList.find({ callback: nodeValue => nodeValue.key === key }); 80 | 81 | return node ? node.value.value : undefined; 82 | } 83 | 84 | /** 85 | * @param {string} key 86 | * @return {boolean} 87 | */ 88 | has(key) { 89 | return Object.hasOwnProperty.call(this.keys, key); 90 | } 91 | 92 | /** 93 | * @return {string[]} 94 | */ 95 | getKeys() { 96 | return Object.keys(this.keys); 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /solutions/data-structures/hash-table/README.md: -------------------------------------------------------------------------------- 1 | # Hash Table 2 | 3 | In computing, a hash table (hash map) is a data 4 | structure which implements an associative array 5 | abstract data type, a structure that can map keys 6 | to values. A hash table uses a hash function to 7 | compute an index into an array of buckets or slots, 8 | from which the desired value can be found 9 | 10 | Ideally, the hash function will assign each key to a 11 | unique bucket, but most hash table designs employ an 12 | imperfect hash function, which might cause hash 13 | collisions where the hash function generates the same 14 | index for more than one key. Such collisions must be 15 | accommodated in some way. 16 | 17 | ![Hash Table](https://upload.wikimedia.org/wikipedia/commons/7/7d/Hash_table_3_1_1_0_1_0_0_SP.svg) 18 | 19 | Hash collision resolved by separate chaining. 20 | 21 | ![Hash Collision](https://upload.wikimedia.org/wikipedia/commons/d/d0/Hash_table_5_0_1_1_1_1_1_LL.svg) 22 | 23 | ## References 24 | 25 | - [Wikipedia](https://en.wikipedia.org/wiki/Hash_table) 26 | - [YouTube](https://www.youtube.com/watch?v=shs0KM3wKv8&index=4&list=PLLXdhg_r2hKA7DPDsunoDZ-Z769jWn4R8) 27 | -------------------------------------------------------------------------------- /solutions/data-structures/hash-table/__test__/HashTable.test.js: -------------------------------------------------------------------------------- 1 | import HashTable from '../HashTable'; 2 | 3 | describe('HashTable', () => { 4 | it('should create hash table of certain size', () => { 5 | const defaultHashTable = new HashTable(); 6 | expect(defaultHashTable.buckets.length).toBe(32); 7 | 8 | const biggerHashTable = new HashTable(64); 9 | expect(biggerHashTable.buckets.length).toBe(64); 10 | }); 11 | 12 | it('should generate proper hash for specified keys', () => { 13 | const hashTable = new HashTable(); 14 | 15 | expect(hashTable.hash('a')).toBe(1); 16 | expect(hashTable.hash('b')).toBe(2); 17 | expect(hashTable.hash('abc')).toBe(6); 18 | }); 19 | 20 | it('should set, read and delete data with collisions', () => { 21 | const hashTable = new HashTable(3); 22 | 23 | expect(hashTable.hash('a')).toBe(1); 24 | expect(hashTable.hash('b')).toBe(2); 25 | expect(hashTable.hash('c')).toBe(0); 26 | expect(hashTable.hash('d')).toBe(1); 27 | 28 | hashTable.set('a', 'sky-old'); 29 | hashTable.set('a', 'sky'); 30 | hashTable.set('b', 'sea'); 31 | hashTable.set('c', 'earth'); 32 | hashTable.set('d', 'ocean'); 33 | 34 | expect(hashTable.has('x')).toBeFalsy(); 35 | expect(hashTable.has('b')).toBeTruthy(); 36 | expect(hashTable.has('c')).toBeTruthy(); 37 | 38 | const stringifier = value => `${value.key}:${value.value}`; 39 | 40 | expect(hashTable.buckets[0].toString(stringifier)).toBe('c:earth'); 41 | expect(hashTable.buckets[1].toString(stringifier)).toBe('a:sky,d:ocean'); 42 | expect(hashTable.buckets[2].toString(stringifier)).toBe('b:sea'); 43 | 44 | expect(hashTable.get('a')).toBe('sky'); 45 | expect(hashTable.get('d')).toBe('ocean'); 46 | expect(hashTable.get('x')).not.toBeDefined(); 47 | 48 | hashTable.delete('a'); 49 | 50 | expect(hashTable.delete('not-existing')).toBeNull(); 51 | 52 | expect(hashTable.get('a')).not.toBeDefined(); 53 | expect(hashTable.get('d')).toBe('ocean'); 54 | 55 | hashTable.set('d', 'ocean-new'); 56 | expect(hashTable.get('d')).toBe('ocean-new'); 57 | }); 58 | 59 | it('should be possible to add objects to hash table', () => { 60 | const hashTable = new HashTable(); 61 | 62 | hashTable.set('objectKey', { prop1: 'a', prop2: 'b' }); 63 | 64 | const object = hashTable.get('objectKey'); 65 | expect(object).toBeDefined(); 66 | expect(object.prop1).toBe('a'); 67 | expect(object.prop2).toBe('b'); 68 | }); 69 | 70 | it('should track actual keys', () => { 71 | const hashTable = new HashTable(3); 72 | 73 | hashTable.set('a', 'sky-old'); 74 | hashTable.set('a', 'sky'); 75 | hashTable.set('b', 'sea'); 76 | hashTable.set('c', 'earth'); 77 | hashTable.set('d', 'ocean'); 78 | 79 | expect(hashTable.getKeys()).toEqual(['a', 'b', 'c', 'd']); 80 | expect(hashTable.has('a')).toBeTruthy(); 81 | expect(hashTable.has('x')).toBeFalsy(); 82 | 83 | hashTable.delete('a'); 84 | 85 | expect(hashTable.has('a')).toBeFalsy(); 86 | expect(hashTable.has('b')).toBeTruthy(); 87 | expect(hashTable.has('x')).toBeFalsy(); 88 | }); 89 | }); 90 | -------------------------------------------------------------------------------- /solutions/data-structures/linked-list/LinkedListNode.js: -------------------------------------------------------------------------------- 1 | export default class LinkedListNode { 2 | constructor(value, next = null) { 3 | this.value = value; 4 | this.next = next; 5 | } 6 | 7 | toString(callback) { 8 | return callback ? callback(this.value) : `${this.value}`; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /solutions/data-structures/linked-list/__test__/LinkedListNode.test.js: -------------------------------------------------------------------------------- 1 | import LinkedListNode from '../LinkedListNode'; 2 | 3 | describe('LinkedListNode', () => { 4 | it('should create list node with value', () => { 5 | const node = new LinkedListNode(1); 6 | 7 | expect(node.value).toBe(1); 8 | expect(node.next).toBeNull(); 9 | }); 10 | 11 | it('should create list node with object as a value', () => { 12 | const nodeValue = { 13 | value: 1, 14 | key: 'test' 15 | }; 16 | const node = new LinkedListNode(nodeValue); 17 | 18 | expect(node.value.value).toBe(1); 19 | expect(node.value.key).toBe('test'); 20 | expect(node.next).toBeNull(); 21 | }); 22 | 23 | it('should link nodes together', () => { 24 | const node2 = new LinkedListNode(2); 25 | const node1 = new LinkedListNode(1, node2); 26 | 27 | expect(node1.next).toBeDefined(); 28 | expect(node2.next).toBeNull(); 29 | expect(node1.value).toBe(1); 30 | expect(node1.next.value).toBe(2); 31 | }); 32 | 33 | it('should convert node to string', () => { 34 | const node = new LinkedListNode(1); 35 | 36 | expect(node.toString()).toBe('1'); 37 | 38 | node.value = 'string value'; 39 | expect(node.toString()).toBe('string value'); 40 | }); 41 | 42 | it('should convert node to string with custom stringifier', () => { 43 | const nodeValue = { 44 | value: 1, 45 | key: 'test' 46 | }; 47 | 48 | const node = new LinkedListNode(nodeValue); 49 | const toStringCallback = value => `value: ${value.value}, key: ${value.key}`; 50 | 51 | expect(node.toString(toStringCallback)).toBe('value: 1, key: test'); 52 | }); 53 | }); 54 | -------------------------------------------------------------------------------- /solutions/data-structures/queue/Queue.js: -------------------------------------------------------------------------------- 1 | export default class Queue { 2 | constructor() { 3 | this.store = []; 4 | } 5 | 6 | isEmpty() { 7 | return !this.store.length; 8 | } 9 | 10 | peek() { 11 | if (this.isEmpty()) { 12 | return null; 13 | } 14 | 15 | return this.store[0]; 16 | } 17 | 18 | enqueue(el) { 19 | return this.store.unshift(el); 20 | } 21 | 22 | dequeue() { 23 | if (this.isEmpty()) { 24 | return null; 25 | } 26 | 27 | return this.store.pop(); 28 | } 29 | 30 | toString() { 31 | return this.store.toString(); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /solutions/data-structures/queue/README.md: -------------------------------------------------------------------------------- 1 | # Queue 2 | 3 | ## Description 4 | 5 | A queue is a data structure that can store or retrieve one item at a time, in first-in-first-out (FIFO) order. Think of FIFO as standing in the line at the grocery store. You would expect the first person that stood in the line to be the first one served by the cashier. 6 | 7 | Queue implements FIFO through two operations; `enqueue` and `dequeue`, which can be visualized in the diagram below: 8 | 9 | ![Queing operations](../../../assets/queue.png) 10 | 11 | - `enqueue` operation stores the item at the back of the queue. 12 | - `dequeue` operation retrieves the item from the front of the queue. 13 | 14 | (_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_) 15 | 16 | ## Implementation 17 | 18 | In this exercise, implement the following functions for the `Queue` class 19 | 20 | - `isEmpty()` 21 | - Write a method that returns `true` if the queue is currently empty. 22 | - `peek()` 23 | - Write a method that returns the element at the front of the queue. 24 | - `enqueue(el)` 25 | - Write a method that stores an element(`el`) into the queue. 26 | - `dequeue()` 27 | - Write a method that retrieves an element from the queue. 28 | - `toString()` 29 | - The stringify method is provided for you. `toString()` is a useful method to implement into data structures for easier debugging. 30 | - For example, you could use it for logging: 31 | ``` 32 | const queue = new Queue(); 33 | constole.log(queue.toString()); 34 | ``` 35 | - A queue is simple enough to be logged without actually needing `toString()`, but with more complex data structures, this is an invaluable method. 36 | 37 | ## Queue Exercises 38 | 39 | **Stack Queue** 40 | 41 | (Solve this exercise after finishing the stack exercise). 42 | 43 | Implement a queue using stacks. Instead of using an array as your store, use a stack: 44 | ``` 45 | const Stack = require('../stack/Stack'); 46 | 47 | export default class Stack { 48 | constructor() { 49 | this.store = new Stack(); 50 | } 51 | 52 | isEmpty() {} 53 | 54 | peek() {} 55 | 56 | push(value) {} 57 | 58 | pop() {} 59 | 60 | toString() { 61 | return this.store.toString(); 62 | } 63 | } 64 | ``` 65 | -------------------------------------------------------------------------------- /solutions/data-structures/queue/__test__/Queue.test.js: -------------------------------------------------------------------------------- 1 | import Queue from '../Queue'; 2 | 3 | describe('Queue', () => { 4 | it('should create empty queue', () => { 5 | const queue = new Queue(); 6 | expect(queue).not.toBeNull(); 7 | expect(queue.store).not.toBeNull(); 8 | }); 9 | 10 | it('should enqueue data to queue', () => { 11 | const queue = new Queue(); 12 | 13 | queue.enqueue(1); 14 | queue.enqueue(2); 15 | 16 | expect(queue.toString()).toBe('1,2'); 17 | }); 18 | 19 | it('should be possible to enqueue/dequeue in order', () => { 20 | const queue = new Queue(); 21 | 22 | queue.enqueue(1); 23 | queue.enqueue(2); 24 | 25 | expect(queue.dequeue()).toBe(1); 26 | expect(queue.dequeue()).toBe(2); 27 | }); 28 | 29 | it('should peek data from queue', () => { 30 | const queue = new Queue(); 31 | 32 | expect(queue.peek()).toBeNull(); 33 | 34 | queue.enqueue(1); 35 | queue.enqueue(2); 36 | 37 | expect(queue.peek()).toBe(1); 38 | expect(queue.peek()).toBe(1); 39 | }); 40 | 41 | it('should check if queue is empty', () => { 42 | const queue = new Queue(); 43 | 44 | expect(queue.isEmpty()).toBeTruthy(); 45 | 46 | queue.enqueue(1); 47 | 48 | expect(queue.isEmpty()).toBeFalsy(); 49 | }); 50 | 51 | it('should dequeue from queue in FIFO order', () => { 52 | const queue = new Queue(); 53 | 54 | queue.enqueue(1); 55 | queue.enqueue(2); 56 | 57 | expect(queue.dequeue()).toBe(1); 58 | expect(queue.dequeue()).toBe(2); 59 | expect(queue.dequeue()).toBeNull(); 60 | expect(queue.isEmpty()).toBeTruthy(); 61 | }); 62 | }); 63 | -------------------------------------------------------------------------------- /solutions/data-structures/stack/README.md: -------------------------------------------------------------------------------- 1 | # Stack 2 | 3 | ## Description 4 | 5 | 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. 6 | 7 | Stack implements LIFO through two operations: `push` and `pop`, which can be visualized in the diagram below: 8 | 9 | ![Queing operations](../../../assets/stack.png) 10 | (_A stack is usually visualized from bottom-to-top_) 11 | 12 | - `push` operation stores the item at the top of the stack. 13 | - `pop` operation retrieves the item from the top of the stack. 14 | 15 | ## Implementation 16 | 17 | In this exercise, implement the following functions for the `Stack` class 18 | 19 | - `isEmpty()` 20 | - Write a method that returns `true` if the stack is currently empty. 21 | - `peek()` 22 | - Write a method that returns the element from the top of the stack. 23 | - `push(el)` 24 | - Write a method that stores an element(`el`) into the stack. 25 | - `pop()` 26 | - Write a method that retrieves an element from the stack. 27 | - `toString()` 28 | - The stringify method has been provided for you. 29 | 30 | ## Stack Exercises 31 | 32 | **Bracket Matching** 33 | 34 | Write an algorithm to determine if all of the delimiters in an expression are matched and closed. 35 | 36 | - Delimiters are `(` `{` `[` 37 | - Closed is `()` and `(()` would not be closed 38 | - Matched is `{}`, and `{]` would not be matched. 39 | 40 | ``` 41 | const bracketMatch = string => { 42 | 43 | } 44 | 45 | console.log(bracketMatch('{ac[bb]}') === true); 46 | console.log(bracketMatch('[dklf(df(kl))d]{}') === true); 47 | console.log(bracketMatch('{[[[]]]}') === true); 48 | console.log(bracketMatch('{3234[fd') === false); 49 | console.log(bracketMatch('{df][d}') === false); 50 | console.log(bracketMatch('([)]') === false); 51 | ``` 52 | 53 | -------------------------------------------------------------------------------- /solutions/data-structures/stack/Stack.js: -------------------------------------------------------------------------------- 1 | export default class Stack { 2 | constructor() { 3 | this.store = []; 4 | } 5 | 6 | isEmpty() { 7 | return !this.store.length; 8 | } 9 | 10 | peek() { 11 | if (this.isEmpty()) { 12 | return; 13 | } 14 | 15 | return this.store[this.store.length - 1]; 16 | } 17 | 18 | push(el) { 19 | return this.store.push(el); 20 | } 21 | 22 | pop() { 23 | return this.store.pop(); 24 | } 25 | 26 | toString() { 27 | return this.store.toString(); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /solutions/data-structures/stack/__test__/Stack.test.js: -------------------------------------------------------------------------------- 1 | import Stack from '../Stack'; 2 | 3 | describe('Stack', () => { 4 | it('should create empty stack', () => { 5 | const stack = new Stack(); 6 | expect(stack).not.toBeUndefined(); 7 | expect(stack.store).not.toBeUndefined(); 8 | }); 9 | 10 | it('should stack data to stack', () => { 11 | const stack = new Stack(); 12 | 13 | stack.push(1); 14 | stack.push(2); 15 | 16 | expect(stack.toString()).toBe('1,2'); 17 | }); 18 | 19 | it('should peek data from stack', () => { 20 | const stack = new Stack(); 21 | 22 | expect(stack.peek()).toBeUndefined(); 23 | 24 | stack.push(1); 25 | stack.push(2); 26 | 27 | expect(stack.peek()).toBe(2); 28 | expect(stack.peek()).toBe(2); 29 | }); 30 | 31 | it('should check if stack is empty', () => { 32 | const stack = new Stack(); 33 | 34 | expect(stack.isEmpty()).toBeTruthy(); 35 | 36 | stack.push(1); 37 | 38 | expect(stack.isEmpty()).toBeFalsy(); 39 | }); 40 | 41 | it('should pop data from stack', () => { 42 | const stack = new Stack(); 43 | 44 | stack.push(1); 45 | stack.push(2); 46 | 47 | expect(stack.pop()).toBe(2); 48 | expect(stack.pop()).toBe(1); 49 | expect(stack.pop()).toBeUndefined(); 50 | expect(stack.isEmpty()).toBeTruthy(); 51 | }); 52 | 53 | it('should be possible to push/pop', () => { 54 | const stack = new Stack(); 55 | 56 | stack.push(1); 57 | stack.push(2); 58 | 59 | expect(stack.toString()).toBe('1,2'); 60 | expect(stack.pop()).toBe(2); 61 | expect(stack.pop()).toBe(1); 62 | }); 63 | }); 64 | -------------------------------------------------------------------------------- /solutions/utils/comparator/Comparator.js: -------------------------------------------------------------------------------- 1 | export default class Comparator { 2 | /** 3 | * @param {function(a: *, b: *)} [compareFunction] 4 | */ 5 | constructor(compareFunction) { 6 | this.compare = compareFunction || Comparator.defaultCompareFunction; 7 | } 8 | 9 | /** 10 | * @param {(string|number)} a 11 | * @param {(string|number)} b 12 | * @returns {number} 13 | */ 14 | static defaultCompareFunction(a, b) { 15 | if (a === b) { 16 | return 0; 17 | } 18 | 19 | return a < b ? -1 : 1; 20 | } 21 | 22 | equal(a, b) { 23 | return this.compare(a, b) === 0; 24 | } 25 | 26 | lessThan(a, b) { 27 | return this.compare(a, b) < 0; 28 | } 29 | 30 | greaterThan(a, b) { 31 | return this.compare(a, b) > 0; 32 | } 33 | 34 | lessThanOrEqual(a, b) { 35 | return this.lessThan(a, b) || this.equal(a, b); 36 | } 37 | 38 | greaterThanOrEqual(a, b) { 39 | return this.greaterThan(a, b) || this.equal(a, b); 40 | } 41 | 42 | reverse() { 43 | const compareOriginal = this.compare; 44 | this.compare = (a, b) => compareOriginal(b, a); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /solutions/utils/comparator/__test__/Comparator.test.js: -------------------------------------------------------------------------------- 1 | import Comparator from '../Comparator'; 2 | 3 | describe('Comparator', () => { 4 | it('should compare with default comparator function', () => { 5 | const comparator = new Comparator(); 6 | 7 | expect(comparator.equal(0, 0)).toBeTruthy(); 8 | expect(comparator.equal(0, 1)).toBeFalsy(); 9 | expect(comparator.equal('a', 'a')).toBeTruthy(); 10 | expect(comparator.lessThan(1, 2)).toBeTruthy(); 11 | expect(comparator.lessThan(-1, 2)).toBeTruthy(); 12 | expect(comparator.lessThan('a', 'b')).toBeTruthy(); 13 | expect(comparator.lessThan('a', 'ab')).toBeTruthy(); 14 | expect(comparator.lessThan(10, 2)).toBeFalsy(); 15 | expect(comparator.lessThanOrEqual(10, 2)).toBeFalsy(); 16 | expect(comparator.lessThanOrEqual(1, 1)).toBeTruthy(); 17 | expect(comparator.lessThanOrEqual(0, 0)).toBeTruthy(); 18 | expect(comparator.greaterThan(0, 0)).toBeFalsy(); 19 | expect(comparator.greaterThan(10, 0)).toBeTruthy(); 20 | expect(comparator.greaterThanOrEqual(10, 0)).toBeTruthy(); 21 | expect(comparator.greaterThanOrEqual(10, 10)).toBeTruthy(); 22 | expect(comparator.greaterThanOrEqual(0, 10)).toBeFalsy(); 23 | }); 24 | 25 | it('should compare with custom comparator function', () => { 26 | const comparator = new Comparator((a, b) => { 27 | if (a.length === b.length) { 28 | return 0; 29 | } 30 | 31 | return a.length < b.length ? -1 : 1; 32 | }); 33 | 34 | expect(comparator.equal('a', 'b')).toBeTruthy(); 35 | expect(comparator.equal('a', '')).toBeFalsy(); 36 | expect(comparator.lessThan('b', 'aa')).toBeTruthy(); 37 | expect(comparator.greaterThanOrEqual('a', 'aa')).toBeFalsy(); 38 | expect(comparator.greaterThanOrEqual('aa', 'a')).toBeTruthy(); 39 | expect(comparator.greaterThanOrEqual('a', 'a')).toBeTruthy(); 40 | 41 | comparator.reverse(); 42 | 43 | expect(comparator.equal('a', 'b')).toBeTruthy(); 44 | expect(comparator.equal('a', '')).toBeFalsy(); 45 | expect(comparator.lessThan('b', 'aa')).toBeFalsy(); 46 | expect(comparator.greaterThanOrEqual('a', 'aa')).toBeTruthy(); 47 | expect(comparator.greaterThanOrEqual('aa', 'a')).toBeFalsy(); 48 | expect(comparator.greaterThanOrEqual('a', 'a')).toBeTruthy(); 49 | }); 50 | }); 51 | -------------------------------------------------------------------------------- /src/data-structures/doubly-linked-list/DoublyLinkedList.js: -------------------------------------------------------------------------------- 1 | import DoublyLinkedListNode from './DoublyLinkedListNode'; 2 | 3 | export default class DoublyLinkedList { 4 | constructor() {} 5 | 6 | /** 7 | * @param {*} value 8 | * @return {DoublyLinkedList} 9 | */ 10 | prepend(value) {} 11 | 12 | /** 13 | * @param {*} value 14 | * @return {DoublyLinkedList} 15 | */ 16 | append(value) {} 17 | 18 | /** 19 | * @param {Object} findParams 20 | * @param {*} findParams.value 21 | * @param {function} [findParams.callback] 22 | * @return {DoublyLinkedListNode} 23 | */ 24 | find({ 25 | value, 26 | callback 27 | }) {} 28 | 29 | /** 30 | * @return {DoublyLinkedListNode} 31 | */ 32 | deleteHead() {} 33 | 34 | /** 35 | * @return {DoublyLinkedListNode} 36 | */ 37 | deleteTail() {} 38 | 39 | /** 40 | * @param {*} value 41 | * @return {DoublyLinkedListNode} 42 | */ 43 | delete(value) {} 44 | 45 | /** 46 | * @return {DoublyLinkedListNode[]} 47 | */ 48 | toArray() { 49 | const nodes = []; 50 | let node = this.head; 51 | 52 | while (node) { 53 | nodes.push(node); 54 | node = node.next; 55 | } 56 | 57 | return nodes; 58 | } 59 | 60 | /** 61 | * @param {function} [callback] 62 | * @return {string} 63 | */ 64 | toString(callback) { 65 | return this.toArray().map(node => node.toString(callback)).toString(); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/data-structures/doubly-linked-list/DoublyLinkedListNode.js: -------------------------------------------------------------------------------- 1 | export default class DoublyLinkedListNode { 2 | constructor(value, next = null, previous = null) { 3 | this.value = value; 4 | } 5 | 6 | toString(callback) { 7 | return callback ? callback(this.value) : `${this.value}`; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/data-structures/doubly-linked-list/README.md: -------------------------------------------------------------------------------- 1 | # Doubly Linked List 2 | 3 | ## Description 4 | 5 | Unlike a singly-linked list, a doubly-linked list node keeps a reference to the previous node in addition to the next node. This allows traversal in both directions of the list, towards the head and the tail. 6 | 7 | ![Linked List](../../../assets/doubly-linked-list.png) 8 | 9 | The operations for the doubly linked list is the same a singly linked list. 10 | 11 | ## Implementation 12 | 13 | In this exercise, implement the following functions for the `DoublyLinkedListNode` and `DoublyLinkedList` classes: 14 | 15 | - `DoublyLinkedListNode` 16 | - `constructor()` 17 | - Write a method that instantiates the node. 18 | - The node takes `value`, `previous` and `next`. 19 | - `LinkedList` 20 | - `constructor()` 21 | - Write a method that instantiates the list. 22 | - The instance variables `head` and `tail` should be set to `null`. 23 | - `prepend(value)` 24 | - Write a method that inserts the `value` at the beginning of the linked list. 25 | - `append(value)` 26 | - Write a method that inserts the `value` at the end of the linked list. 27 | - `find(value)` 28 | - Write a method that returns the `node` that contains the `value`. 29 | - `deleteHead()` 30 | - Write a method that deletes the first element in the linked list. 31 | - `deleteTail()` 32 | - Write a method that deletes the last element in the linked list. 33 | - `delete(value)` 34 | - Write a method that deletes the `value` in the linked list. 35 | 36 | The most important operations are `prepend/append` for adding data, `delete` for removing data, and `find` for retrieving data. 37 | 38 | ## Detailed Walkthrough 39 | 40 | To start, build `DoublyLinkedListNode`. A doubly linked list node keeps a reference to the previous node in addition to the next node. Then, build the constructor the same way as the singly linked list. 41 | 42 | > Jest Tip: If you need to filter by the exercise name, press `p`. 43 | > After pressing `p`, enter the string "Doubly" to filter by doubly linked list tests. 44 | 45 | ### `prepend()` 46 | 47 | - The `prepend` method inserts the item at the beginning of the list. 48 | - Operations: 49 | - Create a new `Node`. 50 | - Set the current head's previous reference to the new node. 51 | - Set the new node's next to the current `head`. 52 | - Update `head` to point at the new node. 53 | - Take into consideration where this is the first value in the linked list. 54 | 55 | ### `append()` 56 | 57 | - The `append` method inserts the item at the end of the list. 58 | - Operations: 59 | - Create a new `Node`. 60 | - Set the current tail's `next` to be the new node. 61 | - Set the new node's previous to the current `tail`. 62 | - Update `tail` to point at the new node. 63 | - Take into consideration where this is the first value in the linked list. 64 | 65 | ### `find(value)` 66 | 67 | - The `find` method returns the node with the target value. 68 | - Traverse the array in the same way as a singly linked list. 69 | 70 | ### `deleteHead()` / `deleteTail()` 71 | 72 | - The `deleteHead/Tail` methods are useful utilities methods. 73 | - Take into consideration where there is only one node in the linked list. 74 | 75 | ### `delete(value)` 76 | 77 | - The `delete` method removes the first node with the specified value. 78 | - The delete operation for a doubly linked list is significantly simpler due to having a reference to the previous node. 79 | - Utilize `find` and `deleteHead/Tail` methods written above to write the method. 80 | -------------------------------------------------------------------------------- /src/data-structures/doubly-linked-list/__test__/DoublyLinkedListNode.test.js: -------------------------------------------------------------------------------- 1 | import DoublyLinkedListNode from '../DoublyLinkedListNode'; 2 | 3 | describe('DoublyLinkedListNode', () => { 4 | it('should create list node with value', () => { 5 | const node = new DoublyLinkedListNode(1); 6 | 7 | expect(node.value).toBe(1); 8 | expect(node.next).toBeNull(); 9 | expect(node.previous).toBeNull(); 10 | }); 11 | 12 | it('should create list node with object as a value', () => { 13 | const nodeValue = { 14 | value: 1, 15 | key: 'test' 16 | }; 17 | const node = new DoublyLinkedListNode(nodeValue); 18 | 19 | expect(node.value.value).toBe(1); 20 | expect(node.value.key).toBe('test'); 21 | expect(node.next).toBeNull(); 22 | expect(node.previous).toBeNull(); 23 | }); 24 | 25 | it('should link nodes together', () => { 26 | const node2 = new DoublyLinkedListNode(2); 27 | const node1 = new DoublyLinkedListNode(1, node2); 28 | const node3 = new DoublyLinkedListNode(10, node1, node2); 29 | 30 | expect(node1.next).toBeDefined(); 31 | expect(node1.previous).toBeNull(); 32 | expect(node2.next).toBeNull(); 33 | expect(node2.previous).toBeNull(); 34 | expect(node3.next).toBeDefined(); 35 | expect(node3.previous).toBeDefined(); 36 | expect(node1.value).toBe(1); 37 | expect(node1.next.value).toBe(2); 38 | expect(node3.next.value).toBe(1); 39 | expect(node3.previous.value).toBe(2); 40 | }); 41 | 42 | it('should convert node to string', () => { 43 | const node = new DoublyLinkedListNode(1); 44 | 45 | expect(node.toString()).toBe('1'); 46 | 47 | node.value = 'string value'; 48 | expect(node.toString()).toBe('string value'); 49 | }); 50 | 51 | it('should convert node to string with custom stringifier', () => { 52 | const nodeValue = { 53 | value: 1, 54 | key: 'test' 55 | }; 56 | 57 | const node = new DoublyLinkedListNode(nodeValue); 58 | const toStringCallback = value => `value: ${value.value}, key: ${value.key}`; 59 | 60 | expect(node.toString(toStringCallback)).toBe('value: 1, key: test'); 61 | }); 62 | }); 63 | -------------------------------------------------------------------------------- /src/data-structures/linked-list/LinkedList.js: -------------------------------------------------------------------------------- 1 | import LinkedListNode from './LinkedListNode'; 2 | 3 | export default class LinkedList { 4 | constructor() {} 5 | 6 | /** 7 | * @param {*} value 8 | * @return {LinkedList} 9 | */ 10 | prepend(value) {} 11 | 12 | /** 13 | * @param {*} value 14 | * @return {LinkedList} 15 | */ 16 | append(value) {} 17 | 18 | /** 19 | * @param {*} value 20 | * @return {LinkedListNode} 21 | */ 22 | delete(value) {} 23 | 24 | /** 25 | * @param {Object} findParams 26 | * @param {*} findParams.value 27 | * @return {LinkedListNode} 28 | */ 29 | find({ 30 | value, 31 | callback 32 | }) {} 33 | 34 | /** 35 | * @param {*} value 36 | * @return {LinkedList} 37 | */ 38 | insertAfter(value, insertValue) {} 39 | 40 | /** 41 | * @return {LinkedListNode} 42 | */ 43 | deleteTail() {} 44 | 45 | /** 46 | * @return {LinkedListNode} 47 | */ 48 | deleteHead() {} 49 | 50 | /** 51 | * @return {LinkedListNode[]} 52 | */ 53 | toArray() { 54 | const nodes = []; 55 | let node = this.head; 56 | 57 | while (node) { 58 | nodes.push(node); 59 | node = node.next; 60 | } 61 | 62 | return nodes; 63 | } 64 | 65 | /** 66 | * @param {function} [callback] 67 | * @return {string} 68 | */ 69 | toString(callback) { 70 | return this.toArray().map(node => node.toString(callback)).toString(); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/data-structures/linked-list/LinkedListNode.js: -------------------------------------------------------------------------------- 1 | export default class LinkedListNode { 2 | constructor(value, next = null) { 3 | this.value = value; 4 | this.next = next; 5 | } 6 | 7 | toString(callback) { 8 | return callback ? callback(this.value) : `${this.value}`; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/data-structures/linked-list/__test__/LinkedListNode.test.js: -------------------------------------------------------------------------------- 1 | import LinkedListNode from '../LinkedListNode'; 2 | 3 | describe('LinkedListNode', () => { 4 | it('should create list node with value', () => { 5 | const node = new LinkedListNode(1); 6 | 7 | expect(node.value).toBe(1); 8 | expect(node.next).toBeNull(); 9 | }); 10 | 11 | it('should create list node with object as a value', () => { 12 | const nodeValue = { 13 | value: 1, 14 | key: 'test' 15 | }; 16 | const node = new LinkedListNode(nodeValue); 17 | 18 | expect(node.value.value).toBe(1); 19 | expect(node.value.key).toBe('test'); 20 | expect(node.next).toBeNull(); 21 | }); 22 | 23 | it('should link nodes together', () => { 24 | const node2 = new LinkedListNode(2); 25 | const node1 = new LinkedListNode(1, node2); 26 | 27 | expect(node1.next).toBeDefined(); 28 | expect(node2.next).toBeNull(); 29 | expect(node1.value).toBe(1); 30 | expect(node1.next.value).toBe(2); 31 | }); 32 | 33 | it('should convert node to string', () => { 34 | const node = new LinkedListNode(1); 35 | 36 | expect(node.toString()).toBe('1'); 37 | 38 | node.value = 'string value'; 39 | expect(node.toString()).toBe('string value'); 40 | }); 41 | 42 | it('should convert node to string with custom stringifier', () => { 43 | const nodeValue = { 44 | value: 1, 45 | key: 'test' 46 | }; 47 | 48 | const node = new LinkedListNode(nodeValue); 49 | const toStringCallback = value => `value: ${value.value}, key: ${value.key}`; 50 | 51 | expect(node.toString(toStringCallback)).toBe('value: 1, key: test'); 52 | }); 53 | }); 54 | -------------------------------------------------------------------------------- /src/data-structures/queue/Queue.js: -------------------------------------------------------------------------------- 1 | export default class Queue { 2 | constructor() { 3 | this.store = []; 4 | } 5 | 6 | isEmpty() {} 7 | 8 | peek() {} 9 | 10 | enqueue(el) {} 11 | 12 | dequeue() {} 13 | 14 | toString() { 15 | return this.store.toString(); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/data-structures/queue/README.md: -------------------------------------------------------------------------------- 1 | # Queue 2 | 3 | ## Description 4 | 5 | A queue is a data structure that can store or retrieve one item at a time, in first-in-first-out (FIFO) order. Think of FIFO as standing in the line at the grocery store. You would expect the first person that stood in the line to be the first one served by the cashier. 6 | 7 | Queue implements FIFO through two operations; `enqueue` and `dequeue`, which can be visualized in the diagram below: 8 | 9 | ![Queing operations](../../../assets/queue.png) 10 | 11 | - `enqueue` operation stores the item at the back of the queue. 12 | - `dequeue` operation retrieves the item from the front of the queue. 13 | 14 | (_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_) 15 | 16 | ## Implementation 17 | 18 | In this exercise, implement the following functions for the `Queue` class 19 | 20 | - `isEmpty()` 21 | - Write a method that returns `true` if the queue is currently empty. 22 | - `peek()` 23 | - Write a method that returns the element at the front of the queue. 24 | - `enqueue(el)` 25 | - Write a method that stores an element(`el`) into the queue. 26 | - `dequeue()` 27 | - Write a method that retrieves an element from the queue. 28 | - `toString()` 29 | - The stringify method is provided for you. `toString()` is a useful method to implement into data structures for easier debugging. 30 | - For example, you could use it for logging: 31 | ``` 32 | const queue = new Queue(); 33 | constole.log(queue.toString()); 34 | ``` 35 | - A queue is simple enough to be logged without actually needing `toString()`, but with more complex data structures, this is an invaluable method. 36 | 37 | ## Queue Exercises 38 | 39 | **Stack Queue** 40 | 41 | (Solve this exercise after finishing the stack exercise). 42 | 43 | Implement a queue using stacks. Instead of using an array as your store, use a stack: 44 | ``` 45 | const Stack = require('../stack/Stack'); 46 | 47 | export default class Stack { 48 | constructor() { 49 | this.store = new Stack(); 50 | } 51 | 52 | isEmpty() {} 53 | 54 | peek() {} 55 | 56 | push(value) {} 57 | 58 | pop() {} 59 | 60 | toString() { 61 | return this.store.toString(); 62 | } 63 | } 64 | ``` 65 | -------------------------------------------------------------------------------- /src/data-structures/queue/__test__/Queue.test.js: -------------------------------------------------------------------------------- 1 | import Queue from '../Queue'; 2 | 3 | describe('Queue', () => { 4 | it('should create empty queue', () => { 5 | const queue = new Queue(); 6 | expect(queue).not.toBeNull(); 7 | expect(queue.store).not.toBeNull(); 8 | }); 9 | 10 | it('should enqueue data to queue', () => { 11 | const queue = new Queue(); 12 | 13 | queue.enqueue(1); 14 | queue.enqueue(2); 15 | 16 | expect(queue.toString()).toBe('1,2'); 17 | }); 18 | 19 | it('should be possible to enqueue/dequeue in order', () => { 20 | const queue = new Queue(); 21 | 22 | queue.enqueue(1); 23 | queue.enqueue(2); 24 | 25 | expect(queue.dequeue()).toBe(1); 26 | expect(queue.dequeue()).toBe(2); 27 | }); 28 | 29 | it('should peek data from queue', () => { 30 | const queue = new Queue(); 31 | 32 | expect(queue.peek()).toBeNull(); 33 | 34 | queue.enqueue(1); 35 | queue.enqueue(2); 36 | 37 | expect(queue.peek()).toBe(1); 38 | expect(queue.peek()).toBe(1); 39 | }); 40 | 41 | it('should check if queue is empty', () => { 42 | const queue = new Queue(); 43 | 44 | expect(queue.isEmpty()).toBeTruthy(); 45 | 46 | queue.enqueue(1); 47 | 48 | expect(queue.isEmpty()).toBeFalsy(); 49 | }); 50 | 51 | it('should dequeue from queue in FIFO order', () => { 52 | const queue = new Queue(); 53 | 54 | queue.enqueue(1); 55 | queue.enqueue(2); 56 | 57 | expect(queue.dequeue()).toBe(1); 58 | expect(queue.dequeue()).toBe(2); 59 | expect(queue.dequeue()).toBeNull(); 60 | expect(queue.isEmpty()).toBeTruthy(); 61 | }); 62 | }); 63 | -------------------------------------------------------------------------------- /src/data-structures/stack/README.md: -------------------------------------------------------------------------------- 1 | # Stack 2 | 3 | ## Description 4 | 5 | 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. 6 | 7 | Stack implements LIFO through two operations: `push` and `pop`, which can be visualized in the diagram below: 8 | 9 | ![Queing operations](../../../assets/stack.png) 10 | (_A stack is usually visualized from bottom-to-top_) 11 | 12 | - `push` operation stores the item at the top of the stack. 13 | - `pop` operation retrieves the item from the top of the stack. 14 | 15 | ## Implementation 16 | 17 | In this exercise, implement the following functions for the `Stack` class 18 | 19 | - `isEmpty()` 20 | - Write a method that returns `true` if the stack is currently empty. 21 | - `peek()` 22 | - Write a method that returns the element from the top of the stack. 23 | - `push(el)` 24 | - Write a method that stores an element(`el`) into the stack. 25 | - `pop()` 26 | - Write a method that retrieves an element from the stack. 27 | - `toString()` 28 | - The stringify method has been provided for you. 29 | 30 | ## Stack Exercises 31 | 32 | **Bracket Matching** 33 | 34 | Write an algorithm to determine if all of the delimiters in an expression are matched and closed. 35 | 36 | - Delimiters are `(` `{` `[` 37 | - Closed is `()` and `(()` would not be closed 38 | - Matched is `{}`, and `{]` would not be matched. 39 | 40 | ``` 41 | const bracketMatch = string => { 42 | 43 | } 44 | 45 | console.log(bracketMatch('{ac[bb]}') === true); 46 | console.log(bracketMatch('[dklf(df(kl))d]{}') === true); 47 | console.log(bracketMatch('{[[[]]]}') === true); 48 | console.log(bracketMatch('{3234[fd') === false); 49 | console.log(bracketMatch('{df][d}') === false); 50 | console.log(bracketMatch('([)]') === false); 51 | ``` 52 | 53 | -------------------------------------------------------------------------------- /src/data-structures/stack/Stack.js: -------------------------------------------------------------------------------- 1 | export default class Stack { 2 | constructor() { 3 | this.store = []; 4 | } 5 | 6 | isEmpty() {} 7 | 8 | peek() {} 9 | 10 | push(el) {} 11 | 12 | pop() {} 13 | 14 | toString() { 15 | return this.store.toString(); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/data-structures/stack/__test__/Stack.test.js: -------------------------------------------------------------------------------- 1 | import Stack from '../Stack'; 2 | 3 | describe('Stack', () => { 4 | it('should create empty stack', () => { 5 | const stack = new Stack(); 6 | expect(stack).not.toBeUndefined(); 7 | expect(stack.store).not.toBeUndefined(); 8 | }); 9 | 10 | it('should stack data to stack', () => { 11 | const stack = new Stack(); 12 | 13 | stack.push(1); 14 | stack.push(2); 15 | 16 | expect(stack.toString()).toBe('1,2'); 17 | }); 18 | 19 | it('should peek data from stack', () => { 20 | const stack = new Stack(); 21 | 22 | expect(stack.peek()).toBeUndefined(); 23 | 24 | stack.push(1); 25 | stack.push(2); 26 | 27 | expect(stack.peek()).toBe(2); 28 | expect(stack.peek()).toBe(2); 29 | }); 30 | 31 | it('should check if stack is empty', () => { 32 | const stack = new Stack(); 33 | 34 | expect(stack.isEmpty()).toBeTruthy(); 35 | 36 | stack.push(1); 37 | 38 | expect(stack.isEmpty()).toBeFalsy(); 39 | }); 40 | 41 | it('should pop data from stack', () => { 42 | const stack = new Stack(); 43 | 44 | stack.push(1); 45 | stack.push(2); 46 | 47 | expect(stack.pop()).toBe(2); 48 | expect(stack.pop()).toBe(1); 49 | expect(stack.pop()).toBeUndefined(); 50 | expect(stack.isEmpty()).toBeTruthy(); 51 | }); 52 | 53 | it('should be possible to push/pop', () => { 54 | const stack = new Stack(); 55 | 56 | stack.push(1); 57 | stack.push(2); 58 | 59 | expect(stack.toString()).toBe('1,2'); 60 | expect(stack.pop()).toBe(2); 61 | expect(stack.pop()).toBe(1); 62 | }); 63 | }); 64 | --------------------------------------------------------------------------------