Group 1 Workshop 3: More About Lists

In the last workshop we learned about lists. Let's review.

In [24]:
names = ['Asha', 'Michael', 'Arjun', 'Roland']
print(names[2])
Arjun

list[i] refers to the ith element of list (where the count starts at 0). We can use this value and we can also change this value.

In [25]:
    names[1] = 'Victoria'
    print(names)
['Asha', 'Victoria', 'Arjun', 'Roland']

List Methods

In Python, lists are objects. They have actions that they can do. We call these methods. For example, we can call method sort on the list of names.

In [26]:
names.sort()
print(names)
['Arjun', 'Asha', 'Roland', 'Victoria']

A method is very like a function, except that it acts on a particular object. It is as if we are telling the list to sort itself.

Like functions, methods can take parameters. The list method append takes one parameter and adds the parameter to the end of the list.

In [27]:
names.append('Golnaz')
print(names)
['Arjun', 'Asha', 'Roland', 'Victoria', 'Golnaz']

Let's use the append method and a for loop that we learned last week to ask the user to enter 5 integers and put them into a list. Each time we ask for an integer we will append it. So what will be in our list before we start looping and asking? Nothing'

In [28]:
numbers = []

We can create an empty list by using the square brackets with no items. Now add one input statement to ask for the integer.

In [29]:
numbers = []
new_number = input("please enter an integer ")
please enter an integer 42

Then append that integer we just received to our list.

In [30]:
numbers.append(new_number)

Now repeat this asking and appending 5 times by using a for loop.

In [31]:
numbers = []
for i in range(5):
    new_number = input("please enter an integer ")
    numbers.append(new_number)
print (numbers)
please enter an integer 35
please enter an integer 1
please enter an integer -3
please enter an integer 5
please enter an integer 17
['35', '1', '-3', '5', '17']

But this isn't right. We wanted integers and we have strings. Remember that the values from the function input are always strings. If we want them to be converted to integers, we call the function int.

In [32]:
numbers = []
for i in range(5):
    new_number = int(input("please enter an integer"))
    numbers.append(new_number)
print (numbers)
please enter an integer1
please enter an integer2
please enter an integer3
please enter an integer4
please enter an integer5
[1, 2, 3, 4, 5]

Iterating Over a List So far we have learned that we can iterate over a list by using a for loop to iterate over the indices of the elements. For example we did this:

In [33]:
for i in range(len(numbers)):
    print(numbers[i])
    numbers[i] = numbers[i] * 2
print("And now the list has changed and is", numbers)
1
2
3
4
5
And now the list has changed and is [2, 4, 6, 8, 10]

If we don't want to change the elements of the list, there is another more common way to iterate over a list. It looks like this:

In [34]:
for item in numbers:
    print(item)
2
4
6
8
10

Let's do a really easy task, just to make sure everyone is with me here. Use this type of loop to add up the total of all the numbers in our list and then print the total.

In [35]:
sum = 0
for item in numbers:
    sum = sum + item
print("The sum is", sum)
The sum is 30

Now let's do a more interesting example. Assume we have a list of names (maybe all the students in this workshop.) We don't want to change that list, but we want to make a new list that contains all the names from the original list that start with 'A'.

In [36]:
A_list = []
for name in names:
    if name[0] == 'A':
        A_list.append(name)

print ("original list", names)
print (" A list ", A_list)
original list ['Arjun', 'Asha', 'Roland', 'Victoria', 'Golnaz']
 A list  ['Arjun', 'Asha']

String Methods

Like lists, strings are also objects and have methods. One difference, though is that while some list methods (like sort and append) change the list, none of the string methods ever change a string.

So what good are they?

Like functions, methods can return a value. Many of the string methods, return another string. For example:

In [37]:
s = "hello"
t = s.upper()
print (s, t)
hello HELLO

Notice that the string s hasn't changed. But calling the method upper on string 's', returned the string 'HELLO' which we assigned to variable t. Here is another example.

In [38]:
food = "   Pad Thai   "
fixed_food = food.strip()
print ("|",food, "|", fixed_food)
|    Pad Thai    | Pad Thai

Notice that the print function inserted one space for each comma and that the function strip remove the leading and trailing spaces from our string food.

There are quite a lot of string methods and you can find the list in the online python documentation at https://docs.python.org/2/library/stdtypes.html#string-methods

A Worked Example

Let's assume we have a list of strings that are supposed to be all in uppercase. But some errors got into the data and there are a few strings in the list that are not completely uppercase letters. Let's write a function together that prints out the elements of the list that are not all in uppercase. Let's also use this as an opportunity to review the design recipe.

We need a name for our function. Let's use find_mistakes

Next we write an example. We need to call find_mistakes on a list and some of the entries (at least one) needs to be a mistake.

In [39]:
def find_mistakes(original_list):   
   """
   >>> find_mistakes(['AA', 'BB', 'oops', 'DD', 'xx'])
   prints oops and prints xx
   """
   pass

Above I've also added the def line which means I had to make up a name for the parameter. Next we add the type contract.

In [40]:
def find_mistakes(original_list):   
   """ (list of str) -> void
   >>> find_mistakes(['AA', 'BB', 'oops', 'DD', 'xx'])
   prints oops and prints xx
   """
   pass

Notice that the return type is void. That means that it doesn't return anything. Because the effect of our function is to print something. But we aren't returning a value. Finally we write a description of what the function should do.

In [41]:
def find_mistakes(original_list):   
   """ (list of str) -> void
   
   Print the elements of original list that are not all uppercase.
   
   >>> find_mistakes(['AA', 'BB', 'oops', 'DD', 'xx'])
   prints oops and prints xx
   """
   pass

Now we write the body. To figure out how to do this we look at how it was that we worked out the examples. We see that we must have looped over the elements of original_list and for each one, checked if it was made up only of uppercase letters. If it wasn't, we printed it. Let's add that code.

But for a single string, how do we decide if it is uppercase? There's a method for that. isupper() returns True when the string is not-empty and is only uppercase letters.

In [42]:
def find_mistakes(original_list):   
   """ (list of str) -> void
   
   Print the elements of original list that are not all uppercase.
   
   >>> find_mistakes(['AA', 'BB', 'oops', 'DD', 'xx'])
   prints oops and prints xx
   """
   for item in original_list:
        if not item.isupper():
            print(item)
            
find_mistakes(['AA', 'BB', 'oops', 'DD', 'xx'])
oops
xx

Exercise

Let's put together what we've learned so far and write a function that takes a list of strings and doesn't change the list. The function should return a new list that only contains the strings from the original list that contain only digits.

In [43]:
def keep_digits(original_list):
    """ (list of str) -> list of str 
    
    >>> keep_digits(['98', 'a', '5'])
    ['98', '5']
    
    Return a list of the elements of original_list that are composed only of digits
    """
    
    result = []
    for item in original_list:
        if item.isdigit():
            result.append(item)
    return result

keep_digits(['98', 'a', '5'])
Out[43]:
['98', '5']

Material For workshop 4

Note for Michael -- I wrote these notes for slicing and splicing not realizing that we had put these 2 topics in W4. We can just save them.

List Slicing

We say that list[i] evalues to the ith element of list. list[i:j] makes a new list that contains the elements from i up to but not including j.

In [44]:
fruit = ['Apples', 'Oranges', 'Pears', 'Bananas']
print(fruit[2:4])
print(fruit[1:-1])
print(fruit[:3])
last_fruit = fruit[-1:]
print(last_fruit)
['Pears', 'Bananas']
['Oranges', 'Pears']
['Apples', 'Oranges', 'Pears']
['Bananas']

Notice that negative indexing can be used. Notice that a : on either side of the comma gets that end of the list. Notice that even though the last list contains only one element, it is still a list.

List Splicing

We can also assign a list to a slice. This replaces the slice with the new list. The slice you replace and the replacement slice don't have to have the same number of elements. That means you can use splicing to increase or decrease lists.

In [45]:
# if the two list are the same size, it does as expected and just replaces them
fruit[1:3] = ['Navel Oranges', 'Anjou Pears']
print(fruit)

# but if you replace with an empty list, it removes items
fruit[2:3] = []
print(fruit)

# or you could replace a slice with a longer list
fruit[1:3] = ['beets', 'carrots', 'peppers', 'cabbage']
print(fruit)

# or you could replace an empty slice even
fruit[1:1] = ['chocolate']
print(fruit)

# notice that assigning a list to a slice of length 1
# is different than assigning a list to a single element
fruit[2] = ['X', 'Y']
print(fruit)
['Apples', 'Navel Oranges', 'Anjou Pears', 'Bananas']
['Apples', 'Navel Oranges', 'Bananas']
['Apples', 'beets', 'carrots', 'peppers', 'cabbage']
['Apples', 'chocolate', 'beets', 'carrots', 'peppers', 'cabbage']
['Apples', 'chocolate', ['X', 'Y'], 'carrots', 'peppers', 'cabbage']

That last example, assigned a list into fruit[2]. That's called a nested list and we will talk about them soon.

BACK TO WORKSHOP 3

More on Looping over Lists

Now that we know about the loop for item in list, let's try to use it to change our list. Suppose we want to call tolower() on every item of a list to convert all the elements to lowercase. We could try to do it like this.

In [46]:
colours = ['White', 'BLUE', 'Red', 'yeLLow']
# we can convert any one element like this
colours[2] = colours[2].lower()
print(colours)
# now try to do this in a loop
for col in colours:
    col = col.lower()
print(colours)
['White', 'BLUE', 'red', 'yeLLow']
['White', 'BLUE', 'red', 'yeLLow']

What's going on? The variable col takes on successive values from the list. We can confirm this by printing out its value inside the loop. And we can see that calling lower() works as expected by printing out col a second time.

In [47]:
colours = ['White', 'BLUE', 'Red', 'yeLLow']
# we can convert any one element like this
colours[2] = colours[2].lower()
print(colours)
# now try to do this in a loop
for col in colours:
    print("The value of col is", col)
    col = col.lower()
    print("And then it is", col)
print(colours)
['White', 'BLUE', 'red', 'yeLLow']
The value of col is White
And then it is white
The value of col is BLUE
And then it is blue
The value of col is red
And then it is red
The value of col is yeLLow
And then it is yellow
['White', 'BLUE', 'red', 'yeLLow']

But these changed values are not assigned back to the list. col is a separate variable that at the top of each loop iteration is assigned the value of the next item in the list. It isn't connected to the element in the list. It just gets its value at the top of the list and then the loop body executes.

A good rule (for now) is that if you will need to change the value of the elements of the list (not just use them), you need to loop over indices with for i in range(len(...))