├── .gitignore ├── .markdownlint.json ├── 01-basics ├── algorithms │ ├── max-of-three-numbers.js │ └── max-of-three-numbers.spec.js ├── img │ ├── combinations-1.jpg │ ├── combinations-2.jpg │ ├── max-of-three-numbers.flowchart.png │ ├── multiplying.jpg │ ├── permutations-1.jpg │ ├── permutations-2.jpg │ ├── permutations-3.jpg │ ├── permutations-4.jpg │ ├── probability-1.jpg │ ├── probability-2.jpg │ ├── probability-3.jpg │ ├── probability-4.jpg │ ├── sums-1.jpg │ └── sums-2.jpg ├── problems │ ├── fragile-system.md │ └── hot-server.md └── summary.md ├── 02-complexity ├── algorithms │ ├── selection-sort.js │ └── selection-sort.spec.js ├── img │ └── big-o.png └── summary.md ├── 03-strategy ├── algorithms │ ├── best-trade-bottom-up.js │ ├── best-trade-bottom-up.spec.js │ ├── best-trade-divide-n-conquer.js │ ├── best-trade-divide-n-conquer.spec.js │ ├── best-trade.js │ ├── best-trade.spec.js │ ├── dynamic-fibonacci.js │ ├── dynamic-fibonacci.spec.js │ ├── eight-queens-puzzle.v1.js │ ├── eight-queens-puzzle.v1.spec.js │ ├── eight-queens-puzzle.v2.js │ ├── eight-queens-puzzle.v2.spec.js │ ├── fibonacci.js │ ├── fibonacci.spec.js │ ├── greedy-knapsack.js │ ├── greedy-knapsack.spec.js │ ├── knapsack-divide-n-conquer.js │ ├── knapsack-divide-n-conquer.spec.js │ ├── knapsack-dnc.js │ ├── knapsack-dnc.spec.js │ ├── knapsack-dp.js │ ├── knapsack-dp.spec.js │ ├── knapsack.js │ ├── knapsack.spec.js │ ├── merge-sort.js │ ├── merge-sort.spec.js │ ├── merge-two-sorted-lists.js │ ├── merge-two-sorted-lists.spec.js │ ├── palindrome.js │ ├── palindrome.spec.js │ ├── powdered-knapsack.js │ ├── powdered-knapsack.spec.js │ ├── power-set-recursive.js │ ├── power-set-recursive.spec.js │ ├── power-set.js │ └── power-set.spec.js └── summary.md ├── 04-data ├── data-structures │ ├── DoublyLinkedList.js │ ├── DoublyLinkedList.spec.js │ ├── DoublyLinkedNode.js │ ├── DoublyLinkedNode.spec.js │ ├── List.js │ ├── List.spec.js │ ├── Map.js │ ├── Map.spec.js │ ├── Node.js │ ├── Node.spec.js │ ├── PriorityQueue.js │ ├── PriorityQueue.spec.js │ ├── Queue.js │ ├── Queue.spec.js │ ├── Set.js │ ├── Set.spec.js │ ├── SortedList.js │ ├── SortedList.spec.js │ ├── Stack.js │ └── Stack.spec.js └── summary.md ├── 05-algorithms └── .gitkeep ├── 06-databases └── .gitkeep ├── 07-computers └── .gitkeep ├── 08-programming └── .gitkeep ├── README.md ├── algorithms.md └── package.json /.gitignore: -------------------------------------------------------------------------------- 1 | out/ 2 | docs/ 3 | *.log -------------------------------------------------------------------------------- /.markdownlint.json: -------------------------------------------------------------------------------- 1 | { 2 | "default": true, 3 | "MD036": false, 4 | "MD026": false, 5 | "MD013": false 6 | } -------------------------------------------------------------------------------- /01-basics/algorithms/max-of-three-numbers.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Returns the max of three numbers. 3 | * @param {int} number A 4 | * @param {int} number B 5 | * @param {int} number C 6 | */ 7 | function maximum(a, b, c) { 8 | if (a > b) { 9 | if (a > c) { 10 | return a; 11 | } else { 12 | return c; 13 | } 14 | } else { 15 | if (b > c) { 16 | return b; 17 | } else { 18 | return c; 19 | } 20 | } 21 | } 22 | 23 | module.exports = maximum; 24 | -------------------------------------------------------------------------------- /01-basics/algorithms/max-of-three-numbers.spec.js: -------------------------------------------------------------------------------- 1 | const assert = require('assert'); 2 | 3 | const maxOfThree = require('./max-of-three-numbers'); 4 | 5 | describe('Algorithm - Max of three numbers', function () { 6 | const testCases = [ 7 | { 8 | input: [1, 2, 3], 9 | expected: 3 10 | }, 11 | { 12 | input: [9, 8, 7], 13 | expected: 9 14 | }, 15 | { 16 | input: [4, 6, 5], 17 | expected: 6 18 | }, 19 | { 20 | input: [5, 5, 5], 21 | expected: 5 22 | } 23 | ]; 24 | 25 | testCases.forEach(({ input, expected }) => { 26 | it(`should return max: ${expected} for input: ${input.join(' ')}`, function () { 27 | assert.equal(maxOfThree.apply(null, input), expected, `should return max: ${expected}`); 28 | }) 29 | }) 30 | }) -------------------------------------------------------------------------------- /01-basics/img/combinations-1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iramirezc-learning/book-computer-science-distilled/e834b8d58871908a0594597d03d99f88128df755/01-basics/img/combinations-1.jpg -------------------------------------------------------------------------------- /01-basics/img/combinations-2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iramirezc-learning/book-computer-science-distilled/e834b8d58871908a0594597d03d99f88128df755/01-basics/img/combinations-2.jpg -------------------------------------------------------------------------------- /01-basics/img/max-of-three-numbers.flowchart.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iramirezc-learning/book-computer-science-distilled/e834b8d58871908a0594597d03d99f88128df755/01-basics/img/max-of-three-numbers.flowchart.png -------------------------------------------------------------------------------- /01-basics/img/multiplying.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iramirezc-learning/book-computer-science-distilled/e834b8d58871908a0594597d03d99f88128df755/01-basics/img/multiplying.jpg -------------------------------------------------------------------------------- /01-basics/img/permutations-1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iramirezc-learning/book-computer-science-distilled/e834b8d58871908a0594597d03d99f88128df755/01-basics/img/permutations-1.jpg -------------------------------------------------------------------------------- /01-basics/img/permutations-2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iramirezc-learning/book-computer-science-distilled/e834b8d58871908a0594597d03d99f88128df755/01-basics/img/permutations-2.jpg -------------------------------------------------------------------------------- /01-basics/img/permutations-3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iramirezc-learning/book-computer-science-distilled/e834b8d58871908a0594597d03d99f88128df755/01-basics/img/permutations-3.jpg -------------------------------------------------------------------------------- /01-basics/img/permutations-4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iramirezc-learning/book-computer-science-distilled/e834b8d58871908a0594597d03d99f88128df755/01-basics/img/permutations-4.jpg -------------------------------------------------------------------------------- /01-basics/img/probability-1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iramirezc-learning/book-computer-science-distilled/e834b8d58871908a0594597d03d99f88128df755/01-basics/img/probability-1.jpg -------------------------------------------------------------------------------- /01-basics/img/probability-2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iramirezc-learning/book-computer-science-distilled/e834b8d58871908a0594597d03d99f88128df755/01-basics/img/probability-2.jpg -------------------------------------------------------------------------------- /01-basics/img/probability-3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iramirezc-learning/book-computer-science-distilled/e834b8d58871908a0594597d03d99f88128df755/01-basics/img/probability-3.jpg -------------------------------------------------------------------------------- /01-basics/img/probability-4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iramirezc-learning/book-computer-science-distilled/e834b8d58871908a0594597d03d99f88128df755/01-basics/img/probability-4.jpg -------------------------------------------------------------------------------- /01-basics/img/sums-1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iramirezc-learning/book-computer-science-distilled/e834b8d58871908a0594597d03d99f88128df755/01-basics/img/sums-1.jpg -------------------------------------------------------------------------------- /01-basics/img/sums-2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iramirezc-learning/book-computer-science-distilled/e834b8d58871908a0594597d03d99f88128df755/01-basics/img/sums-2.jpg -------------------------------------------------------------------------------- /01-basics/problems/fragile-system.md: -------------------------------------------------------------------------------- 1 | # Fragile System 2 | 3 | **Problem:** 4 | 5 | _We have to create a database system with the following requirements:_ 6 | 7 | > I. If the database is locked, we can save data. 8 | > 9 | > II. A database lock on a full write queue cannot happen. 10 | > 11 | > III. Either the write queue is full, or the cache is loaded. 12 | > 13 | > IV. If the cache is loaded, the database cannot be locked. 14 | 15 | _Is this possible? Under which conditions will it work?_ 16 | 17 | **Solution:** 18 | 19 | Conditions 20 | 21 | > `A` = Database Locked 22 | > 23 | > `B` = Can save data 24 | > 25 | > `C` = Write queue is full 26 | > 27 | > `D` = Cache is loaded 28 | 29 | Model 30 | 31 | > `I` = `A -> B` 32 | > 33 | > `II` = `!(A AND C)` 34 | > 35 | > `III` = `C OR D` 36 | > 37 | > `IV` = `D -> !A` 38 | 39 | Truth Table: ✓ ✗ 40 | 41 | | #| A | B | C | D | | I | II | III | IV | * | 42 | |--|---|---|---|---|-|---|----|-----|----|---| 43 | | 1| ✓ | ✓ | ✓ | ✓ | | ✓ | ✗ | ✓ | ✗ | ✗ | 44 | | 2| ✓ | ✓ | ✓ | ✗ | | ✓ | ✗ | ✓ | ✓ | ✗ | 45 | | 3| ✓ | ✓ | ✗ | ✓ | | ✓ | ✓ | ✓ | ✗ | ✗ | 46 | | 4| ✓ | ✓ | ✗ | ✗ | | ✓ | ✓ | ✗ | ✓ | ✗ | 47 | | 5| ✓ | ✗ | ✓ | ✓ | | ✗ | ✗ | ✓ | ✗ | ✗ | 48 | | 6| ✓ | ✗ | ✓ | ✗ | | ✗ | ✗ | ✓ | ✓ | ✗ | 49 | | 7| ✓ | ✗ | ✗ | ✓ | | ✗ | ✓ | ✓ | ✗ | ✗ | 50 | | 8| ✓ | ✗ | ✗ | ✗ | | ✗ | ✓ | ✗ | ✓ | ✗ | 51 | | 9| ✗ | ✓ | ✓ | ✓ | | ✓ | ✓ | ✓ | ✓ | ✓ | 52 | |10| ✗ | ✓ | ✓ | ✗ | | ✓ | ✓ | ✓ | ✓ | ✓ | 53 | |11| ✗ | ✓ | ✗ | ✓ | | ✓ | ✓ | ✓ | ✓ | ✓ | 54 | |12| ✗ | ✓ | ✗ | ✗ | | ✓ | ✓ | ✗ | ✓ | ✗ | 55 | |13| ✗ | ✗ | ✓ | ✓ | | ✓ | ✓ | ✓ | ✓ | ✓ | 56 | |14| ✗ | ✗ | ✓ | ✗ | | ✓ | ✓ | ✓ | ✓ | ✓ | 57 | |15| ✗ | ✗ | ✗ | ✓ | | ✓ | ✓ | ✓ | ✓ | ✓ | 58 | |16| ✗ | ✗ | ✗ | ✗ | | ✓ | ✓ | ✗ | ✓ | ✗ | 59 | 60 | > `*` All conditions (I-IV) are met 61 | 62 | Result 63 | 64 | > All requirements are met in states: `9-11` and `13-15`. 65 | > 66 | > Cache will not be loaded in states `10` and `14`. 67 | > 68 | > Database can **NOT** ever be locked when `A = false`. 69 | -------------------------------------------------------------------------------- /01-basics/problems/hot-server.md: -------------------------------------------------------------------------------- 1 | # Hot Server 2 | 3 | **Problem:** 4 | 5 | _A server crashes if it's overheating while the air conditioning is off. It also crashes if it's overheating and it chassis cooler fails. In which conditions does the server work?_ 6 | 7 | **Solution:** 8 | 9 | Conditions 10 | 11 | > `A` = Overheating 12 | > 13 | > `B` = Air conditioning is off 14 | > 15 | > `C` = Chassis cooler fails 16 | > 17 | > `D` = Server crashes 18 | 19 | Model the problem 20 | 21 | > `(A AND B) OR (A AND C) -> D` 22 | 23 | Use distributivity 24 | 25 | > `A AND (B OR C) -> D` 26 | 27 | Use contrapositive 28 | 29 | > `!D -> !(A AND (B OR C))` 30 | 31 | Use DeMorgan's Law 32 | 33 | > `!D -> !A OR !(B OR C)` 34 | 35 | Use DeMorgan's Law one more time 36 | 37 | > `!D -> !A OR (!B AND !C)` 38 | 39 | Result 40 | 41 | > The server won't crash if it's not overheated or both air conditioning and cooler don't fail. 42 | > 43 | > In other words, The server works if it's not overheated or both air and cooler are working. 44 | -------------------------------------------------------------------------------- /01-basics/summary.md: -------------------------------------------------------------------------------- 1 | # Computer Science Distilled 2 | 3 | ## Summary - Chapter 1: Basics 4 | 5 | > Computer science is not about machines, in the same way that astronomy is not about telescopes. 6 | > There is an essential unity of mathematics and computer science. 7 | > 8 | > -- _Edsger Dijkstra_ 9 | 10 | **:dart: Objectives:** 11 | 12 | > * _Model Ideas into flowcharts and pseudocode_ 13 | > * _Know right from wrong with logic_ 14 | > * _Count stuff_ 15 | > * _Calculate probabilities_ 16 | 17 | --- 18 | 19 | ### Section 1: Ideas 20 | 21 | #### 1.1 - Intro 22 | 23 | ##### organizing methods - write everything down 24 | 25 | > It is very easy for our brains to overflow with fact and ideas, **dump all important stuff on paper**. 26 | 27 | ##### ways to break down a problem 28 | 29 | > There are different ways to help you break down a problem into smaller processable chunks: 30 | > 31 | > * Flowcharts 32 | > * Pseudocode 33 | > * Math Modeling _(important to express abstract ideas)_ 34 | 35 | #### 1.2 - Flowcharts 36 | 37 | ##### guidelines to write flowcharts 38 | 39 | > * write states and instruction steps inside rectangles 40 | > * write decision steps, where the process may go different ways, inside diamonds 41 | > * never mix an instruction step with a decision step 42 | > * connect sequential steps with an arrow 43 | > * mark the start and end of the process 44 | 45 | ##### example of a flowchart: 46 | 47 | ![Flowchart for Maximum of three numbers](./img/max-of-three-numbers.flowchart.png) 48 | 49 | #### 1.3 - Pseudocode 50 | 51 | ##### what's pseudocode? 52 | 53 | > human-friendly code that expresses computational processes 54 | 55 | ##### example of a pseudocode: 56 | 57 | ```bash 58 | # max of three numbers 59 | 60 | function maximum (A, B, C) 61 | if A > B 62 | if A > C 63 | max <- A 64 | else 65 | max <- C 66 | else 67 | if B > C 68 | max <- B 69 | else 70 | max <- C 71 | 72 | return max 73 | ``` 74 | 75 | > Code: [max-of-three-numbers.js](./algorithms/max-of-three-numbers.js) 76 | 77 | #### 1.4 - Mathematical Models 78 | 79 | **:bulb: Tip:** 80 | 81 | > _stand on the shoulders of giants who created these tools_ 82 | 83 | ##### what's a model? 84 | 85 | > a set of concepts that represents a problem and its characteristics 86 | 87 | ##### example of a mathematical model: 88 | 89 | > __Livestock Fence__ 90 | > 91 | > _Your farm has two types of livestock. You have 100 units of barbed wire to make a rectangular fence for the animals, with a straight division for separating them._ 92 | > 93 | > _How do you frame the fence in order to maximize the pasture's area?_ 94 | 95 | **Solution:** 96 | 97 | > Quadratic equation: 98 | > 99 | > $$ A = w \times l $$ 100 | > $$ 100 = 2w + 3l $$ 101 | > $$ l = \frac{100-2w}{3} $$ 102 | > $$ A = \frac{100}{3}w - \frac{2}{3}w^2 $$ 103 | 104 | --- 105 | 106 | ### Section 2: Logic 107 | 108 | #### 2.1 - Intro 109 | 110 | Topics covered in this section: 111 | 112 | > * Logic statements 113 | > * Operators 114 | > * Special Algebra 115 | 116 | ##### Mathematical Logic 117 | 118 | > _variables_ and _operators_ represent validity of things, expressing `true` or `false` values 119 | 120 | #### 2.2 - Operators 121 | 122 | ##### NOT (negation | inversion) 123 | 124 | > `!A` or `~A` 125 | 126 | | A | ~A | 127 | |---|:--:| 128 | | T | F | 129 | | F | T | 130 | 131 | ##### IMPLIES (implication) 132 | 133 | > Dependency between variables 134 | > 135 | > `A -> B` implies that `B` depends on `A` to be `true` 136 | > 137 | > Example: _If it rains, then I'll take my umbrella_ 138 | > 139 | > _**Notice:** `A -> B` is NOT equivalent to `B -> A`_ this is called **inverse error** 140 | > 141 | > _**Notice:** `A -> B` is equivalent to `!A | B`_ 142 | 143 | | A | B | A -> B | 144 | |---|---|:------:| 145 | | T | T | T | 146 | | T | F | F | 147 | | F | T | T | 148 | | F | F | T | 149 | 150 | > 151 | > A `true` premise implies a `true` conclusion. `T -> T = T` 152 | > 153 | > A `true` premise can NOT imply a `false` conclusion. `T -> F = F` 154 | > 155 | > You can conclude anything from a `false` assumption. `F -> * = T` 156 | 157 | ##### CONTRAPOSITIVE 158 | 159 | > _If `A -> B` then the contrapositive is `!B -> !A`_ 160 | > 161 | > Example: _If You love me, I'll kiss you_ 162 | > 163 | > _**Notice:** `A -> B` is the same as `!B -> !A`_ 164 | > 165 | > Example: _I won't kiss you if you don't love me_ 166 | 167 | ##### EQUIVALENCE (biconditional) 168 | 169 | > `A <-> B` expresses that `A` depends on `B` and viceversa 170 | > 171 | > Example: _I'll kiss you only if you love me_ or _Only if you love me I'll kiss you_ 172 | 173 | | A | B | A <-> B | 174 | |---|---|:-------:| 175 | | T | T | T | 176 | | T | F | F | 177 | | F | T | F | 178 | | F | F | T | 179 | 180 | ##### AND (conjunction) 181 | 182 | > `A & B` or $A \bigwedge B$ 183 | 184 | | A | B | A & B | 185 | |---|---|:-----:| 186 | | T | T | T | 187 | | T | F | F | 188 | | F | T | F | 189 | | F | F | F | 190 | 191 | ##### OR (disjunction) 192 | 193 | > `A | B` or $A \bigvee B$ 194 | 195 | | A | B | A \| B | 196 | |---|---|:------:| 197 | | T | T | T | 198 | | T | F | T | 199 | | F | T | T | 200 | | F | F | F | 201 | 202 | ##### XOR (eXclusive OR) 203 | 204 | > `A XOR B` 205 | > 206 | > _**Notice:** `A XOR B` is equivalent to `!(A <-> B)`_ 207 | 208 | | A | B | A X B | 209 | |---|---|:-----:| 210 | | T | T | F | 211 | | T | F | T | 212 | | F | T | T | 213 | | F | F | F | 214 | 215 | > _**Notice:** `A X B` is `T` if either `A` or `B` is `T`, but not both._ 216 | 217 | ##### Summary - Logical operations table 218 | 219 | | A | B | !A | A -> B | A <-> B | A & B | A \| B | A xor B | 220 | |:--:|:--:|:--:|:------:|:-------:|:-----:|:------:|:-------:| 221 | | ✓ | ✓ | ✗ | ✓ | ✓ | ✓ | ✓ | ✗ | 222 | | ✓ | ✗ | ✗ | ✗ | ✗ | ✗ | ✓ | ✓ | 223 | | ✗ | ✓ | ✓ | ✓ | ✗ | ✗ | ✓ | ✓ | 224 | | ✗ | ✗ | ✓ | ✓ | ✓ | ✗ | ✗ | ✗ | 225 | 226 | ##### Operators Precedence 227 | 228 | > 1. `NOT` 229 | > 2. `AND`, 230 | > 3. `OR`, `XOR` 231 | > 4. `IMPLIES`, `EQUIVALENCE` 232 | 233 | #### 2.3 - Boolean Algebra 234 | 235 | Boolean Algebra simplifies logical expressions. 236 | 237 | `Boolean` comes from __George Bool.__ 238 | 239 | ##### Associativity 240 | 241 | > `A AND (B AND C) = (A AND B) AND C` 242 | > 243 | > `A OR (B OR C) = (A OR B) OR C` 244 | 245 | ##### Distributivity 246 | 247 | > `A AND (B OR C) = (A AND B) OR (A AND C)` 248 | > 249 | > `A OR (B AND C) = (A OR B) AND (A OR C)` 250 | 251 | ##### De Morgan's Law 252 | 253 | > `!(A AND B) = !A OR !B` 254 | > 255 | > `!A AND !B = !(A OR B)` 256 | 257 | ###### Problem: [Hot Server](./problems/hot-server.md) 258 | 259 | #### 2.4 - Truth Tables 260 | 261 | ##### A truth table has... 262 | 263 | > **columns** for each variable 264 | > 265 | > **rows** to represent possible combinations of variable states 266 | > 267 | > `Note:` A truth table with `30` variables can have more than a billion rows. 268 | > 269 | > $2^{30} = 1,073,741,824$ 270 | 271 | ##### example of a truth table 272 | 273 | |v1|v2|v3| 274 | |--|--|--| 275 | |:white_check_mark:|:white_check_mark:|:white_check_mark:| 276 | |:white_check_mark:|:white_check_mark:|:x:| 277 | |:white_check_mark:|:x:|:white_check_mark:| 278 | |:white_check_mark:|:x:|:x:| 279 | |:x:|:white_check_mark:|:white_check_mark:| 280 | |:x:|:white_check_mark:|:x:| 281 | |:x:|:x:|:white_check_mark:| 282 | |:x:|:x:|:x:| 283 | 284 | > `3` variables = `8` possible combinations: 285 | > 286 | > $2^3 = 8$ 287 | 288 | ###### Problem: [Fragile System](./problems/fragile-system.md) 289 | 290 | #### 2.5 - Logic in Computing 291 | 292 | ##### logic gates... 293 | 294 | > perform logic operations on electric current 295 | 296 | --- 297 | 298 | ### Section 3: Counting 299 | 300 | #### 3.1 - Intro 301 | 302 | > __Counting__ and __Logic__ belong to an important field to computer science called __Discrete Mathematics__. 303 | 304 | #### 3.2 - Multiplying 305 | 306 | ![Multiplying notes](./img/multiplying.jpg) 307 | 308 | #### 3.3 - Permutations 309 | 310 | ![Permutation notes 1](./img/permutations-1.jpg) 311 | ![Permutation notes 2](./img/permutations-2.jpg) 312 | 313 | #### 3.4 - Permutations with identical items 314 | 315 | ![Permutation notes 3](./img/permutations-3.jpg) 316 | ![Permutation notes 4](./img/permutations-4.jpg) 317 | 318 | #### 3.5 - Combinations 319 | 320 | ![Combinations notes 1](./img/combinations-1.jpg) 321 | ![Combinations notes 2](./img/combinations-2.jpg) 322 | 323 | #### 3.6 - Sums 324 | 325 | ![Sums notes 1](./img/sums-1.jpg) 326 | ![Sums notes 2](./img/sums-2.jpg) 327 | 328 | --- 329 | 330 | ### Section 4: Probability 331 | 332 | #### 4.1 - Counting Outcomes 333 | 334 | ![Probability notes 1](./img/probability-1.jpg) 335 | 336 | #### 4.2 - Independent Events 337 | 338 | ![Probability notes 2](./img/probability-2.jpg) 339 | 340 | #### 4.3 - Mutually Exclusive Events 341 | 342 | ![Probability notes 3](./img/probability-3.jpg) 343 | 344 | #### 4.4 - Complementary Events 345 | 346 | ![Probability notes 4](./img/probability-4.jpg) 347 | 348 | #### 4.5 - The Gambler's Fallacy 349 | 350 | > "Past events never affect the outcome of an independent event." 351 | 352 | ## References: 353 | 354 | * [Logical Operations and Truth Tables](http://kias.dyndns.org/comath/21.html) 355 | 356 | ### In the book: 357 | 358 | * [xkcd: An Apple for a Dollar](https://xkcd.com/) 359 | * [Programmer's Life (@ProgrammersLife) | Twitter](https://twitter.com/programmerslife) 360 | * [Wolfram|Alpha: Computational Intelligence](https://www.wolframalpha.com/) 361 | * [Solving the Zebra Puzzle with Boolean Algebra](https://code.energy/solving-zebra-puzzle/) 362 | * Discrete Mathematics and its Applications, 7th Edition 363 | -------------------------------------------------------------------------------- /02-complexity/algorithms/selection-sort.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Swaps two values in an array by the indexes given. 3 | * @param {Array} list 4 | * @param {int} current (index of array) 5 | * @param {int} smallest (index of array) 6 | */ 7 | function swap(list, current, smallest) { 8 | let copy = list[current]; 9 | 10 | list[current] = list[smallest]; 11 | list[smallest] = copy; 12 | } 13 | 14 | /** 15 | * Selection Sort Algorithm 16 | * Running Time: O(n^2) 17 | * @param {Array} list 18 | */ 19 | function selectionSort(list) { 20 | for (let current = 0, len = list.length; current < len - 1; current++) { 21 | let smallest = current; 22 | 23 | for (let i = current + 1; i < len; i++) { 24 | if (list[i] < list[smallest]) { 25 | smallest = i; 26 | } 27 | } 28 | 29 | swap(list, current, smallest); 30 | } 31 | } 32 | 33 | module.exports = selectionSort; 34 | -------------------------------------------------------------------------------- /02-complexity/algorithms/selection-sort.spec.js: -------------------------------------------------------------------------------- 1 | const assert = require('assert'); 2 | 3 | const selectionSort = require('./selection-sort'); 4 | 5 | describe('Algorithm - Selection Sort', function () { 6 | const testCases = [ 7 | { 8 | input: [10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0], 9 | expected: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10] 10 | }, 11 | { 12 | input: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10], 13 | expected: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10] 14 | }, 15 | { 16 | input: [5, 5, 5, 5, 5], 17 | expected: [5, 5, 5, 5, 5] 18 | } 19 | ]; 20 | 21 | testCases.forEach(({ input, expected }) => { 22 | it(`should sort input: ${input.join(', ')}`, function () { 23 | selectionSort(input); 24 | assert.deepEqual(input, expected, `order should be: ${expected.join(', ')}. Given: ${input.join(', ')}`); 25 | }); 26 | }); 27 | }); -------------------------------------------------------------------------------- /02-complexity/img/big-o.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iramirezc-learning/book-computer-science-distilled/e834b8d58871908a0594597d03d99f88128df755/02-complexity/img/big-o.png -------------------------------------------------------------------------------- /02-complexity/summary.md: -------------------------------------------------------------------------------- 1 | # Computer Science Distilled 2 | 3 | ## Summary - Chapter 2: Complexity 4 | 5 | > In almost every computation, a variety of arrangements for the processes is possible. It is essential to choose that arrangement which shall tend to minimize the time necessary for the calculation. 6 | > 7 | > -- _Ada Lovelace_ 8 | 9 | **:dart: Objectives:** 10 | 11 | > * _Count and interpret time complexities_ 12 | > * _Express growth with Big-O notation_ 13 | > * _Discard exponential algorithms_ 14 | > * _Make sure you have enough computer memory_ 15 | 16 | __Running Time__ 17 | 18 | > $Time\ complexity = T(n) = running\ cost$ 19 | > 20 | > **Problem:** 21 | > 22 | > _Imagine you have a deck of `100` cards. It takes $T(n^2)$ to sort all the cards. How much longer will it take if we double the size?_ 23 | > 24 | > **Solution:** 25 | > 26 | > It will take `4` times longer: 27 | > 28 | > $$n = 100\ cards$$ 29 | > $$T(n) = n^2 = 100^2 = 10,000$$ 30 | > $$T(2n) = 2n^2 = (2 \times 100)^2 = 40,000$$ 31 | > $$\frac{T(2n)}{T(n)} = \frac{2n^2}{n^2} = \frac{40,000}{10,000} = 4$$ 32 | 33 | When an algorithm can have different values of $T(n)$ for the same value of $n$, we resort to cases: 34 | 35 | > * Best case 36 | > * Worst case 37 | > * Average case 38 | 39 | --- 40 | 41 | ### Section 1: Counting Time 42 | 43 | #### Selection Sort algorithm 44 | 45 | ```bash 46 | function selection_sort (list) 47 | for current <- 1 ... list.length - 1 48 | smallest <- current 49 | for i <- current + 1 ... list.length 50 | if list[i] < list[smallest] 51 | smallest <- i 52 | list.swap_items(current, smallest) 53 | ``` 54 | 55 | > Code: [selection-sort.js](./algorithms/selection-sort.js) 56 | 57 | #### Selection Sort time complexity 58 | 59 | > Outer loop runs $n - 1$ times and runs $2$ operations: 60 | > 61 | > $$2(n - 1) = 2n - 2$$ 62 | > 63 | > Inner loop runs $n - 1$ times, then $n - 2$, then $n - 3$ and so on... 64 | > 65 | > Formula: 66 | > 67 | > $$\sum_{i=1}^{n}i = \frac{n(n+1)}{2}$$ 68 | > 69 | > Total inner loop: 70 | > 71 | > $$\sum_{i=1}^{n - 1}i = \frac{n-1(n-1+1)}{2} = \frac{n-1(n)}{2} = \frac{n^2-n}{2}$$ 72 | > 73 | > Inner loop in the worst case does a comparison and one assignment (2 operations): 74 | > 75 | > $$2\times\frac{n^2-n}{2} = \frac{2n^2-2n}{2}=n^2-n$$ 76 | > 77 | > Total cost: 78 | > 79 | > $$2n-2+n^2-n = n^2+n-2$$ 80 | > 81 | > then, Selection Sort time complexity is: 82 | > 83 | > $$T(n) = n^2+n-2$$ 84 | > 85 | > if $n=8$ numbers then: 86 | > 87 | > $$8^2+8-2 = 70$$ 88 | > 89 | > if we double $n\times2$, then: 90 | > 91 | > $$16^2+16-2 = 270$$ 92 | > 93 | > selection sort will be $\approx4$ times slower 94 | > 95 | > $$\frac{T(16)}{T(8)} = \frac{270}{70} = 3.857\ (times\ slower)$$ 96 | > 97 | > if we double the size again: 98 | > 99 | > $$32^2+32-2 = 1054$$ 100 | > 101 | > then: 102 | > 103 | > $$\frac{T(32)}{T(16)} = \frac{1054}{270} = 3.903\ (times\ slower)$$ 104 | > 105 | > **Final thoughts:** we can say that sorting `2 million` of numbers will take `4 times` the time of sorting `1 million`. Everytime we double the size `time = time x 4`. 106 | 107 | #### Dominant Term 108 | 109 | > **Index Cards** 110 | > 111 | > _Yesterday, you knocked over one box of index cards. It took you 2 hours of Selection Sort to fix it. Today you spilled ten boxes. How much time will you need to arrange the cards back in?_ 112 | 113 | **Solution:** 114 | 115 | > Selection sort run $T(n) = n^2 + n -2$ 116 | > 117 | > The fastest growing term is $n^2$ so $T(n) \approx n^2$ 118 | > 119 | > One box has $n$ cards, so: 120 | > 121 | > $$10\ boxes = \frac{T(10n)}{T(n)}\approx\frac{(10n)^2}{n^2}=\frac{100n^2}{n^2}=100\ (times\ slower)$$ 122 | > 123 | > Sorting `1 box` takes `2 hours`, then 124 | > 125 | > sorting `10 boxes = 2 hours x 100 = 200 hours` 126 | 127 | **Example:** 128 | 129 | > $n = 1\ box = 1,000\ cards$ 130 | > 131 | > $10 n = 10\ boxes = 10,000\ cards$ 132 | > 133 | > $$\frac{T(10n)}{T(n)}=\frac{(10\times1,000)^2}{1,000^2}=\frac{100,000,000}{1,000,000}=100\ (times\ slower)$$ 134 | 135 | #### Growths 136 | 137 | > * $n$ linear growth 138 | > * $n^2$ cuadratic growth 139 | > * $n^3$ cubic growth 140 | 141 | --- 142 | 143 | ### Section 2: The Big-O Notation 144 | 145 | Time complexity analysis is important when designing systems that handle very large inputs. 146 | 147 | #### Orders of growth 148 | 149 | _More time from bottom to top_ 150 | 151 | > * $O(n!)$ factorial 152 | > * $O(2^n)$ exponential 153 | > * $O(n^2)$ quadratic or polynomial 154 | > * $O(n\log_{2}n)$ logarithmic linear 155 | > * $O(n)$ linear 156 | > * $O(\log_{2}n)$ logarithmic 157 | > * $O(1)$ constant 158 | 159 | #### NP-complete problems 160 | 161 | > * $O(n!)$ factorial 162 | > * $O(2^n)$ exponential 163 | 164 | #### Big-O Complexity 165 | 166 | ![Big-O Complexity](./img/big-o.png) 167 | 168 | --- 169 | 170 | ### Section 3: Exponentials 171 | 172 | We say $O(2^n)$ algorithms are **exponential time**. We consider these algorithms as **NOT runnable**. Same happens for $O(n!)$ algorithms. Even they both are horrible options, they're needed to solve **NP-complete** problems. 173 | 174 | --- 175 | 176 | ### Section 4: Counting Memory 177 | 178 | #### Space complexity 179 | 180 | > is the measure of working storage an algorithms needs. 181 | 182 | #### Selection Sort's space complexity 183 | 184 | > $O(1)$ _no matter the input size, it requires the same amount of computer memory for working storage._ 185 | 186 | **:bulb: Tip:** 187 | 188 | > Find the balance between time and space complexity. 189 | 190 | --- 191 | 192 | ### Section 5: Conclusion 193 | 194 | #### Given different algorithms, do they have a significant difference in terms of operations required to run? 195 | 196 | > Yes, it depends on their time and space complexity. 197 | 198 | #### Multiplying the input size by a constant, what happens with the time an algorithm takes to run? 199 | 200 | > Increases by its $T(n)$ 201 | 202 | #### Would an algorithm perform a reasonable number of operations once the size of the input grows? 203 | 204 | > Considering the worst case scenario, an algorithm will always perform the same number of operations no matter the input size. 205 | 206 | #### If an algorithm is too slow for running on an input of a given size, would optimizing the algorithm, or using a supercomputer help? 207 | 208 | > Yes, both cases would help, but optimizing the algorithm should be the first option to achieve a better running time. Having a supercomputer won't help much and it will be limited anyways. 209 | 210 | ## References 211 | 212 | ### In the book: 213 | 214 | * [Big-O Algorithm Complexity Cheat Sheet (Know Thy Complexities!) @ericdrowell](http://bigocheatsheet.com/) 215 | * [The Art of Computer Programming, Vol. 1: Fundamental Algorithms, 3rd Edition: 9780201896831: Computer Science Books @ Amazon.com](https://www.amazon.com/dp/0201896834/) 216 | * [P vs. NP and the Computational Complexity Zoo - YouTube](https://www.youtube.com/watch?v=YX40hbAHx3s) 217 | -------------------------------------------------------------------------------- /03-strategy/algorithms/best-trade-bottom-up.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Finds the maximum profit of buying and selling in 3 | * a list of prices. 4 | * Running Time: O(day) 5 | * Technique: Dynamic Programming - Bottom Up 6 | * @param {Array} prices list of prices 7 | */ 8 | function dBestTrade(prices) { 9 | /* `B[n]` = Best day to buy if we're selling on `n`th day. */ 10 | const B = { 11 | 0: 0 /* 0 is the best day to buy if selling on B[0] day 0 */ 12 | }; 13 | let sellDay = 0; 14 | let bestProfit = 0; 15 | 16 | for (let day = 1; day < prices.length; day++) { 17 | /* if price is less than the one stored in the day before */ 18 | if (prices[day] < prices[B[day - 1]]) { 19 | B[day] = day; 20 | } else { 21 | B[day] = B[day - 1]; 22 | } 23 | 24 | let profit = prices[day] - prices[B[day]]; 25 | 26 | if (profit > bestProfit) { 27 | sellDay = day; 28 | bestProfit = profit; 29 | } 30 | } 31 | 32 | return { 33 | maximum: bestProfit, 34 | buyingDate: B[sellDay], 35 | sellingDate: sellDay, 36 | }; 37 | } 38 | 39 | module.exports = dBestTrade; 40 | -------------------------------------------------------------------------------- /03-strategy/algorithms/best-trade-bottom-up.spec.js: -------------------------------------------------------------------------------- 1 | const assert = require('assert'); 2 | 3 | const dBestTrade = require('./best-trade-bottom-up'); 4 | 5 | describe('Algorithm - Best Trade [Bottom Up]', function () { 6 | const testCases = [ 7 | { 8 | input: [27, 53, 7, 25, 33, 2, 32, 47, 43], 9 | expected: { 10 | maximum: 45, 11 | buyingDate: 5, 12 | sellingDate: 7 13 | } 14 | }, 15 | { 16 | input: [11, 5, 2, 6, 7, 8, 4, 10, 3, 4, 6, 9, 8, 1, 3], 17 | expected: { 18 | maximum: 8, 19 | buyingDate: 2, 20 | sellingDate: 7 21 | } 22 | } 23 | ]; 24 | 25 | testCases.forEach(({ input, expected }) => { 26 | const { maximum } = expected; 27 | 28 | it(`should return the maximum: ${maximum}`, function () { 29 | const result = dBestTrade(input); 30 | 31 | assert.deepEqual(result, expected); 32 | }); 33 | }); 34 | }); 35 | -------------------------------------------------------------------------------- /03-strategy/algorithms/best-trade-divide-n-conquer.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Finds the maximum profit of buying and selling in 3 | * a list of prices. 4 | * Running Time: O(n log n) 5 | * Technique: Divide and Conquer 6 | * @param {Array} prices list of prices 7 | */ 8 | function bestTrade(prices) { 9 | if (prices.length === 1) { 10 | return 0; // buying and selling the same day has non profit 11 | } 12 | 13 | const firstHalf = prices.splice(0, Math.floor(prices.length / 2)); 14 | const case3 = Math.max.apply(null, prices) - Math.min.apply(null, firstHalf); 15 | 16 | return Math.max(bestTrade(prices), bestTrade(firstHalf), case3); 17 | } 18 | 19 | module.exports = bestTrade; 20 | -------------------------------------------------------------------------------- /03-strategy/algorithms/best-trade-divide-n-conquer.spec.js: -------------------------------------------------------------------------------- 1 | const assert = require('assert'); 2 | 3 | const bestTrade = require('./best-trade-divide-n-conquer'); 4 | 5 | describe('Algorithm - Best Trade [Divide and Conquer]', function () { 6 | const testCases = [ 7 | { 8 | input: [27, 53, 7, 25, 33, 2, 32, 47, 43], 9 | expected: 45 10 | }, 11 | { 12 | input: [11, 5, 2, 6, 7, 8, 4, 10, 3, 4, 6, 9, 8, 1, 3], 13 | expected: 8 14 | } 15 | ]; 16 | 17 | testCases.forEach(({ input, expected }) => { 18 | 19 | it(`should return the maximum: ${expected}`, function () { 20 | const result = bestTrade(input); 21 | 22 | assert.deepEqual(result, expected); 23 | }); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /03-strategy/algorithms/best-trade.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @typedef {Object} BestTrade 3 | * @property {number} maximum The maximum profit obtained 4 | * @property {number} buyingDate The index of the day to buy 5 | * @property {number} sellingDate the index of the day to sell 6 | */ 7 | 8 | /** 9 | * Finds the maximum profit of buying and selling in 10 | * a list of prices. 11 | * Running time: O(n^2) 12 | * Technique: Brute Force 13 | * @param {Array} prices 14 | * @returns {BestTrade} The maximum profit, and the indexes of buying and selling days 15 | */ 16 | function bestTrade(prices) { 17 | let buyingDate; 18 | let sellingDate; 19 | let maximum = -Infinity; 20 | 21 | for (let i = 0, len = prices.length; i < len - 1; i++) { 22 | for (let j = i; j < len; j++) { 23 | let profit = prices[j] - prices[i]; 24 | if (profit > maximum) { 25 | maximum = profit; 26 | buyingDate = i; 27 | sellingDate = j; 28 | } 29 | } 30 | } 31 | 32 | return { 33 | maximum, 34 | buyingDate, 35 | sellingDate, 36 | }; 37 | } 38 | 39 | module.exports = bestTrade; 40 | 41 | -------------------------------------------------------------------------------- /03-strategy/algorithms/best-trade.spec.js: -------------------------------------------------------------------------------- 1 | const assert = require('assert'); 2 | 3 | const bestTrade = require('./best-trade'); 4 | 5 | describe('Algorithm - Best Trade [Brute Force]', function () { 6 | const testCases = [ 7 | { 8 | input: [27, 53, 7, 25, 33, 2, 32, 47, 43], 9 | expected: { 10 | maximum: 45, 11 | buyingDate: 5, 12 | sellingDate: 7 13 | } 14 | }, 15 | { 16 | input: [11, 5, 2, 6, 7, 8, 4, 10, 3, 4, 6, 9, 8, 1, 3], 17 | expected: { 18 | maximum: 8, 19 | buyingDate: 2, 20 | sellingDate: 7 21 | } 22 | } 23 | ]; 24 | 25 | testCases.forEach(({ input, expected }) => { 26 | const {maximum} = expected; 27 | 28 | it(`should return the maximum: ${maximum}`, function () { 29 | const result = bestTrade(input); 30 | 31 | assert.deepEqual(result, expected); 32 | }); 33 | }); 34 | }); -------------------------------------------------------------------------------- /03-strategy/algorithms/dynamic-fibonacci.js: -------------------------------------------------------------------------------- 1 | 2 | const M = { 0: 0, 1: 1 }; 3 | 4 | /** 5 | * Returns the nth number of the Fibonacci series 6 | * Technique: Dynamic Programming - Memoization 7 | * @param {number} n nth number to calculate 8 | */ 9 | function dFibonacci(n) { 10 | if (!M.hasOwnProperty(n)) { 11 | M[n] = dFibonacci(n - 1) + dFibonacci(n - 2); 12 | } 13 | 14 | return M[n]; 15 | } 16 | 17 | module.exports = dFibonacci; 18 | -------------------------------------------------------------------------------- /03-strategy/algorithms/dynamic-fibonacci.spec.js: -------------------------------------------------------------------------------- 1 | 2 | const assert = require('assert'); 3 | 4 | const dFibonacci = require('./dynamic-fibonacci'); 5 | 6 | describe('Algorithm - Fibonacci [Dynamic Programming]', function () { 7 | const testCases = [ 8 | { 9 | input: 0, 10 | expected: 0 11 | }, 12 | { 13 | input: 1, 14 | expected: 1 15 | }, 16 | { 17 | input: 2, 18 | expected: 1 19 | }, 20 | { 21 | input: 3, 22 | expected: 2 23 | }, 24 | { 25 | input: 4, 26 | expected: 3 27 | }, 28 | { 29 | input: 6, 30 | expected: 8 31 | }, 32 | { 33 | input: 10, 34 | expected: 55 35 | }, 36 | { 37 | input: 15, 38 | expected: 610 39 | } 40 | ]; 41 | 42 | testCases.forEach(({ input, expected }) => { 43 | it(`dFib(${input}) = ${expected}`, function () { 44 | const result = dFibonacci(input); 45 | assert.equal(result, expected, `Given: ${result}`); 46 | }); 47 | }); 48 | }); 49 | -------------------------------------------------------------------------------- /03-strategy/algorithms/eight-queens-puzzle.v1.js: -------------------------------------------------------------------------------- 1 | const QUEEN = '*'; 2 | const ATTACKED = '·'; 3 | const COLLISION = 'x'; 4 | const EMPTY = ' '; 5 | 6 | // How to Document Classes 7 | // https://stackoverflow.com/questions/27343152/jsdoc-how-to-document-prototype-methods 8 | 9 | /** 10 | * Square of a Board. 11 | * @class 12 | */ 13 | const Square = (function () { 14 | 15 | /** 16 | * Initializes a new instance of Square. 17 | * @constructs Square 18 | */ 19 | function Square() { 20 | /** 21 | * Piece that is being hold. 22 | * @name Square#holdingPiece 23 | * @type {object} 24 | * @example 25 | * // { 26 | * // display: QUEEN 27 | * // attacks: [] 28 | * // } 29 | */ 30 | this.holdingPiece = null; 31 | 32 | /** 33 | * List of attackers to this Square. 34 | * @name Square#attackedBy 35 | * @type {Array} 36 | */ 37 | this.attackedBy = Object.create(null); 38 | } 39 | 40 | /** 41 | * Prints the square with its content. 42 | * @example 43 | * // [*] - Square holding a Queen 44 | * // [x] - Attacked by multiple 45 | * // [·] - Attacked Square 46 | * // [ ] - Empty Square 47 | * @function Square#toString 48 | */ 49 | Square.prototype.toString = function () { 50 | function printSquare(val) { 51 | return `[${val}]`; 52 | } 53 | 54 | const attackedCount = Object.keys(this.attackedBy).length; 55 | 56 | if (this.holdingPiece && !attackedCount) { 57 | return printSquare(this.holdingPiece.display); 58 | } else if (this.holdingPiece && attackedCount) { 59 | return printSquare(COLLISION); 60 | } else if (!this.holdingPiece && attackedCount) { 61 | return printSquare(ATTACKED); 62 | } else { 63 | return printSquare(EMPTY); 64 | } 65 | }; 66 | 67 | /** 68 | * Tells if a Square is empty and NOT attacked. 69 | * @function Square#isEmpty 70 | */ 71 | Square.prototype.isEmpty = function () { 72 | return !this.holdingPiece && !Object.keys(this.attackedBy).length; 73 | }; 74 | 75 | return Square; 76 | })(); 77 | 78 | // ================================================== 79 | 80 | /** 81 | * ChessBoard. 82 | * @class 83 | */ 84 | const Board = (function () { 85 | /** 86 | * Initializes a new instance of Board. 87 | * @constructs Board 88 | * @param {number} boardSize 89 | */ 90 | function Board(boardSize) { 91 | /** 92 | * Board Size. 93 | * @name Board#boardSize 94 | * @type {number} 95 | */ 96 | this.boardSize = boardSize; 97 | 98 | /** 99 | * ChessBoard. 100 | * @name Board#board 101 | * @type {Array} 102 | */ 103 | this._board = new Array(boardSize); 104 | 105 | /** 106 | * Number of Queens. 107 | * @name Board#queens 108 | * @type {number} 109 | */ 110 | this.queens = 0; 111 | 112 | /** 113 | * Initializes the board as an Array of Arrays of Squares 114 | */ 115 | function init() { 116 | for (let i = 0; i < boardSize; i++) { 117 | this._board[i] = new Array(boardSize); 118 | for (let j = 0; j < boardSize; j++) { 119 | this._board[i][j] = new Square(); 120 | } 121 | } 122 | } 123 | 124 | init.call(this); 125 | } 126 | 127 | /** 128 | * Prints the board. 129 | * @example 130 | * // board of size 3 131 | * // [*][·][·] 132 | * // [·][*][·] 133 | * // [·][·][*] 134 | * @function Board#toString 135 | */ 136 | Board.prototype.toString = function () { 137 | return this._board.map(row => row.map(square => square.toString()).join('')).join('\n'); 138 | }; 139 | 140 | /** 141 | * Returns the board as a string in a single row. 142 | * @example 143 | * // board of size 3 144 | * // [*][·][·][·][*][·][·][·][*] 145 | * @function Board#serialize 146 | */ 147 | Board.prototype.serialize = function () { 148 | return this._board.map(row => row.map(square => square.toString()).join('')).join(''); 149 | }; 150 | 151 | /** 152 | * Checks if a position is inside the boundaries of the board. 153 | * @param {number} x Square's X position 154 | * @param {number} y Square's Y position 155 | * @function Board#isInLimit 156 | */ 157 | Board.prototype.isInLimit = function (x, y) { 158 | if (y >= 0 && y < this.boardSize && x >= 0 && x < this.boardSize) { 159 | return true; 160 | } 161 | 162 | return false; 163 | }; 164 | 165 | /** 166 | * Gets the list of attacked Squares from a given position. 167 | * @function Board#getTargetSquares 168 | * @param {Object} position Attacker's position in the board 169 | * @param {number} position.x Attacker's X position 170 | * @param {number} position.y Attacker's Y position 171 | * @returns {Array} List of attacked Squares 172 | */ 173 | Board.prototype.getTargetSquares = function ({ y: attackerY, x: attackerX }) { 174 | const squares = []; 175 | 176 | let y, x; 177 | 178 | // horizontal 179 | for (x = 0; x < this.boardSize; x++) { 180 | if (x !== attackerX) { 181 | squares.push({ y: attackerY, x }); 182 | } 183 | } 184 | 185 | // vertical 186 | for (y = 0; y < this.boardSize; y++) { 187 | if (y !== attackerY) { 188 | squares.push({ y, x: attackerX }); 189 | } 190 | } 191 | 192 | // diagonal top left 193 | y = attackerY; 194 | x = attackerX; 195 | while (this.isInLimit(--y, --x)) { 196 | squares.push({ y, x }); 197 | } 198 | 199 | // diagonal top right 200 | y = attackerY; 201 | x = attackerX; 202 | while (this.isInLimit(--y, ++x)) { 203 | squares.push({ y, x }); 204 | } 205 | 206 | // diagonal bottom left 207 | y = attackerY; 208 | x = attackerX; 209 | while (this.isInLimit(++y, --x)) { 210 | squares.push({ y, x }); 211 | } 212 | 213 | // diagonal bottom right 214 | y = attackerY; 215 | x = attackerX; 216 | while (this.isInLimit(++y, ++x)) { 217 | squares.push({ y, x }); 218 | } 219 | 220 | return squares; 221 | }; 222 | 223 | /** 224 | * Places a Queen piece in the board 225 | * and updates the Squares that are attacked by it. 226 | * @function Board#placeQueen 227 | * @param {Object} position Queen's position in the board 228 | * @param {number} position.x Queen's X position 229 | * @param {number} position.y Queen's Y position 230 | */ 231 | Board.prototype.placeQueen = function ({ x, y }) { 232 | const attacks = this.getTargetSquares({ x, y }); 233 | const queenPositionKey = `${y}${x}`; 234 | 235 | this._board[y][x].holdingPiece = { 236 | display: QUEEN, 237 | attacks, 238 | }; 239 | 240 | attacks.forEach(({ y, x }) => { 241 | this._board[y][x].attackedBy[queenPositionKey] = true; 242 | }); 243 | 244 | this.queens++; 245 | }; 246 | 247 | /** 248 | * Removes a Queen piece from the board 249 | * and cleans the attack from the Squares that are attacked by it. 250 | * @function Board#removeQueen 251 | * @param {Object} position Queen's position in the board 252 | * @param {number} position.x Queen's X position 253 | * @param {number} position.y Queen's Y position 254 | */ 255 | Board.prototype.removeQueen = function ({ x, y }) { 256 | if (!this._board[y][x].holdingPiece) { 257 | return; 258 | } 259 | 260 | const queenPositionKey = `${y}${x}`; 261 | const attacks = this._board[y][x].holdingPiece.attacks; 262 | 263 | attacks.forEach(({ y, x }) => { 264 | delete this._board[y][x].attackedBy[queenPositionKey]; 265 | }); 266 | 267 | this._board[y][x].holdingPiece = null; 268 | 269 | this.queens--; 270 | }; 271 | 272 | /** 273 | * Returns a list of the unnattacked Squares in the board. 274 | * @function Board#getUnattackedPositions 275 | * @returns {Array} List of unnatacked Squares 276 | */ 277 | Board.prototype.getUnattackedPositions = function () { 278 | const unattacked = []; 279 | 280 | this._board.forEach((row, y) => { 281 | row.forEach((square, x) => { 282 | if (square.isEmpty()) { 283 | unattacked.push({ y, x }); 284 | } 285 | }); 286 | }); 287 | 288 | return unattacked; 289 | }; 290 | 291 | /** 292 | * Tells if the board has `n` queens. 293 | * @function Board#hasNQueens 294 | * @param {number} nQueens Number of total queens 295 | */ 296 | Board.prototype.hasNQueens = function (nQueens) { 297 | return this.queens === nQueens; 298 | }; 299 | 300 | /** 301 | * Solves the nQueens Puzzle. 302 | * @function Board#solve 303 | * @param {number} nQueens Number of total queens. 304 | */ 305 | Board.prototype.solve = function (nQueens) { 306 | if (this.hasNQueens(nQueens)) { 307 | return this.serialize(); 308 | } 309 | 310 | const unattacked = this.getUnattackedPositions(); 311 | 312 | for (let i = 0, len = unattacked.length; i < len; i++) { 313 | const position = unattacked[i]; 314 | 315 | this.placeQueen(position); 316 | 317 | const solution = this.solve(nQueens); 318 | 319 | if (solution) { 320 | return solution; 321 | } 322 | 323 | this.removeQueen(position); 324 | } 325 | 326 | return false; 327 | }; 328 | 329 | return Board; 330 | })(); 331 | 332 | module.exports = Board; 333 | -------------------------------------------------------------------------------- /03-strategy/algorithms/eight-queens-puzzle.v1.spec.js: -------------------------------------------------------------------------------- 1 | const assert = require('assert'); 2 | 3 | const Board = require('./eight-queens-puzzle.v1'); 4 | 5 | describe('Algorithm - 8 Queens Puzzle [Backtracking] (Object Oriented Version)', function () { 6 | const testCases = [ 7 | { 8 | boardSize: 1, 9 | expected: '[*]', 10 | }, 11 | { 12 | boardSize: 2, 13 | expected: false, 14 | }, 15 | { 16 | boardSize: 3, 17 | expected: false, 18 | }, 19 | { 20 | boardSize: 4, 21 | expected: '[·][*][·][·][·][·][·][*][*][·][·][·][·][·][*][·]', 22 | }, 23 | { 24 | boardSize: 8, 25 | expected: '[*][·][·][·][·][·][·][·][·][·][·][·][*][·][·][·][·][·][·][·][·][·][·][*][·][·][·][·][·][*][·][·][·][·][*][·][·][·][·][·][·][·][·][·][·][·][*][·][·][*][·][·][·][·][·][·][·][·][·][*][·][·][·][·]' 26 | } 27 | ]; 28 | 29 | testCases.forEach(({ boardSize, expected }) => { 30 | const board = new Board(boardSize); 31 | 32 | it(`should solve queens puzzle for a board of size: ${boardSize}`, function () { 33 | const solution = board.solve(boardSize); 34 | 35 | assert.equal(solution, expected); 36 | }); 37 | }); 38 | }); 39 | -------------------------------------------------------------------------------- /03-strategy/algorithms/eight-queens-puzzle.v2.js: -------------------------------------------------------------------------------- 1 | var QUEEN = '*'; 2 | 3 | /** 4 | * Returns an Array of Arrays of `0` Zeros 5 | * @example 6 | * // boardSize = 3 7 | * // [[0,0,0],[0,0,0],[0,0,0]] 8 | * @param {number} boardSize 9 | * @returns {Array} 10 | */ 11 | function createBoard(boardSize) { 12 | var board = new Array(boardSize); 13 | 14 | for (var y = 0; y < boardSize; y++) { 15 | board[y] = new Array(boardSize); 16 | for (var x = 0; x < boardSize; x++) { 17 | board[y][x] = 0; 18 | } 19 | } 20 | 21 | return board; 22 | } 23 | 24 | /** 25 | * Prints the board. 26 | * @example 27 | * // board of size 3 28 | * // [*][·][·] 29 | * // [·][*][·] 30 | * // [·][·][*] 31 | * @param {Array} board 32 | * @returns {string} 33 | */ 34 | function printBoard(board) { 35 | return board.map(row => row.map(col => col === QUEEN ? `[${col}]` : '[ ]').join('')).join('\n'); 36 | } 37 | 38 | /** 39 | * Returns the board as a string in a single row. 40 | * @example 41 | * // board of size 3 42 | * // [*][·][·][·][*][·][·][·][*] 43 | * @param {Array} board 44 | * @returns {string} 45 | */ 46 | function serializeBoard(board) { 47 | if (Array.isArray(board)) { 48 | return board.map(row => row.map(col => col === QUEEN ? `[${col}]` : '[ ]').join('')).join(''); 49 | } else { 50 | return board; 51 | } 52 | } 53 | 54 | /** 55 | * Tells if the board has `n` queens. 56 | * @param {Array} board 57 | * @param {number} nQueens 58 | * @returns {boolean} 59 | */ 60 | function hasNQueens(board, nQueens) { 61 | var totalQueens = 0; 62 | 63 | board.forEach(y => { 64 | y.forEach(x => { 65 | if (x === QUEEN) { 66 | totalQueens++ 67 | } 68 | }); 69 | }); 70 | 71 | return totalQueens === nQueens; 72 | } 73 | 74 | /** 75 | * Gets the list of attacked squares from a given position. 76 | * @param {Array} board 77 | * @param {Object} position Attacker's position in the board 78 | * @param {number} position.x Attacker's X position 79 | * @param {number} position.y Attacker's Y position 80 | * @returns {Array} List of attacked squares 81 | */ 82 | function getTargetSquares(board, position) { 83 | var { y: row, x: col } = position; 84 | var boardSize = board.length; 85 | var squares = []; 86 | 87 | var y, x; 88 | 89 | function isInLimit(y, x) { 90 | if (y >= 0 && y < boardSize && x >= 0 && x < boardSize) { 91 | return true; 92 | } 93 | 94 | return false; 95 | } 96 | 97 | // horizontal 98 | for (x = 0; x < boardSize; x++) { 99 | if (x !== col) { 100 | squares.push({ y: row, x }); 101 | } 102 | } 103 | 104 | // vertical 105 | for (y = 0; y < boardSize; y++) { 106 | if (y !== row) { 107 | squares.push({ y, x: col }); 108 | } 109 | } 110 | 111 | // diagonal top left 112 | y = row; 113 | x = col; 114 | while (isInLimit(--y, --x)) { 115 | squares.push({ y, x }); 116 | } 117 | 118 | // diagonal top right 119 | y = row; 120 | x = col; 121 | while (isInLimit(--y, ++x)) { 122 | squares.push({ y, x }); 123 | } 124 | 125 | // diagonal bottom left 126 | y = row; 127 | x = col; 128 | while (isInLimit(++y, --x)) { 129 | squares.push({ y, x }); 130 | } 131 | 132 | // diagonal bottom right 133 | y = row; 134 | x = col; 135 | while (isInLimit(++y, ++x)) { 136 | squares.push({ y, x }); 137 | } 138 | 139 | return squares; 140 | } 141 | 142 | /** 143 | * Returns a list of the unnattacked squares in the board. 144 | * @param {Array} board 145 | * @returns {Array} 146 | */ 147 | function getUnattackedPositions(board) { 148 | var unattacked = []; 149 | 150 | board.forEach((row, y) => { 151 | row.forEach((col, x) => { 152 | if (board[y][x] === 0) { 153 | unattacked.push({ y, x }); 154 | } 155 | }); 156 | }); 157 | 158 | return unattacked; 159 | } 160 | 161 | /** 162 | * Places a Queen piece in the board 163 | * @param {Array} board 164 | * @param {Object} position Queen's position in the board 165 | * @param {number} position.x Queen's X position 166 | * @param {number} position.y Queen's Y position 167 | * @returns {boolean} 168 | */ 169 | function placeQueen(board, position) { 170 | var { y, x } = position; 171 | 172 | if (board[y][x] !== 0) { 173 | return false; 174 | } 175 | 176 | board[y][x] = QUEEN; 177 | 178 | var attacked = getTargetSquares(board, position); 179 | 180 | attacked.forEach(({ y, x }) => { 181 | board[y][x]++; 182 | }); 183 | 184 | return true; 185 | } 186 | 187 | /** 188 | * Removes a Queen piece from the board 189 | * @param {Array} board 190 | * @param {Object} position Queen's position in the board 191 | * @param {number} position.x Queen's X position 192 | * @param {number} position.y Queen's Y position 193 | * @returns {boolean} 194 | */ 195 | function removeQueen(board, position) { 196 | var { y, x } = position; 197 | 198 | if (board[y][x] !== QUEEN) { 199 | return false; 200 | } 201 | 202 | board[y][x] = 0; 203 | 204 | var attacked = getTargetSquares(board, position); 205 | 206 | attacked.forEach(({ y, x }) => { 207 | board[y][x]--; 208 | }); 209 | 210 | return true; 211 | } 212 | 213 | /** 214 | * Solves the nQueens Puzzle 215 | * @param {Array} board 216 | * @param {number} boardSize 217 | */ 218 | function solveQueens(board, boardSize) { 219 | if (hasNQueens(board, boardSize)) { 220 | return board; 221 | } 222 | 223 | var unattackedPositions = getUnattackedPositions(board); 224 | 225 | for (var i = 0, len = unattackedPositions.length; i < len; i++) { 226 | var position = unattackedPositions[i]; 227 | 228 | placeQueen(board, position); 229 | 230 | var solution = solveQueens(board, boardSize); 231 | 232 | if (solution) { 233 | return solution; 234 | } 235 | 236 | removeQueen(board, position); 237 | } 238 | 239 | return false; 240 | } 241 | 242 | solveQueens.createBoard = createBoard; 243 | solveQueens.printBoard = printBoard; 244 | solveQueens.serializeBoard = serializeBoard; 245 | 246 | module.exports = solveQueens; -------------------------------------------------------------------------------- /03-strategy/algorithms/eight-queens-puzzle.v2.spec.js: -------------------------------------------------------------------------------- 1 | const assert = require('assert'); 2 | 3 | const queensPuzzle = require('./eight-queens-puzzle.v2'); 4 | 5 | describe('Algorithm - 8 Queens Puzzle [Backtracking] (Functional Version)', function () { 6 | const testCases = [ 7 | { 8 | boardSize: 1, 9 | expected: '[*]', 10 | }, 11 | { 12 | boardSize: 2, 13 | expected: false, 14 | }, 15 | { 16 | boardSize: 3, 17 | expected: false, 18 | }, 19 | { 20 | boardSize: 4, 21 | expected: '[ ][*][ ][ ][ ][ ][ ][*][*][ ][ ][ ][ ][ ][*][ ]', 22 | }, 23 | { 24 | boardSize: 8, 25 | expected: '[*][ ][ ][ ][ ][ ][ ][ ][ ][ ][ ][ ][*][ ][ ][ ][ ][ ][ ][ ][ ][ ][ ][*][ ][ ][ ][ ][ ][*][ ][ ][ ][ ][*][ ][ ][ ][ ][ ][ ][ ][ ][ ][ ][ ][*][ ][ ][*][ ][ ][ ][ ][ ][ ][ ][ ][ ][*][ ][ ][ ][ ]' 26 | } 27 | ]; 28 | 29 | testCases.forEach(({ boardSize, expected }) => { 30 | const board = queensPuzzle.createBoard(boardSize); 31 | 32 | it(`should solve queens puzzle for a board of size: ${boardSize}`, function () { 33 | const solution = queensPuzzle(board, boardSize); 34 | 35 | assert.equal(queensPuzzle.serializeBoard(solution), expected); 36 | }); 37 | }); 38 | }); 39 | -------------------------------------------------------------------------------- /03-strategy/algorithms/fibonacci.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Returns the nth number of the Fibonacci series 3 | * Technique: Recursion 4 | * @param {integer} n nth number to calculate 5 | */ 6 | function fibonacci(n) { 7 | if (n === 0) { 8 | return 0; 9 | } 10 | 11 | if (n === 1) { 12 | return 1 13 | } 14 | 15 | return fibonacci(n - 1) + fibonacci(n - 2); 16 | } 17 | 18 | module.exports = fibonacci 19 | -------------------------------------------------------------------------------- /03-strategy/algorithms/fibonacci.spec.js: -------------------------------------------------------------------------------- 1 | 2 | const assert = require('assert'); 3 | 4 | const fibonacci = require('./fibonacci'); 5 | 6 | describe('Algorithm - Fibonacci [Recursive]', function () { 7 | const testCases = [ 8 | { 9 | input: 0, 10 | expected: 0 11 | }, 12 | { 13 | input: 1, 14 | expected: 1 15 | }, 16 | { 17 | input: 2, 18 | expected: 1 19 | }, 20 | { 21 | input: 3, 22 | expected: 2 23 | }, 24 | { 25 | input: 4, 26 | expected: 3 27 | }, 28 | { 29 | input: 6, 30 | expected: 8 31 | }, 32 | { 33 | input: 10, 34 | expected: 55 35 | }, 36 | { 37 | input: 15, 38 | expected: 610 39 | } 40 | ]; 41 | 42 | testCases.forEach(({ input, expected }) => { 43 | it(`fib(${input}) = ${expected}`, function () { 44 | const result = fibonacci(input); 45 | assert.equal(result, expected, `Given ${result}`); 46 | }); 47 | }); 48 | }); -------------------------------------------------------------------------------- /03-strategy/algorithms/greedy-knapsack.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Reduces a list of items to their weight 3 | * @param {Array} items 4 | */ 5 | function calculateTotalWeight(items) { 6 | return items.reduce((prev, currentItem) => prev + currentItem.weight, 0); 7 | } 8 | 9 | /** 10 | * Reduces a list of items to their price 11 | * @param {Array} items 12 | */ 13 | function calculateSalesValue(items) { 14 | return items.reduce((prev, currentItem) => prev + currentItem.price, 0); 15 | } 16 | 17 | /** 18 | * Callback for sorting by price. 19 | * @param {object} a First item 20 | * @param {object} b Second item 21 | */ 22 | function sortByPrice(a, b) { 23 | return b.price - a.price; 24 | } 25 | 26 | /** 27 | * Orders the list of items by its max price 28 | * and then put them in the bag until nothing else fits 29 | * Technique: Heuristics 30 | * @param {Array} items 31 | * @param {number} maxWeight Max weight the bag can carry 32 | */ 33 | function greedyKnapsack(items, maxWeight) { 34 | let bagWeight = 0; 35 | let bagItems = []; 36 | 37 | items.sort(sortByPrice).forEach(item => { 38 | if ((bagWeight + item.weight) <= maxWeight) { 39 | bagWeight += item.weight; 40 | bagItems.push(item); 41 | } 42 | }); 43 | 44 | return bagItems; 45 | } 46 | 47 | greedyKnapsack.calculateTotalWeight = calculateTotalWeight; 48 | greedyKnapsack.calculateSalesValue = calculateSalesValue; 49 | 50 | module.exports = greedyKnapsack; 51 | -------------------------------------------------------------------------------- /03-strategy/algorithms/greedy-knapsack.spec.js: -------------------------------------------------------------------------------- 1 | const assert = require('assert'); 2 | 3 | const greedyKnapsack = require('./greedy-knapsack'); 4 | 5 | describe('Algorithm - Greedy Knapsack [Heuristics]', function () { 6 | const testCases = [ 7 | { 8 | input: { 9 | maxWeight: 3, 10 | items: [ 11 | { 12 | name: 'Computer', 13 | weight: 2, 14 | price: 6000, 15 | }, 16 | { 17 | name: 'Speaker', 18 | weight: 0.5, 19 | price: 2000, 20 | }, 21 | { 22 | name: 'Raspeberry', 23 | weight: 0.2, 24 | price: 1500, 25 | }, 26 | { 27 | name: 'Mouse', 28 | weight: 0.5, 29 | price: 300, 30 | }, 31 | { 32 | name: 'Keyboard', 33 | weight: 0.8, 34 | price: 500, 35 | }, 36 | { 37 | name: 'Tablet', 38 | weight: 1, 39 | price: 3000, 40 | }, 41 | { 42 | name: 'Smartphone', 43 | weight: 0.3, 44 | price: 5000, 45 | }, 46 | { 47 | name: 'iPhone', 48 | weight: 0.4, 49 | price: 8000, 50 | } 51 | ] 52 | }, 53 | expected: { 54 | totalWeight: 2.9, 55 | totalRevenue: 20500, 56 | itemsInBag: [ 57 | { 58 | name: 'iPhone', 59 | weight: 0.4, 60 | price: 8000, 61 | }, 62 | { 63 | name: 'Computer', 64 | weight: 2, 65 | price: 6000, 66 | }, 67 | { 68 | name: 'Smartphone', 69 | weight: 0.3, 70 | price: 5000, 71 | }, 72 | { 73 | name: 'Raspeberry', 74 | weight: 0.2, 75 | price: 1500, 76 | }, 77 | ] 78 | } 79 | }, 80 | { 81 | input: { 82 | maxWeight: 10, 83 | items: [ 84 | { name: 'A', price: 20, weight: 5 }, 85 | { name: 'B', price: 19, weight: 4 }, 86 | { name: 'C', price: 16, weight: 2 }, 87 | { name: 'D', price: 14, weight: 5 }, 88 | { name: 'E', price: 13, weight: 3 }, 89 | { name: 'F', price: 9, weight: 2 } 90 | ] 91 | }, 92 | expected: { 93 | totalWeight: 9, 94 | totalRevenue: 39, 95 | itemsInBag: [ 96 | { name: 'A', price: 20, weight: 5 }, 97 | { name: 'B', price: 19, weight: 4 } 98 | ] 99 | } 100 | } 101 | ]; 102 | 103 | testCases.forEach(({ input, expected }) => { 104 | const { items, maxWeight } = input; 105 | const { itemsInBag, totalWeight, totalRevenue } = expected; 106 | 107 | it(`should return the items: ${itemsInBag.map(i => i.name)}`, function () { 108 | const result = greedyKnapsack(items, maxWeight); 109 | const resultRevenue = greedyKnapsack.calculateSalesValue(result); 110 | const resultWeight = greedyKnapsack.calculateTotalWeight(result); 111 | 112 | assert.equal(resultRevenue, totalRevenue, `Incorrect revenue: ${resultRevenue}`); 113 | assert.equal(resultWeight, totalWeight, `Incorrect weight: ${totalWeight}`); 114 | assert.deepEqual(result, itemsInBag, `Given: ${itemsInBag.map(i => i.name)}`); 115 | }); 116 | }); 117 | }); 118 | -------------------------------------------------------------------------------- /03-strategy/algorithms/knapsack-divide-n-conquer.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Reduces a list of items to their weight 3 | * @param {Array} items 4 | */ 5 | function calculateTotalWeight(items) { 6 | return items.reduce((prev, currentItem) => prev + currentItem.weight, 0); 7 | } 8 | 9 | /** 10 | * Reduces a list of items to their price 11 | * @param {Array} items 12 | */ 13 | function calculateSalesValue(items) { 14 | return items.reduce((prev, currentItem) => prev + currentItem.price, 0); 15 | } 16 | 17 | /** 18 | * Returns the best revenue of items 19 | * that fit in the bag 20 | * WARNING: This is an implementation I came up with, which is not very performant 21 | * Running Time: ??? 22 | * Technique: Divide and Conquer 23 | * @param {Array} items 24 | * @param {number} maxWeight 25 | */ 26 | function knapsack(items, maxWeight) { 27 | if (items.length === 0) { 28 | return 0; 29 | } 30 | 31 | if (calculateTotalWeight(items) <= maxWeight) { 32 | return calculateSalesValue(items); 33 | } 34 | 35 | const combinations = []; 36 | 37 | items.forEach((_, i) => { 38 | const left = items.slice(0, i); 39 | const right = items.slice(i + 1); 40 | combinations.push((knapsack(left.concat(right), maxWeight))); 41 | }); 42 | 43 | return Math.max.apply(null, combinations); 44 | } 45 | 46 | knapsack.calculateSalesValue = calculateSalesValue; 47 | knapsack.calculateTotalWeight = calculateTotalWeight; 48 | 49 | module.exports = knapsack; 50 | -------------------------------------------------------------------------------- /03-strategy/algorithms/knapsack-divide-n-conquer.spec.js: -------------------------------------------------------------------------------- 1 | const assert = require('assert'); 2 | 3 | const knapsack = require('./knapsack-divide-n-conquer'); 4 | 5 | describe('Algorithm - Knapsack [Divide and Conquer] (My Approach)', function () { 6 | const testCases = [ 7 | { 8 | input: { 9 | maxWeight: 10, 10 | items: [] 11 | }, 12 | expected: { 13 | totalRevenue: 0 14 | } 15 | }, 16 | { 17 | input: { 18 | maxWeight: 10, 19 | items: [ 20 | { name: 'A', weight: 5, price: 500 }, 21 | { name: 'B', weight: 5, price: 400 }, 22 | { name: 'C', weight: 10, price: 1000 } 23 | ] 24 | }, 25 | expected: { 26 | totalRevenue: 1000 27 | } 28 | }, 29 | { 30 | input: { 31 | maxWeight: 10, 32 | items: [ 33 | { name: 'A', weight: 5, price: 1000 }, 34 | { name: 'B', weight: 5, price: 1000 }, 35 | { name: 'C', weight: 10, price: 1000 } 36 | ] 37 | }, 38 | expected: { 39 | totalRevenue: 2000 40 | } 41 | }, 42 | { 43 | input: { 44 | maxWeight: 20, 45 | items: [ 46 | { name: 'A', weight: 5, price: 1000 }, 47 | { name: 'B', weight: 5, price: 1000 }, 48 | { name: 'C', weight: 10, price: 1000 } 49 | ] 50 | }, 51 | expected: { 52 | totalRevenue: 3000 53 | } 54 | }, 55 | { 56 | input: { 57 | maxWeight: 10, 58 | items: [ 59 | { name: 'A', weight: 11, price: 300 }, 60 | { name: 'B', weight: 11, price: 400 }, 61 | { name: 'C', weight: 11, price: 500 } 62 | ] 63 | }, 64 | expected: { 65 | totalRevenue: 0 66 | } 67 | }, 68 | { 69 | input: { 70 | maxWeight: 4, 71 | items: [ 72 | { 73 | name: 'Computer', 74 | weight: 2, 75 | price: 6000, 76 | }, 77 | { 78 | name: 'Speaker', 79 | weight: 0.5, 80 | price: 2000, 81 | }, 82 | { 83 | name: 'Raspeberry', 84 | weight: 0.2, 85 | price: 1500, 86 | }, 87 | { 88 | name: 'Mouse', 89 | weight: 0.5, 90 | price: 300, 91 | }, 92 | { 93 | name: 'Keyboard', 94 | weight: 0.8, 95 | price: 500, 96 | }, 97 | { 98 | name: 'Tablet', 99 | weight: 1, 100 | price: 3000, 101 | }, 102 | { 103 | name: 'Smartphone', 104 | weight: 0.3, 105 | price: 5000, 106 | }, 107 | { 108 | name: 'iPhone', 109 | weight: 0.4, 110 | price: 8000, 111 | } 112 | ] 113 | }, 114 | expected: { 115 | totalRevenue: 23500 116 | } 117 | }, 118 | { 119 | input: { 120 | maxWeight: 10, 121 | items: [ 122 | { name: 'A', price: 20, weight: 5 }, 123 | { name: 'B', price: 19, weight: 4 }, 124 | { name: 'C', price: 16, weight: 2 }, 125 | { name: 'D', price: 14, weight: 5 }, 126 | { name: 'E', price: 13, weight: 3 }, 127 | { name: 'F', price: 9, weight: 2 } 128 | ] 129 | }, 130 | expected: { 131 | totalRevenue: 49 132 | } 133 | } 134 | ]; 135 | 136 | testCases.forEach(({ input, expected }) => { 137 | const { items, maxWeight } = input; 138 | const { totalRevenue } = expected; 139 | 140 | it(`should return maxRevenue: ${totalRevenue}`, function () { 141 | const resultRevenue = knapsack(items, maxWeight); 142 | 143 | assert.equal(resultRevenue, totalRevenue, `Incorrect revenue: ${resultRevenue}`); 144 | }); 145 | }); 146 | }); 147 | -------------------------------------------------------------------------------- /03-strategy/algorithms/knapsack-dnc.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Prepares data for Recursive Knapsack Algorithm 3 | * @param {Array} items 4 | * @param {number} maxWeight 5 | */ 6 | function knapsack(items, maxWeight) { 7 | const itemsPrices = items.map(i => i.price); 8 | const itemsWeight = items.map(i => i.weight); 9 | 10 | /** 11 | * Actual implementation of Knapsack Algorithm (Recursive) 12 | * returning the best revenue that can be retrieved 13 | * by selling items that fit in the bag. 14 | * Running Time: O(n log n) 15 | * Technique: Divide and Conquer 16 | * @param {number} itemIndex Current item's index 17 | * @param {number} maxWeight Current left weight in the bag 18 | */ 19 | function doKnapsack(itemIndex, maxWeight) { 20 | /** 21 | * Base cases: 22 | * When there's no more space in the bag 23 | * When there are no more items in the list 24 | */ 25 | if (maxWeight == 0 || itemIndex < 0) return 0; 26 | // discard items that don't fit in the bag 27 | if (itemsWeight[itemIndex] > maxWeight) return doKnapsack(itemIndex - 1, maxWeight); 28 | 29 | return Math.max( 30 | doKnapsack(itemIndex - 1, maxWeight), /* don't take the item */ 31 | doKnapsack(itemIndex - 1, maxWeight - itemsWeight[itemIndex]) + itemsPrices[itemIndex] /* take the item */ 32 | ); 33 | } 34 | 35 | return doKnapsack(items.length - 1, maxWeight); 36 | } 37 | 38 | module.exports = knapsack; 39 | -------------------------------------------------------------------------------- /03-strategy/algorithms/knapsack-dnc.spec.js: -------------------------------------------------------------------------------- 1 | const assert = require('assert'); 2 | 3 | const knapsack = require('./knapsack-dnc'); 4 | 5 | describe('Algorithm - Knapsack [Divide and Conquer]', function () { 6 | const testCases = [ 7 | { 8 | input: { 9 | maxWeight: 10, 10 | items: [] 11 | }, 12 | expected: { 13 | totalRevenue: 0 14 | } 15 | }, 16 | { 17 | input: { 18 | maxWeight: 10, 19 | items: [ 20 | { name: 'A', weight: 5, price: 500 }, 21 | { name: 'B', weight: 5, price: 400 }, 22 | { name: 'C', weight: 10, price: 1000 } 23 | ] 24 | }, 25 | expected: { 26 | totalRevenue: 1000 27 | } 28 | }, 29 | { 30 | input: { 31 | maxWeight: 10, 32 | items: [ 33 | { name: 'A', weight: 5, price: 1000 }, 34 | { name: 'B', weight: 5, price: 1000 }, 35 | { name: 'C', weight: 10, price: 1000 } 36 | ] 37 | }, 38 | expected: { 39 | totalRevenue: 2000 40 | } 41 | }, 42 | { 43 | input: { 44 | maxWeight: 20, 45 | items: [ 46 | { name: 'A', weight: 5, price: 1000 }, 47 | { name: 'B', weight: 5, price: 1000 }, 48 | { name: 'C', weight: 10, price: 1000 } 49 | ] 50 | }, 51 | expected: { 52 | totalRevenue: 3000 53 | } 54 | }, 55 | { 56 | input: { 57 | maxWeight: 10, 58 | items: [ 59 | { name: 'A', weight: 11, price: 300 }, 60 | { name: 'B', weight: 11, price: 400 }, 61 | { name: 'C', weight: 11, price: 500 } 62 | ] 63 | }, 64 | expected: { 65 | totalRevenue: 0 66 | } 67 | }, 68 | { 69 | input: { 70 | maxWeight: 4, 71 | items: [ 72 | { 73 | name: 'Computer', 74 | weight: 2, 75 | price: 6000, 76 | }, 77 | { 78 | name: 'Speaker', 79 | weight: 0.5, 80 | price: 2000, 81 | }, 82 | { 83 | name: 'Raspeberry', 84 | weight: 0.2, 85 | price: 1500, 86 | }, 87 | { 88 | name: 'Mouse', 89 | weight: 0.5, 90 | price: 300, 91 | }, 92 | { 93 | name: 'Keyboard', 94 | weight: 0.8, 95 | price: 500, 96 | }, 97 | { 98 | name: 'Tablet', 99 | weight: 1, 100 | price: 3000, 101 | }, 102 | { 103 | name: 'Smartphone', 104 | weight: 0.3, 105 | price: 5000, 106 | }, 107 | { 108 | name: 'iPhone', 109 | weight: 0.4, 110 | price: 8000, 111 | } 112 | ] 113 | }, 114 | expected: { 115 | totalRevenue: 23500 116 | } 117 | }, 118 | { 119 | input: { 120 | maxWeight: 10, 121 | items: [ 122 | { name: 'A', price: 20, weight: 5 }, 123 | { name: 'B', price: 19, weight: 4 }, 124 | { name: 'C', price: 16, weight: 2 }, 125 | { name: 'D', price: 14, weight: 5 }, 126 | { name: 'E', price: 13, weight: 3 }, 127 | { name: 'F', price: 9, weight: 2 } 128 | ] 129 | }, 130 | expected: { 131 | totalRevenue: 49 132 | } 133 | } 134 | ]; 135 | 136 | testCases.forEach(({ input, expected }) => { 137 | const { items, maxWeight } = input; 138 | const { totalRevenue } = expected; 139 | 140 | it(`should return maxRevenue: ${totalRevenue}`, function () { 141 | const resultRevenue = knapsack(items, maxWeight); 142 | 143 | assert.equal(resultRevenue, totalRevenue, `Incorrect revenue: ${resultRevenue}`); 144 | }); 145 | }); 146 | }); 147 | -------------------------------------------------------------------------------- /03-strategy/algorithms/knapsack-dp.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Prepares data for Recursive Knapsack Algorithm 3 | * @param {Array} items 4 | * @param {number} maxWeight 5 | */ 6 | function knapsack(items, maxWeight) { 7 | const itemsPrices = items.map(i => i.price); 8 | const itemsWeight = items.map(i => i.weight); 9 | const M = {}; /* Memoization */ 10 | 11 | /** 12 | * Actual implementation of Knapsack Algorithm (Dynamic Programming) 13 | * returning the best revenue that can be retrieved 14 | * by selling items that fit in the bag. 15 | * Running Time: O(n log n) + DP 16 | * Technique: Dynamic Programming - Memoization 17 | * @param {number} itemIndex Current item's index 18 | * @param {number} maxWeight Current left weight in the bag 19 | */ 20 | function doKnapsack(itemIndex, maxWeight) { 21 | /** 22 | * Base cases: 23 | * When there's no more space in the bag 24 | * When there are no more items in the list 25 | */ 26 | if (maxWeight == 0 || itemIndex < 0) return 0; 27 | 28 | /* Store doKnapsack calls as subproblems. Example: "k(5,1000)" */ 29 | const subproblem = `k(${itemIndex},${maxWeight})`; 30 | 31 | if (!M[subproblem]) { 32 | // discard items that don't fit in the bag 33 | if (itemsWeight[itemIndex] > maxWeight) { 34 | M[subproblem] = doKnapsack(itemIndex - 1, maxWeight); 35 | } else { 36 | M[subproblem] = Math.max( 37 | doKnapsack(itemIndex - 1, maxWeight), /* don't take the item */ 38 | doKnapsack(itemIndex - 1, maxWeight - itemsWeight[itemIndex]) + itemsPrices[itemIndex] /* take the item */ 39 | ); 40 | } 41 | } 42 | 43 | return M[subproblem]; 44 | } 45 | 46 | return doKnapsack(items.length - 1, maxWeight); 47 | } 48 | 49 | module.exports = knapsack; 50 | -------------------------------------------------------------------------------- /03-strategy/algorithms/knapsack-dp.spec.js: -------------------------------------------------------------------------------- 1 | const assert = require('assert'); 2 | 3 | const dKnapsack = require('./knapsack-dp'); 4 | 5 | describe('Algorithm - Knapsack [Dynamic Programming]', function () { 6 | const testCases = [ 7 | { 8 | input: { 9 | maxWeight: 10, 10 | items: [] 11 | }, 12 | expected: { 13 | totalRevenue: 0 14 | } 15 | }, 16 | { 17 | input: { 18 | maxWeight: 10, 19 | items: [ 20 | { name: 'A', weight: 5, price: 500 }, 21 | { name: 'B', weight: 5, price: 400 }, 22 | { name: 'C', weight: 10, price: 1000 } 23 | ] 24 | }, 25 | expected: { 26 | totalRevenue: 1000 27 | } 28 | }, 29 | { 30 | input: { 31 | maxWeight: 10, 32 | items: [ 33 | { name: 'A', weight: 5, price: 1000 }, 34 | { name: 'B', weight: 5, price: 1000 }, 35 | { name: 'C', weight: 10, price: 1000 } 36 | ] 37 | }, 38 | expected: { 39 | totalRevenue: 2000 40 | } 41 | }, 42 | { 43 | input: { 44 | maxWeight: 20, 45 | items: [ 46 | { name: 'A', weight: 5, price: 1000 }, 47 | { name: 'B', weight: 5, price: 1000 }, 48 | { name: 'C', weight: 10, price: 1000 } 49 | ] 50 | }, 51 | expected: { 52 | totalRevenue: 3000 53 | } 54 | }, 55 | { 56 | input: { 57 | maxWeight: 10, 58 | items: [ 59 | { name: 'A', weight: 11, price: 300 }, 60 | { name: 'B', weight: 11, price: 400 }, 61 | { name: 'C', weight: 11, price: 500 } 62 | ] 63 | }, 64 | expected: { 65 | totalRevenue: 0 66 | } 67 | }, 68 | { 69 | input: { 70 | maxWeight: 4, 71 | items: [ 72 | { 73 | name: 'Computer', 74 | weight: 2, 75 | price: 6000, 76 | }, 77 | { 78 | name: 'Speaker', 79 | weight: 0.5, 80 | price: 2000, 81 | }, 82 | { 83 | name: 'Raspeberry', 84 | weight: 0.2, 85 | price: 1500, 86 | }, 87 | { 88 | name: 'Mouse', 89 | weight: 0.5, 90 | price: 300, 91 | }, 92 | { 93 | name: 'Keyboard', 94 | weight: 0.8, 95 | price: 500, 96 | }, 97 | { 98 | name: 'Tablet', 99 | weight: 1, 100 | price: 3000, 101 | }, 102 | { 103 | name: 'Smartphone', 104 | weight: 0.3, 105 | price: 5000, 106 | }, 107 | { 108 | name: 'iPhone', 109 | weight: 0.4, 110 | price: 8000, 111 | } 112 | ] 113 | }, 114 | expected: { 115 | totalRevenue: 23500 116 | } 117 | }, 118 | { 119 | input: { 120 | maxWeight: 10, 121 | items: [ 122 | { name: 'A', price: 20, weight: 5 }, 123 | { name: 'B', price: 19, weight: 4 }, 124 | { name: 'C', price: 16, weight: 2 }, 125 | { name: 'D', price: 14, weight: 5 }, 126 | { name: 'E', price: 13, weight: 3 }, 127 | { name: 'F', price: 9, weight: 2 } 128 | ] 129 | }, 130 | expected: { 131 | totalRevenue: 49 132 | } 133 | } 134 | ]; 135 | 136 | testCases.forEach(({ input, expected }) => { 137 | const { items, maxWeight } = input; 138 | const { totalRevenue } = expected; 139 | 140 | it(`should return maxRevenue: ${totalRevenue}`, function () { 141 | const resultRevenue = dKnapsack(items, maxWeight); 142 | 143 | assert.equal(resultRevenue, totalRevenue, `Incorrect revenue: ${resultRevenue}`); 144 | }); 145 | }); 146 | }); 147 | -------------------------------------------------------------------------------- /03-strategy/algorithms/knapsack.js: -------------------------------------------------------------------------------- 1 | const powerSet = require('./power-set'); 2 | 3 | /** 4 | * Reduces a list of items to their weight 5 | * @param {Array} items 6 | */ 7 | function calculateTotalWeight(items) { 8 | return items.reduce((prev, currentItem) => prev + currentItem.weight, 0); 9 | } 10 | 11 | /** 12 | * Reduces a list of items to their price 13 | * @param {Array} items 14 | */ 15 | function calculateSalesValue(items) { 16 | return items.reduce((prev, currentItem) => prev + currentItem.price, 0); 17 | } 18 | 19 | /** 20 | * Returns the list of items that 21 | * fit in the bag and that will give the 22 | * best revenue. 23 | * Running Time: O(2^n) 24 | * Technique: Brute Force 25 | * @param {Array} items 26 | * @param {number} maxWeight 27 | */ 28 | function knapsack(items, maxWeight) { 29 | let bestValue = 0; 30 | let bestCandidates = []; 31 | let combinationValue; 32 | 33 | powerSet(items).forEach(combination => { 34 | if (calculateTotalWeight(combination) <= maxWeight) { 35 | combinationValue = calculateSalesValue(combination); 36 | if (combinationValue > bestValue) { 37 | bestValue = combinationValue; 38 | bestCandidates = combination; 39 | } 40 | } 41 | }); 42 | 43 | return bestCandidates; 44 | } 45 | 46 | knapsack.calculateTotalWeight = calculateTotalWeight; 47 | knapsack.calculateSalesValue = calculateSalesValue; 48 | 49 | module.exports = knapsack; -------------------------------------------------------------------------------- /03-strategy/algorithms/knapsack.spec.js: -------------------------------------------------------------------------------- 1 | const assert = require('assert'); 2 | 3 | const knapsack = require('./knapsack'); 4 | 5 | describe('Algorithm - Knapsack [Brute Force]', function () { 6 | const testCases = [ 7 | { 8 | input: { 9 | maxWeight: 10, 10 | items: [] 11 | }, 12 | expected: { 13 | totalWeight: 0, 14 | totalRevenue: 0, 15 | itemsInBag: [] 16 | } 17 | }, 18 | { 19 | input: { 20 | maxWeight: 10, 21 | items: [ 22 | { name: 'A', weight: 5, price: 500 }, 23 | { name: 'B', weight: 5, price: 400 }, 24 | { name: 'C', weight: 10, price: 1000 } 25 | ] 26 | }, 27 | expected: { 28 | totalWeight: 10, 29 | totalRevenue: 1000, 30 | itemsInBag: [{ name: 'C', weight: 10, price: 1000 }] 31 | } 32 | }, 33 | { 34 | input: { 35 | maxWeight: 10, 36 | items: [ 37 | { name: 'A', weight: 5, price: 1000 }, 38 | { name: 'B', weight: 5, price: 1000 }, 39 | { name: 'C', weight: 10, price: 1000 } 40 | ] 41 | }, 42 | expected: { 43 | totalWeight: 10, 44 | totalRevenue: 2000, 45 | itemsInBag: [ 46 | { name: 'A', weight: 5, price: 1000 }, 47 | { name: 'B', weight: 5, price: 1000 } 48 | ] 49 | } 50 | }, 51 | { 52 | input: { 53 | maxWeight: 20, 54 | items: [ 55 | { name: 'A', weight: 5, price: 1000 }, 56 | { name: 'B', weight: 5, price: 1000 }, 57 | { name: 'C', weight: 10, price: 1000 } 58 | ] 59 | }, 60 | expected: { 61 | totalWeight: 20, 62 | totalRevenue: 3000, 63 | itemsInBag: [ 64 | { name: 'A', weight: 5, price: 1000 }, 65 | { name: 'B', weight: 5, price: 1000 }, 66 | { name: 'C', weight: 10, price: 1000 } 67 | ] 68 | } 69 | }, 70 | { 71 | input: { 72 | maxWeight: 10, 73 | items: [ 74 | { name: 'A', weight: 11, price: 300 }, 75 | { name: 'B', weight: 11, price: 400 }, 76 | { name: 'C', weight: 11, price: 500 } 77 | ] 78 | }, 79 | expected: { 80 | totalWeight: 0, 81 | totalRevenue: 0, 82 | itemsInBag: [] 83 | } 84 | }, 85 | { 86 | input: { 87 | maxWeight: 4, 88 | items: [ 89 | { 90 | name: 'Computer', 91 | weight: 2, 92 | price: 6000, 93 | }, 94 | { 95 | name: 'Speaker', 96 | weight: 0.5, 97 | price: 2000, 98 | }, 99 | { 100 | name: 'Raspeberry', 101 | weight: 0.2, 102 | price: 1500, 103 | }, 104 | { 105 | name: 'Mouse', 106 | weight: 0.5, 107 | price: 300, 108 | }, 109 | { 110 | name: 'Keyboard', 111 | weight: 0.8, 112 | price: 500, 113 | }, 114 | { 115 | name: 'Tablet', 116 | weight: 1, 117 | price: 3000, 118 | }, 119 | { 120 | name: 'Smartphone', 121 | weight: 0.3, 122 | price: 5000, 123 | }, 124 | { 125 | name: 'iPhone', 126 | weight: 0.4, 127 | price: 8000, 128 | } 129 | ] 130 | }, 131 | expected: { 132 | totalWeight: 3.9, 133 | totalRevenue: 23500, 134 | itemsInBag: [ 135 | { 136 | name: 'Computer', 137 | weight: 2, 138 | price: 6000, 139 | }, 140 | { 141 | name: 'Raspeberry', 142 | weight: 0.2, 143 | price: 1500, 144 | }, 145 | { 146 | name: 'Tablet', 147 | weight: 1, 148 | price: 3000, 149 | }, 150 | { 151 | name: 'Smartphone', 152 | weight: 0.3, 153 | price: 5000, 154 | }, 155 | { 156 | name: 'iPhone', 157 | weight: 0.4, 158 | price: 8000, 159 | } 160 | ] 161 | } 162 | }, 163 | { 164 | input: { 165 | maxWeight: 10, 166 | items: [ 167 | { name: 'A', price: 20, weight: 5 }, 168 | { name: 'B', price: 19, weight: 4 }, 169 | { name: 'C', price: 16, weight: 2 }, 170 | { name: 'D', price: 14, weight: 5 }, 171 | { name: 'E', price: 13, weight: 3 }, 172 | { name: 'F', price: 9, weight: 2 } 173 | ] 174 | }, 175 | expected: { 176 | totalWeight: 10, 177 | totalRevenue: 49, 178 | itemsInBag: [ 179 | { name: 'A', price: 20, weight: 5 }, 180 | { name: 'C', price: 16, weight: 2 }, 181 | { name: 'E', price: 13, weight: 3 } 182 | ] 183 | } 184 | } 185 | ]; 186 | 187 | testCases.forEach(({ input, expected }) => { 188 | const { items, maxWeight } = input; 189 | const { itemsInBag, totalWeight, totalRevenue } = expected; 190 | 191 | it(`should return the items: ${itemsInBag.map(i => i.name)}`, function () { 192 | const result = knapsack(items, maxWeight); 193 | const resultRevenue = knapsack.calculateSalesValue(result); 194 | const resultWeight = knapsack.calculateTotalWeight(result); 195 | 196 | assert.equal(resultRevenue, totalRevenue, `Incorrect revenue: ${resultRevenue}`); 197 | assert.equal(resultWeight, totalWeight, `Incorrect weight: ${totalWeight}`); 198 | assert.deepEqual(result, itemsInBag, `Given: ${itemsInBag.map(i => i.name)}`); 199 | }); 200 | }); 201 | }); 202 | -------------------------------------------------------------------------------- /03-strategy/algorithms/merge-sort.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Merges two ordered lists into one. 3 | * @param {array} left first sorted list 4 | * @param {array} right second sorted list 5 | */ 6 | function merge(left, right) { 7 | const merged = []; 8 | 9 | while (left.length && right.length) { 10 | if (left[0] < right[0]) { 11 | merged.push(left.shift()); 12 | } else { 13 | merged.push(right.shift()); 14 | } 15 | } 16 | 17 | while (left.length) { 18 | merged.push(left.shift()); 19 | } 20 | 21 | while (right.length) { 22 | merged.push(right.shift()); 23 | } 24 | 25 | return merged; 26 | } 27 | 28 | /** 29 | * Merge Sort Algorithm 30 | * Running Time: O(n log n) 31 | * Technique: Divide and Conquer 32 | * @param {Array} list List of numbers to be sorted 33 | */ 34 | function mergeSort(list) { 35 | if (list.length === 1) { 36 | return list; 37 | } 38 | 39 | const middleIndex = Math.floor(list.length / 2); 40 | const secondHalf = list.splice(middleIndex); 41 | 42 | return merge(mergeSort(secondHalf), mergeSort(list)); 43 | } 44 | 45 | module.exports = mergeSort; 46 | -------------------------------------------------------------------------------- /03-strategy/algorithms/merge-sort.spec.js: -------------------------------------------------------------------------------- 1 | const assert = require('assert'); 2 | 3 | const mergeSort = require('./merge-sort'); 4 | 5 | describe('Algorithm - Merge Sort', function () { 6 | const testCases = [ 7 | { 8 | input: [10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0], 9 | expected: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10] 10 | }, 11 | { 12 | input: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10], 13 | expected: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10] 14 | }, 15 | { 16 | input: [5, 5, 5, 5, 5], 17 | expected: [5, 5, 5, 5, 5] 18 | } 19 | ]; 20 | 21 | testCases.forEach(({ input, expected }) => { 22 | it(`should sort input: ${input.join(', ')}`, function () { 23 | const result = mergeSort(input); 24 | assert.deepEqual(result, expected, `order should be: ${expected.join(', ')}. Given: ${result.join(', ')}`); 25 | }); 26 | }); 27 | }); -------------------------------------------------------------------------------- /03-strategy/algorithms/merge-two-sorted-lists.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Merges two ordered lists into one. 3 | * Running Time: O(n) 4 | * Technique: Iteration 5 | * @param {array} list1 sorted list one 6 | * @param {array} list2 sorted list two 7 | */ 8 | function merge(list1, list2) { 9 | let result = []; 10 | 11 | while (list1.length && list2.length) { 12 | if (list1[0] < list2[0]) { 13 | result.push(list1.shift()); 14 | } else { 15 | result.push(list2.shift()); 16 | } 17 | } 18 | 19 | // appends remaining items in any of the lists 20 | result = result.concat(list1, list2); 21 | 22 | return result; 23 | } 24 | 25 | module.exports = merge; 26 | -------------------------------------------------------------------------------- /03-strategy/algorithms/merge-two-sorted-lists.spec.js: -------------------------------------------------------------------------------- 1 | const assert = require('assert'); 2 | 3 | const merge = require('./merge-two-sorted-lists'); 4 | 5 | describe('Algorithm - Merge Two Sorted Lists', function () { 6 | const testCases = [ 7 | { 8 | input: { 9 | list1: ['Cod', 'Herring', 'Marlin'], 10 | list2: ['Asp', 'Carp', 'Ide', 'Trout'] 11 | }, 12 | expected: ['Asp', 'Carp', 'Cod', 'Herring', 'Ide', 'Marlin', 'Trout'] 13 | }, 14 | { 15 | input: { 16 | list1: [1, 3, 5, 7, 9], 17 | list2: [0, 2, 4, 6, 8, 10] 18 | }, 19 | expected: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10] 20 | } 21 | ]; 22 | 23 | testCases.forEach(({ input, expected }) => { 24 | const {list1, list2} = input; 25 | 26 | it(`should merge: ${list1.join(', ')} and ${list2.join(', ')}`, function () { 27 | const result = merge(list1, list2); 28 | assert.deepEqual(result, expected, `should return: ${expected.join(', ')}. Given ${result.join(', ')}`); 29 | }); 30 | }); 31 | }); -------------------------------------------------------------------------------- /03-strategy/algorithms/palindrome.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Returns if a string is a palindrome. 3 | * Technique: Recursion 4 | * @param {string} word 5 | */ 6 | function palindrome(word) { 7 | let len = word.length; 8 | 9 | if (len <= 1) { 10 | return true; 11 | } 12 | 13 | if (word.charAt(0) !== word.charAt(len - 1)) { 14 | return false; 15 | } 16 | 17 | return palindrome(word.substring(1, len - 1)); 18 | } 19 | 20 | module.exports = palindrome; -------------------------------------------------------------------------------- /03-strategy/algorithms/palindrome.spec.js: -------------------------------------------------------------------------------- 1 | const assert = require('assert'); 2 | 3 | const palindrome = require('./palindrome'); 4 | 5 | describe('Algorithm - Palindrome', function () { 6 | const testCases = [ 7 | { 8 | input: '', 9 | expected: true 10 | }, 11 | { 12 | input: 'a', 13 | expected: true 14 | }, 15 | { 16 | input: 'aa', 17 | expected: true 18 | }, 19 | { 20 | input: 'ab', 21 | expected: false 22 | }, 23 | { 24 | input: 'aba', 25 | expected: true 26 | }, 27 | { 28 | input: 'abc', 29 | expected: false 30 | }, 31 | { 32 | input: 'abba', 33 | expected: true 34 | }, 35 | { 36 | input: 'abca', 37 | expected: false 38 | }, 39 | { 40 | input: 'ababa', 41 | expected: true 42 | }, 43 | { 44 | input: 'abaca', 45 | expected: false 46 | }, 47 | ]; 48 | 49 | testCases.forEach(({ input, expected }) => { 50 | it(`should return: ${expected} for '${input}'`, function () { 51 | const result = palindrome(input); 52 | assert.deepEqual(result, expected, `Given ${result}`); 53 | }); 54 | }); 55 | }); -------------------------------------------------------------------------------- /03-strategy/algorithms/powdered-knapsack.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Callback to attach calculated 3 | * property `valueWeightRatio` to an item 4 | * @param {object} item 5 | */ 6 | function addValueWeightRatio({ name, price, weight }) { 7 | let valueWeightRatio = price / weight; 8 | 9 | return { 10 | name, 11 | price, 12 | weight, 13 | valueWeightRatio: +valueWeightRatio.toFixed(2) 14 | }; 15 | } 16 | 17 | /** 18 | * Callback for sorting by value/weight ratio. 19 | * @param {object} a First item 20 | * @param {object} b Second item 21 | */ 22 | function byValueWeightRatio(a, b) { 23 | return b.valueWeightRatio - a.valueWeightRatio; 24 | } 25 | 26 | /** 27 | * Creates a copy of list of items with a `valueWeightRatio` property 28 | * and sorts it by that new property. 29 | * @param {Array} items 30 | */ 31 | function sortByValueWeightRatio(items) { 32 | return items.map(addValueWeightRatio).sort(byValueWeightRatio); 33 | } 34 | 35 | /** 36 | * Orders the list of items by its value/weight ratio 37 | * and then put them (complete or splitted) in the bag untilg nothing else fits. 38 | * Technique: Heuristics 39 | * @param {Array} items List of items 40 | * @param {number} maxWeight Max weight the bag can carry 41 | */ 42 | function powderedKnapsack(items, maxWeight) { 43 | let bagValue = 0; 44 | let bagWeight = 0; 45 | let bagItems = []; 46 | items = sortByValueWeightRatio(items); 47 | 48 | items.forEach(item => { 49 | let availableSpace = maxWeight - bagWeight; 50 | let weight = Math.min(availableSpace, item.weight); 51 | 52 | if (weight > 0) { 53 | bagWeight += weight; 54 | bagValue += weight * item.valueWeightRatio; 55 | item.baggedWeight = weight; 56 | bagItems.push(item); 57 | } 58 | }) 59 | 60 | return { 61 | bagItems, 62 | bagValue 63 | }; 64 | } 65 | 66 | powderedKnapsack.sortByValueWeightRatio = sortByValueWeightRatio; 67 | 68 | module.exports = powderedKnapsack; 69 | -------------------------------------------------------------------------------- /03-strategy/algorithms/powdered-knapsack.spec.js: -------------------------------------------------------------------------------- 1 | const assert = require('assert'); 2 | 3 | const powderedKnapsack = require('./powdered-knapsack'); 4 | 5 | describe('Algorithm - Powdered Knapsack [Branch and Bound]', function () { 6 | const testCases = [ 7 | { 8 | input: { 9 | maxWeight: 10, 10 | items: [ 11 | { name: 'A', price: 20, weight: 5 }, 12 | { name: 'B', price: 19, weight: 4 }, 13 | { name: 'C', price: 16, weight: 2 }, 14 | { name: 'D', price: 14, weight: 5 }, 15 | { name: 'E', price: 13, weight: 3 }, 16 | { name: 'F', price: 9, weight: 2 } 17 | ] 18 | }, 19 | expected: { 20 | baggedValue: 52.66, 21 | itemsByValueWeightRatio: [ 22 | { name: 'C', price: 16, weight: 2, valueWeightRatio: 8.00 }, 23 | { name: 'B', price: 19, weight: 4, valueWeightRatio: 4.75 }, 24 | { name: 'F', price: 9, weight: 2, valueWeightRatio: 4.50 }, 25 | { name: 'E', price: 13, weight: 3, valueWeightRatio: 4.33 }, 26 | { name: 'A', price: 20, weight: 5, valueWeightRatio: 4.00 }, 27 | { name: 'D', price: 14, weight: 5, valueWeightRatio: 2.80 } 28 | ], 29 | baggedItems: [ 30 | { name: 'C', price: 16, weight: 2, valueWeightRatio: 8.00, baggedWeight: 2 }, 31 | { name: 'B', price: 19, weight: 4, valueWeightRatio: 4.75, baggedWeight: 4 }, 32 | { name: 'F', price: 9, weight: 2, valueWeightRatio: 4.50, baggedWeight: 2 }, 33 | { name: 'E', price: 13, weight: 3, valueWeightRatio: 4.33, baggedWeight: 2 } 34 | ], 35 | } 36 | } 37 | ]; 38 | 39 | testCases.forEach(({ input, expected }) => { 40 | const { items, maxWeight } = input; 41 | const { itemsByValueWeightRatio, baggedItems, baggedValue } = expected; 42 | 43 | it(`should sort items by value/weight ratio`, function () { 44 | const result = powderedKnapsack.sortByValueWeightRatio(items); 45 | 46 | assert.deepEqual(result, itemsByValueWeightRatio, `Given: ${result.map(i => i.name)}`); 47 | }); 48 | 49 | it(`should return the upper bound`, function () { 50 | const result = powderedKnapsack(items, maxWeight); 51 | const { bagItems, bagValue } = result; 52 | 53 | assert.equal(bagValue, baggedValue, `Given: ${bagValue}`); 54 | assert.deepEqual(bagItems, baggedItems, `Given: ${bagItems.map(i => i.name)}`); 55 | }); 56 | 57 | it(`should keep original items list`, function () { 58 | const expectedFirstItem = { name: 'A', price: 20, weight: 5 }; 59 | assert.deepEqual(items[0], expectedFirstItem, `Item was changed: ${JSON.stringify(items[0])}`); 60 | }); 61 | }); 62 | }) 63 | -------------------------------------------------------------------------------- /03-strategy/algorithms/power-set-recursive.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Returns a recursive copy of an array. 3 | * @param {array} array to copy 4 | */ 5 | function copyArray(array) { 6 | const copy = []; 7 | 8 | array.forEach((e, k) => { 9 | if (Array.isArray(e)) { 10 | copy[k] = copyArray(e); 11 | } else { 12 | copy[k] = e; 13 | } 14 | }); 15 | 16 | return copy; 17 | } 18 | 19 | /** 20 | * Creates a power set of a set recursively. 21 | * Technique: Recursion 22 | * @param {array} set 23 | */ 24 | function recursivePowerSet(set) { 25 | if (set.length === 0) { 26 | return [[]]; 27 | } 28 | 29 | let newSubset; 30 | 31 | while (set.length) { 32 | const item = set.pop(); 33 | const subset = recursivePowerSet(set); 34 | 35 | newSubset = copyArray(subset); 36 | 37 | subset.forEach(s => { 38 | s.push(item); 39 | }); 40 | 41 | newSubset = newSubset.concat(subset); 42 | } 43 | 44 | return newSubset; 45 | } 46 | 47 | module.exports = recursivePowerSet; -------------------------------------------------------------------------------- /03-strategy/algorithms/power-set-recursive.spec.js: -------------------------------------------------------------------------------- 1 | 2 | const assert = require('assert'); 3 | 4 | const powerSet = require('./power-set-recursive'); 5 | 6 | describe('Algorithm - Power Set [Recursive]', function () { 7 | const testCases = [ 8 | { 9 | input: ['A', 'B', 'C'], 10 | expected: [ 11 | [], 12 | ['A'], 13 | ['B'], 14 | ['A', 'B'], 15 | ['C'], 16 | ['A', 'C'], 17 | ['B', 'C'], 18 | ['A', 'B', 'C'] 19 | ] 20 | }, 21 | { 22 | input: [1, 2, 3, 4], 23 | expected: [ 24 | [], 25 | [1], 26 | [2], 27 | [1, 2], 28 | [3], 29 | [1, 3], 30 | [2, 3], 31 | [1, 2, 3], 32 | [4], 33 | [1, 4], 34 | [2, 4], 35 | [1, 2, 4], 36 | [3, 4], 37 | [1, 3, 4], 38 | [2, 3, 4], 39 | [1, 2, 3, 4] 40 | ] 41 | } 42 | ]; 43 | 44 | testCases.forEach(({ input, expected }) => { 45 | it(`should return the power set of: ${input}`, function () { 46 | const inputLength = input.length; 47 | const result = powerSet(input); 48 | const expectedLenght = Math.pow(2, inputLength); 49 | 50 | assert.equal(result.length, expectedLenght, `should return: ${expectedLenght} combinations. Given: ${result.length}`); 51 | assert.deepEqual(result, expected, `expected: ${expected.join('\n')}\n\nGiven: ${result.join('\n')}`); 52 | }); 53 | }); 54 | }); -------------------------------------------------------------------------------- /03-strategy/algorithms/power-set.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Returns a recursive copy of an array. 3 | * @param {array} array to copy 4 | */ 5 | function copyArray(array) { 6 | const copy = []; 7 | 8 | array.forEach((e, k) => { 9 | if (Array.isArray(e)) { 10 | copy[k] = copyArray(e); 11 | } else { 12 | copy[k] = e; 13 | } 14 | }); 15 | 16 | return copy; 17 | } 18 | 19 | /** 20 | * Creates a power set of a set. 21 | * Power Set: is a set of all the subsets of a set. 22 | * Running Time: O(2^n) 23 | * Technique: Iteration 24 | * @param {array} set 25 | */ 26 | function powerSet(set) { 27 | let PS = []; 28 | 29 | PS.push([]); 30 | 31 | set.forEach(item => { 32 | let newSubset = copyArray(PS); 33 | 34 | newSubset.forEach(subset => { 35 | subset.push(item); 36 | }); 37 | 38 | PS = PS.concat(newSubset); 39 | }); 40 | 41 | return PS; 42 | } 43 | 44 | module.exports = powerSet; -------------------------------------------------------------------------------- /03-strategy/algorithms/power-set.spec.js: -------------------------------------------------------------------------------- 1 | const assert = require('assert'); 2 | 3 | const powerSet = require('./power-set'); 4 | 5 | describe('Algorithm - Power Set [Iterative]', function () { 6 | const testCases = [ 7 | { 8 | input: ['A', 'B', 'C'], 9 | expected: [ 10 | [], 11 | ['A'], 12 | ['B'], 13 | ['A', 'B'], 14 | ['C'], 15 | ['A', 'C'], 16 | ['B', 'C'], 17 | ['A', 'B', 'C'] 18 | ] 19 | }, 20 | { 21 | input: [1, 2, 3, 4], 22 | expected: [ 23 | [], 24 | [1], 25 | [2], 26 | [1, 2], 27 | [3], 28 | [1, 3], 29 | [2, 3], 30 | [1, 2, 3], 31 | [4], 32 | [1, 4], 33 | [2, 4], 34 | [1, 2, 4], 35 | [3, 4], 36 | [1, 3, 4], 37 | [2, 3, 4], 38 | [1, 2, 3, 4] 39 | ] 40 | } 41 | ]; 42 | 43 | testCases.forEach(({ input, expected }) => { 44 | it(`should return the power set of: ${input}`, function () { 45 | const result = powerSet(input); 46 | const expectedLenght = Math.pow(2, input.length); 47 | 48 | assert.equal(result.length, expectedLenght, `should return: ${expectedLenght} combinations. Given: ${result.length}`); 49 | assert.deepEqual(result, expected, `should return: ${expected.join('\n')}.\nGiven: ${result.join('\n')}`); 50 | }); 51 | }); 52 | }); -------------------------------------------------------------------------------- /03-strategy/summary.md: -------------------------------------------------------------------------------- 1 | # Computer Science Distilled 2 | 3 | ## Summary - Chapter 3: Strategy 4 | 5 | > If you find a good move, look for a better one. 6 | > 7 | > -- _Emanuel Lasker_ 8 | 9 | **:dart: Objectives:** 10 | 11 | > * _Iteration_ 12 | > * _Recursion_ 13 | > * _Brute Force_ 14 | > * _Backtracking_ 15 | > * _Heuristics_ 16 | > * _Divide and conquer_ 17 | > * _Dynamic Programming_ 18 | > * _Branch and Bound_ 19 | 20 | --- 21 | 22 | ### Section 1: Iteration 23 | 24 | #### Merge 25 | 26 | > **Fish Reunion** 27 | > 28 | > _You're given a list of saltwater fish and a list of freshwater fish, both in alphabetical order._ 29 | > 30 | > _How do you create a list featuring all the fish in alphabetical order?_ 31 | 32 | ```bash 33 | function merge (list1, list2) 34 | result <- List.new 35 | 36 | while not (list1.empty and list2.empty) 37 | if list1.top_item < list2.top_item 38 | item <- list1.remove_top_item 39 | else 40 | item <- list2.remove_top_item 41 | 42 | result.append(item) 43 | 44 | return result 45 | ``` 46 | 47 | > Code: [merge-two-sorted-lists.js](./algorithms/merge-two-sorted-lists.js) 48 | 49 | #### Power Sets 50 | 51 | Generating **power sets** = generating **truth tables** 52 | 53 | > **Exploring Scents** 54 | > 55 | > _Floral fragances are made combining scents from flowers. Give a set of flowers `F`, how do you list all fragances that can be made?_ 56 | 57 | ```bash 58 | function power_set (flowers) 59 | fragances <- Set.new 60 | fragances.add(Set.new) 61 | 62 | for each (flower in flowers) 63 | new_fragances <- copy(fragances) 64 | for each (france in new_fragances) 65 | fragances.add(flower) 66 | fragances <- fragances + new_fragances 67 | 68 | return fragances 69 | ``` 70 | 71 | > Code: [power-set.js](./algorithms/power-set.js) 72 | 73 | --- 74 | 75 | ### Section 2: Recursion 76 | 77 | #### A recursive function... 78 | 79 | > is a function that calls _itself_ 80 | > 81 | > has a **base** case 82 | > 83 | > spawn numerous clones of itself, introducing computational overhead 84 | > 85 | > can be visualized with **recursion trees** 86 | 87 | #### Fibonacci 88 | 89 | > _How do you code a function that returns the `nth` Fibonacci number?_ 90 | 91 | ```bash 92 | function fib (n) 93 | if n <= 2 94 | return 1 95 | 96 | return fib(n - 1) + fib(n - 2) 97 | ``` 98 | 99 | > Code: [fibonacci.js](./algorithms/fibonacci.js) 100 | 101 | #### Palindrome 102 | 103 | ```bash 104 | function palindrome (word) 105 | if (word.length <= 1) 106 | return true 107 | if (word.first_char != word.last_char) 108 | return false 109 | w = word.remove_first_and_last_chars 110 | return palindrome(w) 111 | ``` 112 | 113 | > Code: [palindrome.js](./algorithms/palindrome.js) 114 | 115 | #### Recursive Power Set 116 | 117 | ```bash 118 | function recursive_power_set (items) 119 | ps <- copy(items) 120 | 121 | for each (e in items) 122 | ps <- ps.remove(e) 123 | ps <- ps + recursive_power_set(ps) 124 | ps <- ps.add(e) 125 | 126 | return ps 127 | ``` 128 | 129 | > Code: [power-set-recursive.js](./algorithms/power-set-recursive.js) 130 | 131 | --- 132 | 133 | ### Section 3: Brute Force 134 | 135 | #### Brute Force? 136 | 137 | > solves problems by inspecting all of the problem's possible solution candidates aka **exhaustive search** 138 | 139 | #### Best Trade 140 | 141 | > _You have the daily prices of gold for a interval of time. You want to find two days in this interval such that if you had bought then sold gold at those dates, you'd have made the maximum possible profit._ 142 | 143 | ```bash 144 | function best_trade (gold_prices) 145 | buyingDate 146 | sellingDate 147 | maxProfit 148 | 149 | for each (buyingPrice in gold_prices) 150 | for each (sellingPrice in gold_prices) 151 | profit <- sellingPrice - buyingPrice 152 | if profit > maxProfit 153 | maxProfit <- profit 154 | buyingDate <- indexOf(buyingPrice) 155 | sellingDate <- indexOf(sellingPrice) 156 | 157 | return maxProfit 158 | ``` 159 | 160 | > Code: [best-trade.js](./algorithms/best-trade.js) 161 | 162 | #### Knapsack 163 | 164 | > _You have a knapsack to carry products for selling. It holds up to a certain weight, not enough for carrying all your products - you must choose which ones to carry. Knowing the weight and sales value of each product, which choice of products gives the highest revenue?_ 165 | 166 | ```bash 167 | function knapsack (items, max_weight) 168 | best_value <- 0 169 | 170 | for each (candidate in power_set(items)) 171 | if (total_weight(candidate) <= max_weight) 172 | if sales_value(candidate) > best_value 173 | best_value <- sales_value(candidate) 174 | best_candidate <- candidate 175 | 176 | return best_candidate 177 | ``` 178 | 179 | > Code: [knapsack.js](./algorithms/knapsack.js) 180 | 181 | --- 182 | 183 | ### Section 4: Backtracking 184 | 185 | #### Backtracking? 186 | 187 | > works best in problems where the solution is a sequence of choices and making a choice restraings subsequent choices. 188 | > 189 | > _"Fail early, fail often."_ 190 | 191 | #### Eight Queens Puzzle 192 | 193 | > _How do you place eight queens on the board such that no queens attack each other?_ 194 | 195 | ```bash 196 | function queens (board) 197 | if board.has_8_queens 198 | return board 199 | 200 | for each (position in board.unattacked_positions) 201 | board.place_queen(position) 202 | solution <- queens(board) 203 | if (solution) 204 | return solution 205 | board.remove_queen(position) # backtracking 206 | 207 | return false 208 | ``` 209 | 210 | > Code: [eight-queens-puzzle.v1.js (Object Oriented)](./algorithms/eight-queens-puzzle.v1.js) 211 | > 212 | > Code: [eight-queens-puzzle.v2.js](./algorithms/eight-queens-puzzle.v1.js) 213 | 214 | --- 215 | 216 | ### Section 5: Heuristics 217 | 218 | #### Greed 219 | 220 | > A greedy algorithm tries to make the best choice at each step and never coming back. It's the opposite of backtracking. 221 | 222 | #### Greedy Knapsack 223 | 224 | > **Evil Knapsack** 225 | > 226 | > _A greedy burglar breaks into your home to steal the products you wanted to sell. He decides to use your knapsack to carry the stolen items. Which items will he steal? Remember, the less time he spends in your home, the less likely he is to get caught._ 227 | 228 | ```bash 229 | function greedy_knapsack (items, max_weight) 230 | bag_weight <- 0 231 | bag_items <- List.new 232 | 233 | for each (item in sort_by_value(items)) 234 | if max_weight <= bag_weight + item.weight 235 | bag_weight <- bag_weight + item.weight 236 | bag_items.append(items) 237 | 238 | return bag_items; 239 | ``` 240 | 241 | > Code: [greedy-knapsack.js](./algorithms/greedy-knapsack.js) 242 | 243 | --- 244 | 245 | ### Section 6: Divide and Conquer 246 | 247 | Problems with optimal substructure can be divided into similar but smaller subproblems. 248 | 249 | **NOTE:** 250 | 251 | > $x=\log_{2}n \to 2^x = n$ 252 | 253 | #### Merge Sort 254 | 255 | ```bash 256 | function merge_sort(list) 257 | if list.length = 1 258 | return list 259 | 260 | left <- list.first_half 261 | right <- list.last_half 262 | 263 | return merge(merge_sort(left), merge_sort(right)); 264 | ``` 265 | 266 | > Code: [merge-sort.js](./algorithms/merge-sort.js) 267 | 268 | #### Best Trade - Divide and Conquer 269 | 270 | > _You have the daily prices of gold for a interval of time. You want to find two days in this interval such that if you had bought then sold gold at those dates, you'd have made the maximum possible profit._ 271 | 272 | ```bash 273 | function trade (prices) 274 | if prices.length = 1 275 | return 0 276 | 277 | former <- prices.first_half 278 | latter <- prices.last_half 279 | case3 <- max(latter) - min(former) 280 | 281 | return max(trade(latter), trade(former), case3) 282 | ``` 283 | 284 | > Code: [best-trade-divide-n-conquer.js](./algorithms/best-trade-divide-n-conquer.js) 285 | 286 | #### Knapsack - Divide and Conquer 287 | 288 | > _You have a knapsack to carry products for selling. It holds up to a certain weight, not enough for carrying all your products - you must choose which ones to carry. Knowing the weight and sales value of each product, what would be the highest possible revenue?_ 289 | > 290 | > * $w_i$ is the $i^{th}$ item's weight 291 | > * $v_i$ is the $i^{th}$ item's value 292 | 293 | $K(n,\ c) = \max(K(n-1,\ c),\ K(n-1,\ c - w_n) + v_n)$ 294 | 295 | > Code: [knapsack-dnc.js](./algorithms/knapsack-dnc.js) 296 | 297 | --- 298 | 299 | ### Section 7: Dynamic Programming 300 | 301 | #### Dynamic Programming? 302 | 303 | > is identifying repeated subproblems in order to compute them only once. 304 | 305 | #### Memoization 306 | 307 | > is a technique to store and reuse partial calculations to repeated subproblems 308 | 309 | #### Dynamic Fibonacci using Memoization 310 | 311 | > _How do you code a function that returns the `nth` Fibonacci number?_ 312 | 313 | ```bash 314 | M <- [1 -> 1; 2 -> 2] 315 | 316 | function dfib (n) 317 | if n not in M 318 | M[n] <- dfib(n - 1) + dfib(n - 2) 319 | return M[n] 320 | ``` 321 | 322 | > Code: [dynamic-fibonacci.js](./algorithms/dynamic-fibonacci.js) 323 | 324 | #### Memoizing Knapsack 325 | 326 | > _You have a knapsack to carry products for selling. It holds up to a certain weight, not enough for carrying all your products - you must choose which ones to carry. Knowing the weight and sales value of each product, what would be the highest possible revenue?_ 327 | > 328 | > Code: [knapsack-dp.js](./algorithms/knapsack-dp.js) 329 | 330 | #### Bottom-Up Best Trade 331 | 332 | Calculate base cases first, and assemble them over and over again until we get the general solution 333 | 334 | > _You have the daily prices of gold for a interval of time. You want to find two days in this interval such that if you had bought then sold gold at those dates, you'd have made the maximum possible profit._ 335 | 336 | ```bash 337 | function best_trade_dp (P) 338 | B[1] <- 1 339 | sell_day <- 1 340 | best_profit <- 0 341 | 342 | for each (n from 2 to P.length) 343 | if P[n] < P[B[n - 1]] 344 | B[n] <- n 345 | else 346 | B[n] <- B[n - 1] 347 | 348 | profit <- P[n] - P[B[n]] 349 | 350 | if profit > best_profit 351 | sell_day <- n 352 | best_profit <- profit 353 | 354 | return (sell_day, B[sell_day]) 355 | ``` 356 | 357 | > Code: [best-trade-bottom-up.js](./algorithms/best-trade-bottom-up.js) 358 | 359 | --- 360 | 361 | ### Section 8: Branch and Bound 362 | 363 | #### Branch and bound? 364 | 365 | > Many problems involve minimizing or maximizing a target value. They're called **optimization problems**. 366 | > 367 | > When a solution is a sequence of choices, we use the strategy **branch and bound** 368 | 369 | #### Bounds 370 | 371 | > * An **upper bound** sets the limit on how high the value can be. 372 | > * A **lower bound** sets the limit on how low the value can be. 373 | 374 | #### Powdered Knapsack 375 | 376 | This algorithm provides the **upper bound** of the optimal profit, while the [_Greedy Knapsack_](#Greedy-Knapsack) algorithm provides the **lower bound**. 377 | 378 | > _You have a knapsack to carry products for selling. It holds up to a certain weight, not enough for carrying all your products - you must choose which ones to carry. Knowing the weight and sales value of each product, what would be the highest possible revenue?_ 379 | 380 | ```bash 381 | function powdered_knapsack (items, max_weight) 382 | bag_value <- 0 383 | bag_weight <- 0 384 | bag_items <- List.new 385 | items <- sort_by_value_weight_ratio(items) 386 | 387 | for each (i in items) 388 | weight <- min(max_weight - bag_weight, i.weight) 389 | bag_weight <- bag_weight + weight 390 | value <- weight * i.value_weight_ratio 391 | bag_value <- bag_value + value 392 | bag_items.append(item, weight) 393 | 394 | return bag_items, bag_value 395 | ``` 396 | 397 | > Code: [powdered-knapsack.js](./algorithms/powdered-knapsack.js) 398 | 399 | ## References 400 | 401 | ### In the book: 402 | 403 | * [Geek&Poke](http://geek-and-poke.com/) 404 | * [Algorithm Design: 9780321295354: Computer Science Books @ Amazon.com](https://www.amazon.com/dp/0321295358/) 405 | * [Choosing Best Algorithm Design Strategy For A Particular Problem](http://www.gdeepak.com/pubs/intcon/Choosing%20Best%20Algorithm%20Design%20Strategy%20For%20A%20Particular%20Problem.pdf) 406 | * [Dynamic programming](https://people.eecs.berkeley.edu/~vazirani/algorithms/chap6.pdf) 407 | -------------------------------------------------------------------------------- /04-data/data-structures/DoublyLinkedList.js: -------------------------------------------------------------------------------- 1 | const DNode = require('./DoublyLinkedNode'); 2 | const Stack = require('./Stack'); 3 | 4 | const defineProperty = Object.defineProperty; 5 | 6 | /** 7 | * Checks if a parameter is `undefined` 8 | * @private 9 | * @param {*} obj Object to be tested 10 | * @returns {boolean} 11 | */ 12 | function isUndefined(obj) { 13 | return typeof obj === 'undefined'; 14 | } 15 | 16 | /** 17 | * Checks if a parameter is a positive number 18 | * @private 19 | * @param {*} n Object to be tested 20 | * @returns {boolean} 21 | */ 22 | function isPositiveNumber(n) { 23 | return Number.isInteger(n) && n >= 0; 24 | } 25 | 26 | /** 27 | * Validates a parameter to be a positive integer. 28 | * @private 29 | * @param {string} parameterName Parameter name to show in the error message. 30 | * @returns {function} The function validator. 31 | */ 32 | function validatePositiveNumber(parameterName) { 33 | /** 34 | * Throws an exception if a parameter is not a positive number 35 | * @param {*} n Element to be tested 36 | * @throws {RangeError} parameter should be a positive number. 37 | */ 38 | return function throwIfNotPositiveNumber(n) { 39 | if (!isPositiveNumber(n)) { 40 | throw new RangeError(`"${parameterName}" should be an Integer greater than or equal to 0. Given: ${n}`); 41 | } 42 | } 43 | } 44 | 45 | /** 46 | * DoublyLinkedList ADT. 47 | * Inititalizes a new instance of a DoublyLinkedList 48 | * @constructs DoublyLinkedList 49 | */ 50 | function DoublyLinkedList() { 51 | /** 52 | * Pointer to the first DNode in the DoublyLinkedList 53 | * @name DoublyLinkedList#_first 54 | * @type {(DNode|null)} 55 | * @default null 56 | */ 57 | this._first; 58 | 59 | defineProperty(this, '_first', { 60 | writable: true, 61 | value: null 62 | }); 63 | 64 | /** 65 | * Pointer to the last DNode in the DoublyLinkedList 66 | * @name DoublyLinkedList#_last 67 | * @type {(DNode|null)} 68 | * @default null 69 | */ 70 | this._last; 71 | 72 | defineProperty(this, '_last', { 73 | writable: true, 74 | value: null 75 | }); 76 | 77 | /** 78 | * Length of the DoublyLinkedList 79 | * @name DoublyLinkedList#length 80 | * @type {number} 81 | * @readonly 82 | */ 83 | 84 | this.length; 85 | 86 | defineProperty(this, 'length', { 87 | get() { 88 | /** 89 | * @type {DNode} 90 | * @private 91 | */ 92 | let currentNode = this._first; 93 | let length = 0; 94 | 95 | while (currentNode) { 96 | length++; 97 | currentNode = currentNode.getNext(); 98 | } 99 | 100 | return length; 101 | } 102 | }); 103 | 104 | /** 105 | * Helper property to keep track of the current iterator. 106 | * @name DoublyLinkedList#_currentIterator 107 | * @type {number} 108 | * @default 0 109 | * @private 110 | */ 111 | this._currentIterator; 112 | 113 | defineProperty(this, '_currentIterator', { 114 | writable: true, 115 | value: 0 116 | }); 117 | 118 | return this; 119 | } 120 | 121 | /** 122 | * Returns the self instance as iterator. 123 | * @function DoublyLinkedList#Symbol.iterator 124 | * @example 125 | * // const s = new Set(); 126 | * // const it = s[Symbol.iterator](); 127 | * @returns {DoublyLinkedList} instance iterator. 128 | */ 129 | DoublyLinkedList.prototype[Symbol.iterator] = function () { 130 | return this; 131 | }; 132 | 133 | /** 134 | * Returns the next value of the @@iterator 135 | * @function DoublyLinkedList#next 136 | * @returns {object} {value, done} 137 | */ 138 | DoublyLinkedList.prototype.next = function () { 139 | if (this._currentIterator < this.length) { 140 | return { 141 | value: this.get(this._currentIterator++), 142 | done: false 143 | }; 144 | } else { 145 | this._currentIterator = 0; 146 | return { 147 | value: undefined, 148 | done: true 149 | }; 150 | } 151 | }; 152 | 153 | /** 154 | * Checks if the DoublyLinkedList is empty. 155 | * @function DoublyLinkedList#isEmpty 156 | * @returns {boolean} Whether the list is empty or not. 157 | */ 158 | DoublyLinkedList.prototype.isEmpty = function () { 159 | return this._first === null && this._last === null; 160 | }; 161 | 162 | /** 163 | * Returns the node at the 'index' position in the list. 164 | * @function DoublyLinkedList#getNode 165 | * @param {number=} [index = lastIndex] Index of the node 166 | * @returns {DoublyLinkedList} The node the the 'index' position in the list or null if the list is empty. 167 | * @throws {RangeError} when index is not a positive integer or list is empty. 168 | */ 169 | DoublyLinkedList.prototype.getNode = function (index) { 170 | if (this.isEmpty()) { 171 | throw new RangeError('DoublyLinkedList is empty.'); 172 | } else { 173 | const lastIndex = this.length - 1; 174 | 175 | if (isUndefined(index)) { 176 | index = lastIndex; 177 | } else { 178 | validatePositiveNumber('index')(index); 179 | index = (index <= lastIndex ? index : lastIndex); 180 | } 181 | 182 | let currentNode; 183 | let currentIndex; 184 | 185 | if (index <= Math.floor(lastIndex / 2)) { /* search from first to last */ 186 | currentNode = this._first; 187 | currentIndex = 0; 188 | while (currentNode.hasNext()) { 189 | if (currentIndex === index) { 190 | break; 191 | } 192 | currentIndex++; 193 | currentNode = currentNode.getNext(); 194 | } 195 | } else { /* search from last to first */ 196 | currentNode = this._last; 197 | currentIndex = lastIndex; 198 | while (currentNode.hasPrevious()) { 199 | if (currentIndex === index) { 200 | break; 201 | } 202 | currentIndex--; 203 | currentNode = currentNode.getPrevious(); 204 | } 205 | } 206 | 207 | return currentNode; 208 | } 209 | }; 210 | 211 | /** 212 | * Returns the value of a DNode at the 'index' position in the list. 213 | * @function DoublyLinkedList#get 214 | * @param {number=} [index = lastIndex] Index of the DNode to be retrieved. 215 | * @returns {*} Value of the DNode. 216 | */ 217 | DoublyLinkedList.prototype.get = function (index) { 218 | return this.getNode(index)._value; 219 | }; 220 | 221 | /** 222 | * Inserts a new DNode before the 'index' provided. 223 | * @function DoublyLinkedList#insert 224 | * @param {*} value Value to be inserted. 225 | * @param {number=} index 'index' to place the new item. If not provided, item is inserted at the end of the list. 226 | * @returns {DoublyLinkedList} instance 227 | * @throws {RangeError} 'index' should be a positive number. 228 | */ 229 | DoublyLinkedList.prototype.insert = function (value, index) { 230 | const newNode = new DNode(value); 231 | 232 | if (this.isEmpty()) { 233 | this._first = newNode; 234 | this._last = newNode; 235 | } else { 236 | const currentNode = this.getNode(index); 237 | if (index === 0) { /* insert before first */ 238 | newNode.setNext(this._first); 239 | this._first.setPrevious(newNode); 240 | this._first = newNode; 241 | } else if (currentNode === this._last) { /* insert at the end */ 242 | newNode.setPrevious(this._last); 243 | this._last.setNext(newNode); 244 | this._last = newNode; 245 | } else { /* insert before index */ 246 | const prevNode = currentNode.getPrevious(); 247 | prevNode.setNext(newNode); 248 | newNode.setPrevious(prevNode); 249 | newNode.setNext(currentNode); 250 | currentNode.setPrevious(newNode); 251 | } 252 | } 253 | 254 | return this; 255 | }; 256 | 257 | /** 258 | * Removes an item from the list at the 'index' position and returns its value. 259 | * @function DoublyLinkedList#remove 260 | * @param {number=} index Position of the item to be removed. 261 | * @returns {*} Value of the DNode. 262 | * @throws {RangeError} 'index' should be a positive number. 263 | */ 264 | DoublyLinkedList.prototype.remove = function (index) { 265 | const length = this.length; 266 | let nodeToRemove = this.getNode(index); 267 | 268 | if (length === 1) { 269 | this._first = null; 270 | this._last = null; 271 | } else if (nodeToRemove === this._first) { 272 | const newFirst = this._first.getNext(); 273 | newFirst.setPrevious(null); 274 | this._first = newFirst; 275 | } else if (nodeToRemove === this._last) { 276 | const newLast = this._last.getPrevious(); 277 | newLast.setNext(null); 278 | this._last = newLast; 279 | } else { 280 | const prevNode = nodeToRemove.getPrevious(); 281 | const nextNode = nodeToRemove.getNext(); 282 | prevNode.setNext(nextNode); 283 | nextNode.setPrevious(prevNode); 284 | } 285 | 286 | return nodeToRemove._value; 287 | }; 288 | 289 | /** 290 | * My lazy and hackish implementation for sort. 291 | * Moves all elements to an array, sorts them and inserts them back in to the list. 292 | * @function DoublyLinkedList#sort 293 | * @returns {DoublyLinkedList} instance 294 | */ 295 | DoublyLinkedList.prototype.sort = function () { 296 | const data = []; 297 | 298 | while (!this.isEmpty()) { 299 | data.push(this.remove()); 300 | } 301 | 302 | data.sort(); 303 | 304 | data.forEach(i => this.insert(i)); 305 | 306 | return this; 307 | }; 308 | 309 | /** 310 | * Reverses the items in the doubly linked list 311 | * @function DoublyLinkedList#reverse 312 | * @returns {DoublyLinkedList} instance 313 | */ 314 | DoublyLinkedList.prototype.reverse = function () { 315 | const stack = new Stack(); 316 | 317 | while (!this.isEmpty()) { 318 | stack.push(this.remove(0)); 319 | } 320 | 321 | while (!stack.isEmpty()) { 322 | this.insert(stack.pop()); 323 | } 324 | 325 | return this; 326 | }; 327 | 328 | /** 329 | * Returns a doubly linked list as Array. 330 | * @function DoublyLinkedList#toArray 331 | * @returns {Array} The doubly linked list as Array. 332 | */ 333 | DoublyLinkedList.prototype.toArray = function () { 334 | const it = this[Symbol.iterator](); 335 | 336 | return [...it]; 337 | }; 338 | 339 | /** 340 | * Return the doubly linked list as string. 341 | * @function DoublyLinkedList#toString 342 | * @param {function=} [parser = String] A parser function. 343 | * @returns {string} Doubly linked list as string 344 | */ 345 | DoublyLinkedList.prototype.toString = function (parser) { 346 | let listAsString = ''; 347 | let currentNode = this._first; 348 | 349 | if (parser && typeof parser !== 'function') { 350 | throw new TypeError(`Expected 'parser' to be a function. Given: ${typeof parser}`); 351 | } else { 352 | parser = parser || String; 353 | } 354 | 355 | while (currentNode) { 356 | listAsString += parser(currentNode); 357 | if (currentNode.hasNext()) { 358 | listAsString += ','; 359 | } 360 | currentNode = currentNode.getNext(); 361 | } 362 | 363 | return listAsString; 364 | }; 365 | 366 | module.exports = DoublyLinkedList; -------------------------------------------------------------------------------- /04-data/data-structures/DoublyLinkedList.spec.js: -------------------------------------------------------------------------------- 1 | const assert = require('assert'); 2 | 3 | const DoublyLinkedList = require('./DoublyLinkedList'); 4 | 5 | describe('Data Structure - Doubly Linked List', function () { 6 | /** 7 | * @type {DoublyLinkedList} 8 | * @private 9 | */ 10 | let list; 11 | 12 | const EMPTY_LIST = ''; 13 | const EMPTY_LIST_ERROR = /^RangeError: DoublyLinkedList is empty\.$/; 14 | const POSITIVE_INTEGER_ERROR = /^RangeError: ".+" should be an Integer greater than or equal to 0\. Given: .+$/; 15 | 16 | beforeEach(function () { 17 | list = new DoublyLinkedList(); 18 | }); 19 | 20 | it('should be initialized correctly', function () { 21 | assert.ok(list, 'list is not initialized'); 22 | }); 23 | 24 | it('should not have enumerable properties', function () { 25 | const properties = Object.keys(list); 26 | const expectedProperties = []; 27 | 28 | assert.deepEqual(properties, expectedProperties, 'properties do not match'); 29 | }); 30 | 31 | it('should start empty', function () { 32 | assert.equal(list.isEmpty(), true, 'list did not start empty'); 33 | assert.equal(list._first, null, 'first is not null'); 34 | assert.equal(list._last, null, 'last is not null'); 35 | assert.equal(list._currentIterator, 0, 'current iterator should be 0'); 36 | assert.equal(list.length, 0, 'length should be 0'); 37 | }); 38 | 39 | it('should insert the first item correctly', function () { 40 | const val = 1; 41 | list.insert(val); 42 | assert.equal(list.isEmpty(), false, 'list should not be empty'); 43 | assert.equal(list.get(0), val, 'invalid first item'); 44 | assert.equal(list.get(), val, 'invalid last item'); 45 | assert.equal(list.length, 1, 'length should be 1'); 46 | assert.equal(list._first, list.getNode(0), 'invalid first pointer'); 47 | assert.equal(list._last, list.getNode(0), 'invalid last pointer'); 48 | }); 49 | 50 | it('should throw an error when trying to get on the empty list', function () { 51 | assert.throws(() => list.get(), EMPTY_LIST_ERROR, 'list is not throwing empty error'); 52 | }); 53 | 54 | it('should throw an error when trying to get an invalid index position', function () { 55 | list.insert(true); 56 | assert.throws(() => list.get(-1), POSITIVE_INTEGER_ERROR, 'list is not throwing empty error'); 57 | assert.throws(() => list.get(null), POSITIVE_INTEGER_ERROR, 'list is not throwing empty error'); 58 | }); 59 | 60 | it('should throw an error when trying to insert on an invalid index position', function () { 61 | list.insert(true); 62 | assert.throws(() => list.insert(2, -1), POSITIVE_INTEGER_ERROR, 'list is not throwing empty error for: -1'); 63 | assert.throws(() => list.insert(3, false), POSITIVE_INTEGER_ERROR, 'list is not throwing empty error for: false'); 64 | }); 65 | 66 | it('should insert multiple items correctly', function () { 67 | const data = [1, 2, 3, 4, 5, false, {}, [], 'hello']; 68 | 69 | data.forEach((d, k) => list.insert(d, k)); 70 | 71 | const retrievedData = []; 72 | let currentPosition = 0; 73 | 74 | while (currentPosition < data.length) { 75 | retrievedData.push(list.get(currentPosition++)); 76 | } 77 | 78 | assert.equal(list.length, data.length, `length should be ${data.length}`); 79 | assert.deepEqual(retrievedData, data, 'incorrect retrieved data'); 80 | }); 81 | 82 | it('should return the list as string', function () { 83 | const data = [1, 2, 3, 4, 5, false, {}, [], 'hello']; 84 | 85 | assert.equal(list.toString(), EMPTY_LIST, 'incorrect empty list as string'); 86 | 87 | data.forEach((d, k) => list.insert(d)); 88 | 89 | assert.equal(list.length, data.length, `length should be ${data.length}`); 90 | assert.equal(list.toString(), data.join(','), 'incorrect list as string'); 91 | }); 92 | 93 | it('should insert items before the first item correctly', function () { 94 | const val1 = 1; 95 | 96 | list.insert(val1); 97 | assert.equal(list.get(0), val1, `invalid val1: ${list.get(0)}`); 98 | 99 | const val2 = 2; 100 | 101 | list.insert(val2, 0); 102 | assert.equal(list.get(0), val2, `invalid val2: ${list.get(0)}`); 103 | 104 | const val3 = 3; 105 | 106 | list.insert(val3, 0); 107 | assert.equal(list.get(0), val3, `invalid val3: ${list.get(0)}`); 108 | 109 | assert.equal(list.length, 3, `length should be ${3}`); 110 | assert.equal(list.toString(), [val3, val2, val1], `incorrect order: ${list.toString()}`); 111 | }); 112 | 113 | it('should insert items at the end correctly', function () { 114 | const val1 = 1; 115 | 116 | list.insert(val1); 117 | assert.equal(list.get(), val1, `invalid val1: ${list.get(0)}`); 118 | 119 | const val2 = 2; 120 | 121 | list.insert(val2); 122 | assert.equal(list.get(1), val2, `invalid val2: ${list.get(1)}`); 123 | 124 | const val3 = 3; 125 | 126 | list.insert(val3); 127 | assert.equal(list.get(2), val3, `invalid val3: ${list.get(2)}`); 128 | 129 | assert.equal(list.length, 3, `length should be ${3}`); 130 | assert.equal(list.toString(), [val1, val2, val3], `incorrect order: ${list.toString()}`); 131 | }); 132 | 133 | it('should throw an exception if trying to remove on a empty list', function () { 134 | assert.throws(() => list.remove(5), EMPTY_LIST_ERROR, 'remove is not throwing an error'); 135 | }); 136 | 137 | it('should remove the last item if the position is greater than the total length', function () { 138 | list.insert(true); 139 | assert.equal(list.length, 1, `length should be ${1}`); 140 | assert.equal(list.remove(5), true, 'incorrect removed value'); 141 | assert.equal(list.length, 0, `length should be ${0}`); 142 | }); 143 | 144 | it('should remove the first item correctly', function () { 145 | const value = 1; 146 | list.insert(value); 147 | assert.equal(list.length, 1, `length should be ${1}`); 148 | assert.equal(list.remove(0), value, 'incorrect removed value'); 149 | assert.equal(list.length, 0, `length should be ${0}`); 150 | assert.equal(list.isEmpty(), true, 'list should be empty'); 151 | }); 152 | 153 | it('should remove the middle item correctly', function () { 154 | const val1 = 1; 155 | const val2 = 2; 156 | const val3 = 3; 157 | 158 | list.insert(val1).insert(val2).insert(val3); 159 | assert.equal(list.length, 3, `length should be ${3}`); 160 | assert.equal(list.remove(1), val2, 'incorrect removed value'); 161 | assert.equal(list.isEmpty(), false, 'list should not be empty'); 162 | assert.equal(list.length, 2, `length should be ${2}`); 163 | assert.equal(list.toString(), [val1, val3], 'incorrect list as string'); 164 | }); 165 | 166 | it('should remove the last item correctly', function () { 167 | const val1 = 1; 168 | const val2 = 2; 169 | const val3 = 3; 170 | 171 | list.insert(val1).insert(val2).insert(val3); 172 | assert.equal(list.length, 3, `length should be ${3}`); 173 | 174 | assert.equal(list.remove(2), val3, 'incorrect removed value'); 175 | assert.equal(list.isEmpty(), false, 'list should not be empty'); 176 | assert.equal(list.toString(), [val1, val2], 'incorrect list as string'); 177 | 178 | assert.equal(list.remove(1), val2, 'incorrect removed value2'); 179 | assert.equal(list.isEmpty(), false, 'list should not be empty'); 180 | assert.equal(list.toString(), [val1], 'incorrect list as string'); 181 | 182 | assert.equal(list.remove(0), val1, 'incorrect removed value1'); 183 | assert.equal(list.isEmpty(), true, 'list should be empty'); 184 | assert.equal(list.toString(), EMPTY_LIST, 'incorrect list as string'); 185 | 186 | assert.equal(list.length, 0, `length should be ${0}`); 187 | }); 188 | 189 | it('should remove the last item correctly if not position provided', function () { 190 | const val1 = 1; 191 | const val2 = 2; 192 | const val3 = 3; 193 | 194 | list.insert(val1).insert(val2).insert(val3); 195 | assert.equal(list.length, 3, `length should be ${3}`); 196 | 197 | assert.equal(list.remove(), val3, 'incorrect removed value3'); 198 | assert.equal(list.isEmpty(), false, 'list should not be empty'); 199 | assert.equal(list.toString(), [val1, val2], 'incorrect list as string'); 200 | 201 | assert.equal(list.remove(), val2, 'incorrect removed value2'); 202 | assert.equal(list.isEmpty(), false, 'list should not be empty'); 203 | assert.equal(list.toString(), [val1], 'incorrect list as string'); 204 | 205 | assert.equal(list.remove(), val1, 'incorrect removed value1'); 206 | assert.equal(list.isEmpty(), true, 'list should be empty'); 207 | assert.equal(list.toString(), EMPTY_LIST, 'incorrect list as string'); 208 | 209 | assert.equal(list.length, 0, `length should be ${0}`); 210 | }); 211 | 212 | it('should insert and remove multiple items', function () { 213 | const data = [1, 2, 3, 4, 5, false, {}, [], 'hello']; 214 | 215 | data.forEach(i => list.insert(i)); 216 | 217 | assert.equal(list.length, data.length, `length should be ${data.length}`); 218 | assert.equal(list.toString(), data.join(','), 'incorrect list as string'); 219 | 220 | const expectedData = []; 221 | 222 | while (!list.isEmpty()) { 223 | expectedData.unshift(list.remove()); 224 | } 225 | 226 | assert.equal(list.isEmpty(), true, 'list should be empty'); 227 | assert.equal(list.length, 0, `length should be ${0}`); 228 | assert.equal(list.toString(), EMPTY_LIST, 'incorrect empty list as string'); 229 | assert.deepEqual(expectedData, data, 'incorrect retrieved data'); 230 | }); 231 | 232 | it('should sort the items in the list', function () { 233 | const data = [1, 2, 3, 4, 5, false, {}, [], 'hello']; 234 | 235 | data.forEach(i => list.insert(i)); 236 | 237 | data.sort(); 238 | list.sort(); 239 | 240 | assert.equal(list.toString(), data.join(','), 'list should be sorted'); 241 | }); 242 | 243 | it('should reverse the items in the list', function () { 244 | const data = [1, 2, 3, 4, 5, false, {}, [], 'hello']; 245 | 246 | data.forEach(i => list.insert(i)); 247 | 248 | data.reverse(); 249 | list.reverse(); 250 | 251 | assert.equal(list.toString(), data.join(','), 'list should be reversed'); 252 | }); 253 | 254 | it('should return the list as an array', function () { 255 | const data = [1, 2, 3, 4, 5, false, {}, [], 'hello']; 256 | 257 | data.forEach(i => list.insert(i)); 258 | 259 | const asArray = list.toArray(); 260 | 261 | assert.equal(list._currentIterator, 0, 'current iterator should be reset to 0'); 262 | assert.deepEqual(asArray, data, 'incorrect retrieved data as array'); 263 | assert.equal(asArray.join(','), data.join(','), 'arrays do not match'); 264 | }); 265 | }); 266 | -------------------------------------------------------------------------------- /04-data/data-structures/DoublyLinkedNode.js: -------------------------------------------------------------------------------- 1 | const defineProperty = Object.defineProperty; 2 | 3 | /** 4 | * Validates if a node is an instance of a DNode or null. 5 | * @private 6 | * @param {(DNode|null)} node The pointer to check. 7 | */ 8 | function isNodeOrNull(node) { 9 | if (node !== null && !(node instanceof DNode)) { 10 | throw new TypeError(`"node" should be an instance of DNode or null.`); 11 | } 12 | } 13 | 14 | /** 15 | * DoublyLinkedNode ADT. 16 | * Initializes a new instance of a DNode 17 | * @constructs DNode 18 | * @param {*} value Value to be stored in the DNode. 19 | * @param {number} [priority = 0] Priority for a DNode when used with PriorityQueues. 20 | */ 21 | function DNode(value, priority = 0) { 22 | if (!Number.isInteger(priority)) { 23 | throw new TypeError('"priority" should be an integer.'); 24 | } 25 | 26 | /** 27 | * Stored value in the DNode. 28 | * @name DNode#_value 29 | * @type {*} 30 | * @readonly 31 | */ 32 | this._value; 33 | 34 | defineProperty(this, '_value', { 35 | get() { return value; } 36 | }); 37 | 38 | /** 39 | * DNode's priority to be used with PriorityQueues 40 | * @name DNode#_priority 41 | * @type {number} 42 | * @readonly 43 | * @default 0 44 | */ 45 | this._priority; 46 | 47 | defineProperty(this, '_priority', { 48 | get() { return priority; } 49 | }); 50 | 51 | /** 52 | * Pointer to the previous DNode. 53 | * @name DNode#_previous 54 | * @type {(DNode|null)} 55 | * @default null 56 | */ 57 | this._previous; 58 | 59 | defineProperty(this, '_previous', { 60 | writable: true, 61 | value: null 62 | }); 63 | 64 | /** 65 | * Pointer to the next DNode. 66 | * @name DNode#_next 67 | * @type {(DNode|null)} 68 | * @default null 69 | */ 70 | this._next; 71 | 72 | defineProperty(this, '_next', { 73 | writable: true, 74 | value: null 75 | }); 76 | 77 | return this; 78 | } 79 | 80 | /** 81 | * Tells if the current DNode has a next node. 82 | * @function DNode#hasNext 83 | * @returns {boolean} Whether it has a next node or not. 84 | */ 85 | DNode.prototype.hasNext = function () { 86 | return this._next !== null; 87 | }; 88 | 89 | /** 90 | * Tells if the current DNode has a previous node. 91 | * @function DNode#hasPrevious 92 | * @returns {boolean} Whether it has a previous node or not. 93 | */ 94 | DNode.prototype.hasPrevious = function () { 95 | return this._previous !== null; 96 | }; 97 | 98 | /** 99 | * Updates the pointer to the next node. 100 | * @function DNode#setNext 101 | * @param {(Node|null)} newNext New next pointer. 102 | * @returns {DNode} instance 103 | */ 104 | DNode.prototype.setNext = function (newNext) { 105 | isNodeOrNull(newNext); 106 | 107 | this._next = newNext; 108 | 109 | return this; 110 | }; 111 | 112 | /** 113 | * Updates the pointer to the previous node. 114 | * @function DNode#setPrevious 115 | * @param {(Node|null)} newPrevious New previous pointer. 116 | * @returns {DNode} instance 117 | */ 118 | DNode.prototype.setPrevious = function (newPrevious) { 119 | isNodeOrNull(newPrevious); 120 | 121 | this._previous = newPrevious; 122 | 123 | return this; 124 | }; 125 | 126 | /** 127 | * Returns the pointer to the next node. 128 | * @function DNode#getNext 129 | * @returns {(DNode|null)} pointer to next node. 130 | */ 131 | DNode.prototype.getNext = function () { 132 | return this._next; 133 | }; 134 | 135 | /** 136 | * Returns the pointer to the previous node. 137 | * @function DNode#getPrevious 138 | * @returns {(DNode|null)} pointer to the previous node. 139 | */ 140 | DNode.prototype.getPrevious = function () { 141 | return this._previous; 142 | }; 143 | 144 | /** 145 | * Returns the DNode's value as string. 146 | * @function DNode#toString 147 | * @returns {string} DNode as string 148 | */ 149 | DNode.prototype.toString = function () { 150 | return String(this._value); 151 | }; 152 | 153 | module.exports = DNode; -------------------------------------------------------------------------------- /04-data/data-structures/DoublyLinkedNode.spec.js: -------------------------------------------------------------------------------- 1 | const assert = require('assert'); 2 | 3 | const DNode = require('./DoublyLinkedNode'); 4 | 5 | describe('Data Structure - Doubly Linked Node', function () { 6 | const intialValue = 'Initial Value'; 7 | /** 8 | * @type {DNode} 9 | * @private 10 | */ 11 | let node; 12 | 13 | const PRIORITY_ERROR = /^TypeError: "priority" should be an integer\.$/; 14 | const NODE_ERROR = /^TypeError: "node" should be an instance of DNode or null\.$/; 15 | 16 | beforeEach(function () { 17 | node = new DNode(intialValue); 18 | }); 19 | 20 | it('should be initialized correctly', function () { 21 | assert.ok(node, 'node is not initialized'); 22 | assert.equal(node._value, intialValue, 'invalid _value'); 23 | assert.equal(node._priority, 0, 'invalid _priority'); 24 | assert.equal(node._next, null, 'invalid _next pointer'); 25 | assert.equal(node._previous, null, 'invalid _previous pointer'); 26 | }); 27 | 28 | it('should not allow to change the readonly value', function () { 29 | const newValue = 'some other value'; 30 | 31 | node._value = newValue; 32 | assert.equal(node._value, intialValue, '_value has changed'); 33 | }); 34 | 35 | it('should throw an exception when initializing with an invalid priority', function () { 36 | const invalidPriority = '1'; 37 | assert.throws(() => { new DNode(intialValue, invalidPriority) }, PRIORITY_ERROR, 'DNode constructor is not throwing an TypeError'); 38 | }); 39 | 40 | it('should update the next node correctly to null', function () { 41 | const newNext = null; 42 | node.setNext(newNext); 43 | assert.equal(node.getNext(), newNext, 'invalid newNext'); 44 | }); 45 | 46 | it('should update the previous node correctly to null', function () { 47 | const newPrevious = null; 48 | node.setPrevious(newPrevious); 49 | assert.equal(node.getPrevious(), newPrevious, 'invalid newPrevious'); 50 | }); 51 | 52 | it('should update the next node correctly to another node', function () { 53 | const newNext = new DNode(true); 54 | 55 | node.setNext(newNext); 56 | assert.equal(node.getNext(), newNext, 'invalid newNext'); 57 | }); 58 | 59 | it('should update the previous node correctly to another node', function () { 60 | const newPrevious = new DNode(true); 61 | 62 | node.setPrevious(newPrevious); 63 | assert.equal(node.getPrevious(), newPrevious, 'invalid newPrevious'); 64 | }); 65 | 66 | it('should throw an exception when new next is not an instance of DNode or null', function () { 67 | assert.throws(() => { node.setNext(undefined) }, NODE_ERROR, 'setNext is not throwing a TypeError'); 68 | }); 69 | 70 | it('should throw an exception when new previous is not an instance of DNode or null', function () { 71 | assert.throws(() => { node.setPrevious(undefined) }, NODE_ERROR, 'setPrevious is not throwing a TypeError'); 72 | }); 73 | 74 | it('should return false when there is no next node', function () { 75 | assert.equal(node.hasNext(), false, 'invalid hasNext'); 76 | }); 77 | 78 | it('should return false when there is no previous node', function () { 79 | assert.equal(node.hasPrevious(), false, 'invalid hasPrevious'); 80 | }); 81 | 82 | it('should return true when there is a next node', function () { 83 | node.setNext(new DNode('new value')); 84 | assert.equal(node.hasNext(), true, 'invalid hasNext'); 85 | }); 86 | 87 | it('should return true when there is a previous node', function () { 88 | node.setPrevious(new DNode('new value')); 89 | assert.equal(node.hasPrevious(), true, 'invalid hasPrevious'); 90 | }); 91 | 92 | it('should return the node as string', function () { 93 | assert.equal(node.toString(), String(intialValue), 'invalid node as string'); 94 | }); 95 | }); 96 | -------------------------------------------------------------------------------- /04-data/data-structures/List.js: -------------------------------------------------------------------------------- 1 | const Node = require('./Node'); 2 | const Stack = require('./Stack'); 3 | 4 | const defineProperty = Object.defineProperty; 5 | 6 | /** 7 | * Checks if a parameter is `undefined` 8 | * @private 9 | * @param {*} obj Object to be tested 10 | * @returns {boolean} 11 | */ 12 | function isUndefined(obj) { 13 | return typeof obj === 'undefined'; 14 | } 15 | 16 | /** 17 | * Checks if a parameter is a positive number 18 | * @private 19 | * @param {*} n Object to be tested 20 | * @returns {boolean} 21 | */ 22 | function isPositiveNumber(n) { 23 | return Number.isInteger(n) && n >= 0; 24 | } 25 | 26 | /** 27 | * Throws an exception if a parameter is not a positive number 28 | * @private 29 | * @param {*} n Object to be tested 30 | * @param {string} parameterName Parameter name to show in the error message. 31 | * @throws {TypeError} parameter should be a positive number. 32 | */ 33 | function validatePositiveNumber(n, parameterName) { 34 | if (!isPositiveNumber(n)) { 35 | throw new TypeError(`"${parameterName}" should be an Integer greater than or equal to 0. Given: ${n}`); 36 | } 37 | } 38 | 39 | /** 40 | * Linked List ADT. 41 | * Initializes a new instance of a List. 42 | * @constructor List 43 | */ 44 | function List() { 45 | /** 46 | * Pointer to the first Node in the List. 47 | * @name List#first 48 | * @type {(Node|null)} 49 | * @default null 50 | */ 51 | this.first; 52 | 53 | defineProperty(this, 'first', { 54 | writable: true, 55 | value: null 56 | }); 57 | 58 | /** 59 | * Length of the List. 60 | * @name List#length 61 | * @type {Number} 62 | * @readonly 63 | */ 64 | this.length; 65 | 66 | defineProperty(this, 'length', { 67 | get() { 68 | let currentNode = this.first; 69 | let length = 0; 70 | 71 | while (currentNode) { 72 | currentNode = currentNode.getNext(); 73 | length++; 74 | } 75 | 76 | return length; 77 | } 78 | }); 79 | 80 | return this; 81 | } 82 | 83 | /** 84 | * Checks if the list is empty. 85 | * @function List#isEmpty 86 | * @returns {boolean} Whether the list is empty or not. 87 | */ 88 | List.prototype.isEmpty = function () { 89 | return this.first === null; 90 | }; 91 | 92 | /** 93 | * Returns the Node at the `n`th position in the list. 94 | * @function List#getNode 95 | * @param {Number=} [position = last] `n`th position 96 | * @returns {Node} The Node at the 'nth' position in the list. 97 | * @throws {Error} List is empty. 98 | * @throws {TypeError} position should be a number. 99 | */ 100 | List.prototype.getNode = function (position) { 101 | if (this.isEmpty()) { 102 | throw new Error('List is empty.'); 103 | } else { 104 | if (isUndefined(position)) { 105 | position = this.length - 1; 106 | } else { 107 | validatePositiveNumber(position, 'position'); 108 | } 109 | 110 | let currentNode = this.first; 111 | let currentPosition = 0; 112 | 113 | while (currentNode.hasNext()) { 114 | if (currentPosition === position) break; 115 | currentPosition++; 116 | currentNode = currentNode.getNext(); 117 | } 118 | 119 | return currentNode; 120 | } 121 | }; 122 | 123 | /** 124 | * Inserts an item at the 'nth' position in the list. 125 | * @function List#insert 126 | * @param {*} value Value to be inserted. 127 | * @param {Number=} [position = last] 'nth' position in the list for the new value. 128 | * @returns {List} instance 129 | * @throws {TypeError} position should be a number. 130 | */ 131 | List.prototype.insert = function (value, position) { 132 | if (isUndefined(position)) { 133 | position = this.length; /* position to insert */ 134 | } else { 135 | validatePositiveNumber(position, 'position'); 136 | } 137 | 138 | const newNode = new Node(value); 139 | 140 | if (this.isEmpty()) { 141 | this.first = newNode; 142 | } else { 143 | if (position === 0) { 144 | newNode.setNext(this.first) 145 | this.first = newNode; 146 | } else { 147 | let prevNode = this.getNode(position - 1); 148 | 149 | newNode.setNext(prevNode.getNext()) 150 | prevNode.setNext(newNode); 151 | } 152 | } 153 | 154 | return this; 155 | }; 156 | 157 | /** 158 | * Returns the value of an item and removes it from the list at the 'nth' position. 159 | * @function List#remove 160 | * @param {Number=} [position = last] `n`th item's position to remove 161 | * @returns {*} Value of the Node. 162 | * @throws {TypeError} position should be a number. 163 | */ 164 | List.prototype.remove = function (position) { 165 | let nodeToRemove = this.getNode(position); 166 | const length = this.length; 167 | 168 | if (position === 0 || length === 1) { 169 | this.first = this.first.getNext(); 170 | } else { 171 | if (isUndefined(position)) { 172 | position = length - 1; 173 | } 174 | 175 | let prevNode = this.getNode(position - 1); 176 | 177 | prevNode.setNext(nodeToRemove.getNext()); 178 | } 179 | 180 | return nodeToRemove.value; 181 | }; 182 | 183 | /** 184 | * Returns the value of a Node at the 'nth' position in the list. 185 | * @function List#get 186 | * @param {Number=} [position = last] 'nth' position 187 | * @returns {*} Value of the Node 188 | */ 189 | List.prototype.get = function (position) { 190 | return this.getNode(position).value; 191 | }; 192 | 193 | /** 194 | * My lazy and hackish implementation of sort 195 | * Passes all elements to an array, sorts them and then inserts them again. 196 | * @function List#sort 197 | * @returns {List} instance 198 | */ 199 | List.prototype.sort = function () { 200 | const data = []; 201 | 202 | while (!this.isEmpty()) { 203 | data.push(this.remove()); 204 | } 205 | 206 | data.sort(); 207 | 208 | data.forEach(i => this.insert(i)); 209 | 210 | return this; 211 | }; 212 | 213 | /** 214 | * Returns a subset of a List given a starting and ending position 215 | * @function List#slice 216 | * @param {number} start Starting position 217 | * @param {number} end End position (inclusive) 218 | * @returns {List} subset of the List 219 | */ 220 | List.prototype.slice = function (start, end) { 221 | if (isUndefined(start)) { 222 | start = 0; 223 | } else { 224 | validatePositiveNumber(start, 'start'); 225 | } 226 | 227 | if (isUndefined(end)) { 228 | end = this.length === 0 ? 0 : this.length - 1; 229 | } else { 230 | validatePositiveNumber(end, 'end'); 231 | } 232 | 233 | if (start > end) { 234 | throw new Error(`start: ${start} should not be greater than end: ${end}`); 235 | } 236 | 237 | const subset = new List(); 238 | 239 | if (!this.isEmpty()) { 240 | while (start <= end) { 241 | subset.insert(this.get(start++)); 242 | } 243 | } 244 | 245 | return subset; 246 | }; 247 | 248 | /** 249 | * Reverses the items in the list 250 | * @function List#reverse 251 | * @returns {List} instance 252 | */ 253 | List.prototype.reverse = function () { 254 | const stack = new Stack(); 255 | 256 | while (!this.isEmpty()) { 257 | stack.push(this.remove(0)); 258 | } 259 | 260 | while (!stack.isEmpty()) { 261 | this.insert(stack.pop()); 262 | } 263 | 264 | return this; 265 | }; 266 | 267 | /** 268 | * Returns the list as string 269 | * @function List#toString 270 | * @returns {string} list as string. 271 | */ 272 | List.prototype.toString = function () { 273 | if (this.isEmpty()) { 274 | return '[Empty List]'; 275 | } else { 276 | let listAsString = ''; 277 | let currentNode = this.first; 278 | 279 | while (currentNode) { 280 | listAsString += String(currentNode); 281 | if (currentNode.hasNext()) { 282 | listAsString += ',' 283 | } 284 | currentNode = currentNode.getNext(); 285 | } 286 | 287 | return listAsString; 288 | } 289 | } 290 | 291 | module.exports = List; 292 | -------------------------------------------------------------------------------- /04-data/data-structures/List.spec.js: -------------------------------------------------------------------------------- 1 | const assert = require('assert'); 2 | 3 | const List = require('./List'); 4 | 5 | describe('Data Structure - List aka Linked List', function () { 6 | /** 7 | * @type {List} 8 | * @private 9 | */ 10 | let list; 11 | 12 | const EMPTY_LIST = '[Empty List]'; 13 | const EMPTY_LIST_ERROR = /^Error: List is empty\.$/; 14 | const POSITIVE_INTEGER_ERROR = /^TypeError: ".+" should be an Integer greater than or equal to 0\. Given: .+$/; 15 | const SLICE_ERROR = /^Error: start: .+ should not be greater than end: .+$/ 16 | 17 | beforeEach(function () { 18 | list = new List(); 19 | }); 20 | 21 | it('should be initialized correctly', function () { 22 | assert.ok(list, 'list is not initialized'); 23 | }); 24 | 25 | it('should not have enumerable properties', function () { 26 | const properties = Object.keys(list); 27 | const expectedProperties = []; 28 | 29 | assert.deepEqual(properties, expectedProperties, 'properties do not match'); 30 | }); 31 | 32 | it('should start empty', function () { 33 | assert.equal(list.isEmpty(), true, 'list did not start empty'); 34 | assert.equal(list.length, 0, 'length should be 0'); 35 | }); 36 | 37 | it('should insert the first item correctly', function () { 38 | const val = 1; 39 | list.insert(val); 40 | assert.equal(list.isEmpty(), false, 'list should not be empty'); 41 | assert.equal(list.get(), val, 'invalid first item'); 42 | assert.equal(list.get(0), val, 'invalid first item'); 43 | assert.equal(list.length, 1, 'length should be 1'); 44 | }); 45 | 46 | it('should throw an error when trying to get on the empty list', function () { 47 | assert.throws(() => list.get(), EMPTY_LIST_ERROR, 'list is not throwing empty error'); 48 | }); 49 | 50 | it('should throw an error when trying to get on an invalid position', function () { 51 | list.insert(true); 52 | assert.throws(() => list.get(-1), POSITIVE_INTEGER_ERROR, 'list is not throwing empty error'); 53 | assert.throws(() => list.get(null), POSITIVE_INTEGER_ERROR, 'list is not throwing empty error'); 54 | }); 55 | 56 | it('should throw an error when trying to insert on an invalid position', function () { 57 | assert.throws(() => list.insert(2, -1), POSITIVE_INTEGER_ERROR, 'list is not throwing empty error for: -1'); 58 | assert.throws(() => list.insert(3, false), POSITIVE_INTEGER_ERROR, 'list is not throwing empty error for: false'); 59 | }); 60 | 61 | it('should insert multiple items correctly', function () { 62 | const data = [1, 2, 3, 4, 5, false, {}, [], 'hello']; 63 | 64 | data.forEach((d, k) => list.insert(d, k)); 65 | 66 | const retrievedData = []; 67 | let currentPosition = 0; 68 | 69 | while (currentPosition < data.length) { 70 | retrievedData.push(list.get(currentPosition++)); 71 | } 72 | 73 | assert.equal(list.length, data.length, `length should be ${data.length}`); 74 | assert.deepEqual(retrievedData, data, 'incorrect retrieved data'); 75 | }); 76 | 77 | it('should return the list as string', function () { 78 | const data = [1, 2, 3, 4, 5, false, {}, [], 'hello']; 79 | 80 | assert.equal(list.toString(), EMPTY_LIST, 'incorrect empty list as string'); 81 | 82 | data.forEach((d, k) => list.insert(d, k)); 83 | 84 | assert.equal(list.length, data.length, `length should be ${data.length}`); 85 | assert.equal(list.toString(), data.join(','), 'incorrect list as string'); 86 | }); 87 | 88 | it('should insert items before the first item correctly', function () { 89 | const val1 = 1; 90 | 91 | list.insert(val1); 92 | assert.equal(list.get(0), val1, `invalid val1: ${list.get(0)}`); 93 | 94 | const val2 = 2; 95 | 96 | list.insert(val2, 0); 97 | assert.equal(list.get(0), val2, `invalid val2: ${list.get(0)}`); 98 | 99 | const val3 = 3; 100 | 101 | list.insert(val3, 0); 102 | assert.equal(list.get(0), val3, `invalid val3: ${list.get(0)}`); 103 | 104 | assert.equal(list.length, 3, `length should be ${3}`); 105 | assert.equal(list.toString(), [val3, val2, val1], `incorrect order: ${list.toString()}`); 106 | }); 107 | 108 | it('should insert items at the end correctly', function () { 109 | const val1 = 1; 110 | 111 | list.insert(val1); 112 | assert.equal(list.get(), val1, `invalid val1: ${list.get(0)}`); 113 | 114 | const val2 = 2; 115 | 116 | list.insert(val2); 117 | assert.equal(list.get(1), val2, `invalid val2: ${list.get(1)}`); 118 | 119 | const val3 = 3; 120 | 121 | list.insert(val3); 122 | assert.equal(list.get(2), val3, `invalid val3: ${list.get(2)}`); 123 | 124 | assert.equal(list.length, 3, `length should be ${3}`); 125 | assert.equal(list.toString(), [val1, val2, val3], `incorrect order: ${list.toString()}`); 126 | }); 127 | 128 | it('should throw an exception if trying to remove on a empty list', function () { 129 | assert.throws(() => list.remove(5), EMPTY_LIST_ERROR, 'remove is not throwing an error'); 130 | }); 131 | 132 | it('should remove the last item if the position is greater than the total length', function () { 133 | list.insert(true); 134 | assert.equal(list.length, 1, `length should be ${1}`); 135 | assert.equal(list.remove(5), true, 'incorrect removed value'); 136 | assert.equal(list.length, 0, `length should be ${0}`); 137 | }); 138 | 139 | it('should remove the first item correctly', function () { 140 | const value = 1; 141 | list.insert(value); 142 | assert.equal(list.length, 1, `length should be ${1}`); 143 | assert.equal(list.remove(0), value, 'incorrect removed value'); 144 | assert.equal(list.length, 0, `length should be ${0}`); 145 | assert.equal(list.isEmpty(), true, 'list should be empty'); 146 | }); 147 | 148 | it('should remove the middle item correctly', function () { 149 | const val1 = 1; 150 | const val2 = 2; 151 | const val3 = 3; 152 | 153 | list.insert(val1).insert(val2).insert(val3); 154 | assert.equal(list.length, 3, `length should be ${3}`); 155 | assert.equal(list.remove(1), val2, 'incorrect removed value'); 156 | assert.equal(list.isEmpty(), false, 'list should not be empty'); 157 | assert.equal(list.length, 2, `length should be ${2}`); 158 | assert.equal(list.toString(), [val1, val3], 'incorrect list as string'); 159 | }); 160 | 161 | it('should remove the last item correctly', function () { 162 | const val1 = 1; 163 | const val2 = 2; 164 | const val3 = 3; 165 | 166 | list.insert(val1).insert(val2).insert(val3); 167 | assert.equal(list.length, 3, `length should be ${3}`); 168 | 169 | assert.equal(list.remove(2), val3, 'incorrect removed value'); 170 | assert.equal(list.isEmpty(), false, 'list should not be empty'); 171 | assert.equal(list.toString(), [val1, val2], 'incorrect list as string'); 172 | 173 | assert.equal(list.remove(1), val2, 'incorrect removed value2'); 174 | assert.equal(list.isEmpty(), false, 'list should not be empty'); 175 | assert.equal(list.toString(), [val1], 'incorrect list as string'); 176 | 177 | assert.equal(list.remove(0), val1, 'incorrect removed value1'); 178 | assert.equal(list.isEmpty(), true, 'list should be empty'); 179 | assert.equal(list.toString(), EMPTY_LIST, 'incorrect list as string'); 180 | 181 | assert.equal(list.length, 0, `length should be ${0}`); 182 | }); 183 | 184 | it('should remove the last item correctly if not position provided', function () { 185 | const val1 = 1; 186 | const val2 = 2; 187 | const val3 = 3; 188 | 189 | list.insert(val1).insert(val2).insert(val3); 190 | assert.equal(list.length, 3, `length should be ${3}`); 191 | 192 | assert.equal(list.remove(), val3, 'incorrect removed value3'); 193 | assert.equal(list.isEmpty(), false, 'list should not be empty'); 194 | assert.equal(list.toString(), [val1, val2], 'incorrect list as string'); 195 | 196 | assert.equal(list.remove(), val2, 'incorrect removed value2'); 197 | assert.equal(list.isEmpty(), false, 'list should not be empty'); 198 | assert.equal(list.toString(), [val1], 'incorrect list as string'); 199 | 200 | assert.equal(list.remove(), val1, 'incorrect removed value1'); 201 | assert.equal(list.isEmpty(), true, 'list should be empty'); 202 | assert.equal(list.toString(), EMPTY_LIST, 'incorrect list as string'); 203 | 204 | assert.equal(list.length, 0, `length should be ${0}`); 205 | }); 206 | 207 | it('should insert and remove multiple items', function () { 208 | const data = [1, 2, 3, 4, 5, false, {}, [], 'hello']; 209 | 210 | data.forEach(i => list.insert(i)); 211 | 212 | assert.equal(list.length, data.length, `length should be ${data.length}`); 213 | assert.equal(list.toString(), data.join(','), 'incorrect list as string'); 214 | 215 | const expectedData = []; 216 | 217 | while (!list.isEmpty()) { 218 | expectedData.unshift(list.remove()); 219 | } 220 | 221 | assert.equal(list.isEmpty(), true, 'list should be empty'); 222 | assert.equal(list.length, 0, `length should be ${0}`); 223 | assert.equal(list.toString(), EMPTY_LIST, 'incorrect empty list as string'); 224 | assert.deepEqual(expectedData, data, 'incorrect retrieved data'); 225 | }); 226 | 227 | it('should sort the items in the list', function () { 228 | const data = [1, 2, 3, 4, 5, false, {}, [], 'hello']; 229 | 230 | data.forEach(i => list.insert(i)); 231 | 232 | data.sort(); 233 | list.sort(); 234 | 235 | assert.equal(list.toString(), data.join(','), 'list should be sorted'); 236 | }); 237 | 238 | it('should reverse the items in the list', function () { 239 | const data = [1, 2, 3, 4, 5, false, {}, [], 'hello']; 240 | 241 | data.forEach(i => list.insert(i)); 242 | 243 | data.reverse(); 244 | list.reverse(); 245 | 246 | assert.equal(list.toString(), data.join(','), 'list should be reversed'); 247 | }); 248 | 249 | it('should throw an exception if start or end are invalid', function () { 250 | assert.throws(() => list.slice(-1), POSITIVE_INTEGER_ERROR, 'start parameter is not throwing an exception'); 251 | assert.throws(() => list.slice(null), POSITIVE_INTEGER_ERROR, 'start parameter is not throwing an exception'); 252 | assert.throws(() => list.slice(1, -1), POSITIVE_INTEGER_ERROR, 'end parameter is not throwing an exception'); 253 | assert.throws(() => list.slice(1, null), POSITIVE_INTEGER_ERROR, 'end parameter is not throwing an exception'); 254 | assert.throws(() => list.slice(1, 0), SLICE_ERROR, 'start > end is not throwing an exception'); 255 | }); 256 | 257 | it('should slice an empty list as a empty instance of List', function () { 258 | let result = list.slice(0, 0); 259 | assert.equal(result instanceof List, true, 'subset should be an instance of a list'); 260 | assert.equal(result.length, 0, 'subset should be have length 0'); 261 | assert.equal(result.isEmpty(), true, 'subset should be empty'); 262 | assert.equal(result.toString(), EMPTY_LIST, 'incorrect empty list as string'); 263 | 264 | result = list.slice(); 265 | assert.equal(result instanceof List, true, 'subset should be an instance of a list'); 266 | assert.equal(result.length, 0, 'subset should be have length 0'); 267 | assert.equal(result.isEmpty(), true, 'subset should be empty'); 268 | assert.equal(result.toString(), EMPTY_LIST, 'incorrect empty list as string'); 269 | }); 270 | 271 | it('should slice the first element', function () { 272 | const data = [1, 2, 3, 4, 5, false, {}, [], 'hello']; 273 | 274 | data.forEach(i => list.insert(i)); 275 | 276 | let result = list.slice(0, 0); 277 | 278 | assert.equal(result.isEmpty(), false, 'subset should not be empty'); 279 | assert.equal(result.length, 1, 'subset should be have length 1'); 280 | assert.equal(result.toString(), data[0], 'incorrect subset as string'); 281 | }); 282 | 283 | it('should slice the last element', function () { 284 | const data = [1, 2, 3, 4, 5, false, {}, [], 'hello']; 285 | 286 | data.forEach(i => list.insert(i)); 287 | 288 | let result = list.slice(list.length - 1); // index 8 289 | 290 | assert.equal(result.isEmpty(), false, 'subset should not be empty'); 291 | assert.equal(result.length, 1, 'subset should be have length 1'); 292 | assert.equal(result.toString(), data[data.length - 1], 'incorrect subset as string'); 293 | }); 294 | 295 | it('should slice middle elements except first and last', function () { 296 | const data = [1, 2, 3, 4, 5, false, {}, [], 'hello']; 297 | 298 | data.forEach(i => list.insert(i)); 299 | 300 | let result = list.slice(1, list.length - 2); // inclusive 301 | let expectedResult = data.slice(1, data.length - 1); // exclusive 302 | 303 | assert.equal(result.isEmpty(), false, 'subset should not be empty'); 304 | assert.equal(result.length, expectedResult.length, `subset should be have length ${expectedResult.length}`); 305 | assert.equal(result.toString(), expectedResult.join(','), 'incorrect subset as string'); 306 | }); 307 | 308 | it('should slice all elements of a list', function () { 309 | const data = [1, 2, 3, 4, 5, false, {}, [], 'hello']; 310 | 311 | data.forEach(i => list.insert(i)); 312 | 313 | let result = list.slice(); 314 | 315 | assert.equal(result.length, data.length, `subset should be have length ${data.length}`); 316 | assert.equal(result.toString(), data.join(','), 'incorrect subset as string'); 317 | }); 318 | }); 319 | -------------------------------------------------------------------------------- /04-data/data-structures/Map.js: -------------------------------------------------------------------------------- 1 | const defineProperty = Object.defineProperty; 2 | 3 | /** 4 | * Initializes an instance of a Map 5 | * @constructs Map 6 | */ 7 | function Map() { 8 | /** 9 | * Object to store the values of the Map 10 | * @name Map#map 11 | * @type {Object} 12 | */ 13 | this.map; 14 | 15 | defineProperty(this, 'map', { 16 | value: Object.create(null) /* default */ 17 | }); 18 | 19 | /** 20 | * Size of the map 21 | * @name Map#size 22 | * @type {number} 23 | */ 24 | this.size; 25 | 26 | defineProperty(this, 'size', { 27 | get() { return Object.keys(this.map).length; } 28 | }); 29 | 30 | 31 | return this; 32 | } 33 | 34 | /** 35 | * Sets a pair of key, value in the Map 36 | * @function Map#set 37 | * @param {*} key 38 | * @param {*} value 39 | */ 40 | Map.prototype.set = function (key, value) { 41 | this.map[String(key)] = value; 42 | 43 | return this; 44 | }; 45 | 46 | /** 47 | * Tells if the Map has a key 48 | * @function Map#has 49 | * @param {*} key Key to look for 50 | */ 51 | Map.prototype.has = function (key) { 52 | return !!this.map[String(key)]; 53 | }; 54 | 55 | /** 56 | * Deletes a key from the Map 57 | * @function Map#delete 58 | * @param {*} key Key to be deleted 59 | */ 60 | Map.prototype.delete = function (key) { 61 | const result = this.has(key); 62 | 63 | delete this.map[String(key)]; 64 | 65 | return result; 66 | }; 67 | 68 | /** 69 | * Returns the value of a key in the Map 70 | * Throws an exception if the key doesn't exist 71 | * @function Map#get 72 | * @param {*} key Key to be retrieved. 73 | */ 74 | Map.prototype.get = function (key) { 75 | return this.map[String(key)]; 76 | }; 77 | 78 | module.exports = Map; 79 | -------------------------------------------------------------------------------- /04-data/data-structures/Map.spec.js: -------------------------------------------------------------------------------- 1 | const assert = require('assert'); 2 | 3 | const Map = require('./Map'); 4 | 5 | describe('Data Structure - Map', function () { 6 | /** 7 | * @type {Map} 8 | * @private 9 | */ 10 | let map; 11 | 12 | beforeEach(function () { 13 | map = new Map(); 14 | }); 15 | 16 | it('should be initialized correctly', function () { 17 | assert.ok(map, 'map is not initialized'); 18 | }); 19 | 20 | it('should not have enumerable properties', function () { 21 | const properties = Object.keys(map); 22 | const expectedProperties = []; 23 | 24 | assert.deepEqual(properties, expectedProperties, 'properties do not match'); 25 | }); 26 | 27 | it('should start empty', function () { 28 | assert.equal(map.size, 0, 'map should be empty'); 29 | }); 30 | 31 | it('should set a key correctly', function () { 32 | map.set(1, 1); 33 | assert.equal(map.size, 1, `map should have size ${1}`); 34 | }); 35 | 36 | it('should set and has a key correctly', function () { 37 | const key = 'a'; 38 | const value = 1; 39 | map.set(key, value); 40 | assert.equal(map.has(key), true, `map should has value ${value}`); 41 | }); 42 | 43 | it('should set and get a key correctly', function () { 44 | const key = 'a'; 45 | const value = 1; 46 | map.set(key, value); 47 | assert.equal(map.get(key), value, `map should return value ${value}`); 48 | }); 49 | 50 | it('should replace a set key correctly', function () { 51 | const key = 'a'; 52 | const value = 1; 53 | const value2 = 2; 54 | map.set(key, value); 55 | map.set(key, value2); 56 | assert.equal(map.get(key), value2, `map should return value ${value2}`); 57 | assert.equal(map.size, 1, `map should have size ${1}`); 58 | }); 59 | 60 | it('should set and delete a key correctly', function () { 61 | const key = 'a'; 62 | const value = 1; 63 | map.set(key, value); 64 | assert.equal(map.delete(key), true, `map should delete key ${key}`); 65 | assert.equal(map.size, 0, `map should have size ${0}`); 66 | }); 67 | 68 | it('should return false when calling has with a key that is not in the map', function () { 69 | const key = 'a'; 70 | const invalidKey = 'b'; 71 | const value = 1; 72 | map.set(key, value); 73 | assert.equal(map.has(invalidKey), false, `map has should return false`); 74 | }); 75 | 76 | it('should return false when deleting a key that is not in the map', function () { 77 | const key = 'a'; 78 | const invalidKey = 'b'; 79 | const value = 1; 80 | map.set(key, value); 81 | assert.equal(map.delete(invalidKey), false, `map delete should return false`); 82 | }); 83 | }); 84 | -------------------------------------------------------------------------------- /04-data/data-structures/Node.js: -------------------------------------------------------------------------------- 1 | const defineProperty = Object.defineProperty; 2 | 3 | /** 4 | * Validates if a node is an instance of a Node or null. 5 | * @private 6 | * @param {(Node|null)} node The next pointer value. 7 | */ 8 | function isNodeOrNull(node) { 9 | if (node !== null && !(node instanceof Node)) { 10 | throw new Error(`"next" should be an instance of Node or null.`); 11 | } 12 | } 13 | 14 | /** 15 | * Node Abstract Data Type 16 | * Initializes a new instance of a Node 17 | * @constructs Node 18 | * @param {*} value The holding value 19 | */ 20 | function Node(value, priority = 0) { 21 | if (!Number.isInteger(priority)) { 22 | throw new Error('"priority" should be an integer.'); 23 | } 24 | 25 | /** 26 | * Pointer to the next Node 27 | * @type {(Node|null)} 28 | * @private 29 | * @default null 30 | */ 31 | let _next = null; 32 | 33 | /** 34 | * Holding value 35 | * @name Node#_value 36 | * @type {*} 37 | * @readonly 38 | */ 39 | this._value; 40 | 41 | defineProperty(this, '_value', { 42 | get() { return value; } 43 | }); 44 | 45 | /** 46 | * Holding value 47 | * @name Node#value 48 | * @type {*} 49 | * @readonly 50 | */ 51 | this.value; 52 | 53 | defineProperty(this, 'value', { 54 | get() { return value; } 55 | }); 56 | 57 | /** 58 | * Pointer to the next Node 59 | * @name Node#_next 60 | * @type {(Node|null)} 61 | * @readonly 62 | * @default null 63 | */ 64 | this._next; 65 | 66 | defineProperty(this, '_next', { 67 | get() { return _next; } 68 | }); 69 | 70 | /** 71 | * Pointer to the next Node 72 | * @name Node#next 73 | * @type {(Node|null)} 74 | * @default null 75 | */ 76 | this.next; 77 | 78 | defineProperty(this, 'next', { 79 | set(newNext) { isNodeOrNull(newNext); _next = newNext; }, 80 | get() { return _next; } 81 | }); 82 | 83 | /** 84 | * Priority of the Node for PriorityQueues 85 | * @name Node#_priority 86 | * @type {int} 87 | * @readonly 88 | * @default 0 89 | */ 90 | this._priority; 91 | 92 | defineProperty(this, '_priority', { 93 | get() { return priority; } 94 | }); 95 | 96 | /** 97 | * Priority of the Node for PriorityQueues 98 | * @name Node#priority 99 | * @type {int} 100 | * @readonly 101 | * @default 0 102 | */ 103 | this.priority; 104 | 105 | defineProperty(this, 'priority', { 106 | get() { return priority; } 107 | }); 108 | 109 | return this; 110 | } 111 | 112 | /** 113 | * Tells if the current node has a next node 114 | * @function Node#hasNext 115 | */ 116 | Node.prototype.hasNext = function () { 117 | return this.next !== null; 118 | }; 119 | 120 | /** 121 | * Updates the pointer to the next node 122 | * @function Node#setNext 123 | * @param {(Node|null)} newNext 124 | */ 125 | Node.prototype.setNext = function (newNext) { 126 | this.next = newNext; 127 | 128 | return this; 129 | }; 130 | 131 | /** 132 | * Returns the next node. 133 | * @function Node#getNext 134 | */ 135 | Node.prototype.getNext = function () { 136 | return this.next; 137 | }; 138 | 139 | /** 140 | * Returns the value as string 141 | * @function Node#toString 142 | */ 143 | Node.prototype.toString = function () { 144 | return String(this._value); 145 | }; 146 | 147 | module.exports = Node; 148 | -------------------------------------------------------------------------------- /04-data/data-structures/Node.spec.js: -------------------------------------------------------------------------------- 1 | const assert = require('assert'); 2 | 3 | const Node = require('./Node'); 4 | 5 | describe('Data Structure - Node', function () { 6 | const intialValue = 'Initial Value'; 7 | /** 8 | * @type {Node} 9 | * @private 10 | */ 11 | let node; 12 | 13 | beforeEach(function () { 14 | node = new Node(intialValue); 15 | }); 16 | 17 | it('should be initialized correctly', function () { 18 | assert.ok(node, 'node is not initialized'); 19 | assert.equal(node._value, intialValue, '_value is incorrect'); 20 | assert.equal(node.value, intialValue, 'value is incorrect'); 21 | assert.equal(node._next, null, 'invalid _next pointer'); 22 | assert.equal(node.next, null, 'invalid next pointer'); 23 | assert.equal(node._priority, 0, 'invalid _priority'); 24 | assert.equal(node.priority, 0, 'invalid priority'); 25 | }); 26 | 27 | it('should not allow to change the readonly value', function () { 28 | const newValue = 'some other value'; 29 | 30 | node._value = newValue; 31 | assert.equal(node._value, intialValue, '_value has changed'); 32 | node.value = newValue; 33 | assert.equal(node.value, intialValue, 'value has changed'); 34 | }); 35 | 36 | it('should throw an exception when initializing with an invalid priority', function () { 37 | const invalidPriority = '1'; 38 | assert.throws(() => { new Node(intialValue, invalidPriority) }, /^Error: "priority" should be an integer\.$/, 'Node constructor is not throwing an Error'); 39 | }); 40 | 41 | it('should update the next node correctly to null', function () { 42 | const newNext = null; 43 | 44 | node._next = newNext; 45 | assert.equal(node._next, node.getNext(), '_next should not have changed'); 46 | node.next = newNext; 47 | assert.equal(node.next, newNext, 'invalid next'); 48 | node.setNext(newNext); 49 | assert.equal(node.getNext(), newNext, 'invalid getNext'); 50 | }); 51 | 52 | it('should update the next node correctly to another node', function () { 53 | const newNext = new Node(true); 54 | 55 | node._next = newNext; 56 | assert.equal(node._next, node.getNext(), '_next should not have changed'); 57 | node.next = newNext; 58 | assert.equal(node.next, newNext, 'invalid next'); 59 | node.setNext(newNext); 60 | assert.equal(node.getNext(), newNext, 'invalid getNext'); 61 | }); 62 | 63 | it('should throw an exception when new next is not an instance of Node or null', function () { 64 | assert.throws(() => { node.next = undefined }, /^Error: "next" should be an instance of Node or null\.$/, 'next is not throwing an Error'); 65 | assert.throws(() => { node.setNext(undefined) }, /^Error: "next" should be an instance of Node or null\.$/, 'setNext is not throwing an Error'); 66 | }); 67 | 68 | it('should return false when there is no next node', function () { 69 | assert.equal(node.hasNext(), false, 'invalid hasNext'); 70 | }); 71 | 72 | it('should return true when there is a next node', function () { 73 | node.setNext(new Node('new value')); 74 | assert.equal(node.hasNext(), true, 'invalid hasNext'); 75 | }); 76 | 77 | it('should return the node as string', function () { 78 | assert.equal(node.toString(), String(intialValue), 'invalid node as string'); 79 | }); 80 | }); 81 | -------------------------------------------------------------------------------- /04-data/data-structures/PriorityQueue.js: -------------------------------------------------------------------------------- 1 | const Node = require('./Node'); 2 | 3 | const defineProperty = Object.defineProperty; 4 | 5 | /** 6 | * Initializes a new instance of a PriorityQueue 7 | * @constructs PriorityQueue 8 | */ 9 | function PriorityQueue() { 10 | /** 11 | * First Node in the PriorityQueue 12 | * @name PriorityQueue#first 13 | * @type {(Node|null)} 14 | */ 15 | this.first; 16 | 17 | defineProperty(this, 'first', { 18 | writable: true, 19 | value: null /* default */ 20 | }); 21 | 22 | return this; 23 | } 24 | 25 | /** 26 | * Checks if the priority queue is empty. 27 | * @function PriorityQueue#isEmpty 28 | */ 29 | PriorityQueue.prototype.isEmpty = function () { 30 | return this.first === null; 31 | }; 32 | 33 | /** 34 | * Adds a new item in order of its priority. The larger the number the higher the priority. 35 | * @function PriorityQueue#enqueue 36 | * @param {*} value Value to be enqueued 37 | * @param {Number=} [priority = 0] Priority of the value 38 | */ 39 | PriorityQueue.prototype.enqueue = function (value, priority = 0) { 40 | const newNode = new Node(value, priority); 41 | 42 | if (this.isEmpty()) { 43 | this.first = newNode; 44 | } else { 45 | if (newNode.priority > this.first.priority) { 46 | newNode.setNext(this.first); 47 | this.first = newNode; 48 | } else { 49 | let currentNode = this.first; 50 | 51 | while (currentNode.hasNext()) { 52 | if (newNode.priority > currentNode.getNext().priority) { 53 | newNode.setNext(currentNode.getNext()); 54 | break; 55 | } 56 | currentNode = currentNode.getNext(); 57 | } 58 | 59 | currentNode.setNext(newNode); 60 | } 61 | 62 | return this; 63 | } 64 | }; 65 | 66 | /** 67 | * Removes and returns the first item in the queue. 68 | * @function PriorityQueue#dequeue 69 | */ 70 | PriorityQueue.prototype.dequeue = function () { 71 | if (this.isEmpty()) { 72 | throw new Error('PriorityQueue is empty.'); 73 | } else { 74 | const currentFirst = this.first; 75 | this.first = currentFirst.getNext(); 76 | return currentFirst.value; 77 | } 78 | }; 79 | 80 | /** 81 | * Returns the queue as string 82 | * @function PriorityQueue#toString 83 | */ 84 | PriorityQueue.prototype.toString = function () { 85 | if (this.isEmpty()) { 86 | return '[Empty PriorityQueue]'; 87 | } else { 88 | let queueAsString = ''; 89 | let currentNode = this.first; 90 | 91 | while (currentNode) { 92 | queueAsString += String(currentNode); 93 | if (currentNode.hasNext()) { 94 | queueAsString += ','; 95 | } 96 | currentNode = currentNode.getNext(); 97 | } 98 | 99 | return queueAsString; 100 | } 101 | }; 102 | 103 | 104 | module.exports = PriorityQueue; 105 | -------------------------------------------------------------------------------- /04-data/data-structures/PriorityQueue.spec.js: -------------------------------------------------------------------------------- 1 | const assert = require('assert'); 2 | 3 | const PriorityQueue = require('./PriorityQueue'); 4 | 5 | function byPriority(a, b) { 6 | return b.priority - a.priority; 7 | } 8 | 9 | describe('Data Structure - PriorityQueue', function () { 10 | /** 11 | * @type {PriorityQueue} 12 | * @private 13 | */ 14 | let priorityQueue; 15 | 16 | beforeEach(function () { 17 | priorityQueue = new PriorityQueue(); 18 | }); 19 | 20 | it('should be initialized correctly', function () { 21 | assert.ok(priorityQueue, 'priorityQueue is not initialized'); 22 | }); 23 | 24 | it('should start empty', function () { 25 | assert.equal(priorityQueue.isEmpty(), true, 'priorityQueue did not start empty'); 26 | }); 27 | 28 | it('should throw an exception if trying to dequeue on an empty priorityQueue', function () { 29 | assert.throws(() => { priorityQueue.dequeue() }, /^Error: PriorityQueue is empty\.$/, 'priorityQueue is not throwing an Error'); 30 | }); 31 | 32 | it('should throw an exception if the first element is hardcoded to be other than null or a Node instance', function () { 33 | priorityQueue.first = { priority: 0 }; 34 | 35 | assert.throws(() => { priorityQueue.enqueue('some value', 1); }, /^Error: "next" should be an instance of Node or null\.$/, 'priorityQueue is not throwing an Error'); 36 | }); 37 | 38 | it('should throw an exception if the priority is not an integer value', function () { 39 | assert.throws(() => { priorityQueue.enqueue('some value', '1'); }, /^Error: "priority" should be an integer\.$/, 'priorityQueue is not throwing an Error'); 40 | }); 41 | 42 | it('should enqueue and dequeue the first item correctly', function () { 43 | const value = 1; 44 | 45 | priorityQueue.enqueue(value); 46 | 47 | assert.equal(priorityQueue.dequeue(), value, 'incorrect item 1'); 48 | assert.equal(priorityQueue.isEmpty(), true, 'priorityQueue should be empty'); 49 | }); 50 | 51 | it('should enqueue an item with higher priority before the first element', function () { 52 | const values = [{ val: 1, priority: 0 }, { val: 2, priority: 1 }]; 53 | 54 | values.forEach(({ val, priority }) => priorityQueue.enqueue(val, priority)); 55 | 56 | const retrievedData = []; 57 | 58 | while (!priorityQueue.isEmpty()) { 59 | retrievedData.push(priorityQueue.dequeue()); 60 | } 61 | 62 | const expectedData = values.sort(byPriority).map(({ val }) => val); 63 | 64 | assert.deepEqual(retrievedData, expectedData, 'incorrect retrieved data'); 65 | assert.equal(priorityQueue.isEmpty(), true, 'priorityQueue should be empty'); 66 | }); 67 | 68 | it('should enqueue and dequeue multiple items correctly', function () { 69 | const data = [1, 2, 3, 4, 5, false, {}, [], 'hello']; 70 | 71 | data.forEach(d => priorityQueue.enqueue(d)); 72 | 73 | const retrievedData = []; 74 | 75 | while (!priorityQueue.isEmpty()) { 76 | retrievedData.push(priorityQueue.dequeue()); 77 | } 78 | 79 | assert.deepEqual(retrievedData, data, 'incorrect retrieved data'); 80 | assert.equal(priorityQueue.isEmpty(), true, 'priorityQueue should be empty'); 81 | }); 82 | 83 | it('should enqueue and dequeue multiple items with priority correctly', function () { 84 | const values = [ 85 | { val: 1, priority: 0 }, 86 | { val: 2, priority: 1 }, 87 | { val: 3, priority: 1 }, 88 | { val: 4, priority: 2 }, 89 | { val: 5, priority: 2 }, 90 | { val: false, priority: 4 }, 91 | { val: {}, priority: 5 }, 92 | { val: [], priority: 5 }, 93 | { val: 'hello', priority: 3 } 94 | ]; 95 | 96 | values.forEach(({ val, priority }) => priorityQueue.enqueue(val, priority)); 97 | 98 | const retrievedData = []; 99 | 100 | while (!priorityQueue.isEmpty()) { 101 | retrievedData.push(priorityQueue.dequeue()); 102 | } 103 | 104 | const expectedData = values.sort(byPriority).map(({ val }) => val); 105 | 106 | assert.deepEqual(retrievedData, expectedData, 'incorrect retrieved data'); 107 | assert.equal(priorityQueue.isEmpty(), true, 'priorityQueue should be empty'); 108 | }); 109 | 110 | it('should return the priorityQueue as string', function () { 111 | const data = [1, 2, 3, 4, 5, false, {}, [], 'hello']; 112 | 113 | assert.equal(priorityQueue.toString(), '[Empty PriorityQueue]', 'incorrect empty priorityQueue as string'); 114 | 115 | data.forEach(d => priorityQueue.enqueue(d)); 116 | 117 | assert.equal(priorityQueue.toString(), data.join(','), 'incorrect priorityQueue as string'); 118 | }); 119 | }); 120 | -------------------------------------------------------------------------------- /04-data/data-structures/Queue.js: -------------------------------------------------------------------------------- 1 | const Node = require('./Node'); 2 | 3 | const defineProperty = Object.defineProperty; 4 | 5 | /** 6 | * Initializes a new instance of a Queue 7 | * @constructor Queue 8 | */ 9 | function Queue() { 10 | /** 11 | * First Node in the Queue 12 | * @name Queue#_first 13 | * @type {(Node|null)} 14 | */ 15 | this._first; 16 | 17 | defineProperty(this, '_first', { 18 | writable: true, 19 | value: null /* default */ 20 | }); 21 | 22 | return this; 23 | } 24 | 25 | /** 26 | * Checks if the queue is empty. 27 | * @function Queue#isEmpty 28 | */ 29 | Queue.prototype.isEmpty = function () { 30 | return this._first === null; 31 | }; 32 | 33 | /** 34 | * Adds a new item at the end of the queue. 35 | * @function Queue#enqueue 36 | * @param {*} value Value to be enqueued 37 | */ 38 | Queue.prototype.enqueue = function (value) { 39 | const node = new Node(value); 40 | 41 | if (this.isEmpty()) { 42 | this._first = node; 43 | } else { 44 | let lastNode = this._first; 45 | 46 | while (lastNode.hasNext()) { 47 | lastNode = lastNode.next; 48 | } 49 | 50 | lastNode.next = node; 51 | } 52 | 53 | return this; 54 | }; 55 | 56 | /** 57 | * Removes and returns the first item in the queue. 58 | * @function Queue#dequeue 59 | */ 60 | Queue.prototype.dequeue = function () { 61 | if (this.isEmpty()) { 62 | throw new Error('Queue is empty.'); 63 | } 64 | 65 | const currentFirst = this._first; 66 | 67 | if (currentFirst.hasNext()) { 68 | this._first = currentFirst.next; 69 | } else { 70 | this._first = null; 71 | } 72 | 73 | return currentFirst._value; 74 | }; 75 | 76 | /** 77 | * Returns the queue as string 78 | * @function Queue#toString 79 | */ 80 | Queue.prototype.toString = function () { 81 | if (this.isEmpty()) { 82 | return '[Empty Queue]' 83 | } 84 | 85 | let queueAsString = ''; 86 | let currentNode = this._first; 87 | 88 | while (currentNode) { 89 | queueAsString += String(currentNode); 90 | currentNode = currentNode.next; 91 | 92 | if (currentNode) { 93 | queueAsString += ','; 94 | } 95 | } 96 | 97 | return queueAsString; 98 | }; 99 | 100 | module.exports = Queue; 101 | -------------------------------------------------------------------------------- /04-data/data-structures/Queue.spec.js: -------------------------------------------------------------------------------- 1 | const assert = require('assert'); 2 | 3 | const Queue = require('./Queue'); 4 | 5 | describe('Data Structure - Queue', function () { 6 | /** 7 | * @type {Queue} 8 | * @private 9 | */ 10 | let queue; 11 | 12 | beforeEach(function () { 13 | queue = new Queue(); 14 | }); 15 | 16 | it('should be intialized correctly', function () { 17 | assert.ok(queue, 'queue is not intialized'); 18 | }); 19 | 20 | it('should start empty', function () { 21 | assert.equal(queue.isEmpty(), true, 'queue did not start empty'); 22 | }); 23 | 24 | it('should throw an exception if trying to dequeue on an empty queue', function () { 25 | assert.throws(() => { queue.dequeue() }, /^Error: Queue is empty\.$/, 'queue is not throwing an Error'); 26 | }); 27 | 28 | it('should enqueue and dequeue the first item correctly', function () { 29 | const value = 1; 30 | 31 | queue.enqueue(value); 32 | 33 | assert.equal(queue.dequeue(), value, 'incorrect item 1'); 34 | assert.equal(queue.isEmpty(), true, 'queue should be empty'); 35 | }); 36 | 37 | it('should enqueue and dequeue multiple items correctly', function () { 38 | const data = [1, 2, 3, 4, 5, false, {}, [], 'hello']; 39 | 40 | data.forEach(d => queue.enqueue(d)); 41 | 42 | const retrievedData = []; 43 | 44 | while (!queue.isEmpty()) { 45 | retrievedData.push(queue.dequeue()); 46 | } 47 | 48 | assert.deepEqual(retrievedData, data, 'incorrect retrieved data'); 49 | assert.equal(queue.isEmpty(), true, 'queue should be empty'); 50 | }); 51 | 52 | it('should return the queue as string', function () { 53 | const data = [1, 2, 3, 4, 5, false, {}, [], 'hello']; 54 | 55 | assert.equal(queue.toString(), '[Empty Queue]', 'incorrect empty queue as string'); 56 | 57 | data.forEach(d => queue.enqueue(d)); 58 | 59 | assert.equal(queue.toString(), data.join(','), 'incorrect queue as string'); 60 | }); 61 | }); 62 | -------------------------------------------------------------------------------- /04-data/data-structures/Set.js: -------------------------------------------------------------------------------- 1 | defineProperty = Object.defineProperty; 2 | 3 | /** 4 | * Checks if a value is NaN 5 | * @private 6 | * @param {*} val Value to check 7 | */ 8 | function isNaN(val) { 9 | return val !== val; 10 | } 11 | 12 | /** 13 | * Set ADT 14 | * Initializes an instance of a Set 15 | * @constructs Set 16 | */ 17 | function Set() { 18 | /** 19 | * Array to store the values of the Set 20 | * @name Set#_set 21 | * @type {Array} 22 | */ 23 | this._set; 24 | 25 | defineProperty(this, '_set', { 26 | value: [] 27 | }); 28 | 29 | /** 30 | * Size of the Set 31 | * @name Set#size 32 | * @type {number} 33 | */ 34 | this.size; 35 | 36 | defineProperty(this, 'size', { 37 | get() { 38 | return this._set.length; 39 | } 40 | }); 41 | 42 | /** 43 | * Helper property to keep track of the current iterator 44 | * in the iterator function 45 | * @name Set#_currentIterator 46 | * @type {number} 47 | */ 48 | this._currentIterator; 49 | 50 | defineProperty(this, '_currentIterator', { 51 | writable: true, 52 | value: 0 53 | }); 54 | 55 | return this; 56 | } 57 | 58 | /** 59 | * Returns the self instance of the set as iterator. 60 | * @function Set#Symbol.iterator 61 | * @example 62 | * // const s = new Set(); 63 | * // const it = s[Symbol.iterator](); 64 | * @returns {Set} instance 65 | */ 66 | Set.prototype[Symbol.iterator] = function () { 67 | return this; 68 | }; 69 | 70 | /** 71 | * Returns next value of the @@iterator. 72 | * @function Set#next 73 | * @returns {object} {value, done} 74 | */ 75 | Set.prototype.next = function () { 76 | if (this._currentIterator < this.size) { 77 | return { 78 | value: this._set[this._currentIterator++], 79 | done: false, 80 | }; 81 | } else { 82 | this._currentIterator = 0; 83 | return { 84 | value: undefined, 85 | done: true 86 | }; 87 | } 88 | }; 89 | 90 | /** 91 | * Adds a item to the Set 92 | * @function Set#add 93 | * @param {*} value Item to be added. NaN is not supported. 94 | */ 95 | Set.prototype.add = function (value) { 96 | if (isNaN(value)) { 97 | throw new TypeError(`Sorry. I do not support NaN values.`); 98 | } 99 | 100 | if (!this.has(value)) { 101 | this._set.push(value) 102 | } 103 | 104 | return this; 105 | }; 106 | 107 | /** 108 | * Returns an @@iterator to get the items of the set. 109 | * @function Set#list 110 | * @returns {Set} Iterable instance 111 | */ 112 | Set.prototype.list = function () { 113 | return this[Symbol.iterator](); 114 | }; 115 | 116 | /** 117 | * Checks if an item exists in the Set. 118 | * @function Set#has 119 | * @param {*} value Value to look for. 120 | */ 121 | Set.prototype.has = function (value) { 122 | return this._set.indexOf(value) >= 0; 123 | }; 124 | 125 | /** 126 | * Removes an item from the Set. 127 | * @function Set#delete 128 | * @param {*} value Value to be removed. 129 | */ 130 | Set.prototype.delete = function (value) { 131 | const result = this.has(value); 132 | 133 | if (result) { 134 | this._set.splice(this._set.indexOf(value), 1); 135 | } 136 | 137 | return result; 138 | }; 139 | 140 | module.exports = Set; -------------------------------------------------------------------------------- /04-data/data-structures/Set.spec.js: -------------------------------------------------------------------------------- 1 | const assert = require('assert'); 2 | 3 | const Set = require('./Set'); 4 | 5 | describe('Data Structure - Set', function () { 6 | /** 7 | * @type {Set} 8 | * @private 9 | */ 10 | let set; 11 | 12 | const NAN_ERROR = /^TypeError: Sorry\. I do not support NaN values\.$/; 13 | 14 | beforeEach(function () { 15 | set = new Set(); 16 | }); 17 | 18 | it('should be initialized correctly', function () { 19 | assert.ok(set, 'set is not intialized'); 20 | }); 21 | 22 | it('should not have enumerable properties', function () { 23 | const properties = Object.keys(set); 24 | const expectedProperties = []; 25 | 26 | assert.deepEqual(properties, expectedProperties, 'properties do not match'); 27 | }); 28 | 29 | it('should start empty', function () { 30 | assert.equal(set.size, 0, 'set should start with size 0'); 31 | }); 32 | 33 | it('should add a value correctly', function () { 34 | const value = 1; 35 | set.add(value); 36 | assert.equal(set.size, 1, `set should have size: ${1}`); 37 | }); 38 | 39 | it('should have a value correctly', function () { 40 | const value = 1; 41 | set.add(value); 42 | assert.equal(set.has(value), true, `set should has the value: ${value}`); 43 | }); 44 | 45 | it('should not add duplicated value', function () { 46 | const value = 'some value'; 47 | set.add(value); 48 | set.add(value); 49 | assert.equal(set.size, 1, `set should have size: ${1}`); 50 | assert.equal(set.has(value), true, `set should has the value: ${value}`); 51 | }); 52 | 53 | it('should not add a NaN to the set', function () { 54 | const value = NaN; 55 | assert.throws(() => set.add(value), NAN_ERROR, 'set.add is not throwing NaN type error'); 56 | }); 57 | 58 | it('should add multiple types of items to the set', function () { 59 | const values = [1, 'string', 2.5, false, null, undefined, function () { }, [], {}]; 60 | values.forEach(val => set.add(val)); 61 | assert.equal(set.size, values.length, `set should have size: ${values.length}`); 62 | }); 63 | 64 | it('should add multiple unique items to the set', function () { 65 | const obj = {}; 66 | let u; 67 | const values = [1, '2', true, obj, u, () => { }, u, obj, true, '2', 1]; 68 | values.forEach(val => set.add(val)); 69 | const uniqueValues = values.filter((val, index, self) => self.indexOf(val) === index); 70 | assert.equal(set.size, uniqueValues.length, `set should have size: ${uniqueValues.length}`); 71 | }); 72 | 73 | it('should has an iterator', function () { 74 | assert.equal(typeof set[Symbol.iterator], 'function', 'set does not have an iterator function'); 75 | }); 76 | 77 | 78 | it('should return an iterator', function () { 79 | const it = set.list(); 80 | const values = [1, 'string', 2.5, false, null, undefined, function () { }, [], {}]; 81 | values.forEach(val => set.add(val)); 82 | 83 | const retrievedValues = []; 84 | let currentVal; 85 | 86 | while (1) { 87 | currentVal = it.next(); 88 | if (currentVal.done) break; 89 | retrievedValues.push(currentVal.value); 90 | } 91 | 92 | assert.equal(retrievedValues.length, values.length, `retrieved values should have length: ${values.length}`); 93 | assert.deepEqual(retrievedValues, values, 'retrieved values do not match values'); 94 | }); 95 | 96 | it('should restart the iterator', function () { 97 | const it = set.list(); 98 | const values = [1, 2, 3, 4, 5]; 99 | values.forEach(val => set.add(val)); 100 | 101 | let retrievedValues = []; 102 | let retrievedValues2 = []; 103 | let currentVal; 104 | 105 | while (1) { 106 | currentVal = it.next(); 107 | if (currentVal.done) break; 108 | retrievedValues.push(currentVal.value); 109 | } 110 | 111 | assert.equal(retrievedValues.length, values.length, `retrieved values should have length: ${values.length}`); 112 | 113 | // do it one more time 114 | 115 | while (1) { 116 | currentVal = it.next(); 117 | if (currentVal.done) break; 118 | retrievedValues2.push(currentVal.value); 119 | } 120 | 121 | assert.equal(retrievedValues2.length, values.length, `retrieved values should have length: ${values.length}`); 122 | assert.deepEqual(retrievedValues2, values, 'retrieved values 2 do not match values'); 123 | }); 124 | 125 | it('should add and delete the first item', function () { 126 | const value = 1; 127 | set.add(value); 128 | assert.equal(set.delete(value), true, `it should delete value: ${value}`); 129 | assert.equal(set.size, 0, `set should have size: ${0}`); 130 | }); 131 | 132 | it('should delete multiple items', function () { 133 | const values = [1, 'string', 2.5, false, null, undefined, function () { }, [], {}]; 134 | let expectedSize = values.length; 135 | 136 | values.forEach(value => set.add(value)); 137 | 138 | values.forEach(value => { 139 | assert.equal(set.delete(value), true, `it should delete value: ${value}`); 140 | assert.equal(set.size, --expectedSize, `size should be ${expectedSize + 1}`); 141 | }); 142 | 143 | assert.equal(set.size, 0, `set should empty`); 144 | }); 145 | 146 | it('should return false if item trying to delete an item that is not in the set', function () { 147 | assert.equal(set.delete(), false, `result should be false`); 148 | }); 149 | }); -------------------------------------------------------------------------------- /04-data/data-structures/SortedList.js: -------------------------------------------------------------------------------- 1 | const Node = require('./Node'); 2 | 3 | const defineProperty = Object.defineProperty; 4 | 5 | /** 6 | * Checks if a parameter is `undefined` 7 | * @private 8 | * @param {*} obj Object to be tested 9 | */ 10 | function isUndefined(obj) { 11 | return typeof obj === 'undefined'; 12 | } 13 | 14 | /** 15 | * Checks if a parameter is a positive number 16 | * @private 17 | * @param {*} n Object to be tested 18 | */ 19 | function isPositiveNumber(n) { 20 | return Number.isInteger(n) && n >= 0; 21 | } 22 | 23 | /** 24 | * Throws an exception if a parameter is not a positive number 25 | * @private 26 | * @param {*} n Object to be tested 27 | * @param {string} parameterName Parameter name to show in the error message. 28 | */ 29 | function validatePositiveNumber(n, parameterName) { 30 | if (!isPositiveNumber(n)) { 31 | throw new Error(`"${parameterName}" should be an Integer greater than or equal to 0. Given: ${n}`); 32 | } 33 | } 34 | 35 | /** 36 | * Returns if a node is lexicographically smaller than other node. 37 | * @private 38 | * @param {Node} node1 First node to compare 39 | * @param {Node} node2 Second node to compare 40 | */ 41 | function isLexicographicalSmaller(node1, node2) { 42 | return String(node1) <= String(node2); 43 | } 44 | 45 | /** 46 | * Initializes an instance of a SortedList 47 | * @constructs SortedList 48 | */ 49 | function SortedList() { 50 | /** 51 | * First Node in the Sorted List 52 | * @name SortedList#first 53 | * @type {(Node|null)} 54 | * @default null 55 | */ 56 | this.first; 57 | 58 | defineProperty(this, 'first', { 59 | writable: true, 60 | value: null /* default */ 61 | }); 62 | 63 | /** 64 | * Length of the Sorted List 65 | * @name SortedList#length 66 | * @type {Number} 67 | * @readonly 68 | */ 69 | this.length; 70 | 71 | defineProperty(this, 'length', { 72 | get() { 73 | let currentNode = this.first; 74 | let length = 0; 75 | 76 | while (currentNode) { 77 | currentNode = currentNode.getNext(); 78 | length++; 79 | } 80 | 81 | return length; 82 | } 83 | }); 84 | 85 | return this; 86 | } 87 | 88 | /** 89 | * Checks if the sorted list is empty. 90 | * @function SortedListed#isEmpty 91 | */ 92 | SortedList.prototype.isEmpty = function () { 93 | return this.first === null; 94 | }; 95 | 96 | /** 97 | * Returns the sorted list as string 98 | * @function SortedList#toString 99 | */ 100 | SortedList.prototype.toString = function () { 101 | if (this.isEmpty()) { 102 | return '[Empty SortedList]'; 103 | } else { 104 | let listAsString = ''; 105 | let currentNode = this.first; 106 | 107 | while (currentNode) { 108 | listAsString += String(currentNode); 109 | listAsString += currentNode.hasNext() ? ',' : ''; 110 | currentNode = currentNode.getNext(); 111 | } 112 | 113 | return listAsString; 114 | } 115 | } 116 | 117 | /** 118 | * Returns the node at the `n`th position in the sorted list 119 | * @function SortedList#getNode 120 | * @param {number=} [n = last] position 121 | */ 122 | SortedList.prototype.getNode = function (n) { 123 | if (this.isEmpty()) { 124 | throw new Error('SortedList is empty.'); 125 | } else { 126 | if (isUndefined(n)) { 127 | n = this.length - 1; 128 | } else { 129 | validatePositiveNumber(n, 'n'); 130 | } 131 | 132 | let currentNode = this.first; 133 | let currentPosition = 0; 134 | 135 | while (currentNode.hasNext()) { 136 | if (currentPosition === n) break; 137 | currentPosition++; 138 | currentNode = currentNode.getNext(); 139 | } 140 | 141 | return currentNode; 142 | } 143 | }; 144 | 145 | /** 146 | * Returns the value of an item at the `n`th position in the sorted list. 147 | * @function SortedList#get 148 | * @param {number} [n = last] Position of the value 149 | * 150 | */ 151 | SortedList.prototype.get = function (n) { 152 | return this.getNode(n).value; 153 | }; 154 | 155 | /** 156 | * Inserts an element in the corresponding lexical sorted position. 157 | * @function SortedList#insert 158 | * @param {*} value Value to be inserted in order 159 | */ 160 | SortedList.prototype.insert = function (value) { 161 | const newNode = new Node(value); 162 | 163 | if (this.isEmpty()) { 164 | this.first = newNode; 165 | } else { 166 | if (isLexicographicalSmaller(newNode, this.first)) { 167 | newNode.setNext(this.first); 168 | this.first = newNode; 169 | } else { 170 | let currentNode = this.first; 171 | 172 | while (currentNode.hasNext()) { 173 | if (isLexicographicalSmaller(newNode, currentNode.getNext())) { 174 | newNode.setNext(currentNode.getNext()); 175 | break; 176 | } 177 | currentNode = currentNode.getNext(); 178 | } 179 | 180 | currentNode.setNext(newNode); 181 | } 182 | } 183 | 184 | return this; 185 | } 186 | 187 | /** 188 | * Removes an item from the list and returns its value 189 | * @function SortedList#remove 190 | * @param {number} n Position to remove 191 | */ 192 | SortedList.prototype.remove = function (n) { 193 | const nodeToRemove = this.getNode(n); 194 | const length = this.length; 195 | 196 | if (length === 1 || n === 0) { 197 | this.first = this.first.getNext(); 198 | } else { 199 | if (isUndefined(n)) { 200 | n = length - 1; 201 | } 202 | 203 | let prevNode = this.getNode(n - 1); 204 | 205 | prevNode.setNext(nodeToRemove.getNext()); 206 | } 207 | 208 | return nodeToRemove.value; 209 | } 210 | 211 | module.exports = SortedList; 212 | -------------------------------------------------------------------------------- /04-data/data-structures/SortedList.spec.js: -------------------------------------------------------------------------------- 1 | const assert = require('assert'); 2 | 3 | const SortedList = require('./SortedList'); 4 | 5 | describe('Data Structure - SortedList', function () { 6 | /** 7 | * @type {SortedList} 8 | * @private 9 | */ 10 | let sortedList; 11 | 12 | const EMPTY_LIST = '[Empty SortedList]'; 13 | const EMPTY_LIST_ERROR = /^Error: SortedList is empty\.$/; 14 | const POSITIVE_INTEGER_ERROR = /^Error: ".+" should be an Integer greater than or equal to 0\. Given: .+$/; 15 | 16 | beforeEach(function () { 17 | sortedList = new SortedList(); 18 | }); 19 | 20 | it('should be initialized correctly', function () { 21 | assert.ok(sortedList, 'sortedList is not initialized'); 22 | }); 23 | 24 | it('should not have enumerable properties', function () { 25 | const properties = Object.keys(sortedList); 26 | const expectedProperties = []; 27 | 28 | assert.deepEqual(properties, expectedProperties, 'properties do not match'); 29 | }); 30 | 31 | it('should start empty', function () { 32 | assert.equal(sortedList.isEmpty(), true, 'sortedList is not empty'); 33 | assert.equal(sortedList.length, 0, 'length should be 0'); 34 | assert.equal(sortedList.toString(), EMPTY_LIST, 'incorrect empty sortedList as string'); 35 | }); 36 | 37 | it('should throw an error when trying to get an item in the empty sortedList', function () { 38 | assert.throws(() => sortedList.get(), EMPTY_LIST_ERROR, 'get sortedList is not throwing empty error'); 39 | }); 40 | 41 | it('should throw an error when trying to remove an item in the empty sortedList', function () { 42 | assert.throws(() => sortedList.remove(), EMPTY_LIST_ERROR, 'remove sortedList is not throwing empty error'); 43 | }); 44 | 45 | it('should insert and remove the first item correctly', function () { 46 | const val = 1; 47 | sortedList.insert(val); 48 | assert.equal(sortedList.isEmpty(), false, 'sortedList should not be empty'); 49 | assert.equal(sortedList.get(), val, 'invalid first item get()'); 50 | assert.equal(sortedList.get(0), val, 'invalid first item get(0)'); 51 | assert.equal(sortedList.length, 1, 'length should be 1'); 52 | assert.equal(sortedList.toString(), [val], 'incorrect sortedList as string'); 53 | 54 | assert.equal(sortedList.remove(), val, 'invalid first item remove()'); 55 | assert.equal(sortedList.isEmpty(), true, 'sortedList should be empty'); 56 | assert.equal(sortedList.length, 0, 'length should be 0'); 57 | assert.equal(sortedList.toString(), EMPTY_LIST, 'incorrect empty sortedList as string'); 58 | }); 59 | 60 | it('should throw an error when trying to get on an invalid position', function () { 61 | sortedList.insert(true); 62 | assert.throws(() => sortedList.get(-1), POSITIVE_INTEGER_ERROR, 'sortedList is not throwing empty error'); 63 | assert.throws(() => sortedList.get(null), POSITIVE_INTEGER_ERROR, 'sortedList is not throwing empty error'); 64 | }); 65 | 66 | it('should throw an error when trying to remove on an invalid position', function () { 67 | sortedList.insert(true); 68 | assert.throws(() => sortedList.remove(-1), POSITIVE_INTEGER_ERROR, 'sortedList is not throwing empty error'); 69 | assert.throws(() => sortedList.remove(null), POSITIVE_INTEGER_ERROR, 'sortedList is not throwing empty error'); 70 | }); 71 | 72 | it('should insert multiple items correctly in lexicopgraphical order', function () { 73 | const data = ['c', 1, 'b', 10, 'd', ' a', 'aa', 'ab', 0, 5, 4, 3, false, {}, [], 'hello']; 74 | 75 | data.forEach(d => sortedList.insert(d)); 76 | 77 | const retrievedData = []; 78 | let currentPosition = 0; 79 | 80 | while (currentPosition < data.length) { 81 | retrievedData.push(sortedList.get(currentPosition++)); 82 | } 83 | 84 | data.sort(); 85 | 86 | assert.equal(sortedList.length, data.length, `length should be ${data.length}`); 87 | assert.deepEqual(retrievedData, data, 'incorrect retrieved data'); 88 | assert.equal(sortedList.toString(), data.join(','), 'incorrect sortedList as string'); 89 | }); 90 | 91 | it('should insert items before the first item correctly', function () { 92 | const val1 = 1; 93 | 94 | sortedList.insert(val1); 95 | assert.equal(sortedList.get(0), val1, `invalid val1: ${sortedList.get(0)}`); 96 | 97 | const val2 = 0; 98 | 99 | sortedList.insert(val2); 100 | assert.equal(sortedList.get(0), val2, `invalid val2: ${sortedList.get(0)}`); 101 | 102 | const val3 = -1; 103 | 104 | sortedList.insert(val3); 105 | assert.equal(sortedList.get(0), val3, `invalid val3: ${sortedList.get(0)}`); 106 | 107 | assert.equal(sortedList.length, 3, `length should be ${3}`); 108 | assert.equal(sortedList.toString(), [val3, val2, val1], `incorrect order: ${sortedList.toString()}`); 109 | }); 110 | 111 | it('should insert items at the end correctly', function () { 112 | const val1 = 1; 113 | 114 | sortedList.insert(val1); 115 | assert.equal(sortedList.get(), val1, `invalid val1: ${sortedList.get()}`); 116 | 117 | const val2 = 2; 118 | 119 | sortedList.insert(val2); 120 | assert.equal(sortedList.get(1), val2, `invalid val2: ${sortedList.get(1)}`); 121 | 122 | const val3 = 3; 123 | 124 | sortedList.insert(val3); 125 | assert.equal(sortedList.get(2), val3, `invalid val3: ${sortedList.get(2)}`); 126 | 127 | assert.equal(sortedList.length, 3, `length should be ${3}`); 128 | assert.equal(sortedList.toString(), [val1, val2, val3], `incorrect order: ${sortedList.toString()}`); 129 | }); 130 | 131 | it('should remove the last item if the position is greater than the total length', function () { 132 | sortedList.insert(true); 133 | assert.equal(sortedList.length, 1, `length should be ${1}`); 134 | assert.equal(sortedList.remove(5), true, 'incorrect removed value'); 135 | assert.equal(sortedList.length, 0, `length should be ${0}`); 136 | }); 137 | 138 | it('should remove the first item correctly', function () { 139 | const value = 1; 140 | sortedList.insert(value); 141 | assert.equal(sortedList.length, 1, `length should be ${1}`); 142 | assert.equal(sortedList.remove(0), value, 'incorrect removed value'); 143 | assert.equal(sortedList.length, 0, `length should be ${0}`); 144 | assert.equal(sortedList.isEmpty(), true, 'sortedList should be empty'); 145 | }); 146 | 147 | it('should remove the middle item correctly', function () { 148 | const val1 = 1; 149 | const val2 = 2; 150 | const val3 = 3; 151 | 152 | sortedList.insert(val1).insert(val2).insert(val3); 153 | assert.equal(sortedList.length, 3, `length should be ${3}`); 154 | assert.equal(sortedList.remove(1), val2, 'incorrect removed value'); 155 | assert.equal(sortedList.isEmpty(), false, 'sortedList should not be empty'); 156 | assert.equal(sortedList.length, 2, `length should be ${2}`); 157 | assert.equal(sortedList.toString(), [val1, val3], 'incorrect sortedList as string'); 158 | }); 159 | 160 | it('should remove the last item correctly', function () { 161 | const val1 = 1; 162 | const val2 = 2; 163 | const val3 = 3; 164 | 165 | sortedList.insert(val1).insert(val2).insert(val3); 166 | assert.equal(sortedList.length, 3, `length should be ${3}`); 167 | 168 | assert.equal(sortedList.remove(2), val3, 'incorrect removed value'); 169 | assert.equal(sortedList.isEmpty(), false, 'sortedList should not be empty'); 170 | assert.equal(sortedList.toString(), [val1, val2], 'incorrect sortedList as string'); 171 | 172 | assert.equal(sortedList.remove(1), val2, 'incorrect removed value2'); 173 | assert.equal(sortedList.isEmpty(), false, 'sortedList should not be empty'); 174 | assert.equal(sortedList.toString(), [val1], 'incorrect sortedList as string'); 175 | 176 | assert.equal(sortedList.remove(0), val1, 'incorrect removed value1'); 177 | assert.equal(sortedList.isEmpty(), true, 'sortedList should be empty'); 178 | assert.equal(sortedList.toString(), EMPTY_LIST, 'incorrect sortedList as string'); 179 | 180 | assert.equal(sortedList.length, 0, `length should be ${0}`); 181 | }); 182 | 183 | it('should remove the last item correctly if not position provided', function () { 184 | const val1 = 1; 185 | const val2 = 2; 186 | const val3 = 3; 187 | 188 | sortedList.insert(val1).insert(val2).insert(val3); 189 | assert.equal(sortedList.length, 3, `length should be ${3}`); 190 | 191 | assert.equal(sortedList.remove(), val3, 'incorrect removed value3'); 192 | assert.equal(sortedList.isEmpty(), false, 'sortedList should not be empty'); 193 | assert.equal(sortedList.toString(), [val1, val2], 'incorrect sortedList as string'); 194 | 195 | assert.equal(sortedList.remove(), val2, 'incorrect removed value2'); 196 | assert.equal(sortedList.isEmpty(), false, 'sortedList should not be empty'); 197 | assert.equal(sortedList.toString(), [val1], 'incorrect sortedList as string'); 198 | 199 | assert.equal(sortedList.remove(), val1, 'incorrect removed value1'); 200 | assert.equal(sortedList.isEmpty(), true, 'sortedList should be empty'); 201 | assert.equal(sortedList.toString(), EMPTY_LIST, 'incorrect sortedList as string'); 202 | 203 | assert.equal(sortedList.length, 0, `length should be ${0}`); 204 | }); 205 | 206 | it('should insert and remove multiple items', function () { 207 | const data = [1, 2, 3, 4, 5, false, {}, [], 'hello']; 208 | 209 | data.forEach(i => sortedList.insert(i)); 210 | 211 | assert.equal(sortedList.length, data.length, `length should be ${data.length}`); 212 | 213 | const expectedData = []; 214 | 215 | while (!sortedList.isEmpty()) { 216 | expectedData.unshift(sortedList.remove()); 217 | } 218 | 219 | assert.equal(sortedList.isEmpty(), true, 'sortedList should be empty'); 220 | assert.equal(sortedList.length, 0, `length should be ${0}`); 221 | assert.equal(sortedList.toString(), EMPTY_LIST, 'incorrect empty sortedList as string'); 222 | assert.deepEqual(expectedData, data.sort(), 'incorrect retrieved data'); 223 | }); 224 | }); 225 | -------------------------------------------------------------------------------- /04-data/data-structures/Stack.js: -------------------------------------------------------------------------------- 1 | const Node = require('./Node'); 2 | 3 | const defineProperty = Object.defineProperty; 4 | 5 | /** 6 | * Initializes a new instance of a Stack 7 | * @constructor Stack 8 | */ 9 | function Stack() { 10 | /** 11 | * Top Node in the Stack 12 | * @name Stack#_top 13 | * @type {Node|null} 14 | * @default null 15 | */ 16 | this._top; 17 | 18 | defineProperty(this, '_top', { 19 | writable: true, 20 | value: null /* default */ 21 | }); 22 | 23 | return this; 24 | } 25 | 26 | /** 27 | * Checks if the stack is empty. 28 | * @function Stack#isEmpty 29 | */ 30 | Stack.prototype.isEmpty = function () { 31 | return this._top === null; 32 | }; 33 | 34 | /** 35 | * Returns the holding value of the top node. 36 | * @function Stack#peek 37 | */ 38 | Stack.prototype.peek = function () { 39 | return this._top._value; 40 | }; 41 | 42 | /** 43 | * Adds a new value to the stack. 44 | * @function Stack#push 45 | * @param {*} value Value to be stored 46 | */ 47 | Stack.prototype.push = function (value) { 48 | const node = new Node(value); 49 | 50 | node.setNext(this._top); 51 | 52 | this._top = node; 53 | 54 | return this; 55 | }; 56 | 57 | /** 58 | * Retrieves and removes the top value from the stack. 59 | * @function Stack#pop 60 | */ 61 | Stack.prototype.pop = function () { 62 | if (this.isEmpty()) { 63 | throw new Error('Stack is empty.'); 64 | } 65 | 66 | const currentTop = this._top; 67 | 68 | this._top = currentTop.next; 69 | 70 | return currentTop._value; 71 | }; 72 | 73 | /** 74 | * Returns the stack as string. 75 | * Note: could this be considrered as hacking the Stack? 76 | * @function Stack#toString 77 | */ 78 | Stack.prototype.toString = function () { 79 | if (this.isEmpty()) { 80 | return '[Empty Stack]'; 81 | } 82 | 83 | let stackAsString = ''; 84 | let currentNode = this._top; 85 | 86 | while (currentNode) { 87 | stackAsString += String(currentNode); 88 | currentNode = currentNode.next; 89 | 90 | if (currentNode) { 91 | stackAsString += '\n'; 92 | } 93 | } 94 | 95 | return stackAsString; 96 | } 97 | 98 | module.exports = Stack; 99 | -------------------------------------------------------------------------------- /04-data/data-structures/Stack.spec.js: -------------------------------------------------------------------------------- 1 | const assert = require('assert'); 2 | 3 | const Stack = require('./Stack'); 4 | 5 | describe('Data Structure - Stack', function () { 6 | /** 7 | * @type {Stack} 8 | * @private 9 | */ 10 | let stack; 11 | 12 | beforeEach(function () { 13 | stack = new Stack(); 14 | }); 15 | 16 | it('should be intialized correctly', function () { 17 | assert.ok(stack, 'stack is not intialized'); 18 | }); 19 | 20 | it('should start empty', function () { 21 | assert.equal(stack.isEmpty(), true, 'stack is NOT empty'); 22 | }); 23 | 24 | it('should throw an exception if trying to pop on a empty stack', function () { 25 | assert.throws(() => { stack.pop() }, /^Error: Stack is empty\.$/, 'Stack not throwing an Error'); 26 | }); 27 | 28 | it('should push and peek the top item correctly', function () { 29 | const value = 1; 30 | 31 | stack.push(value); 32 | 33 | assert.equal(stack.peek(), value, 'incorrect item 1'); 34 | assert.equal(stack.isEmpty(), false, 'stack should not be empty'); 35 | }); 36 | 37 | it('should push and pop the top item correctly', function () { 38 | const value = 1; 39 | 40 | stack.push(value); 41 | 42 | assert.equal(stack.pop(), value, 'incorrect item 1'); 43 | assert.equal(stack.isEmpty(), true, 'stack should be empty'); 44 | }); 45 | 46 | it('should push and pop multiple items correctly', function () { 47 | const data = [1, 2, 3, 4, 5, false, {}, [], 'hello']; 48 | 49 | data.forEach(d => stack.push(d)); 50 | 51 | const retrievedData = []; 52 | 53 | while (!stack.isEmpty()) { 54 | retrievedData.unshift(stack.pop()); 55 | } 56 | 57 | assert.deepEqual(retrievedData, data, 'incorrect retrieved data'); 58 | assert.equal(stack.isEmpty(), true, 'stack should be empty'); 59 | }); 60 | 61 | it('should return the stack as string', function () { 62 | const data = [1, 2, 3, 4, 5, false, {}, [], 'hello']; 63 | 64 | assert.equal(stack.toString(), '[Empty Stack]', 'incorrect empty stack as string'); 65 | 66 | data.forEach(d => stack.push(d)); 67 | 68 | assert.equal(stack.toString(), data.reverse().join('\n'), 'incorrect stack as string'); 69 | }); 70 | }); 71 | -------------------------------------------------------------------------------- /04-data/summary.md: -------------------------------------------------------------------------------- 1 | # Computer Science Distilled 2 | 3 | ## Summary - Chapter 4: Data 4 | 5 | > Good programmers worry about data structures and their relationships. 6 | > 7 | > -- _Linus Torvalds_ 8 | 9 | **:dart: Objectives:** 10 | 11 | > * _How to abstract **data types** to keep code clean_ 12 | > * _Learn common abstractions_ 13 | > * _Learn different ways to structure data in memory_ 14 | 15 | --- 16 | 17 | ### Intro 18 | 19 | #### Abstractions 20 | 21 | > Abstraction = hidden details 22 | 23 | #### Data Type 24 | 25 | > Examples of data types 26 | > 27 | > * String 28 | > * Boolean 29 | > * Number 30 | 31 | ### Section 1: Abstract Data Types 32 | 33 | #### ADT 34 | 35 | ##### Abstract Data Types 36 | 37 | > an ADT is the specification / interface for working with variables of a given type 38 | 39 | ##### Advantages of ADT 40 | 41 | > * Simplicity: Code simple and easy to understand 42 | > * Flexibility: Same interface 43 | > * Reusability: Same ADT can be used in different algorithms 44 | > * Organization: Separation of concerns 45 | > * Convenience: Learn how to use it, not worried about how it works. 46 | > * Bug-Fixing: Fix once, fix all parts. 47 | 48 | --- 49 | 50 | ### Section 2: Common Abstractions 51 | 52 | > It is equally important to choose which ADT to use when solving a computational problem. 53 | 54 | #### Primitive Data Types 55 | 56 | ##### Primitive Data Types Examples 57 | 58 | > * Integers 59 | > * Floating points 60 | > * Booleans 61 | > * Strings 62 | 63 | #### The Stack 64 | 65 | > Code: [Stack.js](./data-structures/Stack.js) 66 | 67 | ##### What is a Stack? 68 | 69 | > a pile of items where you can only work with the one at the top 70 | > 71 | > processing data this way is also known as **LIFO** (Last-In, First-Out) 72 | 73 | #### The Queue 74 | 75 | > Code: [Queue.js](./data-structures/Queue.js) 76 | 77 | ##### What is a Queue? 78 | 79 | > is just like any real queue. Image a queue of people for a concert where the new person goes to the end of the line 80 | > 81 | > processing data this way is also known as **FIFO** (First-In, First-Out) 82 | 83 | #### The Priority Queue 84 | 85 | > Code: [PriorityQueue.js](./data-structures/PriorityQueue.js) 86 | 87 | ##### What's a PriorityQueue? 88 | 89 | > is just like the Queue but elements are enqueued by their priority. 90 | > 91 | > imagine a line of patients waiting to be the attended in a hospital, patients with the most critical status will be attended first because they have higher priority 92 | 93 | #### The List 94 | 95 | > Code: [List.js](./data-structures/List.js) 96 | 97 | ##### What's a List? 98 | 99 | > is a more flexible ADT in which you can `insert`, `remove` and `get` elements at any position. Functionality like `sort`, `reverse` and `slice` is also available. 100 | 101 | #### The Sorted List 102 | 103 | > Code: [SortedList.js](./data-structures/SortedList.js) 104 | 105 | ##### What's a Sorted List? 106 | 107 | > is a lighter version of a List in which you can `insert`, `remove` and `get` elements at any position. Items are inserted in Lexicographical order, that way the list is always sorted. 108 | 109 | #### The Map 110 | 111 | > Code: [Map.js](./data-structures/Map.js) 112 | 113 | ##### What's a Map? 114 | 115 | > is a dictionary of key => values pairs. 116 | 117 | #### The Set 118 | 119 | > Code: [Set.js](./data-structures/Set.js) 120 | 121 | ##### What's a Set? 122 | 123 | > is an **unordered** collection of **unique** items. 124 | 125 | --- 126 | 127 | ### Section 3: Structures 128 | 129 | > Data Structures provides ways to implement ADT. ADT are built on top of data structures. 130 | > 131 | > ADT don't describe how something works, Data Structures describe how data is stored. 132 | 133 | #### The Array 134 | 135 | > Arrays are stored in memory in sequence. They have fixed size of each element. 136 | > 137 | > **Advantages:** 138 | > 139 | > Arrays are useful to implement **Stacks**, **Lists** and **Queues**. Arrays provide instant access to its elements by index. 140 | > 141 | > **Drawbacks:** 142 | > 143 | > Arrays can be dificult to modify, for instance, by trying to remove an element, memory should be reacollated. 144 | > For big inputs it may not be enough space, since memory should be reserved in sequence. 145 | > 146 | > [Arrays in JS](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array) 147 | 148 | #### The Linked List 149 | 150 | > Linked Lists are stored in different places in memory and they keep reference to the next item with a pointer. 151 | > 152 | > **Advantages:** 153 | > 154 | > Linked Lists are also useful to implement [Stacks](./data-structures/Stack.js), [Lists](./data-structures/List.js) and [Queues](./data-structures/Queue.js). 155 | > We can take more advantage of memory since they're not required to be in sequence. 156 | > 157 | > **Drawbacks:** 158 | > 159 | > In order to get the `nth` element in a linked list, you should start from the first and start couting. 160 | > We can not know the previous item of a current item easy, we need to iterate over all items from the first one until the `n - 1` item. 161 | > 162 | > Code: [LinkedList.js](./data-structures/List.js) 163 | 164 | #### The Doubly Linked List 165 | 166 | > The Doubly Linked List is the same as a Linked List with an extra pointer, pointing to the previous element. 167 | > 168 | > **Advantages:** 169 | > 170 | > It is easier to delete items in the middle. We can iterate forward and backwards in the list. 171 | > 172 | > **Drawbacks:** 173 | > 174 | > We still need to iterate over all items starting from the first one to access any `nth` item. 175 | > Implemetation is more complex in code. 176 | > More memory space required to store the pointer. 177 | > 178 | > Code: [DoublyLinkedList.js](./data-structures/DoublyLinkedList.js) 179 | 180 | #### Arrays vs. Linked Lists 181 | 182 | > Some feature-rich programming languages have some of the ADT built-in. We can make used of this generic implementations when performance is not an issue, otherwise, we should consider to implement the best ADT for a specific problem and also when working with low-level programming languages. 183 | > 184 | > **Use Arrays when:** 185 | > * Accessing data in a random or unordered way. 186 | > * Length of values is fixed or it will never change. 187 | > * Need instant access to elements. 188 | > 189 | > **Use Linked Lists when:** 190 | > * Adding/removing elements in a faster way and in the middle of the list. 191 | > * Length can be variable. 192 | > * No need to access data in a random way. 193 | 194 | #### The Tree 195 | 196 | > Like the Linked List, Trees do not use contiguous memory cells in physical memory to store objects. Unlike the Linked List, values and pointers are not arranged in a linear chain but in a tree-like structure. 197 | > 198 | > * **node**: A cell in the tree. Must have one single parent, except from the root node. 199 | > * **root**: The top-most node of a Tree. Doesn't have a parent. 200 | > * **edge**: A pointer from one node to another. 201 | > * **parent**: The parent of a node 202 | > * **children**: The children of a node 203 | > * **antecesors**: Parent, grandparent, great-grandparent of a node. 204 | > * **decendants**: Children, grand-children, ... of a node. 205 | > * **siblings**: Nodes that share the same parent. 206 | > * **leaf**: Nodes with no children. 207 | > * **path**: The set of nodes and edges to lead from one node to another. 208 | > * **height**: The number of levels from the the root node to the deepest node in the tree. 209 | > * **level**: Position of a node in the tree in respect of the root node. 210 | > * **forest**: A set of trees. 211 | 212 | #### Binary Search Tree 213 | 214 | #### The Binary Heap 215 | 216 | #### The Graph 217 | 218 | #### The Hash Table 219 | 220 | ## References 221 | 222 | ### In the book: 223 | -------------------------------------------------------------------------------- /05-algorithms/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iramirezc-learning/book-computer-science-distilled/e834b8d58871908a0594597d03d99f88128df755/05-algorithms/.gitkeep -------------------------------------------------------------------------------- /06-databases/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iramirezc-learning/book-computer-science-distilled/e834b8d58871908a0594597d03d99f88128df755/06-databases/.gitkeep -------------------------------------------------------------------------------- /07-computers/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iramirezc-learning/book-computer-science-distilled/e834b8d58871908a0594597d03d99f88128df755/07-computers/.gitkeep -------------------------------------------------------------------------------- /08-programming/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iramirezc-learning/book-computer-science-distilled/e834b8d58871908a0594597d03d99f88128df755/08-programming/.gitkeep -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # computer-science-distilled-book 2 | 3 | _**Computer Science Distilled** by Wladston Ferreira Filho_. 4 | 5 | Code exercises, examples and personal notes. 6 | 7 | > :warning: **reading in progress...** 8 | 9 | ## Quick Reference 10 | 11 | * See the [Algorithms & Data Structures List](./algorithms.md) 12 | 13 | ## Summaries 14 | 15 | * [Chapter 1. Basics](./01-basics/summary.md) 16 | 17 | * [Chapter 2: Complexity](./02-complexity/summary.md) 18 | 19 | * [Chapter 3. Strategy](./03-strategy/summary.md) 20 | 21 | * [Chapter 4. Data (incomplete)](./04-data/summary.md) 22 | 23 | * Chapter 5. Algorithms 24 | 25 | * Chapter 6. Databases 26 | 27 | * Chapter 7. Computers 28 | 29 | * Chapter 8. Programming 30 | 31 | ## Tests 32 | 33 | install Mocha globally 34 | 35 | ```sh 36 | npm install -g mocha 37 | ``` 38 | 39 | run the tests 40 | 41 | ```sh 42 | npm run test 43 | ``` 44 | 45 | ## Docs for Data Structures 46 | 47 | install `documentation.js` globally 48 | 49 | ```sh 50 | npm install -g documentation 51 | ``` 52 | 53 | generate the `docs` folder 54 | 55 | ```sh 56 | npm run docs:build:data-structures 57 | ``` 58 | 59 | this will be generated inside the `04-data` folder. -------------------------------------------------------------------------------- /algorithms.md: -------------------------------------------------------------------------------- 1 | # Algorithms and Data Structures List 2 | 3 | List of algorithms and code examples in the book. 4 | 5 | ## Chapter 1. Basics 6 | 7 | ### 1.1 Ideas 8 | 9 | * [Max of three numbers](./01-basics/algorithms/max-of-three-numbers.js) 10 | 11 | ## Chapter 2: Complexity 12 | 13 | ### 2.1 Counting Time 14 | 15 | * [Selection Sort](./02-complexity/algorithms/selection-sort.js) 16 | 17 | ## Chapter 3. Strategy 18 | 19 | ### 3.1 Iteration 20 | 21 | * [Merge two sorted arrays](./03-strategy/algorithms/merge-two-sorted-lists.js) 22 | * [Power Set - Iterative](./03-strategy/algorithms/power-set.js) 23 | 24 | ### 3.2 Recursion 25 | 26 | * [nth Fibonacci](./03-strategy/algorithms/fibonacci.js) 27 | * [Palindrome](./03-strategy/algorithms/palindrome.js) 28 | * [Power Set - Recursive](./03-strategy/algorithms/power-set-recursive.js) 29 | 30 | ### 3.3 Brute Force 31 | 32 | * [Best Trade - Brute Force](./03-strategy/algorithms/best-trade.js) 33 | * [Knapsack - Brute Force](./03-strategy/algorithms/knapsack.js) 34 | 35 | ### 3.4 Backtracking 36 | 37 | * [8 Queens Puzzle](./03-strategy/algorithms/eight-queens-puzzle.v2.js) 38 | * [8 Queens Puzzle (OO Version)](./03-strategy/algorithms/eight-queens-puzzle.v1.js) 39 | 40 | ### 3.5 Heuristics 41 | 42 | * [Greedy Knapsack](./03-strategy/algorithms/greedy-knapsack.js) 43 | 44 | ### 3.6 Divide and Conquer 45 | 46 | * [Merge Sort](./03-strategy/algorithms/merge-sort.js) 47 | * [Best Trade - Divide and Conquer](./03-strategy/algorithms/best-trade-divide-n-conquer.js) 48 | * [Knapsack - Divide and Conquer](./03-strategy/algorithms/knapsack-dnc.js) 49 | * [Knapsack - Divide and Conquer (My approach)](./03-strategy/algorithms/knapsack-divide-n-conquer.js) 50 | 51 | ### 3.7 Dynamic Programming 52 | 53 | * [Fibonacci - DP](./03-strategy/algorithms/dynamic-fibonacci.js) 54 | * [Knapsack - DP](./03-strategy/algorithms/knapsack-dp.js) 55 | * [Best Trade - DP (Bottom-Up)](./03-strategy/algorithms/best-trade-bottom-up.js) 56 | 57 | ### 3.8 Branch and Bound 58 | 59 | * [Powdered Knapsack - Heuristics](./03-strategy/algorithms/powdered-knapsack.js) 60 | * TODO: Knapsack - Branch and Bound 61 | 62 | ## Chapter 4. Data 63 | 64 | ### 4.2 Common Abstractions 65 | 66 | * [The Stack](./04-data/data-structures/Stack.js) 67 | * [The Queue](./04-data/data-structures/Queue.js) 68 | * [The Priority Queue](./04-data/data-structures/PriorityQueue.js) 69 | * [The List](./04-data/data-structures/List.js) 70 | * [The Sorted List](./04-data/data-structures/SortedList.js) 71 | * [The Map](./04-data/data-structures/Map.js) 72 | * [The Set](./04-data/data-structures/Set.js) 73 | 74 | ### 4.3 Data Structures 75 | 76 | * [The Array (MDN)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array) 77 | * [The Linked List](./04-data/data-structures/List.js) 78 | * [The Doubly Linked List](./04-data/data-structures/DoublyLinkedList.js) 79 | * The Tree 80 | * The Binary Search Tree 81 | * The Binary Heap 82 | * The Graph 83 | * The Hash Table 84 | 85 | ## Chapter 5. Algorithms 86 | 87 | ## Chapter 6. Databases 88 | 89 | ## Chapter 7. Computers 90 | 91 | ## Chapter 8. Programming 92 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "computer-science-distilled-book", 3 | "version": "1.0.0", 4 | "description": "Personal notes from the book Computer Science Distilled", 5 | "scripts": { 6 | "test": "mocha './**/*.spec.js'", 7 | "docs:build:data-structures": "documentation build 04-data/data-structures/*.js -f html -o 04-data/docs" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/iramirezc/computer-science-distilled-book.git" 12 | }, 13 | "author": "Isaac Ramírez ", 14 | "license": "ISC" 15 | } 16 | --------------------------------------------------------------------------------