├── .gitignore ├── LICENSE ├── README.md ├── _config.yml ├── basics ├── AlgorithmsBasics.html └── AlgorithmsBasics.ipynb ├── cellular-automata ├── CellularAutomata.html └── CellularAutomata.ipynb ├── chaotic-systems ├── ChaoticSystems.html └── ChaoticSystems.ipynb ├── divide-and-conquer ├── DivideAndConquer.html └── DivideAndConquer.ipynb ├── docs ├── CODE_OF_CONDUCT.md └── CONTRIBUTING.md ├── dynamic-programming ├── DynamicProgramming.html └── DynamicProgramming.ipynb ├── graphs ├── Graphs.html └── Graphs.ipynb ├── probabilistic-algorithms ├── ProbabilisticAlgorithms.html └── ProbabilisticAlgorithms.ipynb └── similarity-functions ├── SimilarityFunctions.html └── SimilarityFunctions.ipynb /.gitignore: -------------------------------------------------------------------------------- 1 | *-checkpoint.ipynb 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Andres Segura 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Python Algorithms Course 2 | ![version](https://img.shields.io/badge/version-Pro-blue) 3 | ![release](https://img.shields.io/badge/release-1.2.3-blue) 4 | ![language](https://img.shields.io/badge/language-Python_3.7%7C3.8-brightgreen) 5 | ![last-update](https://img.shields.io/badge/last_update-10/18/2023-orange) 6 | ![last-update](https://img.shields.io/badge/license-MIT-orange) 7 | 8 | Free hands-on course with the implementation (in Python) and description of several computational, mathematical and statistical algorithms. 9 | 10 | Although it is not intended to have the formal rigor of a book, we tried to be as faithful as possible to the original algorithms and methods, only adding variants, when these were necessary for didactic purposes. 11 | 12 | ## Quick Start 13 | The best way to get the most out of this course is to carefully read each selected problem, try to think of a possible solution (language independent) and then look at the proposed Python code and try to reproduce it in your favorite IDE. If you already have knowledge of the Python language, then you can go directly to programming your solution and then compare it with the one proposed in the course. 14 | 15 | If you want to play with these notebooks online without having to install any library or configure hardware, you can use the following service: 16 | - Open In Colab 17 | 18 | ## What is an algorithm? 19 | In mathematics and computer science, an algorithm is a finite sequence of well-defined, computer-implementable instructions, typically to solve a class of problems or to perform a computation. 20 | 21 | ## Contents 22 |
23 | 1. Algorithm's Basics 24 | 33 |
34 |
35 | 2. Divide and Conquer 36 | 42 |
43 |
44 | 3. Graphs 45 | 54 |
55 |
56 | 4. Dynamic Programming 57 | 65 |
66 |
67 | 5. Probabilistic Algorithms 68 | 76 |
77 |
78 | 6. Similarity Functions 79 | 84 |
85 |
86 | 7. Chaotic Systems 87 | 91 |
92 |
93 | 8. Cellular Automata 94 | 98 |
99 | 100 | ## Python Dependencies 101 | ``` console 102 | conda install -c anaconda numpy 103 | conda install -c anaconda pymc 104 | conda install -c anaconda networkx 105 | ``` 106 | 107 | ## Bibliography 108 | - G. Brassard, P. Bratley. (2006). *Fundamentals of Algorithmics*. Englewood Cliffs, New Jersey: Prentice-Hall, Inc. 109 | - R.C.T. Lee, S.S. Tseng, R.C. Chang, Y.T.Tsai. (2005). *Introduction to the Design and Analysis of Algorithms. A Strategic Approach*. Asia: McGraw-Hill Education. 110 | - K. Rosen. (2012). *Discrete Mathematics and Its Applications*. NewYork, NY: McGraw-Hill Education; Edición: 7th. 111 | 112 | ## Contributing and Feedback 113 | Any kind of feedback/suggestions would be greatly appreciated (algorithm design, documentation, improvement ideas, spelling mistakes, etc...). If you want to make a contribution to the course you can do it through a PR. 114 | 115 | ## Documentation 116 | Please read the [contributing](https://github.com/ansegura7/Algorithms/blob/main/docs/CONTRIBUTING.md) and [code of conduct](https://github.com/ansegura7/Algorithms/blob/main/docs/CODE_OF_CONDUCT.md) documentation. 117 | 118 | ## Author 119 | - Created by Andrés Segura-Tinoco 120 | - Created on May 17, 2019 121 | 122 | ## License 123 | This project is licensed under the terms of the MIT license. 124 | -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | title: Python Algorithms Course 2 | description: Free hands-on course with the implementation (in Python) and description of several computational, mathematical and statistical algorithms 3 | show_downloads: false 4 | google_analytics: 5 | theme: jekyll-theme-cayman 6 | -------------------------------------------------------------------------------- /cellular-automata/CellularAutomata.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# 8. Cellular Automata" 8 | ] 9 | }, 10 | { 11 | "cell_type": "markdown", 12 | "metadata": {}, 13 | "source": [ 14 | "- **Created by Andrés Segura Tinoco**\n", 15 | "- **Created on Jul 08, 2019**\n", 16 | "- **Updated on Mar 19, 2021**" 17 | ] 18 | }, 19 | { 20 | "cell_type": "markdown", 21 | "metadata": {}, 22 | "source": [ 23 | "A **cellular automaton** (abbrev. CA) is a discrete model studied in computer science, mathematics, physics, complexity science, theoretical biology and microstructure modeling." 24 | ] 25 | }, 26 | { 27 | "cell_type": "markdown", 28 | "metadata": {}, 29 | "source": [ 30 | "A **cellular automaton** consists of a regular grid of cells, each in one of a finite number of states, such as on and off. The grid can be in any finite number of dimensions. For each cell, a set of cells called its neighborhood is defined relative to the specified cell. An initial state (time t = 0) is selected by assigning a state for each cell. A new generation is created (advancing t by 1), according to some fixed rule (generally, a mathematical function) that determines the new state of each cell in terms of the current state of the cell and the states of the cells in its neighborhood. Typically, the rule for updating the state of cells is the same for each cell and does not change over time, and is applied to the whole grid simultaneously, though exceptions are known, such as the stochastic cellular automaton and asynchronous cellular automaton." 31 | ] 32 | }, 33 | { 34 | "cell_type": "code", 35 | "execution_count": 1, 36 | "metadata": {}, 37 | "outputs": [], 38 | "source": [ 39 | "# Import Python libraries\n", 40 | "import numpy as np\n", 41 | "import matplotlib.pyplot as plt" 42 | ] 43 | }, 44 | { 45 | "cell_type": "markdown", 46 | "metadata": {}, 47 | "source": [ 48 | "## 8.1. Linear Cellular Automata (LCA)" 49 | ] 50 | }, 51 | { 52 | "cell_type": "markdown", 53 | "metadata": {}, 54 | "source": [ 55 | "### 8.1.1. Circular loop through an Automaton" 56 | ] 57 | }, 58 | { 59 | "cell_type": "code", 60 | "execution_count": 2, 61 | "metadata": {}, 62 | "outputs": [], 63 | "source": [ 64 | "# Get automata cells \n", 65 | "def get_cells_range(automaton, ix_start, ix_end, ix_max):\n", 66 | " cells = ''\n", 67 | " for i in range(ix_start, ix_end + 1):\n", 68 | " ix = i % ix_max\n", 69 | " cells += str(automaton[ix])\n", 70 | " return cells" 71 | ] 72 | }, 73 | { 74 | "cell_type": "code", 75 | "execution_count": 3, 76 | "metadata": {}, 77 | "outputs": [ 78 | { 79 | "name": "stdout", 80 | "output_type": "stream", 81 | "text": [ 82 | "( -1 , 1 ) = 102\n", 83 | "( 0 , 2 ) = 020\n", 84 | "( 1 , 3 ) = 200\n", 85 | "( 2 , 4 ) = 001\n", 86 | "( 3 , 5 ) = 010\n", 87 | "( 4 , 6 ) = 100\n", 88 | "( 5 , 7 ) = 003\n", 89 | "( 6 , 8 ) = 031\n", 90 | "( 7 , 9 ) = 310\n" 91 | ] 92 | } 93 | ], 94 | "source": [ 95 | "# Test get_cells_range function\n", 96 | "automaton = [0, 2, 0, 0, 1, 0, 0, 3, 1]\n", 97 | "n_cell = len(automaton)\n", 98 | "r = 1\n", 99 | "for i in range(n_cell):\n", 100 | " ix_start = i - r\n", 101 | " ix_end = i + r\n", 102 | " print('(', ix_start, ',', ix_end, ') =', get_cells_range(automaton, ix_start, ix_end, n_cell))" 103 | ] 104 | }, 105 | { 106 | "cell_type": "markdown", 107 | "metadata": {}, 108 | "source": [ 109 | "### 8.1.2. Apply evolution rule: 30" 110 | ] 111 | }, 112 | { 113 | "cell_type": "code", 114 | "execution_count": 4, 115 | "metadata": {}, 116 | "outputs": [], 117 | "source": [ 118 | "# Evolve the LCA\n", 119 | "def evolve_lca_automaton(init_state, max_gen, rule, r):\n", 120 | " universe = [init_state]\n", 121 | " n_cell = len(init_state)\n", 122 | " \n", 123 | " for g in range(max_gen):\n", 124 | " new_automaton = []\n", 125 | " automaton = universe[g]\n", 126 | " \n", 127 | " for i in range(n_cell):\n", 128 | " ix_start = i - r\n", 129 | " ix_end = i + r\n", 130 | " gen = get_cells_range(automaton, ix_start, ix_end, n_cell)\n", 131 | " cell = rule[gen]\n", 132 | " new_automaton.append(cell)\n", 133 | " universe.append(new_automaton)\n", 134 | " \n", 135 | " return universe" 136 | ] 137 | }, 138 | { 139 | "cell_type": "code", 140 | "execution_count": 5, 141 | "metadata": {}, 142 | "outputs": [], 143 | "source": [ 144 | "# Creates a LCA rule\n", 145 | "def create_lca_rule(byte, b_size = 8):\n", 146 | " rule = {}\n", 147 | " bits = bin(byte)[2:].zfill(b_size)\n", 148 | " \n", 149 | " for i in range(0, b_size):\n", 150 | " bin_key = bin(i)[2:].zfill(3)\n", 151 | " bin_value = bits[(b_size - i - 1)]\n", 152 | " rule[bin_key] = int(bin_value)\n", 153 | " \n", 154 | " return rule;" 155 | ] 156 | }, 157 | { 158 | "cell_type": "code", 159 | "execution_count": 6, 160 | "metadata": {}, 161 | "outputs": [ 162 | { 163 | "data": { 164 | "text/plain": [ 165 | "{'000': 0,\n", 166 | " '001': 1,\n", 167 | " '010': 1,\n", 168 | " '011': 1,\n", 169 | " '100': 1,\n", 170 | " '101': 0,\n", 171 | " '110': 0,\n", 172 | " '111': 0}" 173 | ] 174 | }, 175 | "execution_count": 6, 176 | "metadata": {}, 177 | "output_type": "execute_result" 178 | } 179 | ], 180 | "source": [ 181 | "# Rule: bin(30) = 00011110\n", 182 | "rule = create_lca_rule(30)\n", 183 | "rule" 184 | ] 185 | }, 186 | { 187 | "cell_type": "code", 188 | "execution_count": 7, 189 | "metadata": {}, 190 | "outputs": [], 191 | "source": [ 192 | "# Initial state of cellular automaton\n", 193 | "n_cell = 43\n", 194 | "init_state = list(np.zeros(n_cell).astype(int))\n", 195 | "init_state[n_cell // 2] = 1" 196 | ] 197 | }, 198 | { 199 | "cell_type": "code", 200 | "execution_count": 8, 201 | "metadata": {}, 202 | "outputs": [], 203 | "source": [ 204 | "# Evolve the linear cellular automaton\n", 205 | "max_generations = 21\n", 206 | "r = 1\n", 207 | "universe = evolve_lca_automaton(init_state, max_generations, rule, r)" 208 | ] 209 | }, 210 | { 211 | "cell_type": "code", 212 | "execution_count": 9, 213 | "metadata": {}, 214 | "outputs": [], 215 | "source": [ 216 | "# Plot the Automaton evolution\n", 217 | "def plot_automata(universe):\n", 218 | " fig, ax = plt.subplots(figsize = (12, 12))\n", 219 | " ax.set_title(\"Automaton Evolution \", fontsize = 20)\n", 220 | " plt.imshow(universe, cmap='Greys', interpolation='nearest')\n", 221 | " plt.xlabel(\"Cells\", fontsize = 11)\n", 222 | " plt.ylabel(\"Generations\", fontsize = 11)\n", 223 | " plt.show()" 224 | ] 225 | }, 226 | { 227 | "cell_type": "code", 228 | "execution_count": 10, 229 | "metadata": {}, 230 | "outputs": [ 231 | { 232 | "data": { 233 | "image/png": "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\n", 234 | "text/plain": [ 235 | "
" 236 | ] 237 | }, 238 | "metadata": { 239 | "needs_background": "light" 240 | }, 241 | "output_type": "display_data" 242 | } 243 | ], 244 | "source": [ 245 | "# Plotting automaton\n", 246 | "plot_automata(universe)" 247 | ] 248 | }, 249 | { 250 | "cell_type": "markdown", 251 | "metadata": {}, 252 | "source": [ 253 | "### 8.1.3. Apply evolution rule: 90" 254 | ] 255 | }, 256 | { 257 | "cell_type": "code", 258 | "execution_count": 11, 259 | "metadata": {}, 260 | "outputs": [ 261 | { 262 | "data": { 263 | "text/plain": [ 264 | "{'000': 0,\n", 265 | " '001': 1,\n", 266 | " '010': 0,\n", 267 | " '011': 1,\n", 268 | " '100': 1,\n", 269 | " '101': 0,\n", 270 | " '110': 1,\n", 271 | " '111': 0}" 272 | ] 273 | }, 274 | "execution_count": 11, 275 | "metadata": {}, 276 | "output_type": "execute_result" 277 | } 278 | ], 279 | "source": [ 280 | "# Rule: bin(90) = 01011010\n", 281 | "rule = create_lca_rule(90)\n", 282 | "rule" 283 | ] 284 | }, 285 | { 286 | "cell_type": "code", 287 | "execution_count": 12, 288 | "metadata": {}, 289 | "outputs": [], 290 | "source": [ 291 | "# Initial state of the linear cellular automaton\n", 292 | "n_cell = 257\n", 293 | "init_state = list(np.zeros(n_cell).astype(int))\n", 294 | "init_state[n_cell // 2] = 1" 295 | ] 296 | }, 297 | { 298 | "cell_type": "code", 299 | "execution_count": 13, 300 | "metadata": {}, 301 | "outputs": [], 302 | "source": [ 303 | "# Evolve the linear cellular automaton\n", 304 | "max_generations = 128\n", 305 | "r = 1\n", 306 | "universe = evolve_lca_automaton(init_state, max_generations, rule, r)" 307 | ] 308 | }, 309 | { 310 | "cell_type": "code", 311 | "execution_count": 14, 312 | "metadata": {}, 313 | "outputs": [ 314 | { 315 | "data": { 316 | "image/png": "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\n", 317 | "text/plain": [ 318 | "
" 319 | ] 320 | }, 321 | "metadata": { 322 | "needs_background": "light" 323 | }, 324 | "output_type": "display_data" 325 | } 326 | ], 327 | "source": [ 328 | "# Plotting automaton\n", 329 | "plot_automata(universe)" 330 | ] 331 | }, 332 | { 333 | "cell_type": "markdown", 334 | "metadata": {}, 335 | "source": [ 336 | "## 8.2. Reversible Linear Cellular Automata (RLCA)" 337 | ] 338 | }, 339 | { 340 | "cell_type": "markdown", 341 | "metadata": {}, 342 | "source": [ 343 | "#### Util Functions: converts byte list to binary string and vice versa" 344 | ] 345 | }, 346 | { 347 | "cell_type": "code", 348 | "execution_count": 15, 349 | "metadata": {}, 350 | "outputs": [], 351 | "source": [ 352 | "# Convert a byte list to a bits string\n", 353 | "def byte_list_to_bit_string(byte_list):\n", 354 | " bit_string = ''\n", 355 | " for byte in byte_list:\n", 356 | " bit_string += bin(byte)[2:].zfill(8)\n", 357 | " return bit_string\n", 358 | "\n", 359 | "# Convert a bits string to a byte list\n", 360 | "def bit_string_to_byte_list(bit_string):\n", 361 | " byte_list = []\n", 362 | " n = len(bit_string) // 8\n", 363 | " for i in range(n):\n", 364 | " ix_start = i * 8\n", 365 | " ix_end = 8 + i * 8\n", 366 | " byte = bit_string[ix_start:ix_end]\n", 367 | " byte_list.append(int(byte, 2))\n", 368 | " return byte_list\n", 369 | "\n", 370 | "# Update a string by index\n", 371 | "def update_string(s, i, v):\n", 372 | " ns = s[:i] + v + s[i+1:]\n", 373 | " return ns" 374 | ] 375 | }, 376 | { 377 | "cell_type": "markdown", 378 | "metadata": {}, 379 | "source": [ 380 | "### 8.2.1. Reversible Linear Cellular Automata functions" 381 | ] 382 | }, 383 | { 384 | "cell_type": "markdown", 385 | "metadata": {}, 386 | "source": [ 387 | "**Totalistic:** A special class of cellular automata are totalistic cellular automata. The state of each cell in a totalistic cellular automaton is represented by a number (usually an integer value drawn from a finite set), and the value of a cell at time t depends only on the sum of the values of the cells in its neighborhood (possibly including the cell itself) at time t − 1." 388 | ] 389 | }, 390 | { 391 | "cell_type": "code", 392 | "execution_count": 16, 393 | "metadata": {}, 394 | "outputs": [], 395 | "source": [ 396 | "# Creates a RLCA totalistic rule\n", 397 | "def create_rlca_rule(byte, r = 2):\n", 398 | " rule = {}\n", 399 | " r_size = 2 * r + 1\n", 400 | " bits = bin(byte)[2:].zfill(r_size)\n", 401 | " b_size = len(bits)\n", 402 | " \n", 403 | " for i in range(0, b_size):\n", 404 | " bin_value = bits[(b_size - i - 1)]\n", 405 | " rule[i] = int(bin_value)\n", 406 | " \n", 407 | " return rule" 408 | ] 409 | }, 410 | { 411 | "cell_type": "code", 412 | "execution_count": 17, 413 | "metadata": {}, 414 | "outputs": [], 415 | "source": [ 416 | "# Get automata cells value\n", 417 | "def get_cells_value(automaton, ix, r, n):\n", 418 | " value = 0\n", 419 | " ix_start = max(ix - r, 0)\n", 420 | " ix_end = min(ix + r + 1, n)\n", 421 | " \n", 422 | " for i in range(ix_start, ix_end):\n", 423 | " if i != ix:\n", 424 | " value += int(automaton[i])\n", 425 | " \n", 426 | " return value" 427 | ] 428 | }, 429 | { 430 | "cell_type": "code", 431 | "execution_count": 18, 432 | "metadata": {}, 433 | "outputs": [], 434 | "source": [ 435 | "# Apply Linear Cellular Automata rule to current automaton\n", 436 | "def apply_rlca_rule(automaton, rule, r, forward):\n", 437 | " n_cell = len(automaton)\n", 438 | " new_automaton = []\n", 439 | " curr_bit_string = (automaton + '.')[:-1]\n", 440 | " \n", 441 | " if forward:\n", 442 | " curr_range = range(0, n_cell, 1)\n", 443 | " else:\n", 444 | " curr_range = range(n_cell - 1, -1, -1)\n", 445 | " \n", 446 | " for i in curr_range:\n", 447 | " gen_ix = get_cells_value(curr_bit_string, i, r, n_cell)\n", 448 | " cell = rule[gen_ix]\n", 449 | " new_cell = str(int(not(int(curr_bit_string[i]) ^ cell)))\n", 450 | " \n", 451 | " if new_cell != curr_bit_string[i]:\n", 452 | " curr_bit_string = update_string(curr_bit_string, i, new_cell)\n", 453 | " \n", 454 | " # Code new automaton and return it\n", 455 | " new_automaton = bit_string_to_byte_list(curr_bit_string)\n", 456 | " return new_automaton" 457 | ] 458 | }, 459 | { 460 | "cell_type": "code", 461 | "execution_count": 19, 462 | "metadata": {}, 463 | "outputs": [], 464 | "source": [ 465 | "# Evolve the RLCA\n", 466 | "def evolve_rlca_automaton(init_state, max_gen, rule, r, forward):\n", 467 | " universe = [init_state]\n", 468 | " n_cell = len(init_state)\n", 469 | " \n", 470 | " for g in range(max_gen):\n", 471 | " automaton = universe[g]\n", 472 | " automaton_bits = byte_list_to_bit_string(automaton)\n", 473 | " new_automaton = apply_rlca_rule(automaton_bits, key, r, forward)\n", 474 | " universe.append(new_automaton)\n", 475 | " \n", 476 | " return universe" 477 | ] 478 | }, 479 | { 480 | "cell_type": "markdown", 481 | "metadata": {}, 482 | "source": [ 483 | "### 8.2.2. Run RLCA: Forward way" 484 | ] 485 | }, 486 | { 487 | "cell_type": "code", 488 | "execution_count": 20, 489 | "metadata": {}, 490 | "outputs": [], 491 | "source": [ 492 | "# Evolution params\n", 493 | "max_generations = 128\n", 494 | "r = 3" 495 | ] 496 | }, 497 | { 498 | "cell_type": "code", 499 | "execution_count": 21, 500 | "metadata": {}, 501 | "outputs": [ 502 | { 503 | "data": { 504 | "text/plain": [ 505 | "{0: 0, 1: 1, 2: 1, 3: 1, 4: 0, 5: 0, 6: 0}" 506 | ] 507 | }, 508 | "execution_count": 21, 509 | "metadata": {}, 510 | "output_type": "execute_result" 511 | } 512 | ], 513 | "source": [ 514 | "# Totalistic Rule: bin(14) = 0001110\n", 515 | "key = create_rlca_rule(14, r)\n", 516 | "key" 517 | ] 518 | }, 519 | { 520 | "cell_type": "code", 521 | "execution_count": 22, 522 | "metadata": {}, 523 | "outputs": [], 524 | "source": [ 525 | "# Initial state of the linear cellular automaton\n", 526 | "n_cell = 257\n", 527 | "init_state = list(np.zeros(n_cell).astype(int))\n", 528 | "init_state[n_cell // 2] = 1" 529 | ] 530 | }, 531 | { 532 | "cell_type": "code", 533 | "execution_count": 23, 534 | "metadata": {}, 535 | "outputs": [], 536 | "source": [ 537 | "# Evolve the reversible linear cellular automata\n", 538 | "forward = True\n", 539 | "universe = evolve_rlca_automaton(init_state, max_generations, rule, r, forward)" 540 | ] 541 | }, 542 | { 543 | "cell_type": "code", 544 | "execution_count": 24, 545 | "metadata": {}, 546 | "outputs": [ 547 | { 548 | "data": { 549 | "image/png": "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\n", 550 | "text/plain": [ 551 | "
" 552 | ] 553 | }, 554 | "metadata": { 555 | "needs_background": "light" 556 | }, 557 | "output_type": "display_data" 558 | } 559 | ], 560 | "source": [ 561 | "# Plotting RLCA\n", 562 | "plot_automata(universe)" 563 | ] 564 | }, 565 | { 566 | "cell_type": "markdown", 567 | "metadata": {}, 568 | "source": [ 569 | "### 8.2.3. Run RLCA: Backward way" 570 | ] 571 | }, 572 | { 573 | "cell_type": "code", 574 | "execution_count": 25, 575 | "metadata": {}, 576 | "outputs": [], 577 | "source": [ 578 | "# Initial state of the linear cellular automaton\n", 579 | "init_state2 = universe[max_generations - 1]" 580 | ] 581 | }, 582 | { 583 | "cell_type": "code", 584 | "execution_count": 26, 585 | "metadata": {}, 586 | "outputs": [], 587 | "source": [ 588 | "# Evolve the reversible linear cellular automata\n", 589 | "forward = False\n", 590 | "universe2 = evolve_rlca_automaton(init_state2, max_generations, rule, r, forward)" 591 | ] 592 | }, 593 | { 594 | "cell_type": "code", 595 | "execution_count": 27, 596 | "metadata": {}, 597 | "outputs": [ 598 | { 599 | "data": { 600 | "image/png": "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\n", 601 | "text/plain": [ 602 | "
" 603 | ] 604 | }, 605 | "metadata": { 606 | "needs_background": "light" 607 | }, 608 | "output_type": "display_data" 609 | } 610 | ], 611 | "source": [ 612 | "# Plotting RLCA\n", 613 | "plot_automata(universe2)" 614 | ] 615 | }, 616 | { 617 | "cell_type": "markdown", 618 | "metadata": {}, 619 | "source": [ 620 | "### 8.2.4. Validation of the Reversibility of the Automata" 621 | ] 622 | }, 623 | { 624 | "cell_type": "code", 625 | "execution_count": 28, 626 | "metadata": {}, 627 | "outputs": [ 628 | { 629 | "data": { 630 | "text/plain": [ 631 | "True" 632 | ] 633 | }, 634 | "execution_count": 28, 635 | "metadata": {}, 636 | "output_type": "execute_result" 637 | } 638 | ], 639 | "source": [ 640 | "universe[0] == universe2[max_generations - 1]" 641 | ] 642 | }, 643 | { 644 | "cell_type": "markdown", 645 | "metadata": {}, 646 | "source": [ 647 | "
\n", 648 | "

« Home

" 649 | ] 650 | } 651 | ], 652 | "metadata": { 653 | "kernelspec": { 654 | "display_name": "Python 3", 655 | "language": "python", 656 | "name": "python3" 657 | }, 658 | "language_info": { 659 | "codemirror_mode": { 660 | "name": "ipython", 661 | "version": 3 662 | }, 663 | "file_extension": ".py", 664 | "mimetype": "text/x-python", 665 | "name": "python", 666 | "nbconvert_exporter": "python", 667 | "pygments_lexer": "ipython3", 668 | "version": "3.8.5" 669 | }, 670 | "varInspector": { 671 | "cols": { 672 | "lenName": 16, 673 | "lenType": 16, 674 | "lenVar": 40 675 | }, 676 | "kernels_config": { 677 | "python": { 678 | "delete_cmd_postfix": "", 679 | "delete_cmd_prefix": "del ", 680 | "library": "var_list.py", 681 | "varRefreshCmd": "print(var_dic_list())" 682 | }, 683 | "r": { 684 | "delete_cmd_postfix": ") ", 685 | "delete_cmd_prefix": "rm(", 686 | "library": "var_list.r", 687 | "varRefreshCmd": "cat(var_dic_list()) " 688 | } 689 | }, 690 | "types_to_exclude": [ 691 | "module", 692 | "function", 693 | "builtin_function_or_method", 694 | "instance", 695 | "_Feature" 696 | ], 697 | "window_display": false 698 | } 699 | }, 700 | "nbformat": 4, 701 | "nbformat_minor": 2 702 | } 703 | -------------------------------------------------------------------------------- /docs/CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | We as members, contributors, and leaders pledge to make participation in our 6 | community a harassment-free experience for everyone, regardless of age, body 7 | size, visible or invisible disability, ethnicity, sex characteristics, gender 8 | identity and expression, level of experience, education, socio-economic status, 9 | nationality, personal appearance, race, religion, or sexual identity 10 | and orientation. 11 | 12 | We pledge to act and interact in ways that contribute to an open, welcoming, 13 | diverse, inclusive, and healthy community. 14 | 15 | ## Our Standards 16 | 17 | Examples of behavior that contributes to a positive environment for our 18 | community include: 19 | 20 | * Demonstrating empathy and kindness toward other people 21 | * Being respectful of differing opinions, viewpoints, and experiences 22 | * Giving and gracefully accepting constructive feedback 23 | * Accepting responsibility and apologizing to those affected by our mistakes, 24 | and learning from the experience 25 | * Focusing on what is best not just for us as individuals, but for the 26 | overall community 27 | 28 | Examples of unacceptable behavior include: 29 | 30 | * The use of sexualized language or imagery, and sexual attention or 31 | advances of any kind 32 | * Trolling, insulting or derogatory comments, and personal or political attacks 33 | * Public or private harassment 34 | * Publishing others' private information, such as a physical or email 35 | address, without their explicit permission 36 | * Other conduct which could reasonably be considered inappropriate in a 37 | professional setting 38 | 39 | ## Enforcement Responsibilities 40 | 41 | Community leaders are responsible for clarifying and enforcing our standards of 42 | acceptable behavior and will take appropriate and fair corrective action in 43 | response to any behavior that they deem inappropriate, threatening, offensive, 44 | or harmful. 45 | 46 | Community leaders have the right and responsibility to remove, edit, or reject 47 | comments, commits, code, wiki edits, issues, and other contributions that are 48 | not aligned to this Code of Conduct, and will communicate reasons for moderation 49 | decisions when appropriate. 50 | 51 | ## Scope 52 | 53 | This Code of Conduct applies within all community spaces, and also applies when 54 | an individual is officially representing the community in public spaces. 55 | Examples of representing our community include using an official e-mail address, 56 | posting via an official social media account, or acting as an appointed 57 | representative at an online or offline event. 58 | 59 | ## Enforcement 60 | 61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 62 | reported to the community leaders responsible for enforcement. 63 | 64 | All complaints will be reviewed and investigated promptly and fairly. 65 | 66 | All community leaders are obligated to respect the privacy and security of the 67 | reporter of any incident. 68 | 69 | ## Enforcement Guidelines 70 | 71 | Community leaders will follow these Community Impact Guidelines in determining 72 | the consequences for any action they deem in violation of this Code of Conduct: 73 | 74 | ### 1. Correction 75 | 76 | **Community Impact**: Use of inappropriate language or other behavior deemed 77 | unprofessional or unwelcome in the community. 78 | 79 | **Consequence**: A private, written warning from community leaders, providing 80 | clarity around the nature of the violation and an explanation of why the 81 | behavior was inappropriate. A public apology may be requested. 82 | 83 | ### 2. Warning 84 | 85 | **Community Impact**: A violation through a single incident or series 86 | of actions. 87 | 88 | **Consequence**: A warning with consequences for continued behavior. No 89 | interaction with the people involved, including unsolicited interaction with 90 | those enforcing the Code of Conduct, for a specified period of time. This 91 | includes avoiding interactions in community spaces as well as external channels 92 | like social media. Violating these terms may lead to a temporary or 93 | permanent ban. 94 | 95 | ### 3. Temporary Ban 96 | 97 | **Community Impact**: A serious violation of community standards, including 98 | sustained inappropriate behavior. 99 | 100 | **Consequence**: A temporary ban from any sort of interaction or public 101 | communication with the community for a specified period of time. No public or 102 | private interaction with the people involved, including unsolicited interaction 103 | with those enforcing the Code of Conduct, is allowed during this period. 104 | Violating these terms may lead to a permanent ban. 105 | 106 | ### 4. Permanent Ban 107 | 108 | **Community Impact**: Demonstrating a pattern of violation of community 109 | standards, including sustained inappropriate behavior, harassment of an 110 | individual, or aggression toward or disparagement of classes of individuals. 111 | 112 | **Consequence**: A permanent ban from any sort of public interaction within 113 | the community. 114 | 115 | ## Attribution 116 | 117 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 118 | version 2.0, available at 119 | https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. 120 | 121 | Community Impact Guidelines were inspired by [Mozilla's code of conduct 122 | enforcement ladder](https://github.com/mozilla/diversity). 123 | 124 | [homepage]: https://www.contributor-covenant.org 125 | 126 | For answers to common questions about this code of conduct, see the FAQ at 127 | https://www.contributor-covenant.org/faq. Translations are available at 128 | https://www.contributor-covenant.org/translations. 129 | -------------------------------------------------------------------------------- /docs/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | Welcome, and thank you for your interest in helping to improve and accelerate/ease the adoption of the Python Algorithms Course. 3 | 4 | There are many ways in which you can contribute, beyond writing code. The goal of this document is to provide a high-level overview of how you can get involved, and hopefully not feel intimidated. 5 | 6 | ## Asking Questions and Providing Feedback 7 | Have a question? Rather than emailing the author, open an issue. 8 | 9 | The community will be eager to assist you. Your well-worded question will serve as a resource to others searching for help. 10 | 11 | ## Reporting Issues and Ideas 12 | Have you identified an oversight or problem? Have a feature request? We want to hear about it! Here's how you can make reporting your issue as effective as possible. 13 | 14 | > **Note:** If you already know what you want to change, feel free to just fork/clone the repo, change it, and submit a pull request. No need to add overhead by creating an issue! 15 | 16 | ### Look For an Existing Issue 17 | Before you create a new issue, please do a search in [open issues](https://github.com/ansegura7/Algorithms/issues) to see if the issue or feature request has already been filed. 18 | 19 | If you cannot find an existing issue that describes your bug or feature, create a new issue using the guidelines below. 20 | 21 | ### Writing Good Bug Reports and Feature Requests 22 | File a single issue per problem and feature request. Do not enumerate multiple bugs or feature requests in the same issue. 23 | 24 | Below is some information you can provide. The more you can provide, the more likely someone will be successful at understanding and incorporating it. However be mindful of the cost/benefit for documenting vs simply implementing the change. 25 | 26 | Please include the following with each issue: 27 | - **Title** - Concise and clear to quickly identify the topic. 28 | - **Problem** - Summary of the issue/idea/feature. 29 | - **Possible Solution** - If a solution seems clear, share it as an option. 30 | - **Examples** - "What was expected" vs "What actually ocurred". 31 | - **Context** - External factors that restrict possible solutions. (stuff that can't be changed) 32 | > Example: Please upgrade the vehicle from 30mph to 60mph. Context: budget is $5000. 33 | 34 | # Thank You! 35 | Your contributions to the Python Algorithms Course, large or small, make great projects like this possible. Thank you for taking the time to contribute! 36 | -------------------------------------------------------------------------------- /dynamic-programming/DynamicProgramming.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# 4. Dynamic Programming" 8 | ] 9 | }, 10 | { 11 | "cell_type": "markdown", 12 | "metadata": {}, 13 | "source": [ 14 | "- **Created by Andrés Segura Tinoco**\n", 15 | "- **Created on Jan 26, 2020**\n", 16 | "- **Updated on May 18, 2021**" 17 | ] 18 | }, 19 | { 20 | "cell_type": "markdown", 21 | "metadata": {}, 22 | "source": [ 23 | "**Dynamic programming** is an efficient technique for solving many combinatorial optimization problems in a polynomial time." 24 | ] 25 | }, 26 | { 27 | "cell_type": "markdown", 28 | "metadata": {}, 29 | "source": [ 30 | "Dynamic programming is both a mathematical optimization method and a computer programming method. In both contexts it refers to simplifying a complicated problem by breaking it down into simpler sub-problems in a recursive manner [1]. There are two key attributes that a problem must have in order for dynamic programming to be applicable: optimal substructure and overlapping sub-problems." 31 | ] 32 | }, 33 | { 34 | "cell_type": "markdown", 35 | "metadata": {}, 36 | "source": [ 37 | "### Principle of Optimality\n", 38 | "An optimal policy has the property that whatever the initial state and initial decision are, the remaining decisions must constitute an optimal policy with regard to the state resulting from the first decision [2]. (See Bellman, 1957, Chap. III.3.)" 39 | ] 40 | }, 41 | { 42 | "cell_type": "markdown", 43 | "metadata": {}, 44 | "source": [ 45 | "## 4.1. Binomial Coefficient" 46 | ] 47 | }, 48 | { 49 | "cell_type": "markdown", 50 | "metadata": {}, 51 | "source": [ 52 | "In mathematics, the **binomial coefficients** are the positive integers that occur as coefficients in the binomial theorem [3]. Commonly, a binomial coefficient is indexed by a pair of integers $ n ≥ k ≥ 0 $ and is written $ \\tbinom {n}{k} $. It is the coefficient of the $ x^k $ term in the polynomial expansion of the binomial power $ (1 + x)^n $, and it is given by the formula:\n", 53 | "\n", 54 | "$$ \\tbinom {n}{k} = \\frac {n!}{k!(n-k)!} \\tag{1}$$" 55 | ] 56 | }, 57 | { 58 | "cell_type": "code", 59 | "execution_count": 1, 60 | "metadata": {}, 61 | "outputs": [], 62 | "source": [ 63 | "# Load the Python libraries\n", 64 | "import timeit\n", 65 | "import math\n", 66 | "import pandas as pd" 67 | ] 68 | }, 69 | { 70 | "cell_type": "code", 71 | "execution_count": 2, 72 | "metadata": {}, 73 | "outputs": [], 74 | "source": [ 75 | "# Example values\n", 76 | "n = 25\n", 77 | "k = 15" 78 | ] 79 | }, 80 | { 81 | "cell_type": "markdown", 82 | "metadata": {}, 83 | "source": [ 84 | "### 4.1.1. Formula approach" 85 | ] 86 | }, 87 | { 88 | "cell_type": "code", 89 | "execution_count": 3, 90 | "metadata": {}, 91 | "outputs": [], 92 | "source": [ 93 | "# Binomial coefficient from the mathematical formula\n", 94 | "def bin_coef_1(n, k):\n", 95 | " return int(math.factorial(n) / (math.factorial(k) * math.factorial(n - k)))" 96 | ] 97 | }, 98 | { 99 | "cell_type": "code", 100 | "execution_count": 4, 101 | "metadata": {}, 102 | "outputs": [ 103 | { 104 | "name": "stdout", 105 | "output_type": "stream", 106 | "text": [ 107 | "3268760\n", 108 | ">> elapsed time 0.3633999999999027 ms\n" 109 | ] 110 | } 111 | ], 112 | "source": [ 113 | "start_time = timeit.default_timer()\n", 114 | "print(bin_coef_1(n, k))\n", 115 | "print('>> elapsed time', (timeit.default_timer() - start_time) * 1000, 'ms')" 116 | ] 117 | }, 118 | { 119 | "cell_type": "markdown", 120 | "metadata": {}, 121 | "source": [ 122 | "### 4.1.2. Simple approach" 123 | ] 124 | }, 125 | { 126 | "cell_type": "code", 127 | "execution_count": 5, 128 | "metadata": {}, 129 | "outputs": [], 130 | "source": [ 131 | "# The recursive natural solution\n", 132 | "def bin_coef_2(n, k):\n", 133 | " if k == 0 or k == n:\n", 134 | " return 1\n", 135 | " return bin_coef_2(n - 1, k - 1) + bin_coef_2(n - 1, k)" 136 | ] 137 | }, 138 | { 139 | "cell_type": "code", 140 | "execution_count": 6, 141 | "metadata": {}, 142 | "outputs": [ 143 | { 144 | "name": "stdout", 145 | "output_type": "stream", 146 | "text": [ 147 | "3268760\n", 148 | ">> elapsed time 1602.7969 ms\n" 149 | ] 150 | } 151 | ], 152 | "source": [ 153 | "start_time = timeit.default_timer()\n", 154 | "print(bin_coef_2(n, k))\n", 155 | "print('>> elapsed time', (timeit.default_timer() - start_time) * 1000, 'ms')" 156 | ] 157 | }, 158 | { 159 | "cell_type": "markdown", 160 | "metadata": {}, 161 | "source": [ 162 | "### 4.1.3. Dynamic Programming" 163 | ] 164 | }, 165 | { 166 | "cell_type": "code", 167 | "execution_count": 7, 168 | "metadata": {}, 169 | "outputs": [], 170 | "source": [ 171 | "# Solution with dynamic programming (supported by a table)\n", 172 | "def bin_coef_3(n, k):\n", 173 | " c = 0\n", 174 | " v = [1] * (k + 1)\n", 175 | " \n", 176 | " for i in range(n + 1):\n", 177 | " for j in range(k, 0, -1):\n", 178 | " if j < i:\n", 179 | " v[j] = v[j - 1] + v[j]\n", 180 | " \n", 181 | " return v[k]" 182 | ] 183 | }, 184 | { 185 | "cell_type": "code", 186 | "execution_count": 8, 187 | "metadata": {}, 188 | "outputs": [ 189 | { 190 | "name": "stdout", 191 | "output_type": "stream", 192 | "text": [ 193 | "3268760\n", 194 | ">> elapsed time 1.0575999999997698 ms\n" 195 | ] 196 | } 197 | ], 198 | "source": [ 199 | "start_time = timeit.default_timer()\n", 200 | "print(bin_coef_3(n, k))\n", 201 | "print('>> elapsed time', (timeit.default_timer() - start_time) * 1000, 'ms')" 202 | ] 203 | }, 204 | { 205 | "cell_type": "markdown", 206 | "metadata": {}, 207 | "source": [ 208 | "With time complexity of $ \\Theta(nk) $ and a space complexity of $ \\Theta(k) $." 209 | ] 210 | }, 211 | { 212 | "cell_type": "markdown", 213 | "metadata": {}, 214 | "source": [ 215 | "## 4.2. World Championship problem" 216 | ] 217 | }, 218 | { 219 | "cell_type": "code", 220 | "execution_count": 9, 221 | "metadata": {}, 222 | "outputs": [], 223 | "source": [ 224 | "# Example values\n", 225 | "n = 10\n", 226 | "p = 0.55\n", 227 | "q = 1 - p" 228 | ] 229 | }, 230 | { 231 | "cell_type": "markdown", 232 | "metadata": {}, 233 | "source": [ 234 | "### 4.2.1. Simple approach" 235 | ] 236 | }, 237 | { 238 | "cell_type": "code", 239 | "execution_count": 10, 240 | "metadata": {}, 241 | "outputs": [], 242 | "source": [ 243 | "# The recursive natural solution\n", 244 | "def WCP(i, j):\n", 245 | " if i == 0:\n", 246 | " return 1\n", 247 | " elif j == 0:\n", 248 | " return 0\n", 249 | " return p * WCP(i - 1, j) + q * WCP(i, j - 1)" 250 | ] 251 | }, 252 | { 253 | "cell_type": "code", 254 | "execution_count": 11, 255 | "metadata": {}, 256 | "outputs": [ 257 | { 258 | "name": "stdout", 259 | "output_type": "stream", 260 | "text": [ 261 | "0.6710359124216079\n", 262 | ">> elapsed time 152.84509999999952 ms\n" 263 | ] 264 | } 265 | ], 266 | "source": [ 267 | "start_time = timeit.default_timer()\n", 268 | "print(WCP(n, n))\n", 269 | "print('>> elapsed time', (timeit.default_timer() - start_time) * 1000, 'ms')" 270 | ] 271 | }, 272 | { 273 | "cell_type": "markdown", 274 | "metadata": {}, 275 | "source": [ 276 | "### 4.2.2. Dynamic Programming" 277 | ] 278 | }, 279 | { 280 | "cell_type": "code", 281 | "execution_count": 12, 282 | "metadata": {}, 283 | "outputs": [], 284 | "source": [ 285 | "# Solution with dynamic programming (supported by a table)\n", 286 | "def WCP2(n, p):\n", 287 | " n = n + 1\n", 288 | " q = 1 - p\n", 289 | " prob = [[0] * n for i in range(n)]\n", 290 | " \n", 291 | " for s in range(n):\n", 292 | " prob[0][s] = 1\n", 293 | " for k in range(1, s):\n", 294 | " prob[k][s - k] = p * prob[k - 1][s - k] + q * prob[k][s - k - 1]\n", 295 | " \n", 296 | " for s in range(1, n):\n", 297 | " for k in range(0, n - s):\n", 298 | " prob[s + k][n - k - 1] = p * prob[s + k - 1][n - k - 1] + q * prob[s + k][n - k - 2]\n", 299 | " \n", 300 | " return prob[n - 1][n - 1]" 301 | ] 302 | }, 303 | { 304 | "cell_type": "code", 305 | "execution_count": 13, 306 | "metadata": {}, 307 | "outputs": [ 308 | { 309 | "name": "stdout", 310 | "output_type": "stream", 311 | "text": [ 312 | "0.6710359124216079\n", 313 | ">> elapsed time 3.256300000000323 ms\n" 314 | ] 315 | } 316 | ], 317 | "source": [ 318 | "start_time = timeit.default_timer()\n", 319 | "print(WCP2(n, p))\n", 320 | "print('>> elapsed time', (timeit.default_timer() - start_time) * 1000, 'ms')" 321 | ] 322 | }, 323 | { 324 | "cell_type": "markdown", 325 | "metadata": {}, 326 | "source": [ 327 | "With time complexity of $ \\Theta(n^2) $ and a space complexity of $ \\Theta(n^2) $." 328 | ] 329 | }, 330 | { 331 | "cell_type": "markdown", 332 | "metadata": {}, 333 | "source": [ 334 | "## 4.3. Coin Change problem" 335 | ] 336 | }, 337 | { 338 | "cell_type": "markdown", 339 | "metadata": {}, 340 | "source": [ 341 | "The **coin-change problem** or change-making problem addresses the question of finding the minimum number of coins (of certain denominations) that add up to a given amount of money. It is a special case of the integer knapsack problem, and has applications wider than just currency [4]." 342 | ] 343 | }, 344 | { 345 | "cell_type": "markdown", 346 | "metadata": {}, 347 | "source": [ 348 | "#### Returns all possible combinations of coins change with Dynamic Programming\n", 349 | "Version with unlimited supply of coins." 350 | ] 351 | }, 352 | { 353 | "cell_type": "code", 354 | "execution_count": 14, 355 | "metadata": {}, 356 | "outputs": [], 357 | "source": [ 358 | "def calc_coin_change(N, d):\n", 359 | " n = len(d)\n", 360 | " matrix = [[0] * (N + 1) for i in range(n)]\n", 361 | " \n", 362 | " for i in range(0, n):\n", 363 | " for j in range(1, N + 1):\n", 364 | " if i == 0 and j < d[i]:\n", 365 | " matrix[i][j] = math.inf\n", 366 | " elif i == 0:\n", 367 | " matrix[i][j] = 1 + matrix[0][j - d[0]]\n", 368 | " elif j < d[i]:\n", 369 | " matrix[i][j] = matrix[i - 1][j]\n", 370 | " else:\n", 371 | " matrix[i][j] = min(matrix[i - 1][j], 1 + matrix[i][j - d[i]])\n", 372 | " \n", 373 | " return matrix" 374 | ] 375 | }, 376 | { 377 | "cell_type": "code", 378 | "execution_count": 15, 379 | "metadata": {}, 380 | "outputs": [ 381 | { 382 | "data": { 383 | "text/html": [ 384 | "
\n", 385 | "\n", 398 | "\n", 399 | " \n", 400 | " \n", 401 | " \n", 402 | " \n", 403 | " \n", 404 | " \n", 405 | " \n", 406 | " \n", 407 | " \n", 408 | " \n", 409 | " \n", 410 | " \n", 411 | " \n", 412 | " \n", 413 | " \n", 414 | " \n", 415 | " \n", 416 | " \n", 417 | " \n", 418 | " \n", 419 | " \n", 420 | " \n", 421 | " \n", 422 | " \n", 423 | " \n", 424 | " \n", 425 | " \n", 426 | " \n", 427 | " \n", 428 | " \n", 429 | " \n", 430 | " \n", 431 | " \n", 432 | " \n", 433 | " \n", 434 | " \n", 435 | " \n", 436 | " \n", 437 | " \n", 438 | " \n", 439 | " \n", 440 | " \n", 441 | " \n", 442 | " \n", 443 | " \n", 444 | " \n", 445 | " \n", 446 | " \n", 447 | " \n", 448 | " \n", 449 | " \n", 450 | " \n", 451 | "
012345678
1012345678
4012312342
6012312122
\n", 452 | "
" 453 | ], 454 | "text/plain": [ 455 | " 0 1 2 3 4 5 6 7 8\n", 456 | "1 0 1 2 3 4 5 6 7 8\n", 457 | "4 0 1 2 3 1 2 3 4 2\n", 458 | "6 0 1 2 3 1 2 1 2 2" 459 | ] 460 | }, 461 | "execution_count": 15, 462 | "metadata": {}, 463 | "output_type": "execute_result" 464 | } 465 | ], 466 | "source": [ 467 | "# Example values\n", 468 | "N = 8\n", 469 | "d = [1, 4, 6]\n", 470 | "\n", 471 | "# Showing results\n", 472 | "dp_table = calc_coin_change(N, d)\n", 473 | "pd.DataFrame(dp_table, index=d)" 474 | ] 475 | }, 476 | { 477 | "cell_type": "markdown", 478 | "metadata": {}, 479 | "source": [ 480 | "With time complexity of $ \\Theta(nN) $ and a space complexity of $ \\Theta(n(N + 1)) $." 481 | ] 482 | }, 483 | { 484 | "cell_type": "markdown", 485 | "metadata": {}, 486 | "source": [ 487 | "#### Calculate the list of coins needed to give change\n", 488 | "Greedy approach" 489 | ] 490 | }, 491 | { 492 | "cell_type": "code", 493 | "execution_count": 16, 494 | "metadata": {}, 495 | "outputs": [], 496 | "source": [ 497 | "def get_coins_list(c, d, N, verbose=False):\n", 498 | " coins_list = []\n", 499 | " i = len(d) - 1\n", 500 | " j = N\n", 501 | " \n", 502 | " while i > -1 and j > -1:\n", 503 | " if verbose:\n", 504 | " print(i, j)\n", 505 | " \n", 506 | " if i - 1 >= 0 and c[i][j] == c[i - 1][j]:\n", 507 | " i = i - 1\n", 508 | " elif j - d[i] >= 0 and c[i][j] == 1 + c[i][j - d[i]]:\n", 509 | " coins_list.append(d[i])\n", 510 | " j = j - d[i]\n", 511 | " else:\n", 512 | " break\n", 513 | " \n", 514 | " return coins_list" 515 | ] 516 | }, 517 | { 518 | "cell_type": "code", 519 | "execution_count": 17, 520 | "metadata": {}, 521 | "outputs": [ 522 | { 523 | "name": "stdout", 524 | "output_type": "stream", 525 | "text": [ 526 | "0 -> []\n", 527 | "1 -> [1]\n", 528 | "2 -> [1, 1]\n", 529 | "3 -> [1, 1, 1]\n", 530 | "4 -> [4]\n", 531 | "5 -> [4, 1]\n", 532 | "6 -> [6]\n", 533 | "7 -> [6, 1]\n", 534 | "8 -> [4, 4]\n" 535 | ] 536 | } 537 | ], 538 | "source": [ 539 | "# List of coins for each scenario\n", 540 | "for j in range(0, N + 1):\n", 541 | " print(j, '->', get_coins_list(dp_table, d, j))" 542 | ] 543 | }, 544 | { 545 | "cell_type": "markdown", 546 | "metadata": {}, 547 | "source": [ 548 | "With time complexity of $ \\Theta(n + c[n, N]) $ and a space complexity of $ \\Theta(n(N + 1)) $." 549 | ] 550 | }, 551 | { 552 | "cell_type": "markdown", 553 | "metadata": {}, 554 | "source": [ 555 | "## 4.4. The Knapsack problem" 556 | ] 557 | }, 558 | { 559 | "cell_type": "markdown", 560 | "metadata": {}, 561 | "source": [ 562 | "The **knapsack problem** or rucksack problem is a problem in combinatorial optimization: Given a set of items, each with a weight and a value, determine the number of each item to include in a collection so that the total weight is less than or equal to a given limit **W** and the total value is as large as possible. It derives its name from the problem faced by someone who is constrained by a fixed-size knapsack and must fill it with the most valuable items [5]." 563 | ] 564 | }, 565 | { 566 | "cell_type": "markdown", 567 | "metadata": {}, 568 | "source": [ 569 | "#### Get best items combination with Dynamic Programming" 570 | ] 571 | }, 572 | { 573 | "cell_type": "code", 574 | "execution_count": 18, 575 | "metadata": {}, 576 | "outputs": [], 577 | "source": [ 578 | "def calc_best_knapsack(w, v, W):\n", 579 | " n = len(v)\n", 580 | " matrix = [[0] * (W + 1) for i in range(n)]\n", 581 | " \n", 582 | " for i in range(0, n):\n", 583 | " for j in range(1, W + 1):\n", 584 | " if i == 0 and j < w[i]:\n", 585 | " matrix[i][j] = -math.inf\n", 586 | " elif i == 0:\n", 587 | " matrix[i][j] = v[i]\n", 588 | " elif j < w[i]:\n", 589 | " matrix[i][j] = matrix[i - 1][j]\n", 590 | " else:\n", 591 | " matrix[i][j] = max(matrix[i - 1][j], matrix[i - 1][j - w[i]] + v[i])\n", 592 | " \n", 593 | " return matrix" 594 | ] 595 | }, 596 | { 597 | "cell_type": "code", 598 | "execution_count": 19, 599 | "metadata": {}, 600 | "outputs": [ 601 | { 602 | "data": { 603 | "text/html": [ 604 | "
\n", 605 | "\n", 618 | "\n", 619 | " \n", 620 | " \n", 621 | " \n", 622 | " \n", 623 | " \n", 624 | " \n", 625 | " \n", 626 | " \n", 627 | " \n", 628 | " \n", 629 | " \n", 630 | " \n", 631 | " \n", 632 | " \n", 633 | " \n", 634 | " \n", 635 | " \n", 636 | " \n", 637 | " \n", 638 | " \n", 639 | " \n", 640 | " \n", 641 | " \n", 642 | " \n", 643 | " \n", 644 | " \n", 645 | " \n", 646 | " \n", 647 | " \n", 648 | " \n", 649 | " \n", 650 | " \n", 651 | " \n", 652 | " \n", 653 | " \n", 654 | " \n", 655 | " \n", 656 | " \n", 657 | " \n", 658 | " \n", 659 | " \n", 660 | " \n", 661 | " \n", 662 | " \n", 663 | " \n", 664 | " \n", 665 | " \n", 666 | " \n", 667 | " \n", 668 | " \n", 669 | " \n", 670 | " \n", 671 | " \n", 672 | " \n", 673 | " \n", 674 | " \n", 675 | " \n", 676 | " \n", 677 | " \n", 678 | " \n", 679 | " \n", 680 | " \n", 681 | " \n", 682 | " \n", 683 | " \n", 684 | " \n", 685 | " \n", 686 | " \n", 687 | " \n", 688 | " \n", 689 | " \n", 690 | " \n", 691 | " \n", 692 | " \n", 693 | " \n", 694 | " \n", 695 | " \n", 696 | " \n", 697 | " \n", 698 | " \n", 699 | " \n", 700 | " \n", 701 | " \n", 702 | " \n", 703 | " \n", 704 | " \n", 705 | " \n", 706 | " \n", 707 | " \n", 708 | " \n", 709 | " \n", 710 | " \n", 711 | " \n", 712 | " \n", 713 | "
01234567891011
w:1, v:1011111111111
w:2, v:6016777777777
w:5, v:180167718192425252525
w:6, v:220167718222428292940
w:7, v:280167718222829343540
\n", 714 | "
" 715 | ], 716 | "text/plain": [ 717 | " 0 1 2 3 4 5 6 7 8 9 10 11\n", 718 | "w:1, v:1 0 1 1 1 1 1 1 1 1 1 1 1\n", 719 | "w:2, v:6 0 1 6 7 7 7 7 7 7 7 7 7\n", 720 | "w:5, v:18 0 1 6 7 7 18 19 24 25 25 25 25\n", 721 | "w:6, v:22 0 1 6 7 7 18 22 24 28 29 29 40\n", 722 | "w:7, v:28 0 1 6 7 7 18 22 28 29 34 35 40" 723 | ] 724 | }, 725 | "execution_count": 19, 726 | "metadata": {}, 727 | "output_type": "execute_result" 728 | } 729 | ], 730 | "source": [ 731 | "# Example values\n", 732 | "w = [1, 2, 5, 6, 7]\n", 733 | "v = [1, 6, 18, 22, 28]\n", 734 | "max_weight = 11\n", 735 | "\n", 736 | "# Run algorithm\n", 737 | "dp_table = calc_best_knapsack(w, v, max_weight)\n", 738 | "df_index = [\"w:\" + str(w[i]) + \", v:\" + str(v[i]) for i in range(len(v))]\n", 739 | "pd.DataFrame(dp_table, index=df_index)" 740 | ] 741 | }, 742 | { 743 | "cell_type": "markdown", 744 | "metadata": {}, 745 | "source": [ 746 | "With time complexity of $ \\Theta(nW) $ and a space complexity of $ \\Theta(n(W + 1)) $." 747 | ] 748 | }, 749 | { 750 | "cell_type": "markdown", 751 | "metadata": {}, 752 | "source": [ 753 | "#### Calculate the list of items needed to fill the backpack\n", 754 | "Greedy approach" 755 | ] 756 | }, 757 | { 758 | "cell_type": "code", 759 | "execution_count": 20, 760 | "metadata": {}, 761 | "outputs": [], 762 | "source": [ 763 | "def get_items_list(values, v, w, W, verbose=False):\n", 764 | " item_list = []\n", 765 | " i = len(w) - 1\n", 766 | " j = W\n", 767 | " \n", 768 | " while i > -1 and j > -1:\n", 769 | " if verbose:\n", 770 | " print(i, j)\n", 771 | " \n", 772 | " if i - 1 >= 0 and values[i][j] == values[i - 1][j]:\n", 773 | " i = i - 1\n", 774 | " elif i - 1 >= 0 and j - w[i] >= 0 and values[i][j] == values[i - 1][j - w[i]] + v[i]:\n", 775 | " item = { \"w\": w[i], \"v\": v[i] }\n", 776 | " item_list.append(item)\n", 777 | " j = j - w[i]\n", 778 | " i = i - 1\n", 779 | " elif i == 0 and values[i][j] == v[i]:\n", 780 | " item = { \"w\": w[i], \"v\": v[i] }\n", 781 | " item_list.append(item)\n", 782 | " break\n", 783 | " else:\n", 784 | " break\n", 785 | " \n", 786 | " return item_list" 787 | ] 788 | }, 789 | { 790 | "cell_type": "code", 791 | "execution_count": 21, 792 | "metadata": {}, 793 | "outputs": [ 794 | { 795 | "name": "stdout", 796 | "output_type": "stream", 797 | "text": [ 798 | "0 -> []\n", 799 | "1 -> [{'w': 1, 'v': 1}]\n", 800 | "2 -> [{'w': 2, 'v': 6}]\n", 801 | "3 -> [{'w': 2, 'v': 6}, {'w': 1, 'v': 1}]\n", 802 | "4 -> [{'w': 2, 'v': 6}, {'w': 1, 'v': 1}]\n", 803 | "5 -> [{'w': 5, 'v': 18}]\n", 804 | "6 -> [{'w': 6, 'v': 22}]\n", 805 | "7 -> [{'w': 7, 'v': 28}]\n", 806 | "8 -> [{'w': 7, 'v': 28}, {'w': 1, 'v': 1}]\n", 807 | "9 -> [{'w': 7, 'v': 28}, {'w': 2, 'v': 6}]\n", 808 | "10 -> [{'w': 7, 'v': 28}, {'w': 2, 'v': 6}, {'w': 1, 'v': 1}]\n", 809 | "11 -> [{'w': 6, 'v': 22}, {'w': 5, 'v': 18}]\n" 810 | ] 811 | } 812 | ], 813 | "source": [ 814 | "# List of coins for each scenario\n", 815 | "for j in range(0, max_weight + 1):\n", 816 | " print(j, '->', get_items_list(dp_table, v, w, j))" 817 | ] 818 | }, 819 | { 820 | "cell_type": "markdown", 821 | "metadata": {}, 822 | "source": [ 823 | "With time complexity of $ \\Theta(n + W) $ and a space complexity of $ \\Theta(n(W + 1)) $." 824 | ] 825 | }, 826 | { 827 | "cell_type": "markdown", 828 | "metadata": {}, 829 | "source": [ 830 | "## 4.5. Longest Common Subsequence (LCS) problem" 831 | ] 832 | }, 833 | { 834 | "cell_type": "markdown", 835 | "metadata": {}, 836 | "source": [ 837 | "The **longest common subsequence** (LCS) problem is the problem of finding the longest subsequence common to all sequences in a set of sequences (often just two sequences). It differs from the longest common substring problem: unlike substrings, subsequences are not required to occupy consecutive positions within the original sequences [6].\n", 838 | "\n", 839 | "The longest common subsequence problem is a classic computer science problem, the basis of data comparison programs such as the diff utility, and has applications in computational linguistics and bioinformatics. It is also widely used by revision control systems such as Git for reconciling multiple changes made to a revision-controlled collection of files." 840 | ] 841 | }, 842 | { 843 | "cell_type": "markdown", 844 | "metadata": {}, 845 | "source": [ 846 | "#### Get the Longest Common Subsequence with Dynamic Programming" 847 | ] 848 | }, 849 | { 850 | "cell_type": "code", 851 | "execution_count": 22, 852 | "metadata": {}, 853 | "outputs": [], 854 | "source": [ 855 | "def calc_lcs(a, b):\n", 856 | " n = len(a)\n", 857 | " m = len(b)\n", 858 | " matrix = [[0] * (m + 1) for i in range(n + 1)]\n", 859 | " \n", 860 | " for i in range(1, n + 1):\n", 861 | " for j in range(1, m + 1):\n", 862 | " if a[i - 1] == b[j - 1]:\n", 863 | " matrix[i][j] = 1 + matrix[i - 1][j - 1]\n", 864 | " else:\n", 865 | " matrix[i][j] = max(matrix[i - 1][j], matrix[i][j - 1])\n", 866 | " \n", 867 | " return matrix" 868 | ] 869 | }, 870 | { 871 | "cell_type": "code", 872 | "execution_count": 23, 873 | "metadata": {}, 874 | "outputs": [ 875 | { 876 | "data": { 877 | "text/html": [ 878 | "
\n", 879 | "\n", 892 | "\n", 893 | " \n", 894 | " \n", 895 | " \n", 896 | " \n", 897 | " \n", 898 | " \n", 899 | " \n", 900 | " \n", 901 | " \n", 902 | " \n", 903 | " \n", 904 | " \n", 905 | " \n", 906 | " \n", 907 | " \n", 908 | " \n", 909 | " \n", 910 | " \n", 911 | " \n", 912 | " \n", 913 | " \n", 914 | " \n", 915 | " \n", 916 | " \n", 917 | " \n", 918 | " \n", 919 | " \n", 920 | " \n", 921 | " \n", 922 | " \n", 923 | " \n", 924 | " \n", 925 | " \n", 926 | " \n", 927 | " \n", 928 | " \n", 929 | " \n", 930 | " \n", 931 | " \n", 932 | " \n", 933 | " \n", 934 | " \n", 935 | " \n", 936 | " \n", 937 | " \n", 938 | " \n", 939 | " \n", 940 | " \n", 941 | " \n", 942 | " \n", 943 | " \n", 944 | " \n", 945 | " \n", 946 | " \n", 947 | " \n", 948 | " \n", 949 | " \n", 950 | " \n", 951 | " \n", 952 | " \n", 953 | " \n", 954 | " \n", 955 | " \n", 956 | " \n", 957 | " \n", 958 | " \n", 959 | " \n", 960 | " \n", 961 | " \n", 962 | " \n", 963 | " \n", 964 | " \n", 965 | " \n", 966 | " \n", 967 | " \n", 968 | " \n", 969 | " \n", 970 | " \n", 971 | " \n", 972 | " \n", 973 | " \n", 974 | " \n", 975 | " \n", 976 | " \n", 977 | " \n", 978 | " \n", 979 | " \n", 980 | " \n", 981 | " \n", 982 | " \n", 983 | " \n", 984 | " \n", 985 | " \n", 986 | " \n", 987 | " \n", 988 | " \n", 989 | " \n", 990 | " \n", 991 | " \n", 992 | " \n", 993 | " \n", 994 | " \n", 995 | " \n", 996 | "
-MZJAWXU
-00000000
X00000011
M01111111
J01122222
Y01122222
A01123333
U01123334
Z01223334
\n", 997 | "
" 998 | ], 999 | "text/plain": [ 1000 | " - M Z J A W X U\n", 1001 | "- 0 0 0 0 0 0 0 0\n", 1002 | "X 0 0 0 0 0 0 1 1\n", 1003 | "M 0 1 1 1 1 1 1 1\n", 1004 | "J 0 1 1 2 2 2 2 2\n", 1005 | "Y 0 1 1 2 2 2 2 2\n", 1006 | "A 0 1 1 2 3 3 3 3\n", 1007 | "U 0 1 1 2 3 3 3 4\n", 1008 | "Z 0 1 2 2 3 3 3 4" 1009 | ] 1010 | }, 1011 | "execution_count": 23, 1012 | "metadata": {}, 1013 | "output_type": "execute_result" 1014 | } 1015 | ], 1016 | "source": [ 1017 | "# Example values\n", 1018 | "a = ['X', 'M', 'J', 'Y', 'A', 'U', 'Z']\n", 1019 | "b = ['M', 'Z', 'J', 'A', 'W', 'X', 'U']\n", 1020 | "\n", 1021 | "# Run algorithm\n", 1022 | "dp_table = calc_lcs(a, b)\n", 1023 | "pd.DataFrame(dp_table, index=['-'] + a, columns=['-'] + b)" 1024 | ] 1025 | }, 1026 | { 1027 | "cell_type": "markdown", 1028 | "metadata": {}, 1029 | "source": [ 1030 | "With time complexity of $ \\Theta(nm) $ and a space complexity of $ \\Theta((n + 1)(m + 1)) $." 1031 | ] 1032 | }, 1033 | { 1034 | "cell_type": "markdown", 1035 | "metadata": {}, 1036 | "source": [ 1037 | "#### Calculate the Longest Common Subsequence\n", 1038 | "Greedy approach" 1039 | ] 1040 | }, 1041 | { 1042 | "cell_type": "code", 1043 | "execution_count": 24, 1044 | "metadata": {}, 1045 | "outputs": [], 1046 | "source": [ 1047 | "def get_lcs(matrix, a, b, verbose=False):\n", 1048 | " lc_seq = []\n", 1049 | " i = len(a)\n", 1050 | " j = len(b)\n", 1051 | " \n", 1052 | " while i > -1 and j > -1:\n", 1053 | " if verbose:\n", 1054 | " print(i, j)\n", 1055 | " \n", 1056 | " if i > 0 and j > 0 and a[i - 1] == b[j - 1]:\n", 1057 | " lc_seq.append(a[i - 1])\n", 1058 | " i = i - 1\n", 1059 | " j = j - 1\n", 1060 | " elif j > 0 and (i == 0 or matrix[i][j - 1] >= matrix[i - 1][j]):\n", 1061 | " j = j - 1\n", 1062 | " elif i > 0 and (j == 0 or matrix[i][j - 1] < matrix[i - 1][j]):\n", 1063 | " i = i - 1\n", 1064 | " else:\n", 1065 | " break\n", 1066 | " \n", 1067 | " return list(reversed(lc_seq))" 1068 | ] 1069 | }, 1070 | { 1071 | "cell_type": "code", 1072 | "execution_count": 25, 1073 | "metadata": {}, 1074 | "outputs": [ 1075 | { 1076 | "data": { 1077 | "text/plain": [ 1078 | "['M', 'J', 'A', 'U']" 1079 | ] 1080 | }, 1081 | "execution_count": 25, 1082 | "metadata": {}, 1083 | "output_type": "execute_result" 1084 | } 1085 | ], 1086 | "source": [ 1087 | "# This function gets the longest common subsequence\n", 1088 | "get_lcs(dp_table, a, b)" 1089 | ] 1090 | }, 1091 | { 1092 | "cell_type": "markdown", 1093 | "metadata": {}, 1094 | "source": [ 1095 | "With time complexity of $ \\Theta(n + m) $ and a space complexity of $ \\Theta((n + 1)(m + 1)) $." 1096 | ] 1097 | }, 1098 | { 1099 | "cell_type": "markdown", 1100 | "metadata": {}, 1101 | "source": [ 1102 | "## 4.6. Sequence Alignment problem" 1103 | ] 1104 | }, 1105 | { 1106 | "cell_type": "markdown", 1107 | "metadata": {}, 1108 | "source": [ 1109 | "In bioinformatics, a sequence alignment is a way of arranging the sequences of DNA, RNA, or protein to identify regions of similarity that may be a consequence of functional, structural, or evolutionary relationships between the sequences [7]. Aligned sequences of nucleotide or amino acid residues are typically represented as rows within a matrix. Gaps are inserted between the residues so that identical or similar characters are aligned in successive columns.\n", 1110 | "\n", 1111 | "Sequence alignments are also used for non-biological sequences, such as calculating the distance cost between strings in a natural language or in financial data." 1112 | ] 1113 | }, 1114 | { 1115 | "cell_type": "markdown", 1116 | "metadata": {}, 1117 | "source": [ 1118 | "### 4.6.1. The Needleman-Wunsch algorithm will be used" 1119 | ] 1120 | }, 1121 | { 1122 | "cell_type": "markdown", 1123 | "metadata": {}, 1124 | "source": [ 1125 | "It was one of the first applications of dynamic programming to compare biological sequences. The algorithm essentially divides a large problem (e.g. the full sequence) into a series of smaller problems, and it uses the solutions to the smaller problems to find an optimal solution to the larger problem [8]." 1126 | ] 1127 | }, 1128 | { 1129 | "cell_type": "code", 1130 | "execution_count": 26, 1131 | "metadata": {}, 1132 | "outputs": [], 1133 | "source": [ 1134 | "# Function to measure the performance of an alignment\n", 1135 | "def s(x, y):\n", 1136 | " if x == '-' or y == '-':\n", 1137 | " # Payment for Gap\n", 1138 | " return -1\n", 1139 | " elif x == y:\n", 1140 | " # Payment for Match\n", 1141 | " return 1\n", 1142 | " # Payment for Mismatch\n", 1143 | " return -1" 1144 | ] 1145 | }, 1146 | { 1147 | "cell_type": "code", 1148 | "execution_count": 27, 1149 | "metadata": {}, 1150 | "outputs": [], 1151 | "source": [ 1152 | "# Needleman–Wunsch algorithm to calculate the sequence alignment\n", 1153 | "def calc_seq_align(a, b):\n", 1154 | " m = len(a)\n", 1155 | " n = len(b)\n", 1156 | " matrix = [[0] * (m + 1) for i in range(n + 1)]\n", 1157 | " \n", 1158 | " for i in range(n + 1):\n", 1159 | " matrix[i][0] = i * s('-', b[i - 1])\n", 1160 | " \n", 1161 | " for j in range(m + 1):\n", 1162 | " matrix[0][j] = j * s(a[j - 1], '-')\n", 1163 | " \n", 1164 | " for i in range(1, n + 1):\n", 1165 | " for j in range(1, m + 1):\n", 1166 | " matrix[i][j] = max(matrix[i - 1][j - 1] + s(a[j - 1], b[i - 1]),\n", 1167 | " matrix[i - 1][j] + s('-', b[i - 1]),\n", 1168 | " matrix[i][j - 1] + s(a[j - 1], '-'))\n", 1169 | " \n", 1170 | " return matrix" 1171 | ] 1172 | }, 1173 | { 1174 | "cell_type": "code", 1175 | "execution_count": 28, 1176 | "metadata": {}, 1177 | "outputs": [ 1178 | { 1179 | "data": { 1180 | "text/html": [ 1181 | "
\n", 1182 | "\n", 1195 | "\n", 1196 | " \n", 1197 | " \n", 1198 | " \n", 1199 | " \n", 1200 | " \n", 1201 | " \n", 1202 | " \n", 1203 | " \n", 1204 | " \n", 1205 | " \n", 1206 | " \n", 1207 | " \n", 1208 | " \n", 1209 | " \n", 1210 | " \n", 1211 | " \n", 1212 | " \n", 1213 | " \n", 1214 | " \n", 1215 | " \n", 1216 | " \n", 1217 | " \n", 1218 | " \n", 1219 | " \n", 1220 | " \n", 1221 | " \n", 1222 | " \n", 1223 | " \n", 1224 | " \n", 1225 | " \n", 1226 | " \n", 1227 | " \n", 1228 | " \n", 1229 | " \n", 1230 | " \n", 1231 | " \n", 1232 | " \n", 1233 | " \n", 1234 | " \n", 1235 | " \n", 1236 | " \n", 1237 | " \n", 1238 | " \n", 1239 | " \n", 1240 | " \n", 1241 | " \n", 1242 | " \n", 1243 | " \n", 1244 | " \n", 1245 | " \n", 1246 | " \n", 1247 | " \n", 1248 | " \n", 1249 | " \n", 1250 | " \n", 1251 | " \n", 1252 | " \n", 1253 | " \n", 1254 | " \n", 1255 | " \n", 1256 | " \n", 1257 | " \n", 1258 | " \n", 1259 | " \n", 1260 | " \n", 1261 | " \n", 1262 | " \n", 1263 | " \n", 1264 | " \n", 1265 | " \n", 1266 | " \n", 1267 | " \n", 1268 | " \n", 1269 | " \n", 1270 | " \n", 1271 | " \n", 1272 | " \n", 1273 | " \n", 1274 | " \n", 1275 | " \n", 1276 | " \n", 1277 | " \n", 1278 | " \n", 1279 | " \n", 1280 | " \n", 1281 | " \n", 1282 | " \n", 1283 | " \n", 1284 | " \n", 1285 | " \n", 1286 | " \n", 1287 | " \n", 1288 | " \n", 1289 | " \n", 1290 | " \n", 1291 | " \n", 1292 | " \n", 1293 | " \n", 1294 | " \n", 1295 | " \n", 1296 | " \n", 1297 | " \n", 1298 | " \n", 1299 | " \n", 1300 | " \n", 1301 | " \n", 1302 | " \n", 1303 | " \n", 1304 | " \n", 1305 | " \n", 1306 | " \n", 1307 | " \n", 1308 | "
-GCATGCUA
-0-1-2-3-4-5-6-7-8
G-110-1-2-3-4-5-6
A-20010-1-2-3-4
T-3-1-10210-1-2
T-4-2-2-1110-1-2
A-5-3-3-1000-10
C-6-4-2-2-1-110-1
A-7-5-3-1-2-2001
\n", 1309 | "
" 1310 | ], 1311 | "text/plain": [ 1312 | " - G C A T G C U A\n", 1313 | "- 0 -1 -2 -3 -4 -5 -6 -7 -8\n", 1314 | "G -1 1 0 -1 -2 -3 -4 -5 -6\n", 1315 | "A -2 0 0 1 0 -1 -2 -3 -4\n", 1316 | "T -3 -1 -1 0 2 1 0 -1 -2\n", 1317 | "T -4 -2 -2 -1 1 1 0 -1 -2\n", 1318 | "A -5 -3 -3 -1 0 0 0 -1 0\n", 1319 | "C -6 -4 -2 -2 -1 -1 1 0 -1\n", 1320 | "A -7 -5 -3 -1 -2 -2 0 0 1" 1321 | ] 1322 | }, 1323 | "execution_count": 28, 1324 | "metadata": {}, 1325 | "output_type": "execute_result" 1326 | } 1327 | ], 1328 | "source": [ 1329 | "# Example values\n", 1330 | "a = list('GCATGCUA')\n", 1331 | "b = list('GATTACA')\n", 1332 | "\n", 1333 | "# Run algorithm\n", 1334 | "dp_table = calc_seq_align(a, b)\n", 1335 | "pd.DataFrame(dp_table, index=['-'] + b, columns=['-'] + a)" 1336 | ] 1337 | }, 1338 | { 1339 | "cell_type": "markdown", 1340 | "metadata": {}, 1341 | "source": [ 1342 | "With time complexity of $ \\Theta(nm) $ and a space complexity of $ \\Theta((n + 1)(m + 1)) $." 1343 | ] 1344 | }, 1345 | { 1346 | "cell_type": "markdown", 1347 | "metadata": {}, 1348 | "source": [ 1349 | "#### Calculate the Sequence Alignment result\n", 1350 | "Greedy approach" 1351 | ] 1352 | }, 1353 | { 1354 | "cell_type": "code", 1355 | "execution_count": 29, 1356 | "metadata": {}, 1357 | "outputs": [], 1358 | "source": [ 1359 | "def get_seq_align(matrix, a, b, verbose=False):\n", 1360 | " alignmentA = \"\"\n", 1361 | " alignmentB = \"\"\n", 1362 | " j = len(a)\n", 1363 | " i = len(b)\n", 1364 | " \n", 1365 | " while i > -1 and j > -1:\n", 1366 | " if verbose:\n", 1367 | " print(i, j)\n", 1368 | " \n", 1369 | " if i > 0 and j > 0 and matrix[i][j] == matrix[i - 1][j - 1] + s(a[j - 1], b[i - 1]):\n", 1370 | " alignmentA = a[j - 1] + alignmentA\n", 1371 | " alignmentB = b[i - 1] + alignmentB\n", 1372 | " i = i - 1\n", 1373 | " j = j - 1\n", 1374 | " elif i > 0 and matrix[i][j] == matrix[i - 1][j] + s('-', b[i - 1]):\n", 1375 | " alignmentA = \"-\" + alignmentA\n", 1376 | " alignmentB = b[i - 1] + alignmentB\n", 1377 | " i = i - 1\n", 1378 | " elif j > 0 and matrix[i][j] == matrix[i][j - 1] + s(a[j - 1], '-'):\n", 1379 | " alignmentA = a[j - 1] + alignmentA\n", 1380 | " alignmentB = \"-\" + alignmentB\n", 1381 | " j = j - 1\n", 1382 | " else:\n", 1383 | " break\n", 1384 | " \n", 1385 | " return (alignmentA, alignmentB)" 1386 | ] 1387 | }, 1388 | { 1389 | "cell_type": "code", 1390 | "execution_count": 30, 1391 | "metadata": {}, 1392 | "outputs": [ 1393 | { 1394 | "data": { 1395 | "text/plain": [ 1396 | "('GCA-TGCUA', 'G-ATTAC-A')" 1397 | ] 1398 | }, 1399 | "execution_count": 30, 1400 | "metadata": {}, 1401 | "output_type": "execute_result" 1402 | } 1403 | ], 1404 | "source": [ 1405 | "# This function gets the Sequence Alignment\n", 1406 | "get_seq_align(dp_table, a, b)" 1407 | ] 1408 | }, 1409 | { 1410 | "cell_type": "markdown", 1411 | "metadata": {}, 1412 | "source": [ 1413 | "With time complexity of $ \\Theta(n + m) $ and a space complexity of $ \\Theta((n + 1)(m + 1)) $." 1414 | ] 1415 | }, 1416 | { 1417 | "cell_type": "markdown", 1418 | "metadata": {}, 1419 | "source": [ 1420 | "## 4.7. All-Pairs Shortest Path" 1421 | ] 1422 | }, 1423 | { 1424 | "cell_type": "markdown", 1425 | "metadata": {}, 1426 | "source": [ 1427 | "The all-pairs shortest path problem is the determination of the shortest graph distances between every pair of vertices in a given graph. The problem can be solved using n applications of Dijkstra's algorithm or all at once using the Floyd-Warshall algorithm [9]." 1428 | ] 1429 | }, 1430 | { 1431 | "cell_type": "markdown", 1432 | "metadata": {}, 1433 | "source": [ 1434 | "- The Dijkstra's algorithm has a time complexity of $ \\Theta(n^2) $ and a space complexity of $ \\Theta(n) $.\n", 1435 | "- The Floyd-Warshall algorithm has a time complexity of $ \\Theta(n^3) $ and a space complexity of $ \\Theta(n^2) $." 1436 | ] 1437 | }, 1438 | { 1439 | "cell_type": "markdown", 1440 | "metadata": {}, 1441 | "source": [ 1442 | "Please click here to see an example of both algorithms in the Graphs section. The second one is solved with dynamic programming." 1443 | ] 1444 | }, 1445 | { 1446 | "cell_type": "markdown", 1447 | "metadata": {}, 1448 | "source": [ 1449 | "## Reference" 1450 | ] 1451 | }, 1452 | { 1453 | "cell_type": "markdown", 1454 | "metadata": {}, 1455 | "source": [ 1456 | "[1] Wikipedia - Dynamic Programming. \n", 1457 | "[2] Wikipedia - Principle of Optimality. \n", 1458 | "[3] Wikipedia - Binomial coefficient. \n", 1459 | "[4] Wikipedia - Change-making problem. \n", 1460 | "[5] Wikipedia - Knapsack problem. \n", 1461 | "[6] Wikipedia - Longest common subsequence problem. \n", 1462 | "[7] Wikipedia - Sequence alignment problem. \n", 1463 | "[8] Wikipedia - Needleman-Wunsch algorithm. \n", 1464 | "[9] Wikipedia - All-Pairs Shortest Path. " 1465 | ] 1466 | }, 1467 | { 1468 | "cell_type": "markdown", 1469 | "metadata": {}, 1470 | "source": [ 1471 | "---\n", 1472 | "« Home" 1473 | ] 1474 | } 1475 | ], 1476 | "metadata": { 1477 | "kernelspec": { 1478 | "display_name": "Python 3", 1479 | "language": "python", 1480 | "name": "python3" 1481 | }, 1482 | "language_info": { 1483 | "codemirror_mode": { 1484 | "name": "ipython", 1485 | "version": 3 1486 | }, 1487 | "file_extension": ".py", 1488 | "mimetype": "text/x-python", 1489 | "name": "python", 1490 | "nbconvert_exporter": "python", 1491 | "pygments_lexer": "ipython3", 1492 | "version": "3.8.5" 1493 | }, 1494 | "varInspector": { 1495 | "cols": { 1496 | "lenName": 16, 1497 | "lenType": 16, 1498 | "lenVar": 40 1499 | }, 1500 | "kernels_config": { 1501 | "python": { 1502 | "delete_cmd_postfix": "", 1503 | "delete_cmd_prefix": "del ", 1504 | "library": "var_list.py", 1505 | "varRefreshCmd": "print(var_dic_list())" 1506 | }, 1507 | "r": { 1508 | "delete_cmd_postfix": ") ", 1509 | "delete_cmd_prefix": "rm(", 1510 | "library": "var_list.r", 1511 | "varRefreshCmd": "cat(var_dic_list()) " 1512 | } 1513 | }, 1514 | "types_to_exclude": [ 1515 | "module", 1516 | "function", 1517 | "builtin_function_or_method", 1518 | "instance", 1519 | "_Feature" 1520 | ], 1521 | "window_display": false 1522 | } 1523 | }, 1524 | "nbformat": 4, 1525 | "nbformat_minor": 2 1526 | } 1527 | -------------------------------------------------------------------------------- /similarity-functions/SimilarityFunctions.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# 6. Similarity Functions" 8 | ] 9 | }, 10 | { 11 | "cell_type": "markdown", 12 | "metadata": {}, 13 | "source": [ 14 | "- **Created by Andrés Segura Tinoco**\n", 15 | "- **Created on May 20, 2019**\n", 16 | "- **Updated on Mar 19, 2021**" 17 | ] 18 | }, 19 | { 20 | "cell_type": "markdown", 21 | "metadata": {}, 22 | "source": [ 23 | "In statistics and related fields, a **similarity measure** or similarity function is a real-valued function that quantifies the similarity between two objects. In short, a similarity function quantifies how much alike two data objects are [1]." 24 | ] 25 | }, 26 | { 27 | "cell_type": "markdown", 28 | "metadata": {}, 29 | "source": [ 30 | "## 6.1. Common similarity functions" 31 | ] 32 | }, 33 | { 34 | "cell_type": "code", 35 | "execution_count": 1, 36 | "metadata": {}, 37 | "outputs": [], 38 | "source": [ 39 | "# Load the Python libraries\n", 40 | "from math import *\n", 41 | "from decimal import Decimal\n", 42 | "from scipy import stats as ss\n", 43 | "import sklearn.metrics.pairwise as sm\n", 44 | "import math" 45 | ] 46 | }, 47 | { 48 | "cell_type": "markdown", 49 | "metadata": {}, 50 | "source": [ 51 | "\\begin{align}\n", 52 | " similarity(X, Y) = d(X, Y) = \\sqrt{\\sum_{i=1}^n (X_i - Y_i)^2} \\tag{1}\n", 53 | "\\end{align}" 54 | ] 55 | }, 56 | { 57 | "cell_type": "code", 58 | "execution_count": 2, 59 | "metadata": {}, 60 | "outputs": [], 61 | "source": [ 62 | "# (1) Euclidean distance function\n", 63 | "def euclidean_distance(x, y):\n", 64 | " return sqrt(sum(pow(a-b,2) for a, b in zip(x, y)))" 65 | ] 66 | }, 67 | { 68 | "cell_type": "markdown", 69 | "metadata": {}, 70 | "source": [ 71 | "\\begin{align}\n", 72 | " similarity(X, Y) = d(X, Y) = \\sum_{i=1}^n |X_i - Y_i| \\tag{2}\n", 73 | "\\end{align}" 74 | ] 75 | }, 76 | { 77 | "cell_type": "code", 78 | "execution_count": 3, 79 | "metadata": {}, 80 | "outputs": [], 81 | "source": [ 82 | "# (2) manhattan distance function\n", 83 | "def manhattan_distance(x, y):\n", 84 | " return sum(abs(a-b) for a,b in zip(x,y))" 85 | ] 86 | }, 87 | { 88 | "cell_type": "markdown", 89 | "metadata": {}, 90 | "source": [ 91 | "\\begin{align}\n", 92 | " similarity(X, Y) = d(X, Y) = (\\sum_{i=1}^n |X_i - Y_i|^p)^\\frac{1}{p} \\tag{3}\n", 93 | "\\end{align}" 94 | ] 95 | }, 96 | { 97 | "cell_type": "code", 98 | "execution_count": 4, 99 | "metadata": {}, 100 | "outputs": [], 101 | "source": [ 102 | "# (3) Minkowski distance function\n", 103 | "def _nth_root(value, n_root):\n", 104 | " root_value = 1/float(n_root)\n", 105 | " return round(Decimal(value) ** Decimal(root_value),3)\n", 106 | "\n", 107 | "def minkowski_distance(x, y, p = 3):\n", 108 | " return float(_nth_root(sum(pow(abs(a-b), p) for a,b in zip(x, y)), p))" 109 | ] 110 | }, 111 | { 112 | "cell_type": "markdown", 113 | "metadata": {}, 114 | "source": [ 115 | "\\begin{align}\n", 116 | " similarity(X, Y) = cos(\\theta) = \\frac{\\vec{X}.\\vec{Y}}{\\|\\vec{X}\\|.\\|\\vec{Y}\\|} = \\frac{\\sum_{i=1}^n X_i.Y_i}{\\sqrt{\\sum_{i=1}^n X_i^2}.\\sqrt{\\sum_{i=1}^n Y_i^2}} \\tag{4}\n", 117 | "\\end{align}" 118 | ] 119 | }, 120 | { 121 | "cell_type": "code", 122 | "execution_count": 5, 123 | "metadata": {}, 124 | "outputs": [], 125 | "source": [ 126 | "# (4) Cosine similarity function\n", 127 | "def _square_rooted(x):\n", 128 | " return round(sqrt(sum([a*a for a in x])),3)\n", 129 | "\n", 130 | "def cosine_similarity(x, y):\n", 131 | " numerator = sum(a*b for a,b in zip(x,y))\n", 132 | " denominator = _square_rooted(x) * _square_rooted(y)\n", 133 | " return round(numerator/float(denominator),3)" 134 | ] 135 | }, 136 | { 137 | "cell_type": "markdown", 138 | "metadata": {}, 139 | "source": [ 140 | "\\begin{align}\n", 141 | " similarity(X, Y) = \\frac{cov(X, Y)}{\\sigma_X . \\sigma_Y} = \\frac{\\sum_{i=1}^n (X_i - \\bar{X}).(Y_i - \\bar{Y})}{\\sqrt{\\sum_{i=1}^n (X_i - \\bar{X})^2 . (Y_i - \\bar{Y})^2}} \\tag{5}\n", 142 | "\\end{align}" 143 | ] 144 | }, 145 | { 146 | "cell_type": "code", 147 | "execution_count": 6, 148 | "metadata": {}, 149 | "outputs": [], 150 | "source": [ 151 | "# (5) Pearson similarity function\n", 152 | "def _avg(x):\n", 153 | " assert len(x) > 0\n", 154 | " return float(sum(x)) / len(x)\n", 155 | "\n", 156 | "def pearson_similarity(x, y):\n", 157 | " assert len(x) == len(y)\n", 158 | " n = len(x)\n", 159 | " assert n > 0\n", 160 | " avg_x = _avg(x)\n", 161 | " avg_y = _avg(y)\n", 162 | " diffprod = 0\n", 163 | " xdiff2 = 0\n", 164 | " ydiff2 = 0\n", 165 | " for idx in range(n):\n", 166 | " xdiff = x[idx] - avg_x\n", 167 | " ydiff = y[idx] - avg_y\n", 168 | " diffprod += xdiff * ydiff\n", 169 | " xdiff2 += xdiff * xdiff\n", 170 | " ydiff2 += ydiff * ydiff\n", 171 | "\n", 172 | " return diffprod / math.sqrt(xdiff2 * ydiff2)" 173 | ] 174 | }, 175 | { 176 | "cell_type": "markdown", 177 | "metadata": {}, 178 | "source": [ 179 | "\\begin{align}\n", 180 | " similarity(X, Y) = J(X, Y) = \\frac{|X \\cap Y|}{|X \\cup Y|} = \\frac{|X \\cap Y|}{|X| + |Y| - |X \\cap Y|} \\tag{6}\n", 181 | "\\end{align}" 182 | ] 183 | }, 184 | { 185 | "cell_type": "code", 186 | "execution_count": 7, 187 | "metadata": {}, 188 | "outputs": [], 189 | "source": [ 190 | "# (6) Jaccard similarity function\n", 191 | "def jaccard_similarity(x, y):\n", 192 | " intersection_cardinality = len(set.intersection(*[set(x), set(y)]))\n", 193 | " union_cardinality = len(set.union(*[set(x), set(y)]))\n", 194 | " return intersection_cardinality / float(union_cardinality)" 195 | ] 196 | }, 197 | { 198 | "cell_type": "markdown", 199 | "metadata": {}, 200 | "source": [ 201 | "## 6.2. Manual examples" 202 | ] 203 | }, 204 | { 205 | "cell_type": "code", 206 | "execution_count": 8, 207 | "metadata": {}, 208 | "outputs": [], 209 | "source": [ 210 | "# Vectors\n", 211 | "x = [-4.593481, -5.478033, 1.127111, 1.252885, -2.286953] # Messi\n", 212 | "y = [-4.080334, -3.406618, 4.334073, -0.485612, -2.817897] # CR\n", 213 | "z = [-4.048185, -5.546171, 0.505673, 0.616553, -1.730906] # Neymar" 214 | ] 215 | }, 216 | { 217 | "cell_type": "markdown", 218 | "metadata": {}, 219 | "source": [ 220 | "### Euclidean distance" 221 | ] 222 | }, 223 | { 224 | "cell_type": "code", 225 | "execution_count": 9, 226 | "metadata": {}, 227 | "outputs": [ 228 | { 229 | "data": { 230 | "text/plain": [ 231 | "4.259455195846412" 232 | ] 233 | }, 234 | "execution_count": 9, 235 | "metadata": {}, 236 | "output_type": "execute_result" 237 | } 238 | ], 239 | "source": [ 240 | "euclidean_distance(x, y)" 241 | ] 242 | }, 243 | { 244 | "cell_type": "code", 245 | "execution_count": 10, 246 | "metadata": {}, 247 | "outputs": [ 248 | { 249 | "data": { 250 | "text/plain": [ 251 | "1.1841800466723797" 252 | ] 253 | }, 254 | "execution_count": 10, 255 | "metadata": {}, 256 | "output_type": "execute_result" 257 | } 258 | ], 259 | "source": [ 260 | "euclidean_distance(x, z)" 261 | ] 262 | }, 263 | { 264 | "cell_type": "markdown", 265 | "metadata": {}, 266 | "source": [ 267 | "### Manhattan distance" 268 | ] 269 | }, 270 | { 271 | "cell_type": "code", 272 | "execution_count": 11, 273 | "metadata": {}, 274 | "outputs": [ 275 | { 276 | "data": { 277 | "text/plain": [ 278 | "8.060965" 279 | ] 280 | }, 281 | "execution_count": 11, 282 | "metadata": {}, 283 | "output_type": "execute_result" 284 | } 285 | ], 286 | "source": [ 287 | "manhattan_distance(x, y)" 288 | ] 289 | }, 290 | { 291 | "cell_type": "code", 292 | "execution_count": 12, 293 | "metadata": {}, 294 | "outputs": [ 295 | { 296 | "data": { 297 | "text/plain": [ 298 | "2.4272509999999996" 299 | ] 300 | }, 301 | "execution_count": 12, 302 | "metadata": {}, 303 | "output_type": "execute_result" 304 | } 305 | ], 306 | "source": [ 307 | "manhattan_distance(x, z)" 308 | ] 309 | }, 310 | { 311 | "cell_type": "markdown", 312 | "metadata": {}, 313 | "source": [ 314 | "### Minkowski distance" 315 | ] 316 | }, 317 | { 318 | "cell_type": "code", 319 | "execution_count": 13, 320 | "metadata": {}, 321 | "outputs": [ 322 | { 323 | "data": { 324 | "text/plain": [ 325 | "3.619" 326 | ] 327 | }, 328 | "execution_count": 13, 329 | "metadata": {}, 330 | "output_type": "execute_result" 331 | } 332 | ], 333 | "source": [ 334 | "minkowski_distance(x, y)" 335 | ] 336 | }, 337 | { 338 | "cell_type": "code", 339 | "execution_count": 14, 340 | "metadata": {}, 341 | "outputs": [ 342 | { 343 | "data": { 344 | "text/plain": [ 345 | "0.941" 346 | ] 347 | }, 348 | "execution_count": 14, 349 | "metadata": {}, 350 | "output_type": "execute_result" 351 | } 352 | ], 353 | "source": [ 354 | "minkowski_distance(x, z)" 355 | ] 356 | }, 357 | { 358 | "cell_type": "markdown", 359 | "metadata": {}, 360 | "source": [ 361 | "### Cosine similarity" 362 | ] 363 | }, 364 | { 365 | "cell_type": "code", 366 | "execution_count": 15, 367 | "metadata": {}, 368 | "outputs": [ 369 | { 370 | "data": { 371 | "text/plain": [ 372 | "0.842" 373 | ] 374 | }, 375 | "execution_count": 15, 376 | "metadata": {}, 377 | "output_type": "execute_result" 378 | } 379 | ], 380 | "source": [ 381 | "cosine_similarity(x, y)" 382 | ] 383 | }, 384 | { 385 | "cell_type": "code", 386 | "execution_count": 16, 387 | "metadata": {}, 388 | "outputs": [ 389 | { 390 | "data": { 391 | "text/plain": [ 392 | "0.99" 393 | ] 394 | }, 395 | "execution_count": 16, 396 | "metadata": {}, 397 | "output_type": "execute_result" 398 | } 399 | ], 400 | "source": [ 401 | "cosine_similarity(x, z)" 402 | ] 403 | }, 404 | { 405 | "cell_type": "markdown", 406 | "metadata": {}, 407 | "source": [ 408 | "### Pearson similarity" 409 | ] 410 | }, 411 | { 412 | "cell_type": "code", 413 | "execution_count": 17, 414 | "metadata": {}, 415 | "outputs": [ 416 | { 417 | "data": { 418 | "text/plain": [ 419 | "0.8214001476231276" 420 | ] 421 | }, 422 | "execution_count": 17, 423 | "metadata": {}, 424 | "output_type": "execute_result" 425 | } 426 | ], 427 | "source": [ 428 | "pearson_similarity(x, y)" 429 | ] 430 | }, 431 | { 432 | "cell_type": "code", 433 | "execution_count": 18, 434 | "metadata": {}, 435 | "outputs": [ 436 | { 437 | "data": { 438 | "text/plain": [ 439 | "0.9888645775446726" 440 | ] 441 | }, 442 | "execution_count": 18, 443 | "metadata": {}, 444 | "output_type": "execute_result" 445 | } 446 | ], 447 | "source": [ 448 | "pearson_similarity(x, z)" 449 | ] 450 | }, 451 | { 452 | "cell_type": "markdown", 453 | "metadata": {}, 454 | "source": [ 455 | "### Jaccard similarity" 456 | ] 457 | }, 458 | { 459 | "cell_type": "code", 460 | "execution_count": 19, 461 | "metadata": {}, 462 | "outputs": [], 463 | "source": [ 464 | "a = [0, 1, 2, 3, 4, 5]\n", 465 | "b = [-1, 1, 2, 0, 3, 5]" 466 | ] 467 | }, 468 | { 469 | "cell_type": "code", 470 | "execution_count": 20, 471 | "metadata": {}, 472 | "outputs": [ 473 | { 474 | "data": { 475 | "text/plain": [ 476 | "0.7142857142857143" 477 | ] 478 | }, 479 | "execution_count": 20, 480 | "metadata": {}, 481 | "output_type": "execute_result" 482 | } 483 | ], 484 | "source": [ 485 | "jaccard_similarity(a, b)" 486 | ] 487 | }, 488 | { 489 | "cell_type": "markdown", 490 | "metadata": {}, 491 | "source": [ 492 | "## 6.3. Sklearn examples" 493 | ] 494 | }, 495 | { 496 | "cell_type": "code", 497 | "execution_count": 21, 498 | "metadata": {}, 499 | "outputs": [ 500 | { 501 | "data": { 502 | "text/plain": [ 503 | "4.259455195846413" 504 | ] 505 | }, 506 | "execution_count": 21, 507 | "metadata": {}, 508 | "output_type": "execute_result" 509 | } 510 | ], 511 | "source": [ 512 | "corr = sm.euclidean_distances([x], [y])\n", 513 | "float(corr[0])" 514 | ] 515 | }, 516 | { 517 | "cell_type": "code", 518 | "execution_count": 22, 519 | "metadata": {}, 520 | "outputs": [ 521 | { 522 | "data": { 523 | "text/plain": [ 524 | "8.060965" 525 | ] 526 | }, 527 | "execution_count": 22, 528 | "metadata": {}, 529 | "output_type": "execute_result" 530 | } 531 | ], 532 | "source": [ 533 | "corr = sm.manhattan_distances([x], [y])\n", 534 | "float(corr[0])" 535 | ] 536 | }, 537 | { 538 | "cell_type": "code", 539 | "execution_count": 23, 540 | "metadata": {}, 541 | "outputs": [ 542 | { 543 | "data": { 544 | "text/plain": [ 545 | "0.841904969009294" 546 | ] 547 | }, 548 | "execution_count": 23, 549 | "metadata": {}, 550 | "output_type": "execute_result" 551 | } 552 | ], 553 | "source": [ 554 | "corr = sm.cosine_similarity([x], [y])\n", 555 | "float(corr[0])" 556 | ] 557 | }, 558 | { 559 | "cell_type": "code", 560 | "execution_count": 24, 561 | "metadata": {}, 562 | "outputs": [ 563 | { 564 | "data": { 565 | "text/plain": [ 566 | "0.8214001476231275" 567 | ] 568 | }, 569 | "execution_count": 24, 570 | "metadata": {}, 571 | "output_type": "execute_result" 572 | } 573 | ], 574 | "source": [ 575 | "corr, p_value = ss.pearsonr(x, y)\n", 576 | "corr" 577 | ] 578 | }, 579 | { 580 | "cell_type": "markdown", 581 | "metadata": {}, 582 | "source": [ 583 | "## Reference" 584 | ] 585 | }, 586 | { 587 | "cell_type": "markdown", 588 | "metadata": {}, 589 | "source": [ 590 | "[1] Wikipedia - Similarity measure. " 591 | ] 592 | }, 593 | { 594 | "cell_type": "markdown", 595 | "metadata": {}, 596 | "source": [ 597 | "---\n", 598 | "« Home" 599 | ] 600 | } 601 | ], 602 | "metadata": { 603 | "kernelspec": { 604 | "display_name": "Python 3", 605 | "language": "python", 606 | "name": "python3" 607 | }, 608 | "language_info": { 609 | "codemirror_mode": { 610 | "name": "ipython", 611 | "version": 3 612 | }, 613 | "file_extension": ".py", 614 | "mimetype": "text/x-python", 615 | "name": "python", 616 | "nbconvert_exporter": "python", 617 | "pygments_lexer": "ipython3", 618 | "version": "3.8.5" 619 | }, 620 | "varInspector": { 621 | "cols": { 622 | "lenName": 16, 623 | "lenType": 16, 624 | "lenVar": 40 625 | }, 626 | "kernels_config": { 627 | "python": { 628 | "delete_cmd_postfix": "", 629 | "delete_cmd_prefix": "del ", 630 | "library": "var_list.py", 631 | "varRefreshCmd": "print(var_dic_list())" 632 | }, 633 | "r": { 634 | "delete_cmd_postfix": ") ", 635 | "delete_cmd_prefix": "rm(", 636 | "library": "var_list.r", 637 | "varRefreshCmd": "cat(var_dic_list()) " 638 | } 639 | }, 640 | "types_to_exclude": [ 641 | "module", 642 | "function", 643 | "builtin_function_or_method", 644 | "instance", 645 | "_Feature" 646 | ], 647 | "window_display": false 648 | } 649 | }, 650 | "nbformat": 4, 651 | "nbformat_minor": 2 652 | } 653 | --------------------------------------------------------------------------------