Level 3 Phase 1 Workshop

Lists

Python's take on arrays are called lists. Like ArrayLists in Java, they can grow or shrink dynamically.

In [1]:
names = ['Asha', 'Michael', 'Arjun', 'Roland']
type(names)
Out[1]:
list

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 [2]:
print("the element at 1 is ", names[1])
names[1] = 'Victoria'
print(names)
the element at 1 is  Michael
['Asha', 'Victoria', 'Arjun', 'Roland']

We can use negative indexing to access list elements counting from the end of the list.

In [3]:
print(names[-1])
print(names[-2])
Roland
Arjun

The + operator adds two lists together.

In [4]:
L1 = [1, 2, 3]
L2 = ['a', 4, 5]
L = L1 + L2
print(L)
# but you can't add a single item to a list with +
wrong = L1 + 6
[1, 2, 3, 'a', 4, 5]
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-4-fe63771de191> in <module>()
      4 print(L)
      5 # but you can't add a single item to a list with +
----> 6 wrong = L1 + 6

TypeError: can only concatenate list (not "int") to list

Because lists are objects, we can call methods on a list. One method, append takes a single parameter and adds it to the end of a list.

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

Another method from the list class is sort. Sort works in place and changes the list. It returns None which is a special value in Python like null.

In [6]:
names.sort()
print(names)
['Arjun', 'Asha', 'Golnaz', '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.

List Slicing

We can use special syntax to take a section of a list or to insert one list into another.

In [7]:
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 [8]:
# 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 results in a nested list.

SHORT PRACTICE TIME

Looping over a List

The most common way to loop over a list is to use a loop like the following:

In [9]:
numbers = [7, 8, 42, 10, 5]
for item in numbers:
    print(item)
7
8
42
10
5

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 [10]:
sum = 0
for item in numbers:
    sum = sum + item
print("The sum is", sum)
The sum is 72

Now, change your code to sum the squares of the numbers (and while you are at it, print each square out.)

In [11]:
sum = 0
for item in numbers:
    square = item * item
    print("square of", item, "is", square)
    sum = sum + square
print("The sum is", sum)
square of 7 is 49
square of 8 is 64
square of 42 is 1764
square of 10 is 100
square of 5 is 25
The sum is 2002

Now change your code again to save the square of each element into the list. Does it work with this loop?

In [12]:
for item in numbers:
    square = item * item
    print("square of", item, "is", square)
    item = square
print(numbers)
square of 7 is 49
square of 8 is 64
square of 42 is 1764
square of 10 is 100
square of 5 is 25
[7, 8, 42, 10, 5]

The elements were squared but the resulting squares were not saved in the list. That's because the variable item is not a pointer to an element in numbers but simply is assigned a copy of value of each element of numbers on each iteration.

We need a different sort of loop if we want to change the elements of the list.

In [13]:
numbers = [3, 5, -1, 0]
for i in range(len(numbers)):
    print(numbers[i])
    numbers[i] = numbers[i] * numbers[i]
print(numbers)
3
5
-1
0
[9, 25, 1, 0]

It is actually a little more complicated, but you can think of range (when called with a single integer parameter i) as generating a list of the integers from 0 up to but not including i.

In [14]:
for i in range(5):
    print(i)
0
1
2
3
4

So range(len(some_list)) is perfect for looping over the indices of the list elements in order.

SHORT PRACTICE TIME

Let's use the append method that we saw earlier and the input function to ask the user for 5 integers and then create a list with those elements. Then let's loop over the list and print out the values of the list that are larger than their indices.

Before we start, you need to know one more thing. You can create an empty list using the square brackets and no elements and appending to an empty list creates a list with just the one item.

In [15]:
days = []
days.append("monday")
print(days)
['monday']

Solution

In [16]:
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 [17]:
numbers = []
new_number = input("please enter an integer ")
please enter an integer 3

Then append that integer we just received to our list.

In [18]:
numbers.append(new_number)

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

In [19]:
numbers = []
for i in range(5):
    new_number = input("please enter an integer ")
    numbers.append(new_number)
print (numbers)
please enter an integer 5
please enter an integer 1
please enter an integer 4
please enter an integer 3
please enter an integer 8
['5', '1', '4', '3', '8']

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 [20]:
numbers = []
for i in range(5):
    new_number = int(input("please enter an integer "))
    numbers.append(new_number)
print (numbers)
please enter an integer 5
please enter an integer 1
please enter an integer 4
please enter an integer 3
please enter an integer 8
[5, 1, 4, 3, 8]

Now we need to iterate over the list and print only the items that are larger than their index. Since we need to know the index, we need to use the i in range(len ... loop

In [21]:
for i in range(len(numbers)):
    if numbers[i] > i:
        print(numbers[i])
5
4
8

Strings

We've used strings a bit as a type already. Let's talk a bit more about strings.

Strings are not simply lists of characters. There are some extra methods that apply to strings and there are some list methods that don't apply to strings. But many things that worked for lists, work for strings.

For example, we access individual elements of a string with [index]

In [22]:
name = "Michelle"
print(name[2])             # counts from 0
print(name[-1])            # negative indexing works
print(name[-3:])           # slicing works
c
e
lle

Assigning to a string element is not allowed.

In [23]:
name = 'michelle'
name[0] = 'M'
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-23-334ba157f038> in <module>()
      1 name = 'michelle'
----> 2 name[0] = 'M'

TypeError: 'str' object does not support item assignment

Slicing also is not allowed.

In [24]:
name = 'michael'
name[-3:] = "elle"
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-24-d6aea72a8490> in <module>()
      1 name = 'michael'
----> 2 name[-3:] = "elle"

TypeError: 'str' object does not support item assignment

Concatenating Strings and the empty String

In [25]:
first = 'santa'
last = 'clause'
print(first + last)
name = first + " " + last
print(name)
empty = ""
print(empty)
print(empty + "a")
santaclause
santa clause

a

Iterating over Strings

We can use both kinds of loops over strings.

In [26]:
disease = "Typhoid"
for ch in disease:
    print(ch)
    
for i in range(len(disease)):
    print(disease[i])
T
y
p
h
o
i
d
T
y
p
h
o
i
d

Use the for ch in s form of the loop when you can --- when you don't need to know the index of a character inside the loop.

It turns out that we don't often have to loop over strings in our own programs, because strings are objects and there are methods provided in the string class for many of the things we would want to do.

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 [27]:
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 [28]:
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.

In [29]:
line = "something\n"
cleaned_line = line.strip("\n")       # we can string a specific character
print(cleaned_line)
value = ",15,"    
value = value.rstrip(',')             # or use lstrip or rstrip to only strip from one end
print(value)
something
,15

To tokenize a string into a list, use the method split()

In [30]:
s = "The quick brown     fox."
tokens = s.split()              # called with no parameter, it splits on whitespace
print(tokens)
line = "14,6,fred,male".split(',')  # or you can specify the delimiter (separator)
print(line)
['The', 'quick', 'brown', 'fox.']
['14', '6', 'fred', 'male']

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

Checking Membership in Strings or Lists

The operator in is used to return a boolean indicating if a character is in a string or an object is in a list.

In [31]:
VOWELS = """aeiouAEIOU"""
"$" in VOWELS
Out[31]:
False
In [32]:
"A" in VOWELS
Out[32]:
True
In [33]:
colours = ['red', 'blue', 'green', 'yellow']
'red' in colours
Out[33]:
True
In [34]:
'grey' in colours
Out[34]:
False
In [35]:
'Yellow' in colours   # match is case sensistive
Out[35]:
False

SHORT PRACTICE TIME (2 questions -- but you may only have time for 1)

Q1: 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. Write Python code that prints out the elements of the list that are not all in uppercase. Hint: look at the string methods that are available.

In [36]:
original_list = ['AA', 'BB', 'oops', 'DD', 'xx']
for item in original_list:
    if not item.isupper():
        print(item)
oops
xx

Q2: Write code that takes a list of strings and doesn't change the list. Instead create a new list that only contains the strings from the original list that contain only digits.

In [37]:
original_list = ['98', 'a', '5']
result = []
for item in original_list:
    if item.isdigit():
        result.append(item)
print("original is still:", original_list)
print("resulting list:", result)
original is still: ['98', 'a', '5']
resulting list: ['98', '5']

Functions

We've used functions quite a bit already. The syntax for defining functions in Python is easy. Let's put the solution to the two practice problems above into functions.

In [38]:
def only_digits(original):
    """ (list of str) -> list of str
    Return a new list that consists of the elements of original that are all digits
    """
    result = []
    for item in original_list:
        if item.isdigit():
            result.append(item)
    return result

original_list = ['98', 'a', '5']
L = only_digits(original_list)
print(original_list)
print(L)
['98', 'a', '5']
['98', '5']

Things to note

  • def keyword then function name
  • parameters in parenthesis (no types specified)
  • colon after parethesis
  • function body indented
  • docstring """ or ''' Displayed when you call help(<function name>)
  • return keyword
In [39]:
help(only_digits)
Help on function only_digits in module __main__:

only_digits(original)
    (list of str) -> list of str
    Return a new list that consists of the elements of original that are all digits

Mutable function parameters

In the example above, we returned a new list and did not modify the original. But it is possible to modify a list in a function by assigning to its elements. Do this in the next exercise.

PRACTICE TIME

Q1. Write a function clean_up that takes a string and returns a new string that is the same as the old string except that all the punctuation has been replaced by spaces and any uppercase letters have been converted to lowercase. Use a constant PUNCTUATION similar to the one we defined earlier.

In [40]:
def clean_up(original):
    """ (str) -> str
    Return a copy of original where all punctuation has been replaced by a space and
    the string is converted to lowercase
    """
    PUNCTUATION = "!@#$%^&*(){}[];:'<>,.?"
    result = ""
    for ch in original:
        if ch in PUNCTUATION:
            result += " "
        else:
            result += ch
    return result.lower()

clean_up("Trudeau, Justin?")    
Out[40]:
'trudeau  justin '