{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "## Lists II\n", "\n", "In this lecture we look at some functions on lists. For the full documentation of all functions and how they work, see:\n", "https://docs.python.org/3/library/stdtypes.html#list and https://docs.python.org/3/tutorial/datastructures.html#more-on-lists.\n", "\n", "**Disclaimer:** most, if not all, techniques below for lists also work for tuples." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Parallel assignment\n", "\n", "The elements of tuples can be assigned to variables in parallel:" ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "a = 4\n", "b = 3\n", "c = 2\n", "d = 1\n" ] } ], "source": [ "T = (4,3,2,1)\n", "a, b, c, d = T\n", "print(\"a =\", a)\n", "print(\"b =\", b)\n", "print(\"c =\", c)\n", "print(\"d =\", d)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The above is the same as the less concise:" ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "a = 4\n", "b = 3\n", "c = 2\n", "d = 1\n" ] } ], "source": [ "T = (4,3,2,1)\n", "a = T[0]\n", "b = T[1]\n", "c = T[2]\n", "d = T[3]\n", "print(\"a =\", a)\n", "print(\"b =\", b)\n", "print(\"c =\", c)\n", "print(\"d =\", d)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "You can choose to use either of the above. At this point, there is no preference apart from your own.\n", "\n", "The same works with lists, but we advise against that. If you have the wrong number of variables, python will not like it." ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "a = 1\n", "b = 2\n", "c = 3\n" ] } ], "source": [ "L = [1,2,3]\n", "a, b, c = L\n", "print(\"a =\", a)\n", "print(\"b =\", b)\n", "print(\"c =\", c)" ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [ { "ename": "ValueError", "evalue": "too many values to unpack (expected 2)", "output_type": "error", "traceback": [ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[0;31mValueError\u001b[0m Traceback (most recent call last)", "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m()\u001b[0m\n\u001b[1;32m 1\u001b[0m \u001b[0mL\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;34m[\u001b[0m\u001b[0;36m1\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;36m2\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;36m3\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 2\u001b[0;31m \u001b[0ma\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mb\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mL\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", "\u001b[0;31mValueError\u001b[0m: too many values to unpack (expected 2)" ] } ], "source": [ "L = [1,2,3]\n", "a, b = L" ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [ { "ename": "ValueError", "evalue": "not enough values to unpack (expected 4, got 3)", "output_type": "error", "traceback": [ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[0;31mValueError\u001b[0m Traceback (most recent call last)", "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m()\u001b[0m\n\u001b[1;32m 1\u001b[0m \u001b[0mL\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;34m[\u001b[0m\u001b[0;36m1\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;36m2\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;36m3\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 2\u001b[0;31m \u001b[0ma\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mb\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mc\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0md\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mL\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", "\u001b[0;31mValueError\u001b[0m: not enough values to unpack (expected 4, got 3)" ] } ], "source": [ "L = [1,2,3]\n", "a, b, c, d = L" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Negative indexing\n", "\n", "List indexing admits the use of negative values to indicate positions starting from the end." ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "last = 5\n", "second_last = 4\n" ] } ], "source": [ "L = [1,2,3,4,5]\n", "last = L[-1]\n", "second_last = L[-2]\n", "# and so on and so forth\n", "print(\"last =\", last)\n", "print(\"second_last =\", second_last)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Double indexing\n", "\n", "Suppose `L` is a list of lists. We can fetch elements from the inner list by first indexing by the list we want, and then by the element we want.\n", "\n", "For example, the first element from the second list can be obtained like this: `L[1][0]`.\n", "\n", "*Note:* Also works for tuples." ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "x = 21\n", "y = 33\n" ] } ], "source": [ "L = [ [11,12,13], [21,22,23], [31,32,33] ]\n", "x = L[1][0]\n", "y = L[2][2]\n", "print(\"x =\", x)\n", "print(\"y =\", y)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "You can do this in two steps if you prefer:" ] }, { "cell_type": "code", "execution_count": 9, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "x = 31\n" ] } ], "source": [ "L = [ [11,12,13], [21,22,23], [31,32,33] ]\n", "last_list = L[-1]\n", "x = last_list[0]\n", "# or: x = L[-1][0]\n", "print(\"x =\", x)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### List comparison\n", "\n", "Lists can be compared using the usual boolean operators: `<, <=, ==, !=, >=, >`.\n", "\n", "Comparison follows the *lexicographic order* (same way as you would sort words):\n", "- compare the first element\n", "- if they are the same, compare the second element\n", "- if they are the same, compare the third element\n", "- ...\n", "\n", "Observe that the length of the list is not important, unless all elements are the same." ] }, { "cell_type": "code", "execution_count": 10, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "L1 < L2? False\n", "L1 > L2? True\n" ] } ], "source": [ "L1 = [3,4,6,2]\n", "L2 = [3,2,8,8,8]\n", "print(\"L1 < L2?\", L1 < L2)\n", "print(\"L1 > L2?\", L1 > L2)" ] }, { "cell_type": "code", "execution_count": 11, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "L1 < L2? True\n", "L1 > L2? False\n" ] } ], "source": [ "L1 = [3,4,6,2]\n", "L2 = [3,4,6,2,8]\n", "print(\"L1 < L2?\", L1 < L2)\n", "print(\"L1 > L2?\", L1 > L2)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### min/max\n", "\n", "`min` and `max` are functions on lists and tuples. It is self-evident what they do." ] }, { "cell_type": "code", "execution_count": 12, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "min in L is 2\n", "max in L is 567\n" ] } ], "source": [ "L = [5,3,4,6,8,9,22,3,563,2,567,3,23,560,148,287,397,517]\n", "mn = min(L)\n", "mx = max(L)\n", "print(\"min in L is\", mn)\n", "print(\"max in L is\", mx)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "You can call `min` and `max` on a set of values as well:" ] }, { "cell_type": "code", "execution_count": 13, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "mn = 134\n", "mx = 543\n" ] } ], "source": [ "mn = min(423,543,134)\n", "mx = max(423,543,134)\n", "print(\"mn =\", mn)\n", "print(\"mx =\", mx)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Exercise:\n", "\n", "Implement the function `nonDecreasing(L)` that returns a list with the elements in `L` in non-decreasing order." ] }, { "cell_type": "code", "execution_count": 14, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Ls = []\n" ] } ], "source": [ "def nonDecreasing(L):\n", " return []\n", "\n", "L = [5,3,4,6,8,9,22,3,563,2,567,3,23,560,148,287,397,517]\n", "Ls = nonDecreasing(L)\n", "print(\"Ls =\", Ls)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Implement the function `nonIncreasing(L)` that returns a list with the elements in `L` in non-increasing order." ] }, { "cell_type": "code", "execution_count": 15, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Ls = []\n" ] } ], "source": [ "def nonIncreasing(L):\n", " return []\n", "\n", "L = [5,3,4,6,8,9,22,3,563,2,567,3,23,560,148,287,397,517]\n", "Ls = nonIncreasing(L)\n", "print(\"Ls =\", Ls)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### sum\n", "\n", "`sum` is a function that returns the sum of elements of a list or tuple." ] }, { "cell_type": "code", "execution_count": 16, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "s = 16\n" ] } ], "source": [ "L = [4,5,5,2]\n", "s = sum(L)\n", "print(\"s =\", s)" ] }, { "cell_type": "code", "execution_count": 17, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "19" ] }, "execution_count": 17, "metadata": {}, "output_type": "execute_result" } ], "source": [ "sum((3,7,9))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### sorted\n", "\n", "`sorted(L)` returns a list with the elements in `L` in non-decreasing order." ] }, { "cell_type": "code", "execution_count": 18, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Ls = [2, 4, 5, 5, 7, 8, 9]\n" ] } ], "source": [ "L = [4,7,5,9,2,5,8]\n", "Ls = sorted(L)\n", "print(\"Ls =\", Ls)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Or in non-increasing order if we specify `reverse=True` as one of the arguments." ] }, { "cell_type": "code", "execution_count": 19, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Ls = [9, 8, 7, 5, 5, 4, 2]\n" ] } ], "source": [ "L = [4,7,5,9,2,5,8]\n", "Ls = sorted(L, reverse=True)\n", "print(\"Ls =\", Ls)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### reversed\n", "\n", "`reversed(L)` returns an *iterator* to the elements iof `L` in reverse order. Iterator is a construct in python, but you do not need to worry about what it is exactly. Just remember to convert it to a list by using `list(reversed(L))`." ] }, { "cell_type": "code", "execution_count": 20, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Lr = [1, 8, 4]\n" ] } ], "source": [ "L = [4,8,1]\n", "Lr = list(reversed(L)) # Remove the list function and see what happens if you are curious.\n", "print(\"Lr =\", Lr)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### count\n", "\n", "`L.count(x)` counts the number of times the element `x` occurs in the list `L`.\n", "\n", "**Side note:**\n", "The dot notation is related to a notion of classes and objects, used in object oriented programming. In this technique, some types are defined in *classes*, and these classes have built-in operators. If `O` is an object of class (\"type\") `C`, and class `C` defined the operator `op()`, then `op()` on `O` can be invoked as `O.op()`.\n", "You do not need to completely understand this at this point in time. Just know that this is a thing." ] }, { "cell_type": "code", "execution_count": 21, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "fives = 2\n", "ones = 0\n" ] } ], "source": [ "L = [4,7,5,9,2,5,8]\n", "fives = L.count(5)\n", "ones = L.count(1)\n", "print(\"fives =\", fives)\n", "print(\"ones =\", ones)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### index\n", "\n", "`L.index(x)` returns the index of the first occurence of `x` in `L`. \n", "\n", "`L.index(x, s)` returns the index of the first occurence of `x` in `L` starting from position `s`.\n", "\n", "*Note:* Observe the dot notation. This means that `index` is a built-in operator in the `list` class." ] }, { "cell_type": "code", "execution_count": 22, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "index5 = 2\n", "index5_from3 = 5\n" ] } ], "source": [ "L = [4,7,5,9,2,5,8]\n", "index5 = L.index(5)\n", "index5_from3 = L.index(5,3)\n", "print(\"index5 =\", index5)\n", "print(\"index5_from3 =\", index5_from3)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## List mutability\n", "\n", "List, as opposed to the other types we have seen in this course so far, are *mutable* or *destructable* structures. This means that we can do *in-place* modification of lists (i.e., without assigning it to another variable). \n", "\n", "The destructive operators are part of the `list` class, so they are used via the dot notation.\n", "\n", "One of the reasons for having destructive operations is to optimize the amount of memory a program takes. Imagine that you have a list with `10 ** 9` elements. Each time you add a new element using `L = L + [x]`, python will create a new list with `(10 ** 9) + 1` elements, by copying all the elements from `L`. This is very inneficient, so python provides a way for you to simply add the element at the end of `L`, modifying it instead of copying all its elements.\n", "\n", "In the scope of this course, you will not have to worry about memory efficiency to that level. But this is useful to know about, in case you want to handle really big data." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### append\n", "\n", "`L.append(x)` will place the element `x` at the end of list `L`, by modifying `L` itself.\n", "\n", "This is the destructive version of `L + [x]`.\n", "\n", "*Side note:* Interestingly, and counter-intuitively, `L += [x]` is the same as `L.append(x)`, in the sense that it does not create a copy of the list, and different from `L = L + [x]`." ] }, { "cell_type": "code", "execution_count": 23, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "before L = [3, 2, 4, 5]\n", "after L = [3, 2, 4, 5, 100]\n" ] } ], "source": [ "L = [3,2,4,5]\n", "print(\"before L =\", L)\n", "L.append(100)\n", "print(\"after L =\", L)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### extend\n", "\n", "`L.extend(L1)` extends the list `L` by list `L1`, by modifying `L` itself. Nothing happens to the list `L1`.\n", "\n", "This is the destructive version of `L + L1`." ] }, { "cell_type": "code", "execution_count": 24, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "before L = [3, 2, 4, 5]\n", "before L1 = [10, 20, 30]\n", "after L = [3, 2, 4, 5, 10, 20, 30]\n", "after L1 = [10, 20, 30]\n" ] } ], "source": [ "L = [3,2,4,5]\n", "L1 = [10,20,30]\n", "print(\"before L =\", L)\n", "print(\"before L1 =\", L1)\n", "L.extend(L1)\n", "print(\"after L =\", L)\n", "print(\"after L1 =\", L1)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### insert\n", "\n", "`L.insert(i,x)` modifies `L` by placing element `x` immediately before element at position `i`.\n", "\n", "This is the destructive version of `L[:i] + [x] + L[i:]`." ] }, { "cell_type": "code", "execution_count": 25, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "L = [0, 42, 1, 2, 3, 4]\n" ] } ], "source": [ "L = [0,1,2,3,4]\n", "L.insert(1,42)\n", "print(\"L =\", L)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### sort and reverse\n", "\n", "`L.sort()` and `L.reverse()` are the destructive versions of `sorted(L)` and `reversed(L)`." ] }, { "cell_type": "code", "execution_count": 26, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "sorted L = [0, 2, 3, 5, 6, 8]\n", "reversed L = [8, 6, 5, 3, 2, 0]\n" ] } ], "source": [ "L = [5,2,8,6,3,0]\n", "L.sort()\n", "print(\"sorted L =\", L)\n", "L.reverse()\n", "print(\"reversed L =\", L)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### List assignment\n", "\n", "Since lists are built from *classes* (i.e., every individual list is an *object*), it behaves differently from basic types in assignments.\n", "\n", "Assignment `L2 = L1` will **not** create a copy of `L1` and store it in `L2`. Instead, it makes the two names `L1` and `L2` refer to the *same* list. That means that, when `L1` is modified, so is `L2`.\n", "\n", "This is better illustrated by an example:" ] }, { "cell_type": "code", "execution_count": 27, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "x = 11\n", "y = 10\n" ] } ], "source": [ "# Behavior for basic types\n", "x = 10\n", "y = x\n", "x = x + 1\n", "print(\"x =\", x)\n", "print(\"y =\", y)" ] }, { "cell_type": "code", "execution_count": 28, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "L1 = [10, 2, 3, 4]\n", "L2 = [10, 2, 3, 4]\n" ] } ], "source": [ "# Behavior for objects\n", "L1 = [1,2,3,4]\n", "L2 = L1\n", "L1[0] = 10\n", "print(\"L1 =\", L1)\n", "print(\"L2 =\", L2)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "If you want an actual copy of the list, you can use the `L.copy()` function." ] }, { "cell_type": "code", "execution_count": 29, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "L1 = [10, 2, 3, 4]\n", "L2 = [1, 2, 3, 4]\n" ] } ], "source": [ "L1 = [1,2,3,4]\n", "L2 = L1.copy()\n", "L1[0] = 10\n", "print(\"L1 =\", L1)\n", "print(\"L2 =\", L2)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**Side note:** If your lists have lists or other complex data-structures inside, *and* you also want those copied, you need to use the `deepcopy()` function." ] }, { "cell_type": "code", "execution_count": 30, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "L1 = [[100, 12], [21, 22], [31, 32], [41, 42]]\n", "L2 = [[100, 12], [21, 22], [31, 32], [41, 42]]\n" ] } ], "source": [ "# Copy will not create an actual copy of the inner lists\n", "L1 = [[11,12],[21,22],[31,32],[41,42]]\n", "L2 = L1.copy()\n", "L1[0][0] = 100\n", "print(\"L1 =\", L1)\n", "print(\"L2 =\", L2)" ] }, { "cell_type": "code", "execution_count": 31, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "L1 = [[100, 12], [21, 22], [31, 32], [41, 42]]\n", "L2 = [[11, 12], [21, 22], [31, 32], [41, 42]]\n" ] } ], "source": [ "# But deepcopy will\n", "import copy\n", "\n", "L1 = [[11,12],[21,22],[31,32],[41,42]]\n", "L2 = copy.deepcopy(L1)\n", "L1[0][0] = 100\n", "print(\"L1 =\", L1)\n", "print(\"L2 =\", L2)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Exercise:\n", "\n", "Multiple choice exams usually have an answer sheet like this:\n", "\n", "![answer sheet](answer-sheet-img.png)\n", "\n", "After students fill in the answer, an optical reader will scan the paper and assign shades of grey to each bubble of each question. A value of 0 indicates that the bubble is all black (i.e. the student marked that bubble), and a value of 255 indicates that the bubble is all white (i.e. the student did not mark that bubble). Since students fill in the bubbles in various different ways and with pens/pencils of different shades, the convention is that shades less than or equal to 127 are considered marked bubbles, and shades greater than 127 are considered unmarked bubbles.\n", "\n", "The result of the scan of one answer sheet is represented as a list of lists. The inner lists have 5 elements, a number between 0 and 255, representing the shades of grey scanned for the bubbles A, B, C, D, and E. Your job is to implement the function `answers(A)` that takes as input the list of lists as described above, and returns a list containing the answers on that answer sheet. The answers are mapped to integers in the following way: 1 for A, 2 for B, 3 for C, 4 for D, and 5 for E. *If no or multiple answers are selected for a question, map it to -1.*\n", "\n", "For example, let:\n", "```\n", "A = [ [0, 255, 255, 255, 255],\n", " [255, 255, 255, 255, 0 ],\n", " [255, 255, 127, 255, 255] ]\n", "```\n", "then `answers(A)` should return `[1,5,3]`." ] }, { "cell_type": "code", "execution_count": 32, "metadata": { "collapsed": true }, "outputs": [], "source": [ "def answers(A):\n", " return []\n", "\n", "A1 = [ [0, 255, 255, 255, 255],\n", " [255, 255, 255, 255, 0 ],\n", " [255, 255, 127, 255, 255] ]\n", "\n", "A2 = [ [200, 200, 200, 0, 200],\n", " [200, 1, 200, 200, 1],\n", " [1, 2, 3, 4, 5],\n", " [255, 5, 200, 130, 205] ]" ] } ], "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.3" } }, "nbformat": 4, "nbformat_minor": 2 }