Help support the author by donating or purchasing a copy of the book (not available yet)



Chapter 8 - Lists

Important Note:

Just a heads up, there's a lot to cover in this chapter and it can get quite technical in places (especially section 8.4) so pay particular attention to that section, its really important!

8.1 - List literals

Lists are, on the surface, the most simple data structure Python has to offer. They are also one of the most important and are used to store elements.

A list is a sequence of elements in which the elements can be of any type (integers, floats, strings, even other lists).

Lists are of type list

The most basic kind of list is the empty list and is represented as []. The empty list is falsey

>>> empty_list = []
>>> empty_list == False
True

In a list, elements are separated by commas.

my_list = [1, 2, 3, 654, 7]

This list contains 5 elements.

We can mix the types within the list, for example:

my_list = [True, 88, 3.14, "Hello", ['Another list', 45938]]

Notice how the last element is another list

In Python, a list is a collection type meaning it can contain a number of objects (strings, ints, etc..) yet still be treated as a single object.

Lists are also a sequence type meaning each object in the collection occupies a specific numbered location within it (which means elements are ordered).

Lists are also iterable so we can loop through their elements using indices.

They sound a lot like strings don't they?

However they differ in two significant ways:

8.2 - List methods and functions

We can get the length of a list in the same way we do with strings, using the len() function.

>>> my_list = [23, 543, "hi"]
>>> len(my_list)
3

We also have three other quite important functions, these are sum(), min() and max() and they work as you'd expect.

>>> my_list = [1, 2, 3, 4, 5, 6]
>>> sum(my_list)
21

min() and max() also work on strings, as strings have a lexicographical ordering, 'a' being the min and 'z' being the max:

>>> my_list = [6, 324, 456, 2, 6574, -452]
>>> max(my_list)
6574
>>> min(my_list)
-452
>>> min("string")
'g'
>>> max("string")
't'

We can also sort a list using the sorted() function. The sorted() function works by converting a collection to a list and returning the sorted list.

>>> student_grades = [88, 23, 56, 75, 34, 23, 84, 63, 52, 77, 96]
>>> sorted(student_grades)
[23, 23, 34, 52, 56, 63, 75, 77, 84, 88, 96]

In computer science, searching and sorting are two big problems and are really important topics.

We can append and pop elements to and from the end of a list. Append is add and pop is remove. We can do this because lists are mutable.

>>> my_list = [1, 5, 8]
>>> my_list.append(99)
>>> my_list
[1, 5, 8, 99]
>>> my_list.pop()
99
>>> my_list
[1, 5, 8]
>>> my_list.pop(2)
5
>>> my_list
[1, 8]

Notice that when we pop we are returned the number we popped. By default, if no arguments are passed to pop() then the last element is popped. We can also pass an index to pop() and it will pop the element at that index and return it to us.

We can store the value that is returned after calling pop() in another variable

>>> my_list = [1, 5, 9]
>>> x = my_list.pop()
>>> x
9

If we have a list of strings or characters, we can join them together using the join() function:

>>> my_list = ["O", "r", "a", "n", "g", "e", "s"]
>>> "".join(my_list)
'Oranges'

The string before the join function is called the joining string and is what will be between each element of the list when joining them together. In this case we want nothing (the empty string).

However if we had words, we might want to join them together with spaces:

>>> my_list = ["Hello", "World.", "This", "is", "a", "string."]
>>> " ".join(my_list)
"Hello World. This is a string."

Side Note:

We can break a string into a list using the split() function:

>>> s = "This is my string"
>>> my_list = s.split()
>>> my_list
['This', 'is', 'my', 'string']

The split() function takes an argument which is the character we want to split the string on. If no argument is passed, then it defaults to splitting on whitespace.

8.3 - List indexing

As we could with strings, we can also index into lists the same way.

>>> my_list = [3, 5.64, "Hello", True]
>>> my_list[2]
"Hello"
>>> my_list[0]
3

I've talked about how we can have other lists as elements inside lists. In Python and many other languages, lists inside lists are useful for representing many types of data (matrices, images, spreadsheets and even higher dimensional spaces). These are typically referred to as multidimensional arrays or multidimensional lists.

We can index into these inner lists too!

>>> my_list = [[1, 2, 3], [7, 8, 9]]
>>> my_list[0][1]
2

This works as follows, we first select the embedded list we want then we select the element from that list. In the above example, we want the list at index 0 ([1, 2, 3]) then we select the element at index 1 (2).

We can 'burrow' down into embedded lists this way.

>>> my_list = [[[1, 2, 3], [4, 5, 6]], [7, 8, 9]]
>>> my_list[0][1][1]
5

We can extend this to strings inside lists

>>> my_list = ["My string", "Another string"]
>>> my_list[0][1]
'y'

8.4 - References & Mutability

We looked at immutable types earlier on. Now we're going to look at mutable types.

A mutable type (or mutable sequence) is one that can be changed after it is created. A list is a mutable sequence and can therefore be changed in place.

Let's refresh our memory on how strings couldn't be changed after they were created

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

We get an error. Take a look back at the chapter on strings (Chapter 6) and revise the diagram that exists in that chapter.

I'm going to introduce the is keyword now. The is keyword compares object IDs (identities). This is similar to == which checks if the objects referred to by the variables have the same content (equality).

To give an analogy, let's say a clothing company is mass producing a certain type of shirt. Let's take two shirts as they are coming off the line.

We would say shirt_1 == shirt_2 evaluates to True (They are both the same color, size and have the same graphic printed on them)

We would say shirt_1 is shirt_2 evaluates to False (Even though they look similar, they are not the same shirt).

Mutability and Immutability is quite like that.

Let's look at some more Python examples:

>>> s = "Hello"
>>> t = s
>>> t is s
True
>>> t == s
True

These variables, s and t are referencing the same object (The string "Hello").What if we try to update s?

>>> s = "Hello"
>>> t = s
>>> t is s
True
>>> s = "World"
>>> t
"Hello"

The variable s is now referencing a different object and t still references "Hello". As strings are immutable we must create a new instance. We cannot change it in place.

With lists however, things operate a little differently.

>>> a = [1, 3, 7]
>>> b = a
>>> b is a
True
>>> a.append(9)
>>> a
[1, 3, 7, 9]
>>> b
[1, 3, 7, 9]
>>> a is b
True

The behavior has changed here and that is because the object pointed to by a and b is mutable. Modifying it does not create a new object and the original reference is not overwritten to point to a new object. We do not overwrite the reference in the variable a, instead we write through it to modify the mutable object it points to.

There are however some little tricks thrown in with this behavior. Consider the following code:

>>> a = [1, 3, 7]
>>> b = a
>>> a = a + [9]
>>> a
[1, 3, 7, 9]
>>> b
[1, 3, 7]

What's going on here? That goes against what we just talked about doesn't it? Well, obviously not, the Python developers wouldn't leave a bug that big in language. What's happening here is we executed the following code: a = a + [9]. This doesn't write through a to modify the list it references. Instead a new list is created from the concatenation of the list a and the list [9].

A reference to this new list overwrites the original reference in a. The list referenced by b is hence unchanged.

That's not all the little tricks thrown in. There's one more subtlety. Consider the following code:

>>> a = [1, 3, 7]
>>> b = a
>>> a += [9]
>>> a
[1, 3, 7, 9]
>>> b
[1, 3, 7, 9]

It turns out x += y isn't always shorthand for x = x + y. Although with immutable types they do the same thing, with lists they behave very differently. It turns out the += operator when applied to lists, modifies the list in-place. This means we write through the reference in a and append [9] to the list.

Writing through references is illustrated below.

Firstly consider the code, then the diagram:

>>> a = [1, 3]
>>> b = a
>>> a[1] = 9
** BEFORE UPDATING A **

         NAME LIST                   LIST OBJECT
        ===========                ===============
                                       |-----|          _____
               _____                   |     |          | 1 |
             a | # |------------------>|  #  |--------->|---|
               |---|                   |     |          _____
               _____                   |  #  |--------->| 3 |
             b | # |------------------>|_____|          |---|
               |---|
             
** AFTER UPDATING A **


         NAME LIST                   LIST OBJECT
        ===========                ===============
                                       |-----|           _____
               _____                   |     |           | 1 |
             a | # | ----------------->|  #  |---------> |---|
               |---|                   |     |           _____
               _____                   |  #  |-----|     | 3 |
             b | # | ----------------->|_____|     |     |---|
               |---|                               |
                                                   |     |---|
                                                   |---> | 9 |
                                                         |---|

So whats happening in the second part of this diagram? The #'s represent references.

a is a reference to a list object which contains references to integers.

b references the same list object. But when we "change" a, we're clearly not changing it. We're updating a reference within it. While b just points to the list, a has changed whats inside it hence b's reference is not effected.

We can still see the 3 floating around in the second part of the diagram. This will be picked up by something called the garbage collector (A garbage collector keeps memory clean and stops it from becoming flooded with stuff like the unreferenced 3).

It's important to keep a mental image of this.

8.5 - List operations

We've seen two operations (Now knowing they're different) in the previous section. The + and += operators. I'll briefly run over them again.

>>> a = [1, 2, 3]
>>> b = [4, 5, 6]
>>> a + b
[1, 2, 3, 4, 5, 6]
>>> a += b
>>> a
[1, 2, 3, 4, 5, 6]

We also have the * operator, which does as expected:

>>> a = [1, 2, 3]
>>> a * 3
[1, 2, 3, 1, 2, 3, 1, 2, 3]

We also have a new operator. This is the in operator. The in operator is a membership test operator and returns True if a sequence with a specified value is in an object

>>> a = [3, 7, 22]
>>> 4 in a
False
>>> 3 in a
True

The in operator can be applied to any iterable type (lists, strings, etc).

8.6 - List slicing

List slicing works exactly how it does with strings. This makes sense as both strings and lists are sequence types).

>>> a = [1, 2, 3, "Hello", True]
>>> a[3:]
["Hello", True]
>>> a[-4: -2]
[2, 3]

Extended slicing also works exactly the same as it does with strings.

>>> a = [1, 2, 3, "Hello", True]
>>> a[::-1]
[True, 'Hello', 3, 2, 1]
>>> a[::2]
[1, 3, True]

Important:

Remember in section 8.4 we wrote something like:

>>> a = [1, 2, 3]
>>> b = a

If we updated a, then b would also be effected. What if we wanted to make a copy of a and store it in b? We can use slicing to do that as slicing returns a new object

>>> a = [1, 2, 3]
>>> b = a[:]
>>> b is a
False

We can see now they do not reference the same object so updating one will not effect the other.

8.7 - Exercises

Important Note: We've learned a lot up to this point. Your solutions should be combining (where necessary) everything we've learned so far. If you are unsure of something, don't be afraid to go back to previous chapters. It's completely normal not to remember everything this early on. The more practice you have the more of this stuff will stick!

Question 1

Write a program that takes a string as input from the user, followed by a number and output each word which has a length greater than or equal to the number.

The string will be a series of words such as "apple orange pear grape melon lemon"

You are to use the split() function to split the string

# EXAMPLE INPUT
"elephant cat dog mouse bear wolf lion horse"
5

# EXAMPLE OUTPUT
elephant
mouse
horse

You may use the following code at the beginning of your program

my_words = input().split()
num = int(input())

# YOUR CODE HERE
# REMEMBER: my_words is a list

Question 2

Write a program that takes a string as input from the user, followed by another string (the suffix) and output each word that ends in the suffix.

The first string will be a series of words such as "apples oranges pears grapes lemons melons"

You are to use the split() function to split the string

# EXAMPLE INPUT
"apples oranges pears grapes lemons melons"
"es"

# EXAMPLE OUTPUT
apples
oranges
grapes

Hint: Your solution should be generalized for any suffix. Don't assume the length of the suffix will be 2

You may use the following code at the beginning of your program

words = input().split()
suffix = input()

# YOUR CODE HERE
# REMEMBER: words is a list

Question 3

Write a program that builds a list of integers from user input. You should stop when the user enters 0.

Your program should print out the list.

# EXAMPLE INPUT
3
5
6
7
0

# EXAMPLE OUTPUT
[3, 5, 6, 7]

Question 4

Building on your solution to question 3, taking a list built up from user input, ask the user for two more pieces of input. Integers this time.

The integers will represent indices. You are to swap the elements at the specified indices with eachother.

# EXAMPLE INPUT
6
3
9
0

*********************************
* LIST SHOULD NOW BE: [6, 3, 9] *
*********************************

1
2

# EXAMPLE OUTPUT
[6, 9, 3]

You may assume that the indices will be within the index range of the list.

Question 4

Write a program that builds a list of integers from user input. You program should then find the smallest of those integers and put it at the first index (0) in the list. Input ends when the user inputs 0.

# EXAMPLE INPUT
10
87
11
5
65
342
12
0

# LIST SHOULD NOW BE: [10, 87, 11, 5, 65, 342, 12]

# EXAMPLE OUTPUT
[5, 87, 11, 10, 65, 342, 12]

This problem is a little harder, think about your solution!

Question 5

** THIS IS A HARD PROBLEM **

Write a program that takes two sorted lists as input from the user and merge them together. The resulting list should also be sorted. Both of the input lists will be in increasing numerical order, the resulting list should be also.

# EXAMPLE INPUT
2
6
34
90
0      # END OF FIRST INPUT
5
34
34
77
98
0     # END OF SECOND INPUT

# EXAMPLE OUTPUT
[2, 5, 6, 34, 34, 34, 77, 90, 98]

Don't assume both lists will be the same length!



Help support the author by donating or purchasing a copy of the book (not available yet)



Previous Chapter - Next Chapter