From 534a80550d552194fe4ae9165464d6faca38236a Mon Sep 17 00:00:00 2001 From: BCampforts Date: Thu, 10 Jun 2021 08:52:39 -0600 Subject: [PATCH] add solutions --- lessons/python/ESPIN-02-lists-Solution.ipynb | 544 +++++++++++++++++++ 1 file changed, 544 insertions(+) create mode 100644 lessons/python/ESPIN-02-lists-Solution.ipynb diff --git a/lessons/python/ESPIN-02-lists-Solution.ipynb b/lessons/python/ESPIN-02-lists-Solution.ipynb new file mode 100644 index 0000000..76ae6e5 --- /dev/null +++ b/lessons/python/ESPIN-02-lists-Solution.ipynb @@ -0,0 +1,544 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Programming with Python\n", + "## Storing Multiple Values in Lists\n", + "### minutes: 30\n", + "---\n", + "> ## Learning Objectives\n", + ">\n", + "> * Explain what a for loop does.\n", + "> * Correctly write for loops to repeat simple calculations.\n", + "> * Trace changes to a loop variable as the loop runs.\n", + "> * Trace changes to other variables as they are updated by a for loop.\n", + "\n", + "In the first lesson we used NumPy arrays to manipulate topographic data. NumPy arrays are not built into Python: we had to import the library `numpy` in order to be able to use them. These data structures are the most useful for scientific computing because the allow us to easily do math on our data. For anyone who's programmed in Matlab or C before, these are also the most familiar data structure (which is why we introduce them first!).\n", + "\n", + "One of the built-in data structures that you will definitely use extensively in Python is the list. Because they are built in, we don't need to load a library to use them. A list is exactly what it sounds like -- **a sequence of things that don't all have to be the same type**. Lists are ordered, so we can access the items through an integer index (like we did with NumPy arrays), and one list can simultaneously contain numbers, strings, other lists, numpy arrays, and even commands to run.\n", + "\n", + "Lists are created by putting values, separated by commas, inside square brackets:" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "odds are: [1, 3, 5, 7]\n" + ] + } + ], + "source": [ + "odds = [1, 3, 5, 7]\n", + "print('odds are:', odds)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Because they are ordered, we can select individual elements from lists by indexing:" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "first and last: 1 7\n" + ] + } + ], + "source": [ + "print('first and last:', odds[0], odds[-1])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We can change individual elements in a list by through item assignment:" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "odds are now: [1, 3, 5, 9]\n" + ] + } + ], + "source": [ + "odds[-1] = 9\n", + "print('odds are now:', odds)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "There are many other ways to change the contents of lists besides assigning new values to individual elements:" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "odds after appending a value: [1, 3, 5, 9, 11]\n" + ] + } + ], + "source": [ + "odds.append(11)\n", + "print('odds after appending a value:', odds)" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "odds after removing the first element: [3, 5, 9, 11]\n" + ] + } + ], + "source": [ + "del odds[0]\n", + "print('odds after removing the first element:', odds)" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "odds after reversing: [11, 9, 5, 3]\n" + ] + } + ], + "source": [ + "odds.reverse()\n", + "print('odds after reversing:', odds)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We can also check if an item is a member of a list:" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "11 in odds" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "False" + ] + }, + "execution_count": 8, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "2 in odds" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Inversely, we can check if an item is NOT a member of a list:" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "False" + ] + }, + "execution_count": 9, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "11 not in odds" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 10, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "2 not in odds" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "There is one important difference between lists and strings: we can change the values in a list, but we cannot change the characters in a string." + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "names is originally: ['Newton', 'Darwing', 'Turing']\n", + "final value of names: ['Newton', 'Darwin', 'Turing']\n" + ] + } + ], + "source": [ + "names = ['Newton', 'Darwing', 'Turing'] # typo in Darwin's name\n", + "print('names is originally:', names)\n", + "names[1] = 'Darwin' # correct the name\n", + "print('final value of names:', names)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Try: \n", + "~~~\n", + "name = 'Tell'\n", + "name[0] = 'B'\n", + "~~~" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "> ## Ch-Ch-Ch-Changes\n", + "> \n", + "> Data which can be modified in place is called **mutable**, while data which cannot be modified is called immutable. **Strings and numbers are immutable**. This does not mean that variable names assigned to string or number objects are forever going to be assigned to those objects, but when we want to change the value of a string or number object, we can only replace the old value with a completely new value.\n", + "> \n", + "> **Lists and NumPy arrays, on the other hand, are mutable**: we can modify them after they have been created. We can change individual elements, append new elements, or reorder the whole list. For some operations, like sorting, we can choose whether to use a function that modifies the data in place or a function that returns a modified copy and leaves the original unchanged.\n", + "> \n", + "> Be careful when modifying data in place. If two variables refer to the same list and you modify a value in the list, it will change for both variables! If you want variables with mutable values to be independent, you must make a copy of the value when you assign it.\n", + "> \n", + "> Because of pitfalls like this, code that modifies data in place can be more difficult to understand (and therefore to debug). However, it is often far more efficient to modify a large data structure in place than to create a modified copy for every small change. You should consider both of these aspects when writing your code.\n", + "\n", + "If we make a list, (attempt to) copy it and then modify in place, we can cause all sorts of trouble:" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Before:\n", + " ----------\n", + "primes: [1, 3, 5, 7]\n", + "odds: [1, 3, 5, 7]\n", + "\n", + "After:\n", + " ----------\n", + "primes: [1, 3, 5, 7, 2]\n", + "odds: [1, 3, 5, 7, 2]\n" + ] + } + ], + "source": [ + "print('Before:\\n', 10 * '-')\n", + "\n", + "odds = [1, 3, 5, 7]\n", + "primes = odds\n", + "\n", + "print('primes:', primes)\n", + "print('odds:', odds)\n", + "\n", + "\n", + "print('\\nAfter:\\n', 10 * '-')\n", + "\n", + "primes += [2]\n", + "\n", + "print('primes:', primes)\n", + "print('odds:', odds)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The command `primes = odds` doesn't create a new copy of the list that the variable name `odds` is assigned to but instead assigns a second variable name to the same list. Essentially, the two variable names point to the exact same location in memory.\n", + "\n", + "Since lists in Python can be modified in place (lists are mutable objects), changing the value of `primes` edits the contents of the part of memory that both `primes` and `odds` point to, causing the value of both variables to change.\n", + "\n", + "To make a copy of a list that is independent of the original, we use the `list()` command:" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Before:\n", + " ----------\n", + "primes: [1, 3, 5, 7]\n", + "odds: [1, 3, 5, 7]\n", + "\n", + "After:\n", + " ----------\n", + "primes: [1, 3, 5, 7, 2]\n", + "odds: [1, 3, 5, 7]\n" + ] + } + ], + "source": [ + "print('Before:\\n', 10 * '-')\n", + "\n", + "odds = [1, 3, 5, 7]\n", + "primes = list(odds)\n", + "\n", + "print('primes:', primes)\n", + "print('odds:', odds)\n", + "\n", + "\n", + "print('\\nAfter:\\n', 10 * '-')\n", + "\n", + "primes += [2]\n", + "\n", + "print('primes:', primes)\n", + "print('odds:', odds)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Also for numpy arrays? \n", + "Repeat the exercise but this time for numpy Arrays rather than strings. Show that numpy arrays are mutable and try to find out how to make an independent copy of a numpy data structure. " + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Before:\n", + " ----------\n", + "primes: [1 3 5 7]\n", + "odds: [1 3 5 7]\n", + "\n", + "After:\n", + " ----------\n", + "primes: [3 5 7 9]\n", + "odds: [3 5 7 9]\n" + ] + } + ], + "source": [ + "import numpy as np\n", + "print('Before:\\n', 10 * '-')\n", + "\n", + "odds = np.array([1, 3, 5, 7])\n", + "primes = odds\n", + "\n", + "print('primes:', primes)\n", + "print('odds:', odds)\n", + "\n", + "\n", + "print('\\nAfter:\\n', 10 * '-')\n", + "\n", + "primes += [2]\n", + "\n", + "print('primes:', primes)\n", + "print('odds:', odds)" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Before:\n", + " ----------\n", + "primes: [1 3 5 7]\n", + "odds: [1 3 5 7]\n", + "\n", + "After:\n", + " ----------\n", + "primes: [3 5 7 9]\n", + "odds: [1 3 5 7]\n" + ] + } + ], + "source": [ + "import numpy as np\n", + "print('Before:\\n', 10 * '-')\n", + "\n", + "odds = np.array([1, 3, 5, 7])\n", + "primes = np.array(odds)\n", + "\n", + "print('primes:', primes)\n", + "print('odds:', odds)\n", + "\n", + "\n", + "print('\\nAfter:\\n', 10 * '-')\n", + "\n", + "primes += [2]\n", + "\n", + "print('primes:', primes)\n", + "print('odds:', odds)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## From 0 to N-1\n", + "\n", + "Python has a built-in function called `range` for creating a sequence of integers. `range` can accept 1-3 parameters. If it gets one parameter as input, `range` creates an array of that length starting at zero and incrementing by 1. If it gets 2 parameters as input, `range` starts at the first and ends at the second, incrementing by one. If `range` is passed 3 parameters, it stars at the first one, ends at the second one, and increments by the third one. For example, `range(3)` produces the numbers 0, 1, 2, `range(2, 5)` produces 2, 3, 4, and `range(3, 10, 3)` produces 3, 6, 9.\n", + "\n", + "Try the build-in `range` function using 1,2 and 3 arguments. Print the values by converting the range values into a list `list(range(...))' " + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[0, 5, 10, 15, 20, 25]\n" + ] + } + ], + "source": [ + "print(list(range(0,30,5)))" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.8" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +}