Lecture 8 — Lists Part 2

Overview

  • List copying versus list aliasing
  • Aliasing: creating new name for lists
    • Assignment
    • Function that change the lists that are input
  • Copying: creating new lists
    • Concatentation, replication and slicing
  • For loops to go through lists
  • Strings and lists
  • Lists of lists

List Aliasing

  • The assignment operator does NOT create a new list. Rather it creates a new name for an existing list:

    >>> L1 = [ 'cat', 'dog', 'ant', 'tiger', 'parrot' ]
    >>> L2 = L1
    >>> L1
    ['cat', 'dog', 'ant', 'tiger', 'parrot']
    >>> L2
    ['cat', 'dog', 'ant', 'tiger', 'parrot']
    >>> L1[2] = 'hawk'
    >>> L1
    ['cat', 'dog', 'hawk', 'tiger', 'parrot']
    >>> L2
    ['cat', 'dog', 'hawk', 'tiger', 'parrot']
    
  • Surprised? This is called an alias. Compare it with:

    >>> S1 = 'this is a string'
    >>> S2 = S1
    >>> S1 = 'I changed the first string'
    >>> S1
    'I changed the first string'
    >>> S2
    'this is a string'
    
  • Python is designed to do this primarily for reasons of efficiency since

    • lists can be quite long and are frequently changed
    • strings tend to be shorter and tend to be fixed
  • If we want to copy a list we should use a special set of methods. We’ll see those next.

Lists as Function Arguments

  • When lists are passed to functions, the parameter becomes an alias for the argument in the function call.

    • Formally in computer science, this is known as pass by reference.
  • Here is an example of a function that returns a list containing the two smallest values in its input list:

    def smallest_two(mylist):
         mylist.sort()
         newlist = []
         if len(mylist) > 0:
             newlist.append(mylist[0])
             if len(mylist) > 1:
                 newlist.append(mylist[1])
         return newlist
    
    values = [35, 34, 20, 40, 60, 30]
    
    print "Before function:", values
    print "Result of function:", smallest_two(values)
    print "After function:", values
    
  • In class we will discuss what happened

Using Indices to “Slice” a List and Create a New List

  • Recall

    >>> co2_levels = [ 320.03, 322.16, 328.07, 333.91, 341.47,
          348.92, 357.29, 363.77, 371.51, 382.47, 392.95 ]
    
  • Now suppose we just want the values at indices 2, 3 and 4 of this in a new list:

    >>> three_values = co2_levels[2:5]
    >>> three_values
    [328.07, 333.91, 341.47]
    >>> co2_levels
    [ 320.03, 322.16, 328.07, 333.91, 341.47, 348.92, 357.29, 363.77,
       371.51, 382.47, 392.95 ]
    
  • We give the first index and one more than the last index we want

  • If we leave off the first index, 0 is assumed, and if we leave off the last index, the length of the list is assumed.

  • Negative indices are allowed — they are just converted to their associated positive values. Some examples:

    >>> L1
    ['cat', 'dog', 'hawk', 'tiger', 'parrot']
    >>> L1[1:-1]
    ['dog', 'hawk', 'tiger']
    >>> L1[1:-2]
    ['dog', 'hawk']
    >>> L1[1:-4]
    []
    >>> L1[1:0]
    []
    >>> L1[1:10]
    ['dog', 'hawk', 'tiger', 'parrot']
    

More on List Slicing

  • The most general form of slicing involves three values

    L[si:ei:inc]
    

    where

    • L is the list
    • si is the start index
    • ei is the end index
    • inc is the increment value

    Any of the three values is optional

  • We’ll work through some examples in class to

    • Use slicing to copy an entire list
    • Use negative indices for slicing
    • Extracting the even indexed values
  • Note: L[:] returns a copy of the whole list of L. This is the same using function list(L):

    >>> L2 = L1[:]
    >>> L2[1] = 'monkey'
    >>> L1
    ['cat', 'dog', 'hawk', 'tiger', 'parrot']
    >>> L2
    ['cat', 'monkey', 'hawk', 'tiger', 'parrot']
    >>> L3 = list(L1)
    >>> L3[1] = 'turtle'
    >>> L1
    ['cat', 'dog', 'hawk', 'tiger', 'parrot']
    >>> L2
    ['cat', 'monkey', 'hawk', 'tiger', 'parrot']
    >>> L3
    ['cat', 'turtle', 'hawk', 'tiger', 'parrot']
    

Concatentation and Replication

  • Concatenation:

    >>> v = [1,2,3]+[4,5]
    >>> v
    [1,2,3,4,5]
    
  • Replication:

    >>> [1]*3
    [1,1,1]
    
  • These are very similar to the analogous operations with strings.

What Operations Change a List? What Operations Create New Lists?

  • Unlike strings, most operations on a list change the contents of the list rather than creating a new list.
  • Operations that change lists include
    • sort, insert, append, pop, remove
  • Operations that create new lists
    • +, *, slicing, list()

Part 1 Exercises

  1. What is the output of the following?

    x = [6,5,4,3,2,1] + [7]*2
    y = x
    x[1] = y[2]
    y[2] = x[3]
    x[0] = x[1]
    print x
    
    y.sort()
    print x
    print y
    
  2. Write a command to extract values from a list L0 indexed by 0,1,4,7 and 10,11, and return a list containing only these values.

  3. Write a slicing command to extract values from a list L0 indexed by 1, 4, 7, 10, etc.

Loops: Writing Our Operations on Lists

  • Despite the presence of a number of list methods, we need to be able to invent new operations that we can apply to lists.

  • This requires the notion of a “loop” where we apply a computation to each item in a list.

    • In the next lecture, we will discuss a more general notion of a loop.
  • As an example, here is code to compute the average CO_2 level, as it would appear when typing directly into the Python interpreter:

    animals = ['cat', 'monkey', 'hawk', 'tiger', 'parrot']
    all_animals = ""
    index = 1
    for value in animals:
        all_animals += "%d. %s\n" %(index, value.capitalize())
        index += 1
    
    print all_animals
    
  • We can understand what is happening by looking at this piece-by-piece:

    • The keyword for signals the start of a loop
    • value is a loop variable that takes on the value of each item in the list in the list (as indicated by the keyword in) in succession
    • The : signals the start of a block of code that is the “body of the loop”
    • The body of the loop here is just a single, indented line of code, but in other cases it can be many more.
    • The end of the loop body is indicated by the blank line

Part 2 Exercise

  1. Try the following code. Let’s discuss what we have learnt from this:

    L1 = ['cat', 'dog', 'hawk', 'tiger', 'parrot']
    for value in L1:
        value = 'sheep'
    
    print L1
    
  2. Write a program to find the number of values greater than the average for the co2_levels given in the following list. Also, print the median of the values that are greater than average.

    co2_levels = [ 320.03, 322.16, 328.07, 333.91, 341.47, \
      348.92, 357.29, 363.77, 371.51, 382.47, 392.95 ]
    

Aside: Reminder on Function Return Values

  • Some functions do NOT return a value:

    • Examples include functions we wrote at the start of the semester and the sort, insert and append functions of lists.
  • What happens when you try to assign or print the value of a function that does not return anything?

    >>> t = [1, 5, 3 ]
    >>> u = t.sort()
    >>> print t
    
    >>> print u
    
  • Moral of the story: if a function does not return a value, do not try to use its return value!!

Converting Strings to Lists

  • Version 1: use the function list to create a list of the characters in the string:

    >>> s = "Hello world"
    >>> t = list(s)
    >>> print t
    ['H', 'e', 'l', 'l', 'o', ' ', 'w', 'o', 'r', 'l', 'd']
    
  • Use the string split function, which breaks a string up into a list of strings based on the character provided as the argument.

    • The default is ' ':
    • Other common splitting characters are ',', '|' and '\t'
  • We will play with the s = "Hello world" example in class.

Converting Lists to Strings

  • What happens when we type the following?

    >>> s = "Hello world"
    >>> t = list(s)
    >>> s1 = str(t)
    

    This is unlikely to achieve what we want, which is usually the concatention of all of the strings in the list (assumming they are strings).

  • We will solve this problem using a for loop in the next exercise.

Indexing and Slicing Strings

  • We can index strings:

    >>> s = "Hello, world!"
    >>> print s[5]
    ,
    >>> print s[-1]
    !
    
  • We can apply all of the slicing operations to strings to create new strings:

    >>> s = "Hello, world!"
    >>> s[:len(s):2]
    'Hlo ol!'
    
  • Unlike lists, however, we can not use indexing to replace individual characters in strings:

    >>> s[4] = 'c'
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    TypeError: 'str' object does not support item assignment
    

Part 3 Exercise

  1. Suppose you have the string

    >>> s = "cat |  dog  | mouse"
    

    and you’d like to have the list of strings

    >>> L = [ "cat", "dog", "mouse"]
    

    Splitting the list alone does not solve the problem. Instead, you need to use a combination of splitting, and a loop that strips off the extra space characters from each string and appends to the final result. Write this code. It should be at most 4-5 lines of Python.

Lists of Lists

  • Lists can contain any mixture of values, including other lists.

  • For example, in

    >>> L = [ 'Alice', 3.75, ['MATH', 'CSCI', 'PSYC' ], 'PA' ]
    
    • L[0] is the name,
    • L[1] is the GPA
    • L[2] is a list of courses
    • L[2][0] is the 0th course, 'MATH'
    • L[3] is a home state abbreviation
  • We will write code to print the courses, to change the math course to a stats course, and to append a zipcode.

Final Example: Thank You Generation

  • Let us create a program to send fancy “Thank you” notes to all our relatives who have given us gifts recently.

  • We’ll start by making a list of lists:

    gift_list = [['Uncle John', 'skateboard'], \
            ['Aunt Marie', 'Amazon gift card'], ['Mr. Idle', 'backpack']]
    
  • Now we’d like a function that takes a name and a gift and prints a fancy output that might look something like:

    Dear Uncle John,
    
    Thank very much for your gift of a skateboard.
    I will enjoy using it very much.  I hope you
    are doing well.
    
    Sincerely,
    John Cleese

    This function should have as arguments, the name of the giver, the name of the gift, and the gift recipient (you), e.g.

    def print_thank_you( giver, gift, recipient ):
  • Once we write this function, we will write a for loop to print all of the thank yous.

  • The result will be posted on the course web site.

Part 4 Exercises

  1. Given a list

    >>> L = [ 'cat', 'dog', 'tiger' ]
    

    write a line of code to append the string 'lion'

  2. Rewrite L so that it is a list of lists, with household pets in the 0th (sub)list, zoo animals in the first.

  3. How can you append an additional list of farm animals (e.g. 'horse', 'pig' and 'cow') to L.

  4. Write code to remove 'tiger' from the sublist of zoo animals.

Summary

  • We can write our own functionality to work with lists using for loops.
  • Concatentation, replication and slicing create new lists.
  • Most other list functions that modify a list do so without creating a new list: insert, sort, append, pop, etc.
  • Assignment of lists and passing a list to a function do NOT create new lists, just aliases of existing lists.
  • Strings may be indexed and sliced, but indexing may not be used to change a string.
  • Conversion of a string to a list is accomplished using either list or split
  • Lists can be converted to strings as well, but it takes more work — it is not a natural operation.
  • Lists can store other lists.

Review Exercises: What Does Python Output?

  1. Without typing into the Python interpreter, find the outputs from the following operations:

    >>> x = ['a','b','c','d', 'e']
    >>> print x
    
    >>> for item in x:
    ...    print "*%s*" %item,
    ...
    
    >>> print x[3]
    
    >>> x[3] = 3
    >>> x
    
    >>> len(x)
    
    >>> x[2]=x[1]
    >>> x
    
    >>> x[5]
    
    >>> y = x[1:4]
    
    >>> y
    
    >>> x
    
  2. What about these operations?

    >>> y = [1, 2, 3]
    
    >>> y.append('cat')
    
    >>> y
    
    >>> y.pop()
    
    >>> y
    
    
    >>> y.remove(2)
    >>> y
    
    >>> y.remove('cat')
    
    >>> z = ['cat','dog']
    >>> z.insert(1,'pig')
    >>> z.insert(0,'ant')
    >>> z
    
    >>> z.sort()
    >>> z
    
    >>> z1 = z[1:3]
    >>> z1
    
    >>> z
    
  3. Write a function that returns a list containing the smallest and largest values in the list that is passed to it as an argument without changing the list? Can you think of ways to do this with sorting and without? Hint: use a for loop.