Lecture 12 --- Controlling Loops ================================ Restatement of the Basics ------------------------- - ``for`` loops tend to have a fixed number of iterations computed at the start of the loop - ``while`` loops tend to have an indefinite termination, determined by the conditions of the data - Most Python ``for`` loops are easily rewritten as ``while`` loops, but not vice-versa. - In other programming languages, ``for`` and ``while`` are almost interchangeable, at least in principle. Overview of Today ----------------- - Ranges and control of loop iterations - Nested loops - Lists of lists - Contolling loops through ``break`` and ``continue`` Reading: *Practical Programming*, rest of Chapter 9. Part 1: Ranges and For Loops--- A Review ---------------------------------------- - A range is a function to generate a sequence of integers: :: for i in range(10): print(i) outputs the digits 0 through 9 in succession, one per line. - Remember that this is up through and **not including** the end value specified! - A range is not quite a list --- instead it generates values for each successive iteration of a ``for`` loop. - For now we will convert each range to a list as the basis for studying them. - If we want to start with something other than 0, we provide two integer values :: >>> list(range(3,8)) [3, 4, 5, 6, 7] - With a third integer values we can create increments. For example, :: >>> list(range(4,20,3)) [4, 7, 10, 13, 16, 19] starts at 4, increments by 3, stops when 20 is reached or surpassed. - We can create backwards increments :: >>> list(range(-1, -10, -1)) [-1, -2, -3, -4, -5, -6, -7, -8, -9] Using Ranges in For Loops ------------------------- - We can use the ``range`` to generate the sequence of loop variable values in a for loop. Our first example is printing the contents of the ``planets`` list :: planets = [ 'Mercury', 'Venus', 'Earth', 'Mars', 'Jupiter', 'Saturn', 'Uranus', 'Neptune', 'Pluto' ] for i in range(len(planets)): print(planets[i]) (In this case we don't need a index variable - we can just iterate over the values in the list.) - The variable ``i`` is variously known as the *index* or the *loop* index variable or the subscript. - We will modify the loop in class to do the following: - Print the indices of the planets (starting at 1!) - Print the planets backward. - Print every other planet. Loops That Do Not Iterate Over All Indices ------------------------------------------ - Sometimes the loop index should not go over the entire range of indices, and we need to think about where to stop it *early*, as the next example shows. - Example: Returning to our example from Lecture 1, we will briefly re-examine our solution to the following problem: Given a string, how can we write a function that decides if it has three consecutive double letters? :: def has_three_doubles(s): for i in range(0, len(s)-5): if s[i] == s[i+1] and s[i+2] == s[i+3] and s[i+4] == s[i+5]: return True return False - We have to think carefully about where to start our looping and where to stop! - Refer back to Lecture 10 for further examples Part 1 Practice --------------- We will only go over a few of these in class, but you should be sure you can handle all of them #. Generate a range for the positive integers less than 100. Use this to calculate the sum of these values, with and without (i.e. use ``sum``) a for loop. #. Use a range and a for loop to print the positive, even numbers less than the integer value associated with ``n``. #. Suppose we want a list of the squares of the digits 0..9. The following does NOT work :: squares = list(range(10)) for s in squares: s = s*s Why not? Write a different for loop that uses indexing into the ``squares`` list to accomplish our goal. #. The following code for finding out if a word has two consecutive double letters is wrong. Why? When, specifically, does it fail? :: def has_two_doubles(s): for i in range(0, len(s)-5): if s[i] == s[i+1] and s[i+2] == s[i+3]: return True return False Part 2: Nested Loops ------------------------ - Some problems require *iterating* over either - two dimensions of data, or - all pairs of values from a list - As an example, here is code to print all of the products of digits: :: digits = range(10) for i in digits: for j in digits: print("{} x {} = {}".format(i,j,i*j)) - How does this work? - for each value of ``i`` the variable in the first, or "outer", loop, Python executes the *entire* second, or ``inner``, loop - Importantly, ``i`` stays fixed during the entire inner loop. - We will look at finding the two closest points in a list. Example: Finding the Two Closest Points --------------------------------------- - Suppose we are given a list of point locations in two dimensions, where each point is a tuple. For example, :: points = [ (1,5), (13.5, 9), (10, 5), (8, 2), (16,3) ] - Our problem is to find the two points that are closest to each other. - We started working on a slightly simpler version of this problem at the end of Lecture 10, but did not have time to complete it. - The natural idea is to compute the distance between any two points and find the minimum. - We can do this with and without using a list of distances. - Let's work through the approach to this and post the result on the course website. Part 3: Lists of Lists ------------------------ - In programming you often must deal with data much more complicated than a single list. For example, we might have a list of lists, where each list might be temperature (or pH) measurements at one location of a study site: :: temps_at_sites = [ [ 12.12, 13.25, 11.17, 10.4], [ 22.1, 29.3, 25.3, 20.2, 26.4, 24.3 ], [ 18.3, 17.9, 24.3, 27.2, 21.7, 22.2 ], [ 12.4, 12.5, 12.14, 14.4, 15.2 ] ] - Here is code to find the site with the maximum average temperature; note that no indices are used. :: averages = [] for site in temps_at_sites: avg = sum(site) / len(site) averages.append(avg) max_avg = max(averages) max_index = averages.index(max_avg) print("Maximum average of {:.2f} occurs at site {}".format(max_avg, max_index)) - Notes: - for loop variable ``site`` is an **alias** for each successive list in ``temps_at_sites`` - A separate list is created to store the computed averages - We will see in class how this would be written without the separate ``averages`` list. Part 4: Controlling Execution of Loops -------------------------------------- - We can control loops through use of - ``break`` - ``continue`` - We need to be careful to avoid infinite loops Using a Break ------------- - We can terminate a loop immediately upon seeing the 0 using Python's ``break``: :: sum = 0 while True: x = int(input("Enter an integer to add (0 to end) ==> ")) if x == 0: break sum += x print(sum) - ``break`` sends the flow of control immediately to the first line of code outside the current loop, and - The while condition of ``True`` essentially means that the only way to stop the loop is when the condition that triggers the ``break`` is met. Continue: Skipping the Rest of a Loop Iteration ----------------------------------------------- - Suppose we want to skip over negative entries in a list. We can do this by telling Python to ``continue`` when it the loop variable, taken from the list, is negative: :: for item in mylist: if item < 0: continue print(item) - When it sees ``continue``, Python immediate goes back to the "top" of the loop, skipping the rest of the code, and initiates the next iteration of the loop with a new value for ``item``. - Any loop that uses ``break`` or ``continue`` can be rewritten without either of these. - Therefore, we choose to use them only if they make our code clearer. - A loop with more than one ``continue`` or ``break`` is rarely well-structured, so if you find that you have written such a loop you should stop and rewrite your code. - The example above, while illustrative, is probably better without the ``continue``. - Usually when we use ``continue`` the rest of the loop is relative longer the condition that triggers the ``continue`` it tested at or near the top of the loop. Termination of a While Loop --------------------------- - When working with a while loop one always needs to ensure that the loop will terminate! Otherwise we have an *infinite loop*. - Sometimes it is easy to decide if a loop will terminate. Sometimes it is not. - Do either of the following examples cause an infinite loop? :: import math x = float(input("Enter a positive number -> ")) while x > 1: x = math.sqrt(x) print(x) :: import math x = float(input("Enter a positive number -> ")) while x >= 1: x = math.sqrt(x) print(x) Summary ------- - ``range`` is used to generate a sequence of indices in a ``for`` loop. - Nested for loops may be needed to iterate over two dimensions of data. - Lists of lists may be used to specify more complex data. We process these using a combination of ``for`` loops, which may need to be nested, and Python's built-in functions. Use of Python's built-in functions, as illustrated in the example here in these notes, is often preferred. - Loops (either for or while) may be controlled using ``continue`` to skip the rest of a loop iteration and using ``break`` to terminate the loop altogether. These should be used sparingly!