├── .gitignore ├── README.md ├── card_games └── HW_card_games.ipynb ├── ciphers └── HW_ciphers.ipynb ├── heat_dispersion └── HW_heat_dispersion.ipynb ├── image_restoration ├── HW_image_restoration.ipynb ├── SOLN_image_restoration.ipynb ├── blowup.png ├── noisy-example.png └── original-image.png ├── odds_and_ends ├── HW_odds_and_ends.ipynb └── resources │ ├── example-survey.txt │ └── mystery-img.npy ├── palindrome └── HW_palindrome.ipynb ├── pizza_shop └── HW_pizza_shop.ipynb ├── relaxation_method └── HW_relaxation_method.ipynb ├── run_length_encoding └── HW_run_length_encoding.ipynb └── three_five └── HW_three_five_threefive.ipynb /.gitignore: -------------------------------------------------------------------------------- 1 | test_solns/ 2 | 3 | # python 4 | __pycache__ 5 | *.egg-info/ 6 | 7 | # jupyter 8 | .ipynb_checkpoints/ 9 | 10 | # log files 11 | logs/ 12 | *.log 13 | 14 | # ide 15 | .vscode 16 | .project 17 | .pydevproject -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # BWSI Python Course Graded Assignments 2 | 3 | These problems are ordered alphabetically, and *not* in order of ascending difficulty. Please consult the BWSI-EdX course page for the due dates for the respective problems. 4 | 5 | ## Installing the auto-grader 6 | 7 | Complete [Module 1 of PLYI](https://www.pythonlikeyoumeanit.com/module_1.html) to install and configure Python on your computer, via Anaconda. 8 | 9 | In order to run the auto-grader for these problems, you must install `bwsi_grader`. In your terminal, execute: 10 | 11 | ```shell 12 | pip install bwsi_grader 13 | ``` 14 | 15 | This grader requires you to use Python 3.7 or later. 16 | 17 | 18 | ## Completing an Assignment 19 | 20 | ### 1. Open the Jupyter notebook for the problem of interest 21 | 22 | The coding problems are provided in Jupyter notebooks. Refer to [this section of PLYMI](https://www.pythonlikeyoumeanit.com/Module1_GettingStartedWithPython/Jupyter_Notebooks.html) for instructions for starting a Jupyter server, and for opening a Jupyter notebook. 23 | 24 | Make sure that the conda environment from which you start the Jupyter server is also the one that you installed `bwsi-grader` in. 25 | 26 | 27 | ### 2. Read the problem description 28 | 29 | At the top of the notebook will be a detailed description of the problem, as well as example that demonstrate how your function ought to behave. Read through this material carefully. 30 | 31 | All of the problems here can be solved based on the materials presented in [PLYMI](https://www.pythonlikeyoumeanit.com/index.html). Please consult the appropriate sections of the website for guidance towards tackling any given problem. The website also has a search bar that you can utilitize. 32 | 33 | Feel free to post questions on the course's piazza page. 34 | 35 | ### 3. Begin working on a solution 36 | 37 | Below the problem description is the template for a function; you are responsible for filling out the body and return statement of that function. For some problems, you are also required to complete [the signature](https://www.pythonlikeyoumeanit.com/Module2_EssentialsOfPython/Functions.html#Arguments) for that function. 38 | 39 | Whenever you make changes to that function, you must *execute the notebook cell containing the function's definition* in order for those changes to take effect. 40 | 41 | ### 4. Test your function 42 | 43 | **Before you run the auto-grader, you should test your solution with some examples**. You will save a lot of time if you build an intuition for the solution that you are working on, and see if it behaves as you expect, before submitting your solution to the auto-grader. 44 | 45 | Note that every problem gives you example use-cases for your function, complete with the input to the function and the expected output. These are ready-made test cases for you to use. 46 | [Make a new cell in your Jupyter notebook](https://www.pythonlikeyoumeanit.com/Module1_GettingStartedWithPython/Jupyter_Notebooks.html#Familiarizing-Yourself-with-Jupyter-Notebooks) and try running your function yourself; do you see the output that you expect? 47 | 48 | **Some Common Issues** 49 | 50 | "My function doesn't return anything when I run it!" 51 | - Did you include [a `return` statement](https://www.pythonlikeyoumeanit.com/Module2_EssentialsOfPython/Functions.html#The-return-Statement) in your function? If not, your function is returning `None` 52 | - Is there an asterisk to the left of the cell where you are running your function? If so this means that your code is still running. Almost all of the solutions to these problems should run quickly (within a few seconds); if your code is running for a long time, there may be a while-loop in your code that is running forever. If this is the case, [kill your notebook's kernel](https://www.pythonlikeyoumeanit.com/Module1_GettingStartedWithPython/Jupyter_Notebooks.html#Familiarizing-Yourself-with-Jupyter-Notebooks) and inspect your function for while-loops that fail to end. 53 | 54 | 55 | ### 5. Submit your solution to the auto-grader 56 | 57 | The notebook will contain a cell that imports a function from `bwsi_grader` and passes your solution to this. Running this cell will run your solution through the grader. 58 | 59 | **You can run the grader as many times as you like**. 60 | 61 | If there is a problem with your solution, the grader will try to print out a descriptive error message for what went wrong. It will also show you *how* your function was called by the grader, and what its expected behavior was; read this message carefully to understand how to fix your code. 62 | 63 | If your solution is correct, the grader will print out a message saying so, and will print a hash code that you can submit on the BWSI Python course page, to prove that you completed the assignment. 64 | 65 | **Some Common Issues** 66 | 67 | "When I run the auto-grader cell I get an `ImportError` indicating that the module `bwsi_grader` cannot be found. 68 | - Make sure that you installed `bwsi_grader` in the same conda environment from which you are running your Jupyter notebook. 69 | 70 | "My function prints the correct solution when I run it, but the grader says that it returns `None`" 71 | - Did you use a `print` statement in your code to print the answer, instead of a `return` statement to actually return the answer? This is likely the cause of this error. -------------------------------------------------------------------------------- /card_games/HW_card_games.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": { 6 | "ein.tags": "worksheet-0", 7 | "slideshow": { 8 | "slide_type": "-" 9 | } 10 | }, 11 | "source": [ 12 | "# Card Games\n", 13 | "\n", 14 | "To exercise the skills that we've learned in [object-oriented programming](https://www.pythonlikeyoumeanit.com/module_4.html) let's write a few card games! First off, we'll need to build ourselves a deck of cards." 15 | ] 16 | }, 17 | { 18 | "cell_type": "markdown", 19 | "metadata": { 20 | "ein.tags": "worksheet-0", 21 | "slideshow": { 22 | "slide_type": "-" 23 | } 24 | }, 25 | "source": [ 26 | "## Problem 1: Card\n", 27 | "\n", 28 | "To play card games, we'll first need some cards! Let's define a `Card` class that we'll be able to store in a `Deck`\n", 29 | "object later on. To get comfortable with writing classes, we'll start out with a skeleton. Later on you'll build your\n", 30 | "own `Deck` class from scratch.\n", 31 | "\n", 32 | "First, let's decide on the set of features we want out of our `Card` object:\n", 33 | "\n", 34 | "##### Rank Each `Card` should keep track of its `rank`. These are the ranks 2, 3, 4, 5, 6, 7, 8, 9, 10, Jack, Queen,\n", 35 | "King, and Ace. We can easily store this as an integer from 2 to 14. We should be able to access this by calling `.rank`\n", 36 | "on a `Card`:\n", 37 | "\n", 38 | "``` python\n", 39 | ">>> Card(3, \"C\").rank\n", 40 | "3\n", 41 | "```\n", 42 | "\n", 43 | "##### Suit\n", 44 | "Each one of our `Card`s will be one of four suits: Clubs, Hearts, Spades, or Diamonds. Let's store this in a string.\n", 45 | "We should be able to access this by calling `.suit` on a `Card`:\n", 46 | "\n", 47 | "``` python\n", 48 | ">>> Card(4, \"D\").suit\n", 49 | "'D'\n", 50 | "```\n", 51 | "\n", 52 | "##### repr\n", 53 | "We should override the `__repr__` function of our `Card` class so that it will print nicely. We'll write this\n", 54 | "to print out \"[rank] of [suit]\" where [rank] is the rank of our card and [suit] is its suit. For example:\n", 55 | "\n", 56 | "``` python\n", 57 | ">>> Card(7, 'H')\n", 58 | "7 of Hearts\n", 59 | "```\n", 60 | "\n", 61 | "##### Comparison functions\n", 62 | "For some games, we may wish to compare the ranks of two `Card`s against each other. The final functions we'll write for our `Card` class are the comparators `<, <=, ==, >=, >`\n", 63 | "\n", 64 | "```python\n", 65 | ">>> Card(2, 'H') < Card(10, 'S')\n", 66 | "True\n", 67 | "\n", 68 | ">>> Card(4, 'C') == Card(4, 'D')\n", 69 | "True\n", 70 | "\n", 71 | ">>> Card(8, 'D') >= Card(14, 'D')\n", 72 | "False\n", 73 | "```" 74 | ] 75 | }, 76 | { 77 | "cell_type": "markdown", 78 | "metadata": { 79 | "ein.tags": "worksheet-0", 80 | "slideshow": { 81 | "slide_type": "-" 82 | } 83 | }, 84 | "source": [ 85 | "Fill out the remainder of the `Card` class below to function as described above." 86 | ] 87 | }, 88 | { 89 | "cell_type": "code", 90 | "execution_count": null, 91 | "metadata": { 92 | "autoscroll": false, 93 | "collapsed": false, 94 | "ein.hycell": false, 95 | "ein.tags": "worksheet-0", 96 | "slideshow": { 97 | "slide_type": "-" 98 | } 99 | }, 100 | "outputs": [], 101 | "source": [ 102 | "class Card:\n", 103 | " \"\"\" A Card object maintains a `rank` and a `suit`. \"\"\"\n", 104 | "\n", 105 | " _rank_to_str = {11: 'Jack', 12: 'Queen', 13: 'King', 14: 'Ace'}\n", 106 | " _suit_to_str = {'C': 'Clubs', 'H': 'Hearts', 'S': 'Spades', 'D': 'Diamonds'}\n", 107 | "\n", 108 | " def __init__(self, rank: int, suit: str):\n", 109 | " \"\"\" Initialize a Card object.\n", 110 | " \n", 111 | " Parameters\n", 112 | " ----------\n", 113 | " rank : int ∈ [2, 14]\n", 114 | " The rank of this card, with order 2, 3, 4, ..., 10, J, Q, K, A.\n", 115 | " \n", 116 | " suit : str ∈ ('C', 'H', 'S', 'D')\n", 117 | " The suit of this card.\n", 118 | " \"\"\"\n", 119 | " assert 2 <= rank <= 14, 'Valid ranks are [2, 14] for the ranks: [2, 3, 4, 5, 6, 7, 8, 9, 10, J, Q, K, A]'\n", 120 | " assert suit.upper() in {'C', 'H', 'S', 'D'}, 'Valid suits are [C, H, S, D]'\n", 121 | "\n", 122 | " # student code goes here\n", 123 | "\n", 124 | " def __repr__(self):\n", 125 | " \"\"\" Return the string representation of this card.\n", 126 | " \n", 127 | " The card should be printed as \" of s\" where is the\n", 128 | " rank of this card and is the suit of this card. For example, the\n", 129 | " desired behavior is:\n", 130 | " \n", 131 | " >>> my_card = Card(4, 'D')\n", 132 | " >>> my_card\n", 133 | " 4 of Diamonds\n", 134 | " \n", 135 | " >>> Card(13, 'H')\n", 136 | " King of Hearts\n", 137 | " \n", 138 | " >>> print(Card(11, 'C'))\n", 139 | " Jack of Clubs\n", 140 | " \"\"\"\n", 141 | " # student code goes here\n", 142 | " return \"\"\n", 143 | "\n", 144 | " def __lt__(self, other):\n", 145 | " \"\"\" Determine whether the rank of this card is less than the rank of the other. \"\"\"\n", 146 | " # student code goes here\n", 147 | " pass\n", 148 | "\n", 149 | " def __gt__(self, other):\n", 150 | " \"\"\" Determine whether the rank of this card is greater than the rank of the other. \"\"\"\n", 151 | " # student code goes here\n", 152 | " pass\n", 153 | "\n", 154 | " def __le__(self, other):\n", 155 | " \"\"\" Determine whether the rank of this card is less than or equal to the rank of the other. \"\"\"\n", 156 | " # student code goes here\n", 157 | " pass\n", 158 | "\n", 159 | " def __ge__(self, other):\n", 160 | " \"\"\" Determine whether the rank of this card is greater than or equal to the rank of the other. \"\"\"\n", 161 | " # student code goes here\n", 162 | " pass\n", 163 | "\n", 164 | " def __eq__(self, other):\n", 165 | " \"\"\" Determine whether the rank of this card is equal to the rank of the other. \"\"\"\n", 166 | " # student code goes here\n", 167 | " pass" 168 | ] 169 | }, 170 | { 171 | "cell_type": "code", 172 | "execution_count": null, 173 | "metadata": { 174 | "autoscroll": false, 175 | "collapsed": false, 176 | "ein.hycell": false, 177 | "ein.tags": "worksheet-0", 178 | "slideshow": { 179 | "slide_type": "-" 180 | } 181 | }, 182 | "outputs": [], 183 | "source": [ 184 | "from bwsi_grader.python.card_games import grade_card\n", 185 | "grade_card(Card)" 186 | ] 187 | }, 188 | { 189 | "cell_type": "markdown", 190 | "metadata": { 191 | "ein.tags": "worksheet-0", 192 | "slideshow": { 193 | "slide_type": "-" 194 | } 195 | }, 196 | "source": [ 197 | "## Problem 2: Deck\n", 198 | "\n", 199 | "Now that we have a `Card` object that we can use, in order to play games we'll need to arrange them in a `Deck`. With\n", 200 | "one class definition under our belts, let's write this one from scratch! Let's define the functionality we need out of\n", 201 | "our `Deck`.\n", 202 | "\n", 203 | "##### init \n", 204 | "A `Deck` should take one argument to its constructor: `shuffled`, which is a boolean variable indicating\n", 205 | "whether the deck should be initialized in sorted order or shuffled. This should be an optional parameter, which is\n", 206 | "`False` by default. It can simply be initialized as `my_deck = Deck()` or you can explicitly pass in whether to shuffle\n", 207 | "the deck: `my_shuffled_deck = Deck(True)`. This initialization function should create a member variable `cards` that\n", 208 | "holds a list of `Card`s. That member variable should be initialized with a whole set of 52 cards: 2, 3, 4, 5, 6, 7, 8,\n", 209 | "9, 10, J, Q, K, A of each of the four suits.\n", 210 | "\n", 211 | "Additionally, the initialization function should keep track of the number of cards that have been dealt. This will be\n", 212 | "initialized to zero.\n", 213 | "\n", 214 | "Finally, a `Deck` should keep track of whether it has been shuffled. If the `Deck` is not shuffled, it should be in\n", 215 | "sorted order. It may start with any suit, but it should follow the order 2, 3, 4, ..., 10, J, Q, K, A, 2, 3, 4, ..., K,\n", 216 | "A for each suit. We should be able to access this through a `shuffled` parameter:\n", 217 | "\n", 218 | "``` python\n", 219 | ">>> Deck().shuffled\n", 220 | "False\n", 221 | "\n", 222 | ">>> Deck(shuffled=True).shuffled\n", 223 | "True\n", 224 | "```\n", 225 | "\n", 226 | "##### shuffle \n", 227 | "A `Deck` object won't do us any good for playing games if we can't shuffle it! We'll write a function\n", 228 | "called `shuffle` that will allow us to shuffle our deck. This will take no parameters. Instead, it will simply be called\n", 229 | "as `my_deck.shuffle()`. You may find the [random](https://docs.python.org/3/library/random.html) module helpful here.\n", 230 | "\n", 231 | "##### deal_card\n", 232 | "A `Deck` object should be able to deal `Card`s off the top. We'll write a function `deal_card` that\n", 233 | "returns the `Card` object at the top of the deck. That is, we might create a `Deck` and pull the top card off like so:\n", 234 | "\n", 235 | "``` python\n", 236 | ">>> my_deck = Deck()\n", 237 | ">>> my_deck.deal_card()\n", 238 | "2 of Clubs\n", 239 | "\n", 240 | ">>> my_deck = Deck()\n", 241 | ">>> my_deck.shuffle()\n", 242 | ">>> my_deck.deal_card()\n", 243 | "Queen of Spades\n", 244 | "```\n", 245 | "\n", 246 | "This function should also increment our variable tracking the number of `Card`s we've dealt. Importantly, our `Deck`\n", 247 | "shouldn't deal cards once we've gotten to the end of the deck. If we reach the end, instead of returning the next\n", 248 | "`Card`, we'll return `None`:\n", 249 | "\n", 250 | "``` python\n", 251 | ">>> my_deck = Deck()\n", 252 | ">>> throwaway = [my_deck.deal_card() for _ in range(50)]\n", 253 | ">>> [my_deck.deal_card() for _ in range(5)]\n", 254 | "[King of Diamonds, Ace of Diamonds, None, None, None]\n", 255 | "\n", 256 | ">>> my_deck\n", 257 | "Deck(dealt 52, shuffled=False)\n", 258 | "```\n", 259 | "\n", 260 | "##### repr\n", 261 | "We'll write our own `__repr__` function for the `Deck` class just as we did with `Card`. The repr in this\n", 262 | "class will simply print out that it is a `Deck` object, the number of cards that have been dealt, and whether the deck\n", 263 | "has been shuffled:\n", 264 | "\n", 265 | "``` python\n", 266 | ">>> my_deck = Deck()\n", 267 | ">>> my_deck\n", 268 | "Deck(dealt 0, shuffled=False)\n", 269 | "\n", 270 | ">>> top_card = my_deck.deal_card()\n", 271 | ">>> my_deck\n", 272 | "Deck(dealt 1, shuffled=False)\n", 273 | "\n", 274 | ">>> my_deck = Deck()\n", 275 | ">>> my_deck.shuffle()\n", 276 | ">>> hand = [my_deck.deal_card() for _ in range(5)]\n", 277 | ">>> my_deck\n", 278 | "Deck(dealt 5, shuffled=True)\n", 279 | "```\n", 280 | "\n", 281 | "##### reset\n", 282 | "Finally, let's write a `reset` function so that we don't have to construct a new `Deck` every time we want\n", 283 | "to use one. Imagine how ridiculous it would be if we had to go out and buy a new set of cards every time we wanted to\n", 284 | "play a game! The `reset` function should do exactly what our `__init__` function does: reset our counter and `shuffled`\n", 285 | "variable and set the `Card`s in our deck in order:\n", 286 | "\n", 287 | "``` python\n", 288 | ">>> my_deck = Deck()\n", 289 | ">>> my_deck.shuffle()\n", 290 | ">>> throwaways = [my_deck.deal_card() for _ in range(27)]\n", 291 | ">>> my_deck\n", 292 | "Deck(dealt 27, shuffled=True)\n", 293 | "\n", 294 | ">>> my_deck.reset()\n", 295 | ">>> my_deck\n", 296 | "Deck(dealt 0, shuffled=False)\n", 297 | "```" 298 | ] 299 | }, 300 | { 301 | "cell_type": "markdown", 302 | "metadata": { 303 | "ein.tags": "worksheet-0", 304 | "slideshow": { 305 | "slide_type": "-" 306 | } 307 | }, 308 | "source": [ 309 | "Create the `Deck` class as decribed above." 310 | ] 311 | }, 312 | { 313 | "cell_type": "code", 314 | "execution_count": null, 315 | "metadata": { 316 | "autoscroll": false, 317 | "collapsed": false, 318 | "ein.hycell": false, 319 | "ein.tags": "worksheet-0", 320 | "slideshow": { 321 | "slide_type": "-" 322 | } 323 | }, 324 | "outputs": [], 325 | "source": [ 326 | "from bwsi_grader.python.card_games import grade_deck\n", 327 | "grade_deck(Deck)" 328 | ] 329 | }, 330 | { 331 | "cell_type": "markdown", 332 | "metadata": { 333 | "ein.tags": "worksheet-0", 334 | "slideshow": { 335 | "slide_type": "-" 336 | } 337 | }, 338 | "source": [ 339 | "With our `Deck` and `Card`s written out, let's write a very simple game of high-low. We'll just deal the top card to\n", 340 | "each of two players and determine whether Player 1 or Player 2 has the highest." 341 | ] 342 | }, 343 | { 344 | "cell_type": "code", 345 | "execution_count": null, 346 | "metadata": { 347 | "autoscroll": false, 348 | "collapsed": false, 349 | "ein.hycell": false, 350 | "ein.tags": "worksheet-0", 351 | "slideshow": { 352 | "slide_type": "-" 353 | } 354 | }, 355 | "outputs": [], 356 | "source": [ 357 | "def play_high_low_game():\n", 358 | " d = Deck(shuffled=True)\n", 359 | " p1 = d.deal_card()\n", 360 | " p2 = d.deal_card()\n", 361 | " print(\"It's a tie!\" if p1 == p2 else f'Player {1 if p1 > p2 else 2} wins!')\n", 362 | " print(f'Player 1 had the {p1} and Player 2 had the {p2}')" 363 | ] 364 | }, 365 | { 366 | "cell_type": "code", 367 | "execution_count": null, 368 | "metadata": { 369 | "autoscroll": false, 370 | "collapsed": false, 371 | "ein.hycell": false, 372 | "ein.tags": "worksheet-0", 373 | "slideshow": { 374 | "slide_type": "-" 375 | } 376 | }, 377 | "outputs": [], 378 | "source": [ 379 | "play_high_low_game()" 380 | ] 381 | }, 382 | { 383 | "cell_type": "markdown", 384 | "metadata": { 385 | "ein.tags": "worksheet-0", 386 | "slideshow": { 387 | "slide_type": "-" 388 | } 389 | }, 390 | "source": [ 391 | "See what other games you can create with your new `Deck` of `Card`s!" 392 | ] 393 | } 394 | ], 395 | "metadata": { 396 | "kernelspec": { 397 | "argv": [ 398 | "/usr/bin/python3", 399 | "-m", 400 | "ipykernel_launcher", 401 | "-f", 402 | "{connection_file}" 403 | ], 404 | "display_name": "Python 3", 405 | "env": null, 406 | "interrupt_mode": "signal", 407 | "language": "python", 408 | "metadata": null, 409 | "name": "python3" 410 | }, 411 | "language_info": { 412 | "codemirror_mode": { 413 | "name": "ipython", 414 | "version": 3 415 | }, 416 | "file_extension": ".py", 417 | "mimetype": "text/x-python", 418 | "name": "python", 419 | "nbconvert_exporter": "python", 420 | "pygments_lexer": "ipython3", 421 | "version": "3.7.2" 422 | }, 423 | "name": "HW_card_games.ipynb" 424 | }, 425 | "nbformat": 4, 426 | "nbformat_minor": 2 427 | } 428 | -------------------------------------------------------------------------------- /ciphers/HW_ciphers.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Fun with Ciphers\n", 8 | "A cipher is an algorithm for encoding or decoding data. In these problems, we'll implement two very simple ciphers: the Caesar cipher and the keyword cipher. We'll use each of these to encrypt and decrypt strings." 9 | ] 10 | }, 11 | { 12 | "cell_type": "markdown", 13 | "metadata": {}, 14 | "source": [ 15 | "## Caesar Cipher\n", 16 | "A Caesar cipher is a cryptographic function that takes as input a plaintext string and outputs a ciphertext string that is the encoding of the input. Specifically, the Caesar cipher shifts each letter of the alphabet by some fixed amount, wrapping at the end of the alphabet and ignoring whitespace. For example, the string\n", 17 | "\n", 18 | "```\n", 19 | "CogWorks is fun\n", 20 | "```\n", 21 | "\n", 22 | "encoded using a Caesar cipher with shift 3 becomes\n", 23 | "\n", 24 | "```\n", 25 | "FrjZrunv lv ixq\n", 26 | "```\n", 27 | "\n", 28 | "Let's break down how this works. We'll order the alphabet so that lowercase letters come before capital letters. This gives us:\n", 29 | "\n", 30 | "```\n", 31 | "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ\n", 32 | "```\n", 33 | "\n", 34 | "The Caesar cipher will shift each letter by some amount. In our case, we chose to shift by 3 characters. This means we'll map `a` to `d`, `b` to `e`, ..., `w` to `z`, `x` to `A`, ... `W` to `Z`, `X` to `a`, `Y` to `b`, and `Z` to `c`. We can create a mapping just like this:\n", 35 | "\n", 36 | "```\n", 37 | "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ\n", 38 | "defghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabc\n", 39 | "```\n", 40 | "\n", 41 | "This is essentially what the Caesar cipher is doing. Now if we want to encode our string, we see that `C` maps to `F`, `o` maps to `r`, and so on.\n", 42 | "\n", 43 | "Reversing the process to decode a string is quite straightforward. If we receive the encrypted string\n", 44 | "\n", 45 | "```\n", 46 | "Wlys yvjrz\n", 47 | "```\n", 48 | "\n", 49 | "and we know that it was encoded using a Caesar cipher with shift 7, we can easily create our reverse substitution alphabet:\n", 50 | "\n", 51 | "```\n", 52 | "hijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefg\n", 53 | "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ\n", 54 | "```\n", 55 | "\n", 56 | "and decode the message:\n", 57 | "\n", 58 | "```\n", 59 | "Perl rocks\n", 60 | "```\n", 61 | "\n", 62 | "Fun fact: The ROT13 algorithm is a famous special case of the Caesar cipher. It assumes all letters are the same case. What's special about ROT13 is that it is its own inverse: you apply the exact same function to both encode and decode your string.\n", 63 | "\n", 64 | "#### Breaking the Caesar Cipher\n", 65 | "While not very secure, the Caesar cipher can be effective as a very weak form of encryption. Note that to break the encryption, we can simply try shift amounts from 1 through 25 and see which one comes out as correct English. This is a *brute-force* solution." 66 | ] 67 | }, 68 | { 69 | "cell_type": "markdown", 70 | "metadata": {}, 71 | "source": [ 72 | "### Problem 1\n", 73 | "Write a function that takes as input a string and a shift amount and returns the string encoded using a Caesar cipher with the specified shift. Your function should:\n", 74 | "\n", 75 | "- Only replace letters; leaving numbers, punctuation, and whitespace alone\n", 76 | "- Order the alphabet from lowercase to uppercase, as above (ab...zAB...Z)" 77 | ] 78 | }, 79 | { 80 | "cell_type": "code", 81 | "execution_count": null, 82 | "metadata": { 83 | "collapsed": true 84 | }, 85 | "outputs": [], 86 | "source": [ 87 | "def encode_caesar(string, shift_amt):\n", 88 | " ''' Encodes the specified `string` using a Caesar cipher with shift `shift_amt`\n", 89 | " \n", 90 | " Parameters\n", 91 | " ----------\n", 92 | " string : str\n", 93 | " The string to encode.\n", 94 | " \n", 95 | " shift_amt : int\n", 96 | " How much to shift the alphabet by.\n", 97 | " \n", 98 | " Returns\n", 99 | " -------\n", 100 | " str\n", 101 | " The encoded string.\n", 102 | " '''\n", 103 | " # student code goes here" 104 | ] 105 | }, 106 | { 107 | "cell_type": "code", 108 | "execution_count": null, 109 | "metadata": { 110 | "collapsed": true 111 | }, 112 | "outputs": [], 113 | "source": [ 114 | "from bwsi_grader.python.ciphers import grade_cesar_cipher\n", 115 | "grade_cesar_cipher(encode_caesar)" 116 | ] 117 | }, 118 | { 119 | "cell_type": "markdown", 120 | "metadata": {}, 121 | "source": [ 122 | "## Keyword Cipher\n", 123 | "A keyword cipher (or substitution cipher) is another cryptographic function. Rather than shift the alphabet by some fixed amount like the Caesar cipher, the Keyword cipher shifts the alphabet based on a keyword or phrase. Let's illustrate with an example.\n", 124 | "\n", 125 | "Suppose our keyphrase is\n", 126 | "\n", 127 | "```\n", 128 | "beaverworks is cool\n", 129 | "```\n", 130 | "\n", 131 | "and we want to encode the string\n", 132 | "\n", 133 | "```\n", 134 | "python rules\n", 135 | "```\n", 136 | "\n", 137 | "Like the Caesar cipher, we'll build a substitution alphabet. We'll go through our keyphrase and record the **unique** letters, in order, then append all the rest of the alphabet that wasn't in the keyphrase. For simplicity, we'll only use lowercase letters for this cipher.\n", 138 | "\n", 139 | "Going through the keyphrase we pull out\n", 140 | "\n", 141 | "```\n", 142 | "beavrwoksicl\n", 143 | "```\n", 144 | "\n", 145 | "Note that each letter is unique, and is in the order we saw it in the keyphrase. The second 'e' we see in 'beaverworks' is simply ignored. Now all we have to do is go through the alphabet and append all the letters that we don't already have. This gives us the following mapping:\n", 146 | "\n", 147 | "```\n", 148 | "abcdefghijklmnopqrstuvwxyz\n", 149 | "beavrwoksicldfghjmnpqtuxyz\n", 150 | "```\n", 151 | "\n", 152 | "Now we can make a straightforward substitution for each letter in our string, `'python rules'`, and get the encoded string\n", 153 | "\n", 154 | "```\n", 155 | "hypkgf mqlrn\n", 156 | "```\n", 157 | "\n", 158 | "Taking another example, if we receive the encoded string\n", 159 | "\n", 160 | "```\n", 161 | "kqbhj khsjhp kpjige\n", 162 | "```\n", 163 | "\n", 164 | "and we know that the keyword used to create the cipher was\n", 165 | "\n", 166 | "```\n", 167 | "massachusetts institute of technology\n", 168 | "```\n", 169 | "\n", 170 | "then we can easily assemble the substitution alphabet\n", 171 | "\n", 172 | "```\n", 173 | "abcdefghijklmnopqrstuvwxyz\n", 174 | "maschuetinoflgybdjkpqrvwxz\n", 175 | "```\n", 176 | "\n", 177 | "and decode the string\n", 178 | "\n", 179 | "```\n", 180 | "super secret string\n", 181 | "```\n", 182 | "\n", 183 | "#### Breaking the Keyword Cipher\n", 184 | "You might be thinking that this is more difficult to decrypt than our Caesar cipher if we don't know the keyword, and you'd be right. With the Caesar cipher, all we had to do to break it was try 25 different shifts and see which one came out in sensible English.\n", 185 | "\n", 186 | "With the keyword cipher, there are `403,291,461,126,605,635,584,000,000` permutations of the alphabet. And this is with only lowercase letters! Even if we could check 2 billion permutations each second, it would take more than 6 billion years to try all the possible permutations here.\n", 187 | "\n", 188 | "Practically, this is where things like frequency analysis come into play, where if we have enough encoded text we can look at the frequency of each letter and try to match these to known frequencies of English. For example, the letter 'e' is very common and if the frequency of 'q' in the encoded text is close to that known frequency, then 'q' probably maps to 'e'. Of course, there are many sophisticated techniques in cryptanalysis worth exploring." 189 | ] 190 | }, 191 | { 192 | "cell_type": "markdown", 193 | "metadata": {}, 194 | "source": [ 195 | "### Problem 2\n", 196 | "Write a function that takes as input a string and a keyword (or phrase) and returns the string encoded using a Keyword (substitution) cipher. Your function should:\n", 197 | "\n", 198 | "- Only replace letters; leaving numbers, punctuation, and whitespace alone\n", 199 | "- Ignore case, using only lowercase letters\n", 200 | "\n", 201 | "Assume that `keyword` will always be a non-empty string." 202 | ] 203 | }, 204 | { 205 | "cell_type": "code", 206 | "execution_count": null, 207 | "metadata": { 208 | "collapsed": true 209 | }, 210 | "outputs": [], 211 | "source": [ 212 | "def encode_keyword(string, keyword):\n", 213 | " ''' Encodes the specified `string` using a Keyword cipher with keyword `keyword`.\n", 214 | " \n", 215 | " Parameters\n", 216 | " ----------\n", 217 | " string : str\n", 218 | " The string to encode.\n", 219 | " \n", 220 | " keyword : str\n", 221 | " The keyword to use in the substitution alphabet.\n", 222 | " \n", 223 | " Returns\n", 224 | " -------\n", 225 | " str\n", 226 | " The encoded string.\n", 227 | " '''\n", 228 | " # student code goes here" 229 | ] 230 | }, 231 | { 232 | "cell_type": "code", 233 | "execution_count": null, 234 | "metadata": { 235 | "collapsed": true 236 | }, 237 | "outputs": [], 238 | "source": [ 239 | "from bwsi_grader.python.ciphers import grade_keyword_cipher\n", 240 | "grade_keyword_cipher(encode_keyword)" 241 | ] 242 | } 243 | ], 244 | "metadata": { 245 | "kernelspec": { 246 | "display_name": "Python 3", 247 | "language": "python", 248 | "name": "python3" 249 | }, 250 | "language_info": { 251 | "codemirror_mode": { 252 | "name": "ipython", 253 | "version": 3 254 | }, 255 | "file_extension": ".py", 256 | "mimetype": "text/x-python", 257 | "name": "python", 258 | "nbconvert_exporter": "python", 259 | "pygments_lexer": "ipython3", 260 | "version": "3.7.2" 261 | } 262 | }, 263 | "nbformat": 4, 264 | "nbformat_minor": 2 265 | } 266 | -------------------------------------------------------------------------------- /heat_dispersion/HW_heat_dispersion.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Simulating The Spread of Heat Through a Material\n", 8 | "> Written by David Mascharka and Ryan Soklaski\n", 9 | "\n", 10 | "## Understanding the Heat Propagation\n", 11 | "In this problem, we will learn about a simple algorithm for numerically simulating the spread of heat through a material. We will want to use vectorization to write an efficient algorithm.\n", 12 | "\n", 13 | "Imagine that we have a rectangular piece of steel. For now, let's treat this piece of steel as a 5x5 grid - we are only able to measure the average temperature of each of these 25 grid regions. Let's assume that steel starts off at a uniform 0-degrees. Thus, our temperature readout for each of its grid positions is:\n", 14 | "\n", 15 | "```\n", 16 | " 0 0 0 0 0 \n", 17 | " 0 0 0 0 0 \n", 18 | " 0 0 0 0 0 \n", 19 | " 0 0 0 0 0 \n", 20 | " 0 0 0 0 0 \n", 21 | "```\n", 22 | "\n", 23 | "Now, we will clamp hot contacts, which are always at a constant 100-degrees, along the outer edges of the steel. Upon clamping these contacts, our temperature readout at time-0 becomes:\n", 24 | "\n", 25 | "```\n", 26 | " 100 100 100 100 100\n", 27 | " 100 0 0 0 100\n", 28 | " 100 0 0 0 100\n", 29 | " 100 0 0 0 100\n", 30 | " 100 100 100 100 100\n", 31 | "```\n", 32 | "\n", 33 | "We will adopt the same indexing scheme as a 2D NumPy array. That is, element (i,j) of this grid is row-i, column-j in the grid. The top-left corner is located at (0, 0), and has a temperature of 100. \n", 34 | "\n", 35 | "Moving forward, we want to describe, numerically, how the heat from the contacts will spread through the material as time carries on. The heat equation is a partial differential equation that describes the flow of heat through space and time. In the following equation, the function $u(x, y, t)$ describes how much heat resides at the location $(x, y)$ at time $t$:\n", 36 | "\n", 37 | "\\begin{equation}\n", 38 | "\\frac{\\partial u}{\\partial t} - \\alpha \\left(\\frac{\\partial^{2} u}{\\partial x^{2}} + \\frac{\\partial^{2} u}{\\partial y^{2}} \\right)= 0\n", 39 | "\\end{equation}\n", 40 | "\n", 41 | "Do not worry if you have no clue what a partial differential equation is! You do not need to know anything about the heat equation, we are simply providing some background here. \n", 42 | "\n", 43 | "What this equation ultimately says is that heat will spread such that a point will take on the average amount of heat among its neighboring points. Numerically, we can write this out as:\n", 44 | "\n", 45 | "\\begin{equation}\n", 46 | "u^{(t)}_{ij} = \\frac{u^{(t-1)}_{i+1,j} + u^{(t-1)}_{i-1,j} + u^{(t-1)}_{i,j+1} + u^{(t-1)}_{i,j-1}}{4}\n", 47 | "\\end{equation}\n", 48 | "\n", 49 | "That is, $u^{(t)}_{ij}$ is the heat at grid-location $(i, j)$ at time-step $t$. It's value is given by the average of the heat of all four of its neighboring grid positions from time-step $t-1$. See that the right side of the equation averages the heat from above, below, left-of, and right-of $(i, j)$, at time-step $t-1$. This means of evolving the heat through our gridded material is known as the *finite difference method*.\n", 50 | "\n", 51 | "Let's use the finite difference method to figure out what the distribution of heat looks like throughout our steel square at time-step 1. Keep in mind that we need not update any of the outer-edges of the steel - those positions are held at a fixed heat. We'll start at the upper-left corner and get\n", 52 | "\n", 53 | "\\begin{equation}\n", 54 | "u^{t=1}_{1,1} = \\frac{0 + 100 + 0 + 100}{4} = 50\\\\\n", 55 | "u^{t=1}_{1,2} = \\frac{0 + 100 + 0 + 0}{4} = 25\\\\\n", 56 | "\\end{equation}\n", 57 | "\n", 58 | "and so on, yielding the heat distribution at timestep-1 of:\n", 59 | "```\n", 60 | " 100 100 100 100 100\n", 61 | " 100 50 25 50 100\n", 62 | " 100 25 0 25 100\n", 63 | " 100 50 25 50 100\n", 64 | " 100 100 100 100 100\n", 65 | "```\n", 66 | "\n", 67 | "Repeating this process again will produce the heat distribution at timestep-2, and so on. After many iterations, we see the entire region becomes 100 degrees. This is because the heat from the edges flows inward until everything is the same temperature. This stabilized distribution of heat is known as the *steady state*. If we change the boundary conditions, i.e. change what we clamp to the edges of our steel, we will observe different steady states.\n" 68 | ] 69 | }, 70 | { 71 | "cell_type": "markdown", 72 | "metadata": {}, 73 | "source": [ 74 | "## Problem 1: A Simple Implementation of Finite Differences\n", 75 | "Write a Python function that takes in a 2-dimensional numpy-array containing heat-values, and uses the finite difference method to produce the heat distribution for that material at the next time-step. Do this using simple for-loops to iterate over the values of the array.\n", 76 | "\n", 77 | "Assume that the boundary-values of the array are fixed, so you need not update them. However, do *not* assume that the boundary values are all the same as one another, as they were in the preceding example.\n", 78 | "\n", 79 | "Also, be careful not to change the content of the array that your function is given. You need to use the values in that array, unchanged, to compute the new heat distribution. Consider making use of `np.copy` to create a copy of the input array (so that your new array will have the appropriate boundary values)." 80 | ] 81 | }, 82 | { 83 | "cell_type": "code", 84 | "execution_count": null, 85 | "metadata": {}, 86 | "outputs": [], 87 | "source": [ 88 | "# make sure to execute this cell so that your function is defined\n", 89 | "# you must re-run this cell any time you make a change to this function\n", 90 | "import numpy as np\n", 91 | "def evolve_heat_slow(u):\n", 92 | " \"\"\" Given a 2D array of heat-values (at fixed boundary), produces\n", 93 | " the new heat distribution after one iteration of the finite \n", 94 | " difference method.\n", 95 | " \n", 96 | " Parameters\n", 97 | " ----------\n", 98 | " u : numpy.ndarray shape=(M, N)\n", 99 | " An MxN array of heat values at time-step t-1.\n", 100 | " (M and N are both at least 2)\n", 101 | " \n", 102 | " Returns\n", 103 | " -------\n", 104 | " numpy.ndarray, shape=(M, N)\n", 105 | " An MxN array of heat values at time-step t.\n", 106 | " \"\"\" \n", 107 | " # student code goes here" 108 | ] 109 | }, 110 | { 111 | "cell_type": "code", 112 | "execution_count": null, 113 | "metadata": {}, 114 | "outputs": [], 115 | "source": [ 116 | "from bwsi_grader.python.heat_dispersion import grader\n", 117 | "grader(evolve_heat_slow, grade_ver=1)" 118 | ] 119 | }, 120 | { 121 | "cell_type": "markdown", 122 | "metadata": {}, 123 | "source": [ 124 | "Armed with this function, we will find the steady state of a more finely-gridded sheet of steel, with a less trivial set of boundary heat-values.\n", 125 | "\n", 126 | "We will create an 80x96 grid with the following boundary conditions:\n", 127 | "\n", 128 | "- Along the top row, we linearly increase the heat from 0 to 300 degrees from left to right\n", 129 | "- Along the bottom row, we fade from 0 to 80 degrees at the middle and back to 0 on the right\n", 130 | "- Along the left side, we linearly increase from 0 degrees at the bottom to 90 at the top (note that the very corner point is 0 from the 0 -> 300 continuum above)\n", 131 | "- Along the right side, we linearly increase the heat from 0 to 300 degrees from bottom to top" 132 | ] 133 | }, 134 | { 135 | "cell_type": "code", 136 | "execution_count": null, 137 | "metadata": {}, 138 | "outputs": [], 139 | "source": [ 140 | "# creating the 80x96-grid sheet with the non-trivial boundary conditions\n", 141 | "# simply execute this cell; you need not change anything.\n", 142 | "\n", 143 | "import numpy as np\n", 144 | "# discretize our rectangle into an 80x96 grid\n", 145 | "rows = 80 \n", 146 | "columns = 96\n", 147 | "u = np.zeros((rows, columns))\n", 148 | "\n", 149 | "# set up the boundary conditions\n", 150 | "u[0] = np.linspace(0, 300, columns) # top row runs 0 -> 300\n", 151 | "u[1:,0] = np.linspace(90, 0, rows-1) # left side goes 0 -> 90 bottom to top\n", 152 | "u[-1,:columns//2] = np.linspace(0, 80, columns//2) # 0 (left) to 80 (middle) along the bottom\n", 153 | "u[-1,columns//2:] = np.linspace(80, 0, columns//2) # 80 (middle) to 0 (left) along the bottom\n", 154 | "u[:,-1] = np.linspace(300,0,rows) # 0 -> 300 bottom to top along the right" 155 | ] 156 | }, 157 | { 158 | "cell_type": "markdown", 159 | "metadata": {}, 160 | "source": [ 161 | "Let's plot the initial condition for this steel sheet. You should see a \"hot spot\" in the top-right corner, and varying amounts of heat elsewhere on the boundary. Check that this corresponds to the boundary conditions that we imposed." 162 | ] 163 | }, 164 | { 165 | "cell_type": "code", 166 | "execution_count": null, 167 | "metadata": {}, 168 | "outputs": [], 169 | "source": [ 170 | "# execute this cell\n", 171 | "\n", 172 | "# matplotlib is a Python library used for visualizing data\n", 173 | "import matplotlib.pyplot as plt\n", 174 | "from matplotlib.animation import FuncAnimation\n", 175 | "%matplotlib notebook\n", 176 | "\n", 177 | "fig, ax = plt.subplots()\n", 178 | "ax.imshow(u, cmap='hot');" 179 | ] 180 | }, 181 | { 182 | "cell_type": "markdown", 183 | "metadata": {}, 184 | "source": [ 185 | "Now, we will make an animation of the heat spreading through this material. However, our current implementation is too slow - let's time the amount of time required to evolve the heat in the material for 5000 iterations. This should take 20 sec - 1 minute." 186 | ] 187 | }, 188 | { 189 | "cell_type": "code", 190 | "execution_count": null, 191 | "metadata": {}, 192 | "outputs": [], 193 | "source": [ 194 | "import time\n", 195 | "slow = u.copy()\n", 196 | "start = time.time()\n", 197 | "for _ in range(5000): # perform 5000 iterations to reach a steady state\n", 198 | " slow = evolve_heat_slow(slow)\n", 199 | "t = round(time.time() - start, 1)\n", 200 | "print(\"`evolve_heat_slow` took {} seconds to complete 5000 iterations\".format(t))" 201 | ] 202 | }, 203 | { 204 | "cell_type": "markdown", 205 | "metadata": {}, 206 | "source": [ 207 | "# Problem 2: A Vectorized Version of Finite Differences\n", 208 | "Use NumPy array arithmetic to vectorize the finite-difference method that you implemented in problem #1. Your code should not utilize any for-loops." 209 | ] 210 | }, 211 | { 212 | "cell_type": "code", 213 | "execution_count": null, 214 | "metadata": {}, 215 | "outputs": [], 216 | "source": [ 217 | "def evolve_heat_fast(u):\n", 218 | " \"\"\" Given a 2D array of heat-values (at fixed boundary), produces\n", 219 | " the new heat distribution after one iteration of the finite \n", 220 | " difference method.\n", 221 | " \n", 222 | " Parameters\n", 223 | " ----------\n", 224 | " u : numpy.ndarray shape=(M, N)\n", 225 | " An MxN array of heat values at time-step t-1.\n", 226 | " (M and N are both at least 2)\n", 227 | " \n", 228 | " Returns\n", 229 | " -------\n", 230 | " numpy.ndarray, shape=(M, N)\n", 231 | " An MxN array of heat values at time-step t.\n", 232 | " \"\"\" \n", 233 | " # student code goes here" 234 | ] 235 | }, 236 | { 237 | "cell_type": "code", 238 | "execution_count": null, 239 | "metadata": {}, 240 | "outputs": [], 241 | "source": [ 242 | "from bwsi_grader.python.heat_dispersion import grader\n", 243 | "grader(evolve_heat_fast, grade_ver=2)" 244 | ] 245 | }, 246 | { 247 | "cell_type": "markdown", 248 | "metadata": {}, 249 | "source": [ 250 | "Now let's use our vectorized code to perform 5000 iterations to evolve the heat in our system. This should be nearly 100-times faster than our original implementation." 251 | ] 252 | }, 253 | { 254 | "cell_type": "code", 255 | "execution_count": null, 256 | "metadata": {}, 257 | "outputs": [], 258 | "source": [ 259 | "# execute this cell\n", 260 | "\n", 261 | "import time\n", 262 | "fast = u.copy()\n", 263 | "start = time.time()\n", 264 | "all_frames = []\n", 265 | "for _ in range(5000):\n", 266 | " all_frames.append(fast.copy())\n", 267 | " fast = evolve_heat_fast(fast)\n", 268 | "t = round(time.time() - start, 1)\n", 269 | "print(\"`evolve_heat_fast` took {} seconds to complete 5000 iterations\".format(t))" 270 | ] 271 | }, 272 | { 273 | "cell_type": "markdown", 274 | "metadata": {}, 275 | "source": [ 276 | "Plotting the distribution of heat after 5000 time-steps of evolution. " 277 | ] 278 | }, 279 | { 280 | "cell_type": "code", 281 | "execution_count": null, 282 | "metadata": {}, 283 | "outputs": [], 284 | "source": [ 285 | "# execute this cell\n", 286 | "\n", 287 | "fig, ax = plt.subplots()\n", 288 | "ax.imshow(fast, cmap='hot');" 289 | ] 290 | }, 291 | { 292 | "cell_type": "markdown", 293 | "metadata": {}, 294 | "source": [ 295 | "Even better, let's plot an animation of the heat spreading through the steel sheet! The animation will loop back to the beginning after playing through." 296 | ] 297 | }, 298 | { 299 | "cell_type": "code", 300 | "execution_count": null, 301 | "metadata": {}, 302 | "outputs": [], 303 | "source": [ 304 | "# execute this cell\n", 305 | "\n", 306 | "fig = plt.figure()\n", 307 | "t = u.copy()\n", 308 | "im = plt.imshow(t, animated=True, cmap='hot')\n", 309 | "\n", 310 | "def updatefig(*args):\n", 311 | " im.set_array(all_frames[args[0]])\n", 312 | " return im,\n", 313 | "\n", 314 | "ani = FuncAnimation(fig, updatefig, range(5000), interval=1, blit=True, repeat=True,\n", 315 | " repeat_delay=1000)\n", 316 | "plt.show()" 317 | ] 318 | }, 319 | { 320 | "cell_type": "markdown", 321 | "metadata": {}, 322 | "source": [ 323 | "Try creating your own boundary conditions for the temperature distribution on this steel sheet. Reuse the code from above to animate how the heat spreads through it. Also congratulate yourself for numerically solving a fixed-boundary partial differential equation :)" 324 | ] 325 | } 326 | ], 327 | "metadata": { 328 | "kernelspec": { 329 | "display_name": "Python 3", 330 | "language": "python", 331 | "name": "python3" 332 | }, 333 | "language_info": { 334 | "codemirror_mode": { 335 | "name": "ipython", 336 | "version": 3 337 | }, 338 | "file_extension": ".py", 339 | "mimetype": "text/x-python", 340 | "name": "python", 341 | "nbconvert_exporter": "python", 342 | "pygments_lexer": "ipython3", 343 | "version": "3.7.2" 344 | }, 345 | "name": "Untitled.ipynb" 346 | }, 347 | "nbformat": 4, 348 | "nbformat_minor": 2 349 | } 350 | -------------------------------------------------------------------------------- /image_restoration/HW_image_restoration.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "attachments": { 5 | "blowup.png": { 6 | "image/png": "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" 7 | }, 8 | "noisy-example.png": { 9 | "image/png": "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" 10 | } 11 | }, 12 | "cell_type": "markdown", 13 | "metadata": {}, 14 | "source": [ 15 | "# Restoring a Noisy Image\n", 16 | "> Written by David Mascharka\n", 17 | "\n", 18 | "**This problem is *not* graded. The autograder will tell you if your solution is correct, but will not provide a hash. The solution notebook is available on EdX. **\n", 19 | "\n", 20 | "## Understanding image restoration\n", 21 | "In this problem, we will learn about a simple algorithm for removing noise from ('denoising') an image. We will want to use vectorization to write an efficient algorithm for this.\n", 22 | "\n", 23 | "Suppose we have an image that we want to transmit to a friend. During transmission, there is a 10% chance that any given pixel gets distorted. After transmission, about 10% of our pixels have changed colors due to this distortion. For example, we might start with the image on the left and our friend may receive the image on the right:\n", 24 | "\n", 25 | "\n", 26 | "![noisy-example.png](attachment:noisy-example.png)\n", 27 | "\n", 28 | "We want to write an algorithm that will automatically remove as much noise from the received image as possible. The algorithm we'll use for this is called Iterated Conditional Modes (ICM). We'll only concern ourselves with grayscale images for this, but note that the same technique can be applied to color images as well.\n", 29 | "\n", 30 | "#### Iterated Conditional Modes \n", 31 | "The idea behind ICM is pretty straightforward. It hinges on a very simple observation: in an image, color changes are very infrequent. Objects tend to have the same color throughout them, so the only color differences we run into are when we transition from one object to another. For example, in the image above we see a color transition when we look at the boundary of the circle and the triangle. However, within an object the color is the same.\n", 32 | "\n", 33 | "Taking a look at the noisy image, we can see that there are a lot of color changes; far more than in the original image. Most of these are isolated to a single pixel. For example, see this blowup of the triangle:\n", 34 | "\n", 35 | "![blowup.png](attachment:blowup.png)\n", 36 | "\n", 37 | "In the middle of the red rectangle we can see a dark gray pixel that's all by itself. We can be almost certain that this kind of discontinuity is caused by noise. The chances of a single pixel being a different color than all of its neighbors in a normal image is very low. If we change the color of that dark gray pixel to light gray, we'll probably improve the image quality.\n", 38 | "\n", 39 | "This is exactly what the Iterated Conditional Modes algorithm does. We define the *energy* of a pixel $p$ as the number of $p$'s neighbors that differ in color from $p$. We will define $p$'s neighborhood as the four pixels immediately adjacent to $p$. If $p$ is at $(3, 5)$, then $p$'s neighborhood is $\\{(2, 5), (4, 5), (3, 4), (3, 6)\\}$.\n", 40 | " \n", 41 | "The energy of the whole image is the sum of each pixel's energy. The aim of the ICM algorithm is to minimize the energy of the noisy image by appropriately choosing color values for the most energetic pixels.\n", 42 | "\n", 43 | "As you can probably tell by the name, *Iterated* Conditional Modes is an iterative algorithm. At each iteration, we change the color value of the highest-energy pixel to match the most neighbors it can.\n", 44 | "\n", 45 | "Let's fix a concrete example. Suppose we're transmitting a 5x8 image that looks like this:\n", 46 | "\n", 47 | "```\n", 48 | " 0 0 0 0 1 1 1 1\n", 49 | " 0 0 0 0 1 1 1 1\n", 50 | " 0 0 0 0 1 1 1 1\n", 51 | " 0 0 0 0 1 1 1 1\n", 52 | " 0 0 0 0 1 1 1 1\n", 53 | "```\n", 54 | "\n", 55 | "Not very interesting, but it illustrates our point. Suppose the transmitted image that our friend receives is:\n", 56 | "\n", 57 | "```\n", 58 | " 0 0 0 0 1 1 0 1\n", 59 | " 0 1 0 0 1 1 1 1\n", 60 | " 0 0 0 0 0 1 1 1\n", 61 | " 0 0 0 0 1 1 1 1\n", 62 | " 0 0 1 0 1 1 1 1\n", 63 | "```\n", 64 | "\n", 65 | "We can compute the energy at each pixel by recording how many of the pixels above, below, left, and right are a different value. If we do this, we get these energy values:\n", 66 | "\n", 67 | "```\n", 68 | " 0 1 0 1 1 1 3 1\n", 69 | " 1 4 1 1 2 0 1 0\n", 70 | " 0 1 0 1 3 1 0 0\n", 71 | " 0 0 1 1 2 0 0 0\n", 72 | " 0 1 3 2 1 0 0 0\n", 73 | "```\n", 74 | "\n", 75 | "Remeber, these are *energy* values, not *color* values. We'll pick the highest-energy pixel, which is at $(1, 1)$ and has energy 4. We change its color to minimize its energy. This is the value $0$. Flipping it yields the image:\n", 76 | "\n", 77 | "```\n", 78 | " 0 0 0 0 1 1 0 1\n", 79 | " 0 0 0 0 1 1 1 1\n", 80 | " 0 0 0 0 0 1 1 1\n", 81 | " 0 0 0 0 1 1 1 1\n", 82 | " 0 0 1 0 1 1 1 1\n", 83 | "```\n", 84 | "\n", 85 | "We can now recompute the energy across the entire image and choose the next-highest value. Eventually, we reach a point where we don't swap any labels, and our algorithm terminates, yielding the restored image:\n", 86 | "\n", 87 | "```\n", 88 | " 0 0 0 0 1 1 1 1\n", 89 | " 0 0 0 0 1 1 1 1\n", 90 | " 0 0 0 0 1 1 1 1\n", 91 | " 0 0 0 0 1 1 1 1\n", 92 | " 0 0 0 0 1 1 1 1\n", 93 | "```\n", 94 | "\n", 95 | "which perfectly restores what we transmitted. Note that in general, the restoration will not be perfect. For an example of an imperfect restoration, if we get this transmission:\n", 96 | "\n", 97 | "```\n", 98 | " 0 0 0 0 1 1 1 1\n", 99 | " 0 1 1 0 1 1 1 1\n", 100 | " 0 1 1 0 1 1 1 1\n", 101 | " 0 0 0 0 1 1 1 1\n", 102 | " 0 0 0 0 1 1 1 1\n", 103 | "```\n", 104 | "\n", 105 | "we will not remove that block of $1$s in the middle of the $0$s." 106 | ] 107 | }, 108 | { 109 | "cell_type": "markdown", 110 | "metadata": {}, 111 | "source": [ 112 | "## Problem 1: Local energy of each pixel\n", 113 | "Write a Python function that takes in a 2-dimensional NumPy array containing color values, and returns the energy at each pixel in the image. Recall that the energy of a pixel is the number of a pixel's neighbors that differ from it.\n", 114 | "\n", 115 | "Hint: the vectorized solution to the heat equation problem can be easily adapted to compute the energy at each pixel.\n", 116 | "\n", 117 | "Fun fact: `True` and `False` can be interpreted as integers! A `True` value is a binary 1, while `False` is binary 0. We can use this fact to help with our vectorization. Try it out!\n", 118 | "\n", 119 | "```\n", 120 | ">>> np.sum([False, True, False])\n", 121 | "1\n", 122 | "```" 123 | ] 124 | }, 125 | { 126 | "cell_type": "code", 127 | "execution_count": null, 128 | "metadata": { 129 | "collapsed": true 130 | }, 131 | "outputs": [], 132 | "source": [ 133 | "import numpy as np\n", 134 | "\n", 135 | "def compute_energy(img):\n", 136 | " ''' Given a 2D array of color values, produces a 2D array with\n", 137 | " the energy at each pixel, where energy is defined as the sum\n", 138 | " of each's pixels neighbors differing in color from that pixel.\n", 139 | " \n", 140 | " Parameters\n", 141 | " ----------\n", 142 | " img : numpy.ndarray, shape=(M, N)\n", 143 | " An MxN array of color values.\n", 144 | "\n", 145 | " Returns\n", 146 | " -------\n", 147 | " numpy.ndarray, shape=(M, N)\n", 148 | " An MxN array of energy values.\n", 149 | " '''\n", 150 | " # student code goes here" 151 | ] 152 | }, 153 | { 154 | "cell_type": "code", 155 | "execution_count": null, 156 | "metadata": { 157 | "collapsed": true 158 | }, 159 | "outputs": [], 160 | "source": [ 161 | "from bwsi_grader.python.image_restoration import grader1\n", 162 | "grader1(compute_energy)" 163 | ] 164 | }, 165 | { 166 | "cell_type": "markdown", 167 | "metadata": {}, 168 | "source": [ 169 | "## Problem 2: Neighbor colors\n", 170 | "Once we get the highest-energy pixel, we need to get the color values of its neighbors, so we know what color will minimize the energy. Write a Python function that takes in a 2D NumPy array of color values and (row, column) coordinates of a pixel, and returns a list containing the color values of $(r, c)$'s neighbors." 171 | ] 172 | }, 173 | { 174 | "cell_type": "code", 175 | "execution_count": null, 176 | "metadata": { 177 | "collapsed": true 178 | }, 179 | "outputs": [], 180 | "source": [ 181 | "def get_neighbor_colors(img, pixel):\n", 182 | " ''' Given a 2D array of color values and the position of a pixel,\n", 183 | " returns a list of `pixel`'s neighboring color values.\n", 184 | " \n", 185 | " Parameters\n", 186 | " ----------\n", 187 | " img : numpy.ndarray, shape=(M, N)\n", 188 | " An MxN array of color values\n", 189 | " pixel : tuple[int, int]\n", 190 | " The (r, c) index of the pixel whose neighbors to retrieve.\n", 191 | " \n", 192 | " Returns\n", 193 | " -------\n", 194 | " List[int]\n", 195 | " The color (or label) value of each of `pixel`'s neighbors.\n", 196 | " '''\n", 197 | " # student code goes here" 198 | ] 199 | }, 200 | { 201 | "cell_type": "code", 202 | "execution_count": null, 203 | "metadata": { 204 | "collapsed": true 205 | }, 206 | "outputs": [], 207 | "source": [ 208 | "from bwsi_grader.python.image_restoration import grader2\n", 209 | "grader2(get_neighbor_colors)" 210 | ] 211 | }, 212 | { 213 | "cell_type": "markdown", 214 | "metadata": {}, 215 | "source": [ 216 | "## Problem 3: Iterated Conditional Modes\n", 217 | "With these two functions in place, we can implement Iterated Conditional Modes. Write a Python function takes as input a 2D NumPy array of color values and returns a new image with the highest-energy pixel replaced.\n", 218 | "\n", 219 | "Your function should:\n", 220 | " - Use the `compute_energy` function to get the energy at each pixel\n", 221 | " - Find the pixel coordinates of the highest-energy pixel (*hint: NumPy's argmax may be useful*)\n", 222 | " - Use the `get_neighbor_colors` function to get the colors of the highest-energy pixel's neighbors\n", 223 | " - Change the highest-energy pixel color to best match its neighbors (we can choose the most-common neighbor value)" 224 | ] 225 | }, 226 | { 227 | "cell_type": "code", 228 | "execution_count": null, 229 | "metadata": { 230 | "collapsed": true 231 | }, 232 | "outputs": [], 233 | "source": [ 234 | "def denoise_iter(noisy):\n", 235 | " ''' Given a 2D array of color values, performs one step of the\n", 236 | " Iterated Conditional Modes algorithm, changing the color of\n", 237 | " the highest-energy pixel.\n", 238 | "\n", 239 | " Paramters\n", 240 | " ---------\n", 241 | " noisy : numpy.ndarray, shape=(M, N)\n", 242 | " An MxN array of color values.\n", 243 | "\n", 244 | " Returns\n", 245 | " -------\n", 246 | " numpy.ndarray, shape=(M, N)\n", 247 | " An MxN array of color values, after applying one step of ICM.\n", 248 | " '''\n", 249 | " # student code goes here" 250 | ] 251 | }, 252 | { 253 | "cell_type": "code", 254 | "execution_count": null, 255 | "metadata": { 256 | "collapsed": true 257 | }, 258 | "outputs": [], 259 | "source": [ 260 | "from bwsi_grader.python.image_restoration import grader3\n", 261 | "grader3(denoise_iter)" 262 | ] 263 | }, 264 | { 265 | "cell_type": "markdown", 266 | "metadata": {}, 267 | "source": [ 268 | "Now we'll see your functions in action. Let's generate a noisy image." 269 | ] 270 | }, 271 | { 272 | "cell_type": "code", 273 | "execution_count": null, 274 | "metadata": { 275 | "collapsed": true 276 | }, 277 | "outputs": [], 278 | "source": [ 279 | "# This cell generates a noisy image and displays it\n", 280 | "# You need not modify this code, simply run the cell\n", 281 | "\n", 282 | "import matplotlib.pyplot as plt\n", 283 | "%matplotlib notebook\n", 284 | "\n", 285 | "def generate_noisy_copy(img, pct_noise):\n", 286 | " ''' Given a 2D array of color values and an approximate noise percentage,\n", 287 | " returns a noisy image.\n", 288 | " \n", 289 | " Parameters\n", 290 | " ----------\n", 291 | " img : numpy.ndarray, shape=(M, N)\n", 292 | " An MxN array of color values -- the pristine image.\n", 293 | " pct_noise : float\n", 294 | " The amount of noise to add, as an approximate percentage.\n", 295 | "\n", 296 | " Returns\n", 297 | " -------\n", 298 | " numpy.ndarray, shape=(M, N)\n", 299 | " An MxN array of color values -- a noisy copy of the pristine image.\n", 300 | " '''\n", 301 | " # create a noise image built by randomly choosing a color from our image\n", 302 | " noise = np.random.choice(np.unique(img), img.shape).astype(np.uint8)\n", 303 | " # make a random choice for each pixel, as to whether to draw that pixel from \n", 304 | " # the `noise` image or from input image\n", 305 | " rands = np.random.rand(img.size).reshape(img.shape)\n", 306 | " \n", 307 | " # start with a copy of the pristine image and distort it\n", 308 | " noisy = img.copy()\n", 309 | " idxs_to_change = np.where(rands < pct_noise)\n", 310 | " noisy[idxs_to_change] = noise[idxs_to_change]\n", 311 | " \n", 312 | " return noisy\n", 313 | "\n", 314 | "# load the original image\n", 315 | "pristine = (plt.imread('original-image.png')*255).astype(np.uint8)\n", 316 | "\n", 317 | "# add about 10% noise\n", 318 | "noisy = generate_noisy_copy(pristine, 0.1)\n", 319 | "\n", 320 | "# display the images\n", 321 | "fig, axs = plt.subplots(1, 2, figsize=(8, 5))\n", 322 | "axs[0].imshow(pristine, 'gray')\n", 323 | "axs[1].imshow(noisy, 'gray')" 324 | ] 325 | }, 326 | { 327 | "cell_type": "markdown", 328 | "metadata": {}, 329 | "source": [ 330 | "With all the code in place, let's finally denoise our image!" 331 | ] 332 | }, 333 | { 334 | "cell_type": "code", 335 | "execution_count": null, 336 | "metadata": { 337 | "collapsed": true 338 | }, 339 | "outputs": [], 340 | "source": [ 341 | "# You may simply run this cell\n", 342 | "\n", 343 | "num_iters = 0 # how many iterations we have performed, to see progress\n", 344 | "cleaned_up = noisy.copy() # the denoised image\n", 345 | "old = np.zeros_like(cleaned_up) # the previous iteration, for a stopping condition\n", 346 | "while np.any(old != cleaned_up): # loop until no labels change values\n", 347 | " num_iters += 1\n", 348 | " if (num_iters%1000) == 0: # print progress\n", 349 | " print(num_iters, 'Energy {}'.format(compute_energy(cleaned_up).sum()))\n", 350 | " old = cleaned_up.copy()\n", 351 | " cleaned_up = denoise_iter(cleaned_up)" 352 | ] 353 | }, 354 | { 355 | "cell_type": "markdown", 356 | "metadata": {}, 357 | "source": [ 358 | "Display the noisy version, our denoised image, and the original alongside each other" 359 | ] 360 | }, 361 | { 362 | "cell_type": "code", 363 | "execution_count": null, 364 | "metadata": { 365 | "collapsed": true 366 | }, 367 | "outputs": [], 368 | "source": [ 369 | "# Again, you only need to run this\n", 370 | "\n", 371 | "fig, axs = plt.subplots(1, 3, figsize=(8, 3))\n", 372 | "axs[0].imshow(noisy, 'gray')\n", 373 | "axs[1].imshow(cleaned_up, 'gray')\n", 374 | "axs[2].imshow(pristine, 'gray')" 375 | ] 376 | }, 377 | { 378 | "cell_type": "markdown", 379 | "metadata": {}, 380 | "source": [ 381 | "Now we can see the difference in energy between the original and denoised version" 382 | ] 383 | }, 384 | { 385 | "cell_type": "code", 386 | "execution_count": null, 387 | "metadata": { 388 | "collapsed": true 389 | }, 390 | "outputs": [], 391 | "source": [ 392 | "compute_energy(cleaned_up).sum()" 393 | ] 394 | }, 395 | { 396 | "cell_type": "code", 397 | "execution_count": null, 398 | "metadata": { 399 | "collapsed": true 400 | }, 401 | "outputs": [], 402 | "source": [ 403 | "compute_energy(pristine).sum()" 404 | ] 405 | }, 406 | { 407 | "cell_type": "code", 408 | "execution_count": null, 409 | "metadata": { 410 | "collapsed": true 411 | }, 412 | "outputs": [], 413 | "source": [] 414 | } 415 | ], 416 | "metadata": { 417 | "kernelspec": { 418 | "display_name": "Python 3", 419 | "language": "python", 420 | "name": "python3" 421 | }, 422 | "language_info": { 423 | "codemirror_mode": { 424 | "name": "ipython", 425 | "version": 3 426 | }, 427 | "file_extension": ".py", 428 | "mimetype": "text/x-python", 429 | "name": "python", 430 | "nbconvert_exporter": "python", 431 | "pygments_lexer": "ipython3", 432 | "version": "3.7.2" 433 | } 434 | }, 435 | "nbformat": 4, 436 | "nbformat_minor": 2 437 | } 438 | -------------------------------------------------------------------------------- /image_restoration/SOLN_image_restoration.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "attachments": { 5 | "blowup.png": { 6 | "image/png": "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" 7 | }, 8 | "noisy-example.png": { 9 | "image/png": "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" 10 | } 11 | }, 12 | "cell_type": "markdown", 13 | "metadata": {}, 14 | "source": [ 15 | "# Restoring a Noisy Image\n", 16 | "> Written by David Mascharka\n", 17 | "\n", 18 | "**This problem is *not* graded. The autograder will tell you if your solution is correct, but will not provide a hash. The solution notebook is available on EdX. **\n", 19 | "\n", 20 | "## Understanding image restoration\n", 21 | "In this problem, we will learn about a simple algorithm for removing noise from ('denoising') an image. We will want to use vectorization to write an efficient algorithm for this.\n", 22 | "\n", 23 | "Suppose we have an image that we want to transmit to a friend. During transmission, there is a 10% chance that any given pixel gets distorted. After transmission, about 10% of our pixels have changed colors due to this distortion. For example, we might start with the image on the left and our friend may receive the image on the right:\n", 24 | "\n", 25 | "\n", 26 | "![noisy-example.png](attachment:noisy-example.png)\n", 27 | "\n", 28 | "We want to write an algorithm that will automatically remove as much noise from the received image as possible. The algorithm we'll use for this is called Iterated Conditional Modes (ICM). We'll only concern ourselves with grayscale images for this, but note that the same technique can be applied to color images as well.\n", 29 | "\n", 30 | "#### Iterated Conditional Modes \n", 31 | "The idea behind ICM is pretty straightforward. It hinges on a very simple observation: in an image, color changes are very infrequent. Objects tend to have the same color throughout them, so the only color differences we run into are when we transition from one object to another. For example, in the image above we see a color transition when we look at the boundary of the circle and the triangle. However, within an object the color is the same.\n", 32 | "\n", 33 | "Taking a look at the noisy image, we can see that there are a lot of color changes; far more than in the original image. Most of these are isolated to a single pixel. For example, see this blowup of the triangle:\n", 34 | "\n", 35 | "![blowup.png](attachment:blowup.png)\n", 36 | "\n", 37 | "In the middle of the red rectangle we can see a dark gray pixel that's all by itself. We can be almost certain that this kind of discontinuity is caused by noise. The chances of a single pixel being a different color than all of its neighbors in a normal image is very low. If we change the color of that dark gray pixel to light gray, we'll probably improve the image quality.\n", 38 | "\n", 39 | "This is exactly what the Iterated Conditional Modes algorithm does. We define the *energy* of a pixel $p$ as the number of $p$'s neighbors that differ in color from $p$. We will define $p$'s neighborhood as the four pixels immediately adjacent to $p$. If $p$ is at $(3, 5)$, then $p$'s neighborhood is $\\{(2, 5), (4, 5), (3, 4), (3, 6)\\}$.\n", 40 | " \n", 41 | "The energy of the whole image is the sum of each pixel's energy. The aim of the ICM algorithm is to minimize the energy of the noisy image by appropriately choosing color values for the most energetic pixels.\n", 42 | "\n", 43 | "As you can probably tell by the name, *Iterated* Conditional Modes is an iterative algorithm. At each iteration, we change the color value of the highest-energy pixel to match the most neighbors it can.\n", 44 | "\n", 45 | "Let's fix a concrete example. Suppose we're transmitting a 5x8 image that looks like this:\n", 46 | "\n", 47 | "```\n", 48 | " 0 0 0 0 1 1 1 1\n", 49 | " 0 0 0 0 1 1 1 1\n", 50 | " 0 0 0 0 1 1 1 1\n", 51 | " 0 0 0 0 1 1 1 1\n", 52 | " 0 0 0 0 1 1 1 1\n", 53 | "```\n", 54 | "\n", 55 | "Not very interesting, but it illustrates our point. Suppose the transmitted image that our friend receives is:\n", 56 | "\n", 57 | "```\n", 58 | " 0 0 0 0 1 1 0 1\n", 59 | " 0 1 0 0 1 1 1 1\n", 60 | " 0 0 0 0 0 1 1 1\n", 61 | " 0 0 0 0 1 1 1 1\n", 62 | " 0 0 1 0 1 1 1 1\n", 63 | "```\n", 64 | "\n", 65 | "We can compute the energy at each pixel by recording how many of the pixels above, below, left, and right are a different value. If we do this, we get these energy values:\n", 66 | "\n", 67 | "```\n", 68 | " 0 1 0 1 1 1 3 1\n", 69 | " 1 4 1 1 2 0 1 0\n", 70 | " 0 1 0 1 3 1 0 0\n", 71 | " 0 0 1 1 2 0 0 0\n", 72 | " 0 1 3 2 1 0 0 0\n", 73 | "```\n", 74 | "\n", 75 | "Remeber, these are *energy* values, not *color* values. We'll pick the highest-energy pixel, which is at $(1, 1)$ and has energy 4. We change its color to minimize its energy. This is the value $0$. Flipping it yields the image:\n", 76 | "\n", 77 | "```\n", 78 | " 0 0 0 0 1 1 0 1\n", 79 | " 0 0 0 0 1 1 1 1\n", 80 | " 0 0 0 0 0 1 1 1\n", 81 | " 0 0 0 0 1 1 1 1\n", 82 | " 0 0 1 0 1 1 1 1\n", 83 | "```\n", 84 | "\n", 85 | "We can now recompute the energy across the entire image and choose the next-highest value. Eventually, we reach a point where we don't swap any labels, and our algorithm terminates, yielding the restored image:\n", 86 | "\n", 87 | "```\n", 88 | " 0 0 0 0 1 1 1 1\n", 89 | " 0 0 0 0 1 1 1 1\n", 90 | " 0 0 0 0 1 1 1 1\n", 91 | " 0 0 0 0 1 1 1 1\n", 92 | " 0 0 0 0 1 1 1 1\n", 93 | "```\n", 94 | "\n", 95 | "which perfectly restores what we transmitted. Note that in general, the restoration will not be perfect. For an example of an imperfect restoration, if we get this transmission:\n", 96 | "\n", 97 | "```\n", 98 | " 0 0 0 0 1 1 1 1\n", 99 | " 0 1 1 0 1 1 1 1\n", 100 | " 0 1 1 0 1 1 1 1\n", 101 | " 0 0 0 0 1 1 1 1\n", 102 | " 0 0 0 0 1 1 1 1\n", 103 | "```\n", 104 | "\n", 105 | "we will not remove that block of $1$s in the middle of the $0$s." 106 | ] 107 | }, 108 | { 109 | "cell_type": "markdown", 110 | "metadata": {}, 111 | "source": [ 112 | "## Problem 1: Local energy of each pixel\n", 113 | "Write a Python function that takes in a 2-dimensional NumPy array containing color values, and returns the energy at each pixel in the image. Recall that the energy of a pixel is the number of a pixel's neighbors that differ from it.\n", 114 | "\n", 115 | "Hint: the vectorized solution to the heat equation problem can be easily adapted to compute the energy at each pixel.\n", 116 | "\n", 117 | "Fun fact: `True` and `False` can be interpreted as integers! A `True` value is a binary 1, while `False` is binary 0. We can use this fact to help with our vectorization. Try it out!\n", 118 | "\n", 119 | "```\n", 120 | ">>> np.sum([False, True, False])\n", 121 | "1\n", 122 | "```" 123 | ] 124 | }, 125 | { 126 | "cell_type": "code", 127 | "execution_count": null, 128 | "metadata": { 129 | "collapsed": true 130 | }, 131 | "outputs": [], 132 | "source": [ 133 | "import numpy as np\n", 134 | "\n", 135 | "def compute_energy(img):\n", 136 | " \"\"\" Given a 2D array of color values, produces a 2D array with\n", 137 | " the energy at each pixel, where energy is defined as the sum\n", 138 | " of each's pixels neighbors differing in color from that pixel.\n", 139 | " \n", 140 | " Parameters\n", 141 | " ----------\n", 142 | " img : numpy.ndarray, shape=(M, N)\n", 143 | " An MxN array of color values.\n", 144 | "\n", 145 | " Returns\n", 146 | " -------\n", 147 | " numpy.ndarray, shape=(M, N)\n", 148 | " An MxN array of energy values.\n", 149 | " \"\"\"\n", 150 | " energies = np.zeros_like(img)\n", 151 | " \n", 152 | " energies[:-1] += img[:-1] != img[1:] # below\n", 153 | " energies[1:] += img[1:] != img[:-1] # above\n", 154 | " energies[:, :-1] += img[:, :-1] != img[:, 1:] # right\n", 155 | " energies[:, 1:] += img[:, 1:] != img[:, :-1] # left\n", 156 | " return energies\n" 157 | ] 158 | }, 159 | { 160 | "cell_type": "code", 161 | "execution_count": null, 162 | "metadata": { 163 | "collapsed": true 164 | }, 165 | "outputs": [], 166 | "source": [ 167 | "from bwsi_grader.python.image_restoration import grader1\n", 168 | "grader1(compute_energy)" 169 | ] 170 | }, 171 | { 172 | "cell_type": "markdown", 173 | "metadata": {}, 174 | "source": [ 175 | "## Problem 2: Neighbor colors\n", 176 | "Once we get the highest-energy pixel, we need to get the color values of its neighbors, so we know what color will minimize the energy. Write a Python function that takes in a 2D NumPy array of color values and (row, column) coordinates of a pixel, and returns a list containing the color values of $(r, c)$'s neighbors." 177 | ] 178 | }, 179 | { 180 | "cell_type": "code", 181 | "execution_count": null, 182 | "metadata": { 183 | "collapsed": true 184 | }, 185 | "outputs": [], 186 | "source": [ 187 | "def get_neighbor_colors(img, pixel):\n", 188 | " \"\"\" Given a 2D array of color values and the position of a pixel,\n", 189 | " returns a list of `pixel`'s neighboring color values.\n", 190 | "\n", 191 | " Parameters\n", 192 | " ----------\n", 193 | " img : numpy.ndarray, shape=(M, N)\n", 194 | " An MxN array of color values\n", 195 | " pixel : tuple[int, int]\n", 196 | " The (r, c) index of the pixel whose neighbors to retrieve.\n", 197 | "\n", 198 | " Returns\n", 199 | " -------\n", 200 | " List[int]\n", 201 | " The color (or label) value of each of `pixel`'s neighbors.\n", 202 | " \"\"\"\n", 203 | " neighbor_vals = []\n", 204 | " if pixel[0] > 0:\n", 205 | " neighbor_vals.append(img[pixel[0]-1, pixel[1]])\n", 206 | " if pixel[1] > 0:\n", 207 | " neighbor_vals.append(img[pixel[0], pixel[1]-1])\n", 208 | " if pixel[0] < img.shape[0]-1:\n", 209 | " neighbor_vals.append(img[pixel[0]+1, pixel[1]])\n", 210 | " if pixel[1] < img.shape[1]-1:\n", 211 | " neighbor_vals.append(img[pixel[0], pixel[1]+1])\n", 212 | " return neighbor_vals" 213 | ] 214 | }, 215 | { 216 | "cell_type": "code", 217 | "execution_count": null, 218 | "metadata": { 219 | "collapsed": true 220 | }, 221 | "outputs": [], 222 | "source": [ 223 | "from bwsi_grader.python.image_restoration import grader2\n", 224 | "grader2(get_neighbor_colors)" 225 | ] 226 | }, 227 | { 228 | "cell_type": "markdown", 229 | "metadata": {}, 230 | "source": [ 231 | "## Problem 3: Iterated Conditional Modes\n", 232 | "With these two functions in place, we can implement Iterated Conditional Modes. Write a Python function takes as input a 2D NumPy array of color values and returns a new image with the highest-energy pixel replaced.\n", 233 | "\n", 234 | "Your function should:\n", 235 | " - Use the `compute_energy` function to get the energy at each pixel\n", 236 | " - Find the pixel coordinates of the highest-energy pixel (*hint: NumPy's argmax may be useful*)\n", 237 | " - Use the `get_neighbor_colors` function to get the colors of the highest-energy pixel's neighbors\n", 238 | " - Change the highest-energy pixel color to best match its neighbors (we can choose the most-common neighbor value)" 239 | ] 240 | }, 241 | { 242 | "cell_type": "code", 243 | "execution_count": null, 244 | "metadata": { 245 | "collapsed": true 246 | }, 247 | "outputs": [], 248 | "source": [ 249 | "def denoise_iter(noisy):\n", 250 | " \"\"\" Given a 2D array of color values, performs one step of the\n", 251 | " Iterated Conditional Modes algorithm, changing the color of\n", 252 | " the highest-energy pixel.\n", 253 | "\n", 254 | " Paramters\n", 255 | " ---------\n", 256 | " noisy : numpy.ndarray, shape=(M, N)\n", 257 | " An MxN array of color values.\n", 258 | "\n", 259 | " Returns\n", 260 | " -------\n", 261 | " numpy.ndarray, shape=(M, N)\n", 262 | " An MxN array of color values, after applying one step of ICM.\n", 263 | " \"\"\"\n", 264 | " noisy = noisy.copy()\n", 265 | " # get the energy\n", 266 | " energies = compute_energy(noisy)\n", 267 | "\n", 268 | " # get the highest-energy pixel coordinates\n", 269 | " highest_energy = np.divmod(np.argmax(energies), noisy.shape[1])\n", 270 | "\n", 271 | " # compute the mode of the pixel's neighbors\n", 272 | " neighbors = get_neighbor_colors(noisy, highest_energy)\n", 273 | " (neighbor_labels, neighbor_counts) = np.unique(neighbors, return_counts=True)\n", 274 | "\n", 275 | " # assign the best label (mode of the neighbors) to the highest-energy pixel\n", 276 | " best_label = neighbor_labels[np.argmax(neighbor_counts)]\n", 277 | " noisy[highest_energy] = best_label\n", 278 | " return noisy" 279 | ] 280 | }, 281 | { 282 | "cell_type": "code", 283 | "execution_count": null, 284 | "metadata": { 285 | "collapsed": true 286 | }, 287 | "outputs": [], 288 | "source": [ 289 | "from bwsi_grader.python.image_restoration import grader3\n", 290 | "grader3(denoise_iter)" 291 | ] 292 | }, 293 | { 294 | "cell_type": "markdown", 295 | "metadata": {}, 296 | "source": [ 297 | "Now we'll see your functions in action. Let's generate a noisy image." 298 | ] 299 | }, 300 | { 301 | "cell_type": "code", 302 | "execution_count": null, 303 | "metadata": { 304 | "collapsed": true 305 | }, 306 | "outputs": [], 307 | "source": [ 308 | "# This cell generates a noisy image and displays it\n", 309 | "# You need not modify this code, simply run the cell\n", 310 | "\n", 311 | "import matplotlib.pyplot as plt\n", 312 | "%matplotlib notebook\n", 313 | "\n", 314 | "def generate_noisy_copy(img, pct_noise):\n", 315 | " ''' Given a 2D array of color values and an approximate noise percentage,\n", 316 | " returns a noisy image.\n", 317 | " \n", 318 | " Parameters\n", 319 | " ----------\n", 320 | " img : numpy.ndarray, shape=(M, N)\n", 321 | " An MxN array of color values -- the pristine image.\n", 322 | " pct_noise : float\n", 323 | " The amount of noise to add, as an approximate percentage.\n", 324 | "\n", 325 | " Returns\n", 326 | " -------\n", 327 | " numpy.ndarray, shape=(M, N)\n", 328 | " An MxN array of color values -- a noisy copy of the pristine image.\n", 329 | " '''\n", 330 | " # create a noise image built by randomly choosing a color from our image\n", 331 | " noise = np.random.choice(np.unique(img), img.shape).astype(np.uint8)\n", 332 | " # make a random choice for each pixel, as to whether to draw that pixel from \n", 333 | " # the `noise` image or from input image\n", 334 | " rands = np.random.rand(img.size).reshape(img.shape)\n", 335 | " \n", 336 | " # start with a copy of the pristine image and distort it\n", 337 | " noisy = img.copy()\n", 338 | " idxs_to_change = np.where(rands < pct_noise)\n", 339 | " noisy[idxs_to_change] = noise[idxs_to_change]\n", 340 | " \n", 341 | " return noisy\n", 342 | "\n", 343 | "# load the original image\n", 344 | "pristine = (plt.imread('original-image.png')*255).astype(np.uint8)\n", 345 | "\n", 346 | "# add about 10% noise\n", 347 | "noisy = generate_noisy_copy(pristine, 0.1)\n", 348 | "\n", 349 | "# display the images\n", 350 | "fig, axs = plt.subplots(1, 2, figsize=(8, 5))\n", 351 | "axs[0].imshow(pristine, 'gray')\n", 352 | "axs[1].imshow(noisy, 'gray')" 353 | ] 354 | }, 355 | { 356 | "cell_type": "markdown", 357 | "metadata": {}, 358 | "source": [ 359 | "With all the code in place, let's finally denoise our image!" 360 | ] 361 | }, 362 | { 363 | "cell_type": "code", 364 | "execution_count": null, 365 | "metadata": { 366 | "collapsed": true 367 | }, 368 | "outputs": [], 369 | "source": [ 370 | "# You may simply run this cell\n", 371 | "\n", 372 | "num_iters = 0 # how many iterations we have performed, to see progress\n", 373 | "cleaned_up = noisy.copy() # the denoised image\n", 374 | "old = np.zeros_like(cleaned_up) # the previous iteration, for a stopping condition\n", 375 | "while np.any(old != cleaned_up): # loop until no labels change values\n", 376 | " num_iters += 1\n", 377 | " if (num_iters%1000) == 0: # print progress\n", 378 | " print(num_iters, 'Energy {}'.format(compute_energy(cleaned_up).sum()))\n", 379 | " old = cleaned_up.copy()\n", 380 | " cleaned_up = denoise_iter(cleaned_up)" 381 | ] 382 | }, 383 | { 384 | "cell_type": "markdown", 385 | "metadata": {}, 386 | "source": [ 387 | "Display the noisy version, our denoised image, and the original alongside each other" 388 | ] 389 | }, 390 | { 391 | "cell_type": "code", 392 | "execution_count": null, 393 | "metadata": { 394 | "collapsed": true 395 | }, 396 | "outputs": [], 397 | "source": [ 398 | "# Again, you only need to run this\n", 399 | "\n", 400 | "fig, axs = plt.subplots(1, 3, figsize=(8, 3))\n", 401 | "axs[0].imshow(noisy, 'gray')\n", 402 | "axs[1].imshow(cleaned_up, 'gray')\n", 403 | "axs[2].imshow(pristine, 'gray')" 404 | ] 405 | }, 406 | { 407 | "cell_type": "markdown", 408 | "metadata": {}, 409 | "source": [ 410 | "Now we can see the difference in energy between the original and denoised version" 411 | ] 412 | }, 413 | { 414 | "cell_type": "code", 415 | "execution_count": null, 416 | "metadata": { 417 | "collapsed": true 418 | }, 419 | "outputs": [], 420 | "source": [ 421 | "compute_energy(cleaned_up).sum()" 422 | ] 423 | }, 424 | { 425 | "cell_type": "code", 426 | "execution_count": null, 427 | "metadata": { 428 | "collapsed": true 429 | }, 430 | "outputs": [], 431 | "source": [ 432 | "compute_energy(pristine).sum()" 433 | ] 434 | }, 435 | { 436 | "cell_type": "code", 437 | "execution_count": null, 438 | "metadata": { 439 | "collapsed": true 440 | }, 441 | "outputs": [], 442 | "source": [] 443 | } 444 | ], 445 | "metadata": { 446 | "kernelspec": { 447 | "display_name": "Python 3", 448 | "language": "python", 449 | "name": "python3" 450 | }, 451 | "language_info": { 452 | "codemirror_mode": { 453 | "name": "ipython", 454 | "version": 3 455 | }, 456 | "file_extension": ".py", 457 | "mimetype": "text/x-python", 458 | "name": "python", 459 | "nbconvert_exporter": "python", 460 | "pygments_lexer": "ipython3", 461 | "version": "3.7.2" 462 | } 463 | }, 464 | "nbformat": 4, 465 | "nbformat_minor": 2 466 | } 467 | -------------------------------------------------------------------------------- /image_restoration/blowup.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CogWorksBWSI/PythonHW/fd50b34c5032dd20a43d439bdaeb49dade75689c/image_restoration/blowup.png -------------------------------------------------------------------------------- /image_restoration/noisy-example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CogWorksBWSI/PythonHW/fd50b34c5032dd20a43d439bdaeb49dade75689c/image_restoration/noisy-example.png -------------------------------------------------------------------------------- /image_restoration/original-image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CogWorksBWSI/PythonHW/fd50b34c5032dd20a43d439bdaeb49dade75689c/image_restoration/original-image.png -------------------------------------------------------------------------------- /odds_and_ends/HW_odds_and_ends.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "## Odds and Ends: File Reading and Matplotlib\n", 8 | "\n", 9 | "Now that we're familiar with the essentials of the Python language we're going to practice [reading files](https://www.pythonlikeyoumeanit.com/Module5_OddsAndEnds/WorkingWithFiles.html) and [plotting with Matplotlib](https://www.pythonlikeyoumeanit.com/Module5_OddsAndEnds/Matplotlib.html). \n", 10 | "\n", 11 | "Although these topics may be considered \"odds and ends\", they are common in many day-to-day applications. You'll find that spending some time up front to become familiar with these materials will save a lot of time down the road." 12 | ] 13 | }, 14 | { 15 | "cell_type": "markdown", 16 | "metadata": {}, 17 | "source": [ 18 | "## Problem 1: Reading and Parsing Files\n", 19 | "Let's pretend we were conducting a survey of favorite foods. Each participant is asked to list their favorite foods along with its category (e.g. dessert, snack, fruit). The food and category are separated by a colon, and each food-category pair is separated by a comma like so\n", 20 | "\n", 21 | "```food: category, food: category, food: category, ... ```\n", 22 | "\n", 23 | "The results of this survey are stored in a text file, `results.txt`, giving us a great opportunity to practice our file reading skills!\n", 24 | "\n", 25 | "Our task is to write a function called `get_most_popular_foods` that takes a file path of survey results and returns the most common response for each food category in the form of a dictionary where the keys are the food categories and the values are the most common food of that type. If there is a tie, return the food that comes first alphabetically. Note, we don't know which food categories will be given before reading the file.\n", 26 | "\n", 27 | "So, if we had data in the file `example.txt` with the contents below\n", 28 | "\n", 29 | "``` granola bars: snack, shrimp: seafood\n", 30 | "granola bars: snack\n", 31 | "tuna: seafood ```\n", 32 | "\n", 33 | "Our function would produce the following result\n", 34 | " ``` python\n", 35 | " >>> get_most_popular_foods('example.txt')\n", 36 | " {'snack': 'granola bars', 'seafood': ' shrimp'}\n", 37 | " ```\n", 38 | " \n", 39 | " The `collections.Counter` object will be useful for this problem. Also, the function `itertools.chain` may come in handy.\n", 40 | " \n", 41 | "For reference, there is a short example input under `resources/example-survey.txt`. On this input, your function should produce the response as follows\n", 42 | " ``` python\n", 43 | ">>> get_most_popular_foods('resources/example-survey.txt')\n", 44 | " {'dessert': 'cake', 'vegetable': 'carrots', 'fruit': 'peaches'}\n", 45 | "```" 46 | ] 47 | }, 48 | { 49 | "cell_type": "code", 50 | "execution_count": null, 51 | "metadata": {}, 52 | "outputs": [], 53 | "source": [ 54 | "def get_most_popular_foods(file_path):\n", 55 | " \"\"\" Read in survey and determine the most common food of each type.\n", 56 | " \n", 57 | " Parameters\n", 58 | " ----------\n", 59 | " file_path : str\n", 60 | " Path to text file containing favorite food survey responses.\n", 61 | " \n", 62 | " Returns\n", 63 | " -------\n", 64 | " Dict[str, str]\n", 65 | " Dictionary with the key being food type and value being food.\n", 66 | " \"\"\"\n", 67 | " pass" 68 | ] 69 | }, 70 | { 71 | "cell_type": "code", 72 | "execution_count": null, 73 | "metadata": {}, 74 | "outputs": [], 75 | "source": [ 76 | "from bwsi_grader.python.odds_and_ends import grade_file_parser\n", 77 | "grade_file_parser(get_most_popular_foods)" 78 | ] 79 | }, 80 | { 81 | "cell_type": "markdown", 82 | "metadata": {}, 83 | "source": [ 84 | "## Problem 2: Plotting an Image with Matplotlib\n", 85 | "\n", 86 | "We have an image in the file `resources/mystery-img.npy`. Read and plot the image, then answer the following for Question 2 of the homework:\n", 87 | "\n", 88 | "__What is in this image?__" 89 | ] 90 | } 91 | ], 92 | "metadata": { 93 | "kernelspec": { 94 | "display_name": "Python 3", 95 | "language": "python", 96 | "name": "python3" 97 | }, 98 | "language_info": { 99 | "codemirror_mode": { 100 | "name": "ipython", 101 | "version": 3 102 | }, 103 | "file_extension": ".py", 104 | "mimetype": "text/x-python", 105 | "name": "python", 106 | "nbconvert_exporter": "python", 107 | "pygments_lexer": "ipython3", 108 | "version": "3.7.2" 109 | } 110 | }, 111 | "nbformat": 4, 112 | "nbformat_minor": 2 113 | } 114 | -------------------------------------------------------------------------------- /odds_and_ends/resources/example-survey.txt: -------------------------------------------------------------------------------- 1 | cake: dessert, kale: vegetable 2 | carrots: vegetable 3 | pears: fruit 4 | peaches: fruit 5 | peaches: fruit -------------------------------------------------------------------------------- /odds_and_ends/resources/mystery-img.npy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CogWorksBWSI/PythonHW/fd50b34c5032dd20a43d439bdaeb49dade75689c/odds_and_ends/resources/mystery-img.npy -------------------------------------------------------------------------------- /palindrome/HW_palindrome.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Is it a palindrome?\n", 8 | "\n", 9 | "A palindrome is a word, phrase, or sequence that reads the same backward as forward, e.g., madam or nurses run.\n", 10 | "\n", 11 | "Write code that takes a string and returns `True` if that string is a palindrome. Your analysis should be case-insensitive and should disregard spaces. E.g. \"Race car\" should be considered a palindrome, despite beginning with a capitalized letter and containing a space. None of the strings will contain punctuation. Thus your function should produce the following behavior:\n", 12 | "\n", 13 | "- \"Are we not drawn onward to new era\" -> True\n", 14 | "- \"batman\" -> False\n", 15 | "\n", 16 | "## Required Concepts\n", 17 | "Basic Object Types\n", 18 | "- strings\n", 19 | "- checking equality\n", 20 | "\n", 21 | "Sequence Types\n", 22 | "- slicing\n", 23 | "\n", 24 | "## Examples\n", 25 | "```python\n", 26 | ">>> student_func(\"Race car\")\n", 27 | "True\n", 28 | "\n", 29 | ">>> student_func(\"RaCeCaR\")\n", 30 | "True\n", 31 | "\n", 32 | ">>> student_func(\"apple\")\n", 33 | "False\n", 34 | "```" 35 | ] 36 | }, 37 | { 38 | "cell_type": "code", 39 | "execution_count": null, 40 | "metadata": {}, 41 | "outputs": [], 42 | "source": [ 43 | "# make sure to execute this cell so that your function is defined\n", 44 | "# you must re-run this cell any time you make a change to this function\n", 45 | "\n", 46 | "def student_func(x):\n", 47 | " # `x` is a string\n", 48 | " # this function should return either `True` or `False`\n", 49 | " \n", 50 | " # write your code here\n", 51 | " # be sure to include a `return` statement so that\n", 52 | " # your function returns the appropriate object.\n", 53 | " pass" 54 | ] 55 | }, 56 | { 57 | "cell_type": "code", 58 | "execution_count": null, 59 | "metadata": {}, 60 | "outputs": [], 61 | "source": [ 62 | "# Execute this cell to grade your work\n", 63 | "from bwsi_grader.python.palindrome import grader\n", 64 | "grader(student_func)" 65 | ] 66 | } 67 | ], 68 | "metadata": { 69 | "kernelspec": { 70 | "display_name": "Python 3", 71 | "language": "python", 72 | "name": "python3" 73 | }, 74 | "language_info": { 75 | "codemirror_mode": { 76 | "name": "ipython", 77 | "version": 3 78 | }, 79 | "file_extension": ".py", 80 | "mimetype": "text/x-python", 81 | "name": "python", 82 | "nbconvert_exporter": "python", 83 | "pygments_lexer": "ipython3", 84 | "version": "3.7.2" 85 | } 86 | }, 87 | "nbformat": 4, 88 | "nbformat_minor": 2 89 | } 90 | -------------------------------------------------------------------------------- /pizza_shop/HW_pizza_shop.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Pizza Shop\n", 8 | "You work at a pizza restaurant, which is starting to accept orders online. You need to\n", 9 | "provide a python function that will accept an arbitrary order as its arguments, \n", 10 | "and compute the correct price for the order.\n", 11 | "\n", 12 | "Your cost-calculator function should have four arguments:\n", 13 | "- pizzas\n", 14 | "- drinks\n", 15 | "- wings\n", 16 | "- coupon\n", 17 | "\n", 18 | "\n", 19 | "A single pizza order is formed as a list of toppings. For example\n", 20 | "- A pizza with no toppings (other than cheese and sauce is: [] (an empty list)\n", 21 | "- A pizza with pepperoni and a double order of olives is : [\"pepperoni\", \"olives\", \"olives\"]\n", 22 | "\n", 23 | "*An arbitrary number of pizzas may be ordered, including no pizzas as all*\n", 24 | "\n", 25 | "Drinks come in as a named order (i.e. a keyword argument 'drinks'). If drinks are ordered,\n", 26 | "they are specified as a list of sizes (possible sizes: \"small\", \"medium\", \"large\", \"tub\"). For example, `drinks=[\"small\", \"small\", \"large\"]` would indicate an order including two small drinks and a large drink. \n", 27 | "\n", 28 | "Wings come in as a named order as well (keyword argument 'wings'). If wings are ordered,\n", 29 | "they are specified as a list of integers (possible sizes: 10, 20, 40, 100). For example, `wings=[20]` would indicate a single order of 20-piece wings.\n", 30 | "\n", 31 | "A coupon may be specified as the keyword argument 'coupon'. It is will be a single\n", 32 | "floating point number between 0 and 1. This indicates the fraction of the *pre-tax*\n", 33 | "price that is to be subtracted. For example `coupon=.25` would indicate a 25%-off coupon.\n", 34 | "\n", 35 | "A 6.25% tax is applied to every order. The tax is computed on the total cost of the\n", 36 | "order *before a coupon is applied*\n", 37 | "\n", 38 | "Round the price to the nearest cent, using the built-in function round. `round(x, 2)` will round `x` to two decimal places.\n", 39 | "\n", 40 | "## Prices\n", 41 | "The prices are as follows:\n", 42 | "\n", 43 | "**Pizza**\n", 44 | "- \\$13.00\n", 45 | "\n", 46 | "**Toppings**\n", 47 | "- pepperoni : \\$1.00\n", 48 | "- mushroom : \\$0.50\n", 49 | "- olive : \\$0.50\n", 50 | "- anchovy : \\$2.00\n", 51 | "- ham : \\$1.50\n", 52 | "\n", 53 | "**Drinks**\n", 54 | "- small : \\$2.00\n", 55 | "- medium : \\$3.00\n", 56 | "- large : \\$3.50\n", 57 | "- tub : \\$3.75\n", 58 | "\n", 59 | "**Wings**\n", 60 | "- 10 : \\$5.00\n", 61 | "- 20 : \\$9.00\n", 62 | "- 40 : \\$17.50\n", 63 | "- 100 : \\$48.00\n", 64 | "\n", 65 | "\n", 66 | "## Examples\n", 67 | "The following is an order for a plain pizza, a ham and anchovy pizza, two \"tub\"-sized\n", 68 | "drinks, with a 10%-off coupon:\n", 69 | "```python\n", 70 | ">>>cost_calculator([], [\"ham\", \"anchovy\"], drinks=[\"tub\", \"tub\"], coupon=0.1)\n", 71 | "35.61\n", 72 | "```\n", 73 | "\n", 74 | "This order consists only of a small drink.\n", 75 | "```python\n", 76 | ">>> cost_calculator(drinks=[\"small\"])\n", 77 | "2.12\n", 78 | "```\n", 79 | "\n", 80 | "This is an order of two plain pizzas, a pizza with double-pepperoni, an order of a 10-piece and a 20-piece wings, and a small drink.\n", 81 | "```python\n", 82 | ">>> cost_calculator([], [], [\"pepperoni\", \"pepperoni\"], wings=[10, 20], drinks=[\"small\"])\n", 83 | "60.56\n", 84 | "```\n", 85 | "\n", 86 | "## Details\n", 87 | "You can assume that the front-end of the website will never pass your function erroneous\n", 88 | "orders. That is, you will never receive orders for items that do not exist nor\n", 89 | "items that contain typos.\n", 90 | "\n", 91 | "Consider defining individual functions responsible for computing\n", 92 | "the cost of the pizzas, drinks, and wings, respectively. Have `cost_calculator`\n", 93 | "invoke these internally. Alternatively, you can read ahead about dictionaries and make nice \n", 94 | "use of these in this problem.\n", 95 | "\n", 96 | "Our `cost_calculator` signature is empty. Part of the assignment is to come up with the\n", 97 | "correct function signature!" 98 | ] 99 | }, 100 | { 101 | "cell_type": "code", 102 | "execution_count": null, 103 | "metadata": {}, 104 | "outputs": [], 105 | "source": [ 106 | "# make sure to execute this cell so that your function is defined\n", 107 | "# you must re-run this cell any time you make a change to this function\n", 108 | "\n", 109 | "def cost_calculator():\n", 110 | " # write your code here\n", 111 | " # be sure to include a `return` statement so that\n", 112 | " # your function returns the appropriate object.\n" 113 | ] 114 | }, 115 | { 116 | "cell_type": "code", 117 | "execution_count": null, 118 | "metadata": {}, 119 | "outputs": [], 120 | "source": [ 121 | "# Execute this cell to grade your work\n", 122 | "from bwsi_grader.python.pizza_shop import grader\n", 123 | "grader(cost_calculator)" 124 | ] 125 | } 126 | ], 127 | "metadata": { 128 | "kernelspec": { 129 | "display_name": "Python 3", 130 | "language": "python", 131 | "name": "python3" 132 | }, 133 | "language_info": { 134 | "codemirror_mode": { 135 | "name": "ipython", 136 | "version": 3 137 | }, 138 | "file_extension": ".py", 139 | "mimetype": "text/x-python", 140 | "name": "python", 141 | "nbconvert_exporter": "python", 142 | "pygments_lexer": "ipython3", 143 | "version": "3.7.2" 144 | } 145 | }, 146 | "nbformat": 4, 147 | "nbformat_minor": 2 148 | } 149 | -------------------------------------------------------------------------------- /relaxation_method/HW_relaxation_method.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# The Iterative Relaxation Method\n", 8 | "> Written by Ryan Soklaski\n", 9 | "\n", 10 | "## Understanding Fixed-Points\n", 11 | "In mathematics, a function $f(x)$ is said to have a \"fixed-point\" solution if there exists a value $x_{*}$ such that $f(x_{*}) = x_{*}$. That is, $f$ maps $x_{*}$ to itself. As a simple example, given $f(x) = x^{2}$, check that $f$ has a fixed-points at $x = 0$ and $x = 1$.\n", 12 | "\n", 13 | "For a less trivial example, let's see if the function $f(x) = x^{2} - 1$ has any fixed points. That is, we want to find all solutions to $f(x) = x$:\n", 14 | "\n", 15 | "\\begin{equation}\n", 16 | "x^{2} - 1 = x \\\\\\\n", 17 | "x^{2} - x - 1 = 0 \\\\\\\n", 18 | "x = -0.61803...,\\; x = 1.61803...\n", 19 | "\\end{equation}\n", 20 | "\n", 21 | "We made use of the [quadratic formula](https://en.wikipedia.org/wiki/Quadratic_formula) to find the two fixed-points for $f(x) = x^{2} - 1$. \n", 22 | "\n", 23 | "Consider that solving the fixed-point equation $f(x) = x$ is tantamount to finding where the values of $x$ where $f(x)$ intersects the line $y = x$:\n", 24 | "![Fixed-points for a quadratic equation](attachment:quadratic.png)\n", 25 | "\n", 26 | "There are many functions such that one cannot simply solve for $x$. For example, neither of the following equations are amenable to any algebraic manipulation that would reveal their solutions :\n", 27 | "\\begin{equation}\n", 28 | "\\sin{x} = x \\\\\\\n", 29 | "e^{x} - 1 = x\n", 30 | "\\end{equation}\n", 31 | "\n", 32 | "Such equations are thus known as *transcendental* equations. How, then, can we find the fixed-point solutions (if they exist) for such functions? There is a simple numerical method, known as the relaxation method, that can be used towards this end." 33 | ] 34 | }, 35 | { 36 | "cell_type": "markdown", 37 | "metadata": {}, 38 | "source": [ 39 | "## The Relaxation Method\n", 40 | "Suppose for now that $f(x)$ has one fixed-point solution, $x_{*}$. The relaxation method allows us to \"guess\" a fixed-point solution, and then iteratively improve upon this guess until you have arrived at a value that is sufficiently close to the true fixed-point, $x_{*}$. \n", 41 | "\n", 42 | "Specifically, given your initial guess of $f$'s fixed-point, $x_{0}$, you can generate a better guess by simply feeding $x_{0}$ to $f$, and using the output as your updated guess, $x_{1}$: \n", 43 | "\\begin{equation}\n", 44 | "x_{1} = f(x_{0})\n", 45 | "\\end{equation}\n", 46 | "\n", 47 | "You can then improve this guess by feeding $x_{1}$ to $f$ and using the output as the next guess. Repeating this process $n$ times will produce $n$ consecutively-improved guesses at the true fixed-point, $x_{*}$:\n", 48 | "\\begin{equation}\n", 49 | "x_{1} = f(x_{0}) \\\\\\\n", 50 | "x_{2} = f(x_{1}) \\\\\\\n", 51 | "x_{3} = f(x_{2}) \\\\\\\n", 52 | "... \\\\\\\n", 53 | "x_{n} = f(x_{n-1}) \\\\\\\n", 54 | "\\\\\\\n", 55 | "x_{n} \\approx x_{*}\n", 56 | "\\end{equation}\n", 57 | "\n", 58 | "For example, let's find a fixed point for $f(x) = \\tanh{5x}$ taking an initial guess of $0.5$.\n", 59 | "\\begin{equation}\n", 60 | "-0.9866143 = f(0.5) \\\\\\\n", 61 | "-0.9998962 = f(-0.9866143) \\\\\\\n", 62 | "-0.99990912 = f(-0.99990911) \\\\\\\n", 63 | "-0.99990912 = f(-0.99990912) \\\\\\\n", 64 | "\\end{equation}\n", 65 | "\n", 66 | "We arrived at a fixed-point (within 7 decimal-places of precision) after four iterations!\n", 67 | "\n", 68 | "### Caveats to the relaxation method \n", 69 | "For all its simplicity, the relaxation method is not a completely robust solution for finding fixed points, in which case this method can only potentially find one, for a given initial guess. It will fail if your function does not have fixed points. It is also very much possible for a function to have multiple fixed points. Additionally, this iterative process can \"blow up\" and lead you to ever-growing numbers if you use a \"bad\" starting guess. It is possible for the relaxation method to get stuck in a loop. If you try to find the fixed points for $x^2 - 1$ using an initial guess of $x_{o} = 0.5$, you will find that you eventually repeatedly guess 0, -1, 0, -1, 0, .... Be aware of these pitfalls when you are testing your code - they are a fundamental issue of the relaxation method, and not a symptom of bad code. \n", 70 | "\n", 71 | "You need not worry about accounting for these issues in your code. You will never be given pathological functions/guesses that would cause these issues, in this homework.\n", 72 | "\n", 73 | "\n" 74 | ] 75 | }, 76 | { 77 | "cell_type": "markdown", 78 | "metadata": {}, 79 | "source": [ 80 | "### Problem #1\n", 81 | "Write a relaxation-method function that accepts three arguments:\n", 82 | "- a python function, which accepts a number as an input, and returns a float as an output\n", 83 | "- an initial guess for the fixed-point, $x_{0}$, a floating-point number\n", 84 | "- the number of iterations, $n$, to perform the relaxation method on the provided function\n", 85 | "\n", 86 | "Your function should return a list of the $n+1$ numbers: the initial guess and the $n$ guesses that you generate using the relaxation method. \n", 87 | "\n", 88 | "So, in the context of the preceding relaxation example, I could define the function:\n", 89 | "```python\n", 90 | "from math import tanh\n", 91 | "def f(x):\n", 92 | " return tanh(5*x)\n", 93 | "```\n", 94 | "and then calling your relaxation function, passing it this function, an initial guess of $x_{o}=0.5$, and instructing it to perform 5 iterations, should produce the following list:\n", 95 | "```python\n", 96 | ">>> relaxation_method(f, xo=-.5, num_it=5)\n", 97 | "[-0.5,\n", 98 | " -0.98661429815143031,\n", 99 | " -0.99989620032332682,\n", 100 | " -0.99990910997226823,\n", 101 | " -0.99990912170456125,\n", 102 | " -0.99990912171522284]\n", 103 | "```\n", 104 | "That is, your `relaxation_method` function should call `f(xo)` to obtain the updated-guess for the fixed point, as the first iteration, and so on. Use the parameters provided in this example to test your code. " 105 | ] 106 | }, 107 | { 108 | "cell_type": "code", 109 | "execution_count": null, 110 | "metadata": {}, 111 | "outputs": [], 112 | "source": [ 113 | "def relaxation_method1(func, xo, num_it):\n", 114 | " \"\"\" Performs the relaxation method to find a fixed-point for `func`,\n", 115 | " given the initial guess `xo`. The relaxation process is carried out for\n", 116 | " `num_it` steps.\n", 117 | " \n", 118 | " Parameters\n", 119 | " ----------\n", 120 | " func : Callable[[float], float]\n", 121 | " The function whose fixed point is being found.\n", 122 | " xo : float\n", 123 | " The initial \"guess\" value.\n", 124 | " num_it : int\n", 125 | " The number of relaxation-iterations to perform.\n", 126 | " \n", 127 | " Returns\n", 128 | " -------\n", 129 | " List[float]\n", 130 | " A list of the initial guess, and all of the subsequent guesses generated\n", 131 | " by the relaxation method. \"\"\"\n", 132 | " # student code goes here\n", 133 | " " 134 | ] 135 | }, 136 | { 137 | "cell_type": "code", 138 | "execution_count": null, 139 | "metadata": {}, 140 | "outputs": [], 141 | "source": [ 142 | "# run this cell to grade your work\n", 143 | "from bwsi_grader.python.relaxation_method import grader1 \n", 144 | "grader1(relaxation_method1)" 145 | ] 146 | }, 147 | { 148 | "cell_type": "markdown", 149 | "metadata": {}, 150 | "source": [ 151 | "## Problem #2\n", 152 | "Our current implementation of the relaxation method is quite crude in that we must specify the number of iterations that it performs, and then simply look at the output to see if we have converged to a fix-point. It would instead be better if we could have our algorithm check its own numbers to see if they are converging to a single value, and then terminate itself if it has converged.\n", 153 | "\n", 154 | "We can measure how close our most recent guess is to a fixed-point by looking at our most-recent three guesses $x_{n-2}, x_{n-1}, x_{n}$ , and seeing if $x_{n-1}$ and $x_{n}$ are closer to one another than are $x_{n-1}$ and $x_{n-2}$. Skipping a formal derivation, the following formula gives an upper-bound estimate on how close $x_{n}$ is to a true fixed-point:\n", 155 | "\n", 156 | "\\begin{equation}\n", 157 | "\\epsilon_{n} = \\lvert\\frac{(x_n - x_{n-1})^2}{2x_{n-1} - x_{n-2} - x_{n}}\\rvert\n", 158 | "\\end{equation}\n", 159 | "\n", 160 | "That is, if your previous three guesses were $1.0$, then $1.63$, and then $1.80$, plugging these into the preceding formula produces an error bound of $\\epsilon = 0.06$. This means that the guess $1.80$ is within $0.06$ of the true fixed-point. To prevent division-by-zero errors, if your denominator is equal to 0.0, replace it with the value `1e-14`.\n", 161 | "\n", 162 | "Armed with this formula, we can now write a much better algorithm, which can operate based on a tolerance rather than a strict number of iterations.\n", 163 | "\n", 164 | "Write a second version of the relaxation-method. This function should accept four arguments:\n", 165 | "- a python function, which accepts a number as an input, and returns a float as an output\n", 166 | "- an initial guess for the fixed-point, $x_{0}$, a floating-point number\n", 167 | "- a tolerance value, a positive-valued floating-point number\n", 168 | "- a max number of iterations that your algorithm is permitted to run\n", 169 | "\n", 170 | "Your algorithm should produce guesses until $\\epsilon_{n}$ is smaller than the specified tolerance value, or until the number of guesses produced (including the initial guess) matches/exceeds the max number of iterations. Like the last function, it should return a list of all the guesses. You will need to have three guesses before you can assess the tolerance." 171 | ] 172 | }, 173 | { 174 | "cell_type": "code", 175 | "execution_count": null, 176 | "metadata": { 177 | "code_folding": [] 178 | }, 179 | "outputs": [], 180 | "source": [ 181 | "def relaxation_method2(func, xo, tol, max_it):\n", 182 | " \"\"\" Performs the relaxation method to find a fixed-point for `func`,\n", 183 | " given the initial guess `xo`. The relaxation process is carried out for\n", 184 | " `num_it` steps.\n", 185 | " \n", 186 | " Parameters\n", 187 | " ----------\n", 188 | " func : Callable[[float], float]\n", 189 | " The function whose fixed point is being found.\n", 190 | " xo : float\n", 191 | " The initial \"guess\" value.\n", 192 | " tol : float\n", 193 | " A positive value that sets the maximum permissable error\n", 194 | " in the final fixed-point estimate.\n", 195 | " max_it : int\n", 196 | " The maximum number relaxation-guesses (i.e. the length of the\n", 197 | " list you are creating) allotted before the \n", 198 | " algorithm will end. The length of the list you return should\n", 199 | " never exceed this number.\n", 200 | " \n", 201 | " Returns\n", 202 | " -------\n", 203 | " List[float]\n", 204 | " A list of the initial guess, and all of the subsequent guesses generated\n", 205 | " by the relaxation method. \"\"\"\n", 206 | " # student code goes here\n" 207 | ] 208 | }, 209 | { 210 | "cell_type": "code", 211 | "execution_count": null, 212 | "metadata": {}, 213 | "outputs": [], 214 | "source": [ 215 | "# run this cell to grade your work\n", 216 | "from bwsi_grader.python.relaxation_method import grader2\n", 217 | "grader2(relaxation_method2)" 218 | ] 219 | }, 220 | { 221 | "cell_type": "markdown", 222 | "metadata": {}, 223 | "source": [ 224 | "## A fun application of the relaxation method\n", 225 | "The relaxation method is not just a parlor trick, nor are fixed-point equations a relic of pure-mathematics. The following will put the relaxation-function that you wrote to use, in order to solve a very real physics problem. You don't need to do any work here, just follow along and enjoy!\n", 226 | "\n", 227 | "You have likely held a bar-magnet before - a special kind of metal that can use its magnetic field to push or pull on other magnets. Such a material is known as a ferromagnet, and it's magnetic properties are created by coordinated behavior among the electrons belonging to its atoms. The electrons in a ferromagnet naturally coordinate in such a way to create an overall magnetic field throughout and around the material. However, heating up a ferromagnet will jostle its atoms and electrons around, disturbing the coordination of the electrons and thus weakening the net magnetic field of the material. If you set out to describe the statistical behavior of a ferromagnetic material's electrons, you will eventually find that the strength of its magnetization, $M$, depends on temperature, $T$, according to the following equation:\n", 228 | "\n", 229 | "\\begin{equation}\n", 230 | "M = \\mu\\tanh{\\frac{JM}{k_{B}T}}\n", 231 | "\\end{equation}\n", 232 | "\n", 233 | "where $\\mu$ and $J$ are physical constants particular to the specific ferromagnetic material we are interested in, and $k_B$ is a fundamental constant from statistical mechanics. If $M = 0$, then the material is completely non-magnetic. For simplicity's sake, we'll set these constants to 1, without changing the essence of the problem at hand. Thus our equation for the magnetization of our material becomes: \n", 234 | "\n", 235 | "\\begin{equation}\n", 236 | "M = \\tanh{\\frac{M}{T}}\n", 237 | "\\end{equation}\n", 238 | "\n", 239 | "This is a fixed-point equation! We can pick a value of $T$, and then use the relaxation method to solve for $M$. By varying $T$, we can measure the magnetization for each value of $T$, and thus understand how the material's magnetization depends on temperature.\n", 240 | "\n", 241 | "In the following code, we will pick a value of $T$, and then solve for $M$ (within a given tolerance). Then we pick our next value of $T$ and repeat the process. Ultimately, we will have a collection of temperature values and corresponding magnetization values. We will plot $M$ vs $T$ to understand the temperature dependence for a ferromagnetic material." 242 | ] 243 | }, 244 | { 245 | "cell_type": "code", 246 | "execution_count": null, 247 | "metadata": {}, 248 | "outputs": [], 249 | "source": [ 250 | "# just run this cell - you don't need to change any of this code\n", 251 | "\n", 252 | "import numpy as np\n", 253 | "# `temps` is 1000 evenly-spaced values within [0, 1.5]\n", 254 | "temps = np.linspace(0, 1.5, 1000)\n", 255 | "\n", 256 | "mags = []\n", 257 | "for T in temps:\n", 258 | " \n", 259 | " # define the magnetization function, given\n", 260 | " # the current temperature value\n", 261 | " def mag_func(m, temp=T): \n", 262 | " return np.tanh(m / temp) if temp > 0. else 1.\n", 263 | " \n", 264 | " # Use the relaxation value to compute M within an error\n", 265 | " # of 1e-6.\n", 266 | " mag = relaxation_method2(mag_func, 1., 1e-6, 1000)[-1]\n", 267 | " mags.append(mag)\n", 268 | "\n", 269 | "print(\"number of magnetization-values computed: {}\".format(len(mags)))" 270 | ] 271 | }, 272 | { 273 | "cell_type": "code", 274 | "execution_count": null, 275 | "metadata": {}, 276 | "outputs": [], 277 | "source": [ 278 | "# just run this cell - you don't need to change any of this code\n", 279 | "\n", 280 | "# Plotting M vs T\n", 281 | "import matplotlib.pyplot as plt\n", 282 | "%matplotlib notebook\n", 283 | "fig, ax = plt.subplots()\n", 284 | "ax.plot(temps, mags)\n", 285 | "ax.grid(True)\n", 286 | "ax.set_ylabel(\"Magnetization\")\n", 287 | "ax.set_xlabel(r\"$T$\")\n", 288 | "ax.set_title(\"Magnetization vs Temperature\");" 289 | ] 290 | }, 291 | { 292 | "cell_type": "markdown", 293 | "metadata": {}, 294 | "source": [ 295 | "See that $M = 0$ for high temperatures, meaning that the material is non-magnetic when it is at a temperature greater than 1 (no actual units, since we set all those constants to be $1$). However, once the material is cooled to $T \\leq 1$, the material suddenly magnetizes, and its magnetization strengthens as you cool it further.\n", 296 | "\n", 297 | "This sudden magnetization at $T = 1$ is a *phase transition*. This is very similar to water freezing: water's atoms molecules will rapidly begin to form a crystal once they are cooled to 0-celsius or below. Similarly, we see that a ferromagnetic material's electrons will suddenly be able to coordinate and produce a net magnetic field throughout the material, once they are cooled to $T=1$ and below.\n", 298 | "\n", 299 | "This is a no-joke physics problem that we were able to solve thanks to the relaxation method! If you look up the constants, $\\mu$ and $J$, for a specific material, you can repeat this computation to produce its actual magnetic phase diagram. This rules!" 300 | ] 301 | } 302 | ], 303 | "metadata": { 304 | "kernelspec": { 305 | "display_name": "Python 3", 306 | "language": "python", 307 | "name": "python3" 308 | }, 309 | "language_info": { 310 | "codemirror_mode": { 311 | "name": "ipython", 312 | "version": 3 313 | }, 314 | "file_extension": ".py", 315 | "mimetype": "text/x-python", 316 | "name": "python", 317 | "nbconvert_exporter": "python", 318 | "pygments_lexer": "ipython3", 319 | "version": "3.7.2" 320 | } 321 | }, 322 | "nbformat": 4, 323 | "nbformat_minor": 2 324 | } 325 | -------------------------------------------------------------------------------- /run_length_encoding/HW_run_length_encoding.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Run-Length Encoding\n", 8 | "Run-length encoding is a simple method for compressing data that contains long sequences of repeated characters. \n", 9 | "\n", 10 | "In this compression algorithm:\n", 11 | "1. A standalone character will be unchanged. E.g `\"a\"` $\\rightarrow$ `[\"a\"]`. \n", 12 | "2. A run of a character, `c`, repeated `N` times will be compressed to `[\"c\", \"c\", N]`. E.g. `\"bbbb\"` $\\rightarrow$ `['b', 'b', 4]`. \n", 13 | "\n", 14 | "These two rules are all that you need to perform run-length encoding.\n", 15 | "\n", 16 | "Let's look at a few examples of run-length-encoding:\n", 17 | "\n", 18 | "- `\"abcd\"` $\\rightarrow$ `['a', 'b', 'c', 'd']`\n", 19 | "- `\"abbbba\"` $\\rightarrow$ `['a', 'b', 'b', 4, 'a']`\n", 20 | "- `\"aaaabbcccd\"` $\\rightarrow$ `['a', 'a', 4, 'b', 'b', 2, 'c', 'c', 3, 'd']`\n", 21 | "- `\"\"` $\\rightarrow$ `[]`\n", 22 | "- `\"1\"` $\\rightarrow$ `[\"1\"]`\n", 23 | "\n", 24 | "The decompression algorithm, run-length decoding, simply reverses this process:\n", 25 | "\n", 26 | "- `['q', 'a', 'a', 4, 'b', 'b', 2, 'c', 'c', 3, 'd']` $\\rightarrow$ `'qaaaabbcccd'`\n", 27 | "\n", 28 | "Here, you will implement a run-length encoding and decoding algorithms. As indicated above, the run-length encoding algorithm should be able to accept a string and return a list with the appropriate string/integer entries, according to the encoding. The decoding algorithm need be able to accept a list with an encoded sequence, and return the decoded string.\n", 29 | "\n", 30 | "You should be able to test both of your algorithms by feeding them into one another:\n", 31 | "```python\n", 32 | ">>> decoder(encoder(\"Wooooow!!!!! I'm totally getting compressed\"))\n", 33 | "\"Wooooow!!!!! I'm totally getting compressed\"\n", 34 | "```" 35 | ] 36 | }, 37 | { 38 | "cell_type": "code", 39 | "execution_count": null, 40 | "metadata": {}, 41 | "outputs": [], 42 | "source": [ 43 | "# make sure to execute this cell so that your function is defined\n", 44 | "# you must re-run this cell any time you make a change to this function\n", 45 | "\n", 46 | "def run_length_encoder(in_string):\n", 47 | " # write your code here\n", 48 | " # be sure to include a `return` statement so that\n", 49 | " # your function returns the appropriate object." 50 | ] 51 | }, 52 | { 53 | "cell_type": "code", 54 | "execution_count": null, 55 | "metadata": {}, 56 | "outputs": [], 57 | "source": [ 58 | "# Execute this cell to grade your work\n", 59 | "from bwsi_grader.python.run_length_encoding import encoder_grader\n", 60 | "encoder_grader(run_length_encoder)" 61 | ] 62 | }, 63 | { 64 | "cell_type": "code", 65 | "execution_count": null, 66 | "metadata": {}, 67 | "outputs": [], 68 | "source": [ 69 | "# make sure to execute this cell so that your function is defined\n", 70 | "# you must re-run this cell any time you make a change to this function\n", 71 | "\n", 72 | "def run_length_decoder(in_list):\n", 73 | " # write your code here\n", 74 | " # be sure to include a `return` statement so that\n", 75 | " # your function returns the appropriate object." 76 | ] 77 | }, 78 | { 79 | "cell_type": "code", 80 | "execution_count": null, 81 | "metadata": {}, 82 | "outputs": [], 83 | "source": [ 84 | "# Execute this cell to grade your work\n", 85 | "from bwsi_grader.python.run_length_encoding import decoder_grader\n", 86 | "decoder_grader(run_length_decoder)" 87 | ] 88 | } 89 | ], 90 | "metadata": { 91 | "kernelspec": { 92 | "display_name": "Python 3", 93 | "language": "python", 94 | "name": "python3" 95 | }, 96 | "language_info": { 97 | "codemirror_mode": { 98 | "name": "ipython", 99 | "version": 3 100 | }, 101 | "file_extension": ".py", 102 | "mimetype": "text/x-python", 103 | "name": "python", 104 | "nbconvert_exporter": "python", 105 | "pygments_lexer": "ipython3", 106 | "version": "3.7.2" 107 | } 108 | }, 109 | "nbformat": 4, 110 | "nbformat_minor": 2 111 | } 112 | -------------------------------------------------------------------------------- /three_five/HW_three_five_threefive.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Three, Five, Three-Five\n", 8 | "Write code that detects if a number is divisible by 3, 5, both, or neither.\n", 9 | "Specifically, write a function that, given `x` (an integer), returns:\n", 10 | "\n", 11 | " - the string \"threefive\", if x is a multiple of both 3 and 5.\n", 12 | " - the string \"three\", if x is a multiple of 3 and not 5.\n", 13 | " - the string \"five\", if x is a multiple of 5 and not 3.\n", 14 | " - the integer x, if x is not divisible by either 3 or 5.\n", 15 | "\n", 16 | "You may assume that your input is an integer greater than 0.\n", 17 | "\n", 18 | "## Required Concepts\n", 19 | "Basic Object Types\n", 20 | "- basic math with Python\n", 21 | "- strings\n", 22 | "\n", 23 | "Conditional Statements\n", 24 | "- basic usage of: if, else, elif\n", 25 | "\n", 26 | "\n", 27 | "Basics of Functions\n", 28 | "- the return statement\n", 29 | "- basic function arguments\n", 30 | "\n", 31 | "## Examples\n", 32 | "```python\n", 33 | ">>> student_func(3)\n", 34 | "'three'\n", 35 | "\n", 36 | ">>> student_func(5)\n", 37 | "'five'\n", 38 | "\n", 39 | ">>> student_func(15)\n", 40 | "'threefive'\n", 41 | "\n", 42 | ">>> student_func(2)\n", 43 | "2\n", 44 | "```" 45 | ] 46 | }, 47 | { 48 | "cell_type": "code", 49 | "execution_count": null, 50 | "metadata": {}, 51 | "outputs": [], 52 | "source": [ 53 | "# make sure to execute this cell so that your function is defined\n", 54 | "# you must re-run this cell any time you make a change to this function\n", 55 | "\n", 56 | "def student_func(x):\n", 57 | " # write your code here\n", 58 | " # be sure to include a `return` statement so that\n", 59 | " # your function returns the appropriate object.\n", 60 | " pass" 61 | ] 62 | }, 63 | { 64 | "cell_type": "code", 65 | "execution_count": null, 66 | "metadata": {}, 67 | "outputs": [], 68 | "source": [ 69 | "# Execute this cell to grade your work\n", 70 | "from bwsi_grader.python.three_five import grader\n", 71 | "grader(student_func)" 72 | ] 73 | } 74 | ], 75 | "metadata": { 76 | "kernelspec": { 77 | "display_name": "Python 3", 78 | "language": "python", 79 | "name": "python3" 80 | }, 81 | "language_info": { 82 | "codemirror_mode": { 83 | "name": "ipython", 84 | "version": 3 85 | }, 86 | "file_extension": ".py", 87 | "mimetype": "text/x-python", 88 | "name": "python", 89 | "nbconvert_exporter": "python", 90 | "pygments_lexer": "ipython3", 91 | "version": "3.7.2" 92 | } 93 | }, 94 | "nbformat": 4, 95 | "nbformat_minor": 2 96 | } 97 | --------------------------------------------------------------------------------