Lecture 7

Review

  1. How many times does this while loop execute? num = float(input("Enter a number: "))
    while num < 0:
        print("Only the square root of positive numbers can be calculated")
        num = float(input("Enter a number: "))
  2. How many times does this while loop execute? repeat = 'no'
    while repeat != 'no':
        repeat = input("Do you want to repeat the loop? ")
  3. What value prints out at the end? total = 0
    i = 0
    while i < 5:
        total += i
    print(total)
    What is the accumulator variable in the program?

Nested Loops

Just like nested if statements, it is possible to nest loops inside of other loops. For each time through the outer loop, the inner loop loops completely:

hours = 0
while hours < 25:
    minutes = 0
    while minutes < 60:
        print(hours, ':', minutes, sep='')
        minutes += 1
    hours += 1

Why do you think minutes is set to zero inside the first while loop? Why isn't it:

hours = 0
minutes = 0 #minutes set to zero outside the first while loop, in contrast to above
while hours < 25:
    while minutes < 60:
        print(hours, ':', minutes, sep='')
        minutes += 1
    hours += 1

for Loops

The other kind of loop Python supports is the for loop. Python's for loop is more like a for-each loop (which some programming languages, like Java and PHP, support) than the standard for loop. It will execute the body of the for loop for each element in a collection. The only collection type we've seen so far is the string (it's a collection of characters), but we'll see others very soon.

Here is the basic form of a for loop:

for variable in collection:
    #statements to execute for each element in collection

The variable is the loop's iterator variable. It iterates through each element in the collection. In other words, it takes on each value in the collection.

Here is an example of using a for loop. In this example, the loop counts the number of lowercase vowels in a user-provided string.

value = input("Enter text: ")
count = 0 #number of vowels in value
for letter in value:
    if letter == 'a' or letter == 'e' or letter == 'i' or letter == 'o' or letter == 'u':
        count += 1

print("There are", count, "lowercase vowels in the text you entered.")

The range function

What if you want to loop a set number of times? You could write a while loop like this:

hour = 0
while hour < 25:
    print(hour)
    hour += 1

However, for loops are much better for looping a set number of times (this is true across programming languages). In Python, this is accomplished with the help of the range function. It creates a special collection of integers from a specified lower-bound to a specified upper-bound (not including this upper-bound). For example:

>>> for i in range(10, 20):
        print(i)

10
11
12
13
14
15
16
17
18
19

Notice that the numbers start at 10 and go up to, but not including 20.

Range is a function that takes between 1 and 3 arguments. We haven't seen many functions yet that can do this. The number of arguments you give it changes how it behaves.

  1. If you give it one argument, it is the upper-bound and the lower-bound is assumed to be 0. >>> for i in range(5):
            print(i)
    0
    1
    2
    3
    4
  2. If you give it two arguments, the first is the lower-bound and the second is the upper-bound. >>> for i in range(3, 5):
            print(i)
    3
    4
  3. If you give it three arguments, the first is the lower-bound, the second is the upper-bound, and the third is the step size to take: >>> for i in range(3, 13, 2):
            print(i)
    3
    5
    7
    9
    11

Using the range function, we can rewrite the while loop above to be:

for hour in range(25):
    print(hour)

Notice how much shorter it is!

You can combine the range and len functions to iterate over the indices of a collection:

value = input("Enter text: ")
count = 0 #number of vowels in value
for i in range(len(value)):
    if value[i] == 'a' or value[i] == 'e' or value[i] == 'i' or value[i] == 'o' or value[i] == 'u':
        count += 1

print("There are", count, "lowercase vowels in the text you entered.")

How is this an improvement over the original version of this code? It's not. In most cases, you will want to iterate over the elements in the collection (as done in the example earlier). However, it you want to compare two elements in the loop, you will most often need to know their indices and the only way to get them is through range(len(collection)). For example, to count the number of consecutive vowels in a string:

value = input("Enter text: ")
count = 0 #number of vowels in value
for i in range(len(value)-1):
    if value[i] == 'a' or value[i] == 'e' or value[i] == 'i' or value[i] == 'o' or value[i] == 'u':
        #current letter is a vowel, is the next?
        if value[i+1] == 'a' or value[i+1] == 'e' or value[i+1] == 'i' or value[i+1] == 'o' or value[i+1] == 'u':
            count += 1

print("There are", count, "consecutive lowercase vowels in the text you entered.")

Why is it range(len(value)-1) and not range(len(value))? In other words, why do we need to subtract 1 from len(value)?

A couple of notes about the range function:

while loops and for loops

The while loop executes while a condition is True. The for loop executes once for each element in a collection. In general:

Lists and Tuples

Sometimes, it is very useful to be able to store a list of values, but so far all of the datatypes we've seen can only hold one value. A float can only hold one floating-point number, an int can only hold one integer, and a string can only hold one text. If you want to hold multiple values, you would need multiple variables, but what if you don't know how many values you'll need? This is where lists can help! (In other programming languages, lists are called arrays.) A list is one variable that can hold multiple values. (Unlike other programming languages, python does not require all data in the list to be the same type.)

To create a list in Python, you can use either the square brackets or the list function:

>>> #create two empty lists:
>>> a = []
>>> b = list()
>>>
>>> #to create a list with values in it, use the square brackets:
>>> a = [1, 4, 7, 'asdf']
>>>
>>> print(a)
[1, 4, 7, 'asdf']

Indexing

When you store elements in a list, the order is preserved. Just like strings, the first position in a list is at index 0 and the last position is at index len(list_name) - 1. To access an element in a list, use the index notation (just like with strings.

>>> a = [1, 4, 7, 'asdf']
>>> len(a)
4
>>> a[0]
1
>>> a[2]
7
>>> a[len(a)-1]
'asdf'

Getting elements from the end of the list is a common task, so the Python language developers tried to make it more convenient than requiring programmers to always type list_name[len(list_name)-1]. You can use negative indices to indicate starting at the end of the list. The trick is to just drop the len(list_name) part of the index. For example:

>>> a = [1, 4, 7, 'asdf']
>>> a[-1] #this is the same as a[len(a) - 1]
'asdf'
>>> a[-2] #this is the same as a[len(a) - 2]
7
>>> a[-len(a)] #what do you think this will do?

This is also true with string indexing:

>>> b = 'cat'
>>> b[-1]
't'

With lists, you can change the value of an element by assigning a new value to the list at that index, e.g.:

>>> a = [1, 4, 7, 'asdf']
>>> a[0] = 'a'
>>> a[-1] = 3
>>> print(a)
['a', 4, 7, 3]
>>> a[0] = ['x', 'y', 'z'] #what do you think this will produce?

Note: this does not work with strings or with tuples.

Slicing

The slicing operation creates a new list, taking elements from the first list that the programmer specifies. You again use the square brackets, but now specify two or three values instead of just one. Separate the values with the colon (:). The general form for slicing is:

list_name[start_index:stop_index]

or

list_name[start_index:stop_index:step_size]

The start_index is the index to start slicing at. Stop slicing at the stop_index (will not include this index). The optional step_size indicates how many steps to take before picking the next element; if it is not specified, the default is 1.

Some examples:

>>> a = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l']
>>> a[0:5] #start at index 0 and go up to but not including index 5; no step size is specified, so defaults to 1
['a', 'b', 'c', 'd', 'e']
>>>
>>> a[0:len(a):2] #get every other element in the list
['a', 'c', 'e', 'g', 'i', 'k']
>>>
>>> #just like with the range function, if you want to go backwards, you must specify a negative step size
>>> a[5:0] #start at 5, go up to but not including 0 (with implicit step size of 1) ... you can't go from 5 to 0 by adding 1, so you get an empty list
[]
>>> a[5:0:-1] #start at 5, go up to but not including 0, step size -1
['f', 'e', 'd', 'c', 'b']
>>> #why isn't 'a' in that list?

You can leave out the start_index or the stop_index (although you still need the colons). If you leave out start_index, it defaults to 0. If you leave out stop_index, it defaults to len(list_name).

Some examples:

>>> a = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l']
>>> a[:5] #no start_index specified, so start at 0; go up to but not including index 5; no step_size specified, so defaults to 1
['a', 'b', 'c', 'd', 'e']
>>>
>>> #get the last 5 elements in the list:
>>> a[-5:]
['h', 'i', 'j', 'k', 'l']
>>>
>>> a[::2] #get every other element in the list
['a', 'c', 'e', 'g', 'i', 'k']
>>>
>>> a[5::-1] #start at 5, walk with a step size -1, and stop when you reach the end of the list
['f', 'e', 'd', 'c', 'b', 'a']

Slicing makes a new list and puts the elements into that new list. So, if you change the new list, the old list will not change.

>>> a = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l']
>>> b = a[:5]
>>> b[0] = 3
>>> b
[3, 'b', 'c', 'd', 'e']
>>> a
['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l']

Just like with indexing, you can do assignments to a slice. It replaces the specified part of the list with the given value.

>>> a = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l']
>>> a[:5] = 'z' #replace elements 0 through 4 (inclusive) with the value 'z'
>>> a
['z', 'f', 'g', 'h', 'i', 'j', 'k', 'l']
>>> #notice that it doesn't replace each element with a 'z', it replaces that whole part of the list with a 'z'
>>>
>>> a[-2:] = 'a'
>>> a
['z', 'f', 'g', 'h', 'i', 'j', 'a']
>>>
>>> #you can only do this with a step size of 1, other steps don't work
>>> a[0:6:2] = 'x'
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: attempt to assign sequence of size 1 to extended slice of size 3
>>> a[6:0:-1] = 'x'
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: attempt to assign sequence of size 1 to extended slice of size 6

You can also replace parts of a list with another list:

>>> a = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l']
>>> a[0:2] = [1, 2, 3]
>>> a
[1, 2, 3, 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l']
>>>
>>> a[4:8:3] = [0, 1] #replace the elements at index 4 and 7 with the values in the list [0, 1]
>>> a
[1, 2, 3, 'c', 0, 'e', 'f', 1, 'h', 'i', 'j', 'k', 'l']
>>>
>>> #this will only work if the number of elements sliced equals the number of elements in the list being assigned
>>> a[4:8:3] = [10, 20, 30] #replace the elements at index 4 and 7 with the values in the list [10, 20, 30]
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: attempt to assign sequence of size 3 to extended slice of size 2
<< Previous Notes Daily Schedule Next Notes >>