{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# More on Loops\n", "\n", "## Overview, Objectives, and Key Terms\n", " \n", "In [Lecture 8](ME400_Lecture_8.ipynb), the use of `while` loops in Python introduced to solve problems requiring iteration. The `while` loop structure in Python is very similar to structure introduced via pseudocode in\n", "[Lecture_5](ME400_Lecture_5.ipynb). In this lecture, an additional `for`-loop construct is introduced, which reduces the amount of \"bookkeeping\" required in some cases. All loops can also be *nested*, which provides substantial flexibility when processing data, defining multidimensional arrays, and performing similar, potentially multidimensional tasks.\n", "\n", "### Objectives\n", "\n", "By the end of this lesson, you should be able to\n", "\n", "- Use a `for` loop to solve simple problems using iteration\n", "- Use nested `for` loops to fill the elements of a multidimensional array.\n", "- Use the graphical debugger in Spyder to trace and debug a program with iteration\n", "\n", "### Key Terms\n", "\n", "- `for`\n", "- `range`\n", "- `continue`\n", "- loop variable\n", "- dependent loop variable" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## The `for` Loop\n", "\n", "Remember once again the problem of printing out each element of an array `a`. In [Lecture 8](ME400_Lecture_8.ipynb), the following solution was proposed using a `while` loop:\n", "\n", "```python\n", "# print out the elements of an array using a while loop\n", "import numpy as np\n", "a = np.array([1, 1, 2, 3, 5, 8, 13])\n", "n = len(a)\n", "i = 0\n", "while i < n: # always remember the :\n", " print(a[i]) # indented 4 spaces\n", " i = i + 1 # also indented 4 spaces\n", "```\n", "\n", "The hallmark of such a `while` loop is the counter (here, the `i`). Defining a counter is not hard, but it's a pain when we forget to update it (infinite loop), and there's always a chance that the update is wrong. An alternative construct in Python (and other languages) that works for such \"counter\" problems is the `for` loop. Let me introduce this by example:" ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "1\n", "1\n", "2\n", "3\n", "5\n", "8\n", "13\n" ] } ], "source": [ "# print out the elements of an array using a for loop\n", "import numpy as np\n", "a = np.array([1, 1, 2, 3, 5, 8, 13])\n", "for i in range(0, len(a)): # always remember the :\n", " print(a[i]) # indented 4 spaces" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Some of this is identical to the `while`-loop solution: the same `a` and the same `print`. However, we don't have the `i = 0` initialization, nor is `i` updated inside the `while` loop. And, in place of `while i < n`, we have `for i in range(0, len(a))`. That is a *very common* pattern when using Python `for` loops. Here's what `range` does:" ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "range(0, 7)" ] }, "execution_count": 2, "metadata": {}, "output_type": "execute_result" } ], "source": [ "range(0, len(a))" ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "range" ] }, "execution_count": 3, "metadata": {}, "output_type": "execute_result" } ], "source": [ "type(range(0, len(a)))" ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([0, 1, 2, 3, 4, 5, 6])" ] }, "execution_count": 4, "metadata": {}, "output_type": "execute_result" } ], "source": [ "np.array(range(0, len(a)))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "In other words, `range` is a built-in function and type (note the syntax color), but it represents a sequence of integers as shown by the conversion to an `ndarray`. The `range` function accepts three arguments: `start`, `end`, and `stride`, similar to slicing:" ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([0, 1, 2, 3, 4])" ] }, "execution_count": 5, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# just one argument means the end of the range\n", "np.array(range(5)) " ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([1, 2, 3, 4])" ] }, "execution_count": 6, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# two arguments means the start and end\n", "np.array(range(1,5)) " ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([0, 2, 4])" ] }, "execution_count": 7, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# three arguments means the start, end, and stride\n", "np.array(range(0, 5, 2))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The `for i in` structure is not limited to `range`, though. In fact, `i` can come from any *sequential type*, like `ndarray` or the `list` and `tuple` types we'll cover later on. Hence, we can also do" ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "0\n", "1\n", "2\n", "3\n", "4\n" ] } ], "source": [ "for i in np.arange(5):\n", " print(i)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "and" ] }, { "cell_type": "code", "execution_count": 9, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "x = 0.0\n", "x = 0.2\n", "x = 0.4\n", "x = 0.6\n", "x = 0.8\n", "x = 1.0\n" ] } ], "source": [ "for x in np.linspace(0, 1, 6):\n", " print(\"x = \", x)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## A Pythonic Quirk\n", "\n", "One reason that `while` loops were introduced first is because a `for` loop in Python actually follows a slightly different logic than an apparently identical `while` loop. Consider these examples:" ] }, { "cell_type": "code", "execution_count": 10, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "i = 0\n", "i = 2\n", "i = 4\n" ] } ], "source": [ "n = 5\n", "i = 0\n", "while i < n:\n", " print(\"i = \", i)\n", " i += 2" ] }, { "cell_type": "code", "execution_count": 11, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "j = 0\n", "j = 1\n", "j = 2\n", "j = 3\n", "j = 4\n" ] } ], "source": [ "# A \"quirky\" for loop\n", "for j in range(n):\n", " print (\"j = \", j)\n", " j += 2" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Something is amiss. Both loops appear to take the counter `i` (or `j`) from 0 to 5 in jumps of 2. However, even though we set `j` to 2 after just one iteration of the `for` loop, the next time around, `j` appears to be 1. In other words, even if we modify the counter variable within a `for` loop, it will take on a predefined value at the next iteration. These predefined values are those numbers in `range(n)`. They are defined once, and unless the `for` loop is terminated using a `break`, the counter `j` will be take on each value in `range(n)`. Of course, if we really want to have `j` jump by two each time, we could do" ] }, { "cell_type": "code", "execution_count": 12, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "j = 0\n", "j = 2\n", "j = 4\n" ] } ], "source": [ "for j in range(0, n, 2):\n", " print (\"j = \", j)\n", " j += 2" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "In practice, this \"quirk\" of Python `for` loops should not lead to problems, but it is important to understand (especially for those who plan to program in other languages like C++, for which changes made to the counter inside a `for` modify the loop execution).\n", "\n", "> **Note**. Changing the counter in a Python `for` loop does not modify the loop behavior.\n", "\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "> **Exercise**: Try to define a while loop (in pseudocode or Python, or as a flowchart) that exhibits the same behavior as the \"quirky\" `for` loop above." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Nested `for` loops\n", "\n", "Just like `if` statements can be nested, so, too, can `for` (and `while`) loops be nested. While nested `if` statements can always be written as (potentially much) more complicated, single statements, there are some tasks for which nested loops are truly required---but I have not proven that!\n", "\n", "**Exercise**: Use a nested `for` loop to find the sum of the elements of $5\\times 5$ array of random numbers.\n", "\n", "*Solution*:" ] }, { "cell_type": "code", "execution_count": 13, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "sum = 13.7180626162\n" ] } ], "source": [ "n = 5\n", "# The seed function just makes the random numbers the \n", "# same every time so that these notes don't keep changing!\n", "np.random.seed(1234) \n", "# The rand function can produce single values or arrays.\n", "# Note that its syntax for 2-D arrays does *not* use\n", "# the double parentheses like np.ones and np.zeros\n", "A = np.random.rand(n, n)\n", "s = 0\n", "for i in range(n):\n", " for j in range(n):\n", " s += A[i, j]\n", "print('sum = ', s)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Of course, we can use the built-in `sum` function (useful here to check our logic):" ] }, { "cell_type": "code", "execution_count": 14, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "13.718062616246673" ] }, "execution_count": 14, "metadata": {}, "output_type": "execute_result" } ], "source": [ "sum(sum(A))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**Exercise**: Why do we need to use `sum` twice?" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "For the exercise above, each loop used a variable (`i` and `j`) that were independent. In other words, `j` was not dependent on `i`, and `i` did not dependend on `j`. This represents the simplest case of nested loops. Sometimes, though, an inner loop variable (here, `j`) depends on `i`. For example, consider the problem of computing the cumulative sum of an array $a$. The cumulative sum of an array is another array of the same length whose $i$th element is defined as\n", "\n", "$$\n", " c_i = \\sum^i_{j=0} a_j \\, .\n", "$$\n", "\n", "For example, the cumulative sum of an array of three ones has the elements 1, 2, and 3.\n", "\n", "**Exercise**: Use a `for` loop to compute the cumulative sum of an array of 5 random numbers.\n", "\n", "*Solution*:" ] }, { "cell_type": "code", "execution_count": 15, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[ 0.19151945 0.62210877 0.43772774 0.78535858 0.77997581]\n", "[ 0.19151945 0.81362822 1.25135596 2.03671454 2.81669035]\n" ] } ], "source": [ "a = A[0] # steal the first row of the 2-D array above\n", "c = 0*a # easy way to initialize array of same size\n", "for i in range(n):\n", " for j in range(i+1):\n", " c[i] += a[j]\n", "print(a)\n", "print(c)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The last element of `c` ought to be the sum of `a`:" ] }, { "cell_type": "code", "execution_count": 16, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "True" ] }, "execution_count": 16, "metadata": {}, "output_type": "execute_result" } ], "source": [ "c[-1]==sum(a)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Using loops effectively requires practice, and nested loops in particular deserve ample attention. Here are some additional exercises to tackle:\n", "\n", "**Exercise**: Starting with `A = np.zeros((5, 5))`, use two loops to produce\n", "```python\n", "array([[ 1, 2, 3, 4, 5],\n", " [ 6, 7, 8, 9, 10],\n", " [11, 12, 13, 14, 15],\n", " [16, 17, 18, 19, 20],\n", " [21, 22, 23, 24, 25]])\n", "```\n", "\n", "**Exercise**: Starting with `A = np.zeros((5, 5))`, use two loops to produce\n", "```python\n", "array([[ 1, 2, 3, 4, 5],\n", " [ 0, 7, 8, 9, 10],\n", " [ 0, 0, 13, 14, 15],\n", " [ 0, 0, 0, 19, 20],\n", " [ 0, 0, 0, 0, 25]])\n", "```\n", "\n", "**Exercise**: Starting with `A = np.zeros((5, 5))`, use two loops to produce\n", "```python\n", "array([[ 1, 2, 3, 4, 5],\n", " [ 0, 6, 7, 8, 9],\n", " [ 0, 0, 10, 11, 12],\n", " [ 0, 0, 0, 13, 14],\n", " [ 0, 0, 0, 0, 15]])\n", "```\n", "\n", "**Exercise**: Use loops to compute the cumulative *product* of `a = np.array([2,4,7,3,9])`. \n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## One Last Tidbit: `continue`\n", "\n", "There are some occasions where, once something is done within a loop, you want to move right to the next iteration. For example, suppose we want to sum and print all the even integers from 1 through $n$. An obvious solution is" ] }, { "cell_type": "code", "execution_count": 17, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "2\n", "4\n", "6\n", "8\n", "10\n", "s = 30\n" ] } ], "source": [ "n = 10\n", "s = 0\n", "for i in range(1, n+1):\n", " if not i % 2:\n", " s += i\n", " print(i)\n", "print('s = ', s)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "However, the `continue` statement let's us rewrite the program slightly as" ] }, { "cell_type": "code", "execution_count": 18, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "2\n", "4\n", "6\n", "8\n", "10\n", "s = 30\n" ] } ], "source": [ "n = 10\n", "s = 0\n", "for i in range(1, n+1):\n", " if i % 2:\n", " continue\n", " s += i\n", " print(i)\n", "print('s = ', s)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "In some cases, use of `continue` may be simpler than `if` statements alone. Personally, I rarely use them. Note that `continue` (and, for that matter, `break`) applies only to the nearest `for` or `while` containing it. For example:" ] }, { "cell_type": "code", "execution_count": 19, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "i= 0\n", " j= 0\n", " j= 2\n", " j= 4\n", "i= 1\n", " j= 1\n", " j= 3\n", "i= 2\n", "i= 3\n", " j= 1\n", " j= 3\n", "i= 4\n", " j= 0\n", " j= 2\n", " j= 4\n" ] } ], "source": [ "for i in range(5):\n", " print('i=', i)\n", " for j in range(5):\n", " if (j + i) % 2:\n", " continue\n", " elif i == 2:\n", " break\n", " print(' j=', j)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**Exercise**: Load the previous example in Spyder and trace it using the graphical debugger. Try to guess whether or not the `j` will be printed for any possible values of `i` and `j`." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Further Reading\n", "\n", "None at this time." ] } ], "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.6.5" } }, "nbformat": 4, "nbformat_minor": 2 }