Lesson 6: Lists and tuples
Subject: | A more systematic review of lists and tuples |
New concepts: | Lists and tuples |
Work procedure: | You are encouraged to discuss the work with others but should write your own code.
Try to solve the exercises before you look at the answers. Ask a teaching assistant if you don't understand the answers. |
Estimated working time: | 6 hours. |
Examination: | It is mandatory to present your solution to a teacher or assistant teacher according the time at the course home page. |
Lists
We've already used lists in previous lessons but now we'll go through the concept and usage more thoroughly.
The list is one of the most important and useful tools in Python. The list concept also includes what in many other (programming) languages are called arrays, but lists are more general than arrays usually are.
A list consists of a number of elements of arbitrary types.
Examples:-
[2, 3, 5, 7, 11]
is a list with 5 integer objects. -
['Eva', 'Olle', 'Lisa', 'Gustav']
is a list with 4 string objects. -
[22, 'Pears', 4, 3.14159]
is a list with 4 elements of different types. -
[[1, 2, 3], 'Kalle', 25, ['x', 'y']]
is a list with 4 elements, of which two are lists, one is a string, and one is an integer. -
[]
is a list with 0 elements.
Construction of lists
A list can be created by literally listing all the objects within square brackets, as done in the examples above.
The elements are indexed starting at 0. An element can be accessed by putting its index in the square brackets.
The operator =
and the methods append
and insert
can be used to change the list.
Example | Printout | Comment |
---|---|---|
fib = [1, 1, 2, 3, 5, 8] print(fib) | [1, 1, 2, 3, 5, 8] | |
print(fib[3]) print(fib[2] + fib[5]) | 3 10 | Accessing single elements using []. First index is always 0. |
fib.append(12) print(fib) | [1, 1, 2, 3, 5, 8, 12] |
The method append adds the new element at the end of the list; it appends the element.
|
fib[6] = 13 print(fib) | [1, 1, 2, 3, 5, 8, 13] | A single element can be changed using the assignment operator. |
fib.insert(0, 0) print(fib) | [0, 1, 1, 2, 3, 5, 8, 13] |
insert( index, value) inserts a new value at the given index.
|
We can use negative index to reference objects at the end of the list. Index -1 references the last element, -2 the next to last, etc.
Example | Printout |
---|---|
fib.append(fib[-1] + fib[-2]) print(fib) | [0, 1, 1, 2, 3, 5, 8, 13, 21] |
for i in range(3): fib.append(fib[-1] + fib[-2]) print(fib) | [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89] |
Working with lists
There are several standard functions for lists, such as len
for the number of elements,
max
and min
for the largest and smallest value(s),
sum
for the sum of all the elements,
and sorted
which returns a new list that is sorted.
All of these except the first one require the elements of the list to be "comparable",
that is, the elements can all be compared to one another.
For example, an int and a string is not comparable,
so a list containing both ints and strings could not make use of those methods.
Example | Printout | Comment |
---|---|---|
m = [1, 3, 0.5, 4, 3.5, 8] print(len(m)) | 6 | |
print(max(m), min(m), sum(m)) | 8 0.5 20.0 | |
print(sorted(m)) print(m) | [0.5, 1, 3, 3.5, 4, 8] [1, 3, 0.5, 4, 3.5, 8] |
The original list is unaltered. |
print(sorted(['Ärlig', 'Eva', 'Åke'])) | ['Eva', 'Ärlig', 'Åke'] | Note: 'Å' and 'Ä' have not been sorted correctly. In the Swedish alphabet, 'Ä' comes after 'Å' ( ...XYZÅÄÖ). |
sorted(['x', 5]) | Traceback (most recent call last):
File "<stdin", line 1, in <module>
TypeError: '<' not supported between instances of 'int' and 'str' |
The datatypes int and str are not comparable.
|
Exercises
-
Write a function
mean(m)
that calculates and returns the mean value of the numbers in the listm
! What happens if the list contains strings?def mean(m): return sum(m)/len(m)
-
Write a function
median(m)
that calculates and returns the median value of the numbers in the listm
!The median is the value that is placed in the middle when the list is sorted. If the number of elements is even, we define the median as the mean value of the two elements in the middle.
For example, for a list with eight elements the median would mean value of the third and forth element.
def median(m): l = sorted(m) mid = len(l)//2 if len(l)%2 == 1: return l[len(l)//2] else: return(l[mid-1] + l[mid])/2
The for
statements on lists
The for loop is perfect for iterating over all the elements in a list.
Example: Sum all the negative numbers in a list.
m = [-1, 3, 5, -3, 3, -8] sum = 0 for x in m: if x < 0: sum += x print(sum) # Prints out -12
Exercises
-
Given a list with numbers,
write code that constructs a new list without the negative numbers.
m = [-1, 3, 5, -3, 3, -8] n = [] # Create an empty list for x in m: if x >= 0: n.append(x) print(n) # Prints out [3, 5, 3]
-
Write a function
between(a_list, low, high)
that creates and returns a list of the elements in the givena_list
that lays betweenlow
andhigh
.def between(a_list, low, high): result = [] for x in a_list: if low <= x <= high: # It's OK to write like this! result.append(x) return result l = [3, 1, 8, 19, 2, 5, 12] print(between(l, 3, 12)) # Prints out [3, 8, 5, 12]
Example: World of wandering turtles
Instead of the two turtles we had in the previous lesson,
we will now have a list with several dizzy turtles.
Using the methods random_turtle
and move_random
from the previous lesson:
# Create a set of dizzy turtles turtles = [] for t in range(6): turtles.append(random_turtle()) for i in range(1, 200): for t in turtles: move_random(t)
You can find the entire program here. Feel free to download and test it!
Exercise
-
Create a list with 4 turtles and place them in the corners of a square. Point the first turtle towards the second, the second towards the third, the third towards the fourth, and the fourth towards the first. Like this:
Then, let the turtles walk one step (length 5) towards the turtle it is pointing at. Update the directions of the turtles so that they are pointing towards their target turtle's new position. After a while, it'll look like this:
Stop the program when the turtles meet in the middle.
ts = [create_turtle(-200, 200, 270), create_turtle(-200, -200, 0), create_turtle(200, -200, 90), create_turtle(200, 200, 180)] while ts[0].distance(ts[1]) > 10: for t in ts: t.forward(5) for i in range(4): j = (i + 1) % 4 # index for "the next" turtle ts[i].setheading(ts[i].towards(ts[j]))
A program with the help function
create_turtle
can be found here.
Partial lists — "slicing"
We can create partial lists using colons.
Slice | Meaning |
---|---|
lst[m:n] |
The part of the list lst that has index from m to n - 1.
|
lst[:] |
A new list with the same elements as lst .
|
lst[m:] | A list with all the elements at index m and up. |
lst[:m] | All elements from the beginning of the list up until the element with index m-1. |
Example
Some examples using the list lst = [10, 11, 12, 13, 14, 15]
Code | Value |
---|---|
lst[2:5] | [12, 13, 14] |
lst[:2] | [10, 11] |
lst[2:2] | [] |
lst[:] | [10, 11, 12, 13, 14, 15] |
lst[-3:] | [13, 14, 15] |
lst[-3:10] | [13, 14, 15] |
lst[-100:2] | [10, 11] |
As is evident, it is quite alright to use index values in the colon expression that are actually outside of range for the list in question.
It is also possible to change the list using both the assignment operator and
the slicing
statement.
Code | The list afterwards |
---|---|
lst | [10, 11, 12, 13, 14, 15] |
lst[1:3] = [21, 22, 23] | [10, 21, 22, 23, 13, 14, 15] |
del lst[2:4] | [10, 21, 13, 14, 15] |
lst[:2] = [] | [13, 14, 15] |
Exercises
-
Write a function
smooth(a)
that takes in a lista
of numbers, and then creates and returns a new list with the same number of elements. The first and last element should be the same as ina
, while the element at index i should be the mean value ofa[i-1]
,a[i]
anda[i+1]
.Example:
print([1, 2, 6, 4, 5, 0])
should give us
[1, 3.0, 4.0, 5.0, 3.0, 0]
def smooth(a): res = [] res.append(a[0]) for i in range(1, len(a)-1): res.append(sum(a[i-1:i+2])/3) res.append(a[-1]) return res
Methods and operators that gives us information about lists
Expression | Value | Comment |
---|---|---|
a = [3, 9, 2, 7, 9, 2, 3] | ||
a.count(9) | 2 | |
a.count('a') | 0 | |
a.index(7) | 3 | |
a.index(9) | 1 | |
a.index(9, 3) | 4 | Starts looking at index 3. |
3 in a | True | |
'x' in a | False | |
'x' not in a | True | |
a = [[1, 1], 1, [1, 2], [1, 1]] | ||
a.count(1) | 1 | Only counts on the "top" level, so the values within the sublists are disregarded |
a.count([1, 1]) | 2 | |
a.index([1, 2]) | 2 |
Exercises
-
Write a function
counter2(x, lst)
that counts how many times the value x exists on the second level in the given list lst. The top level would be level 1.Example:
counter2(1, [[[1, 1]], [1, 2, 1], [1, 2]])
should return 3, that is, it should count the red elements.def counter2(x, lst): res = 0 for l in lst: res += l.count(x) return res
-
In the function above we assume that the elements in
list
are lists. What happens if they aren't? Try it out! -
We can use the standard function
type
to examine the type of a value. For example, the expressiontype(42) == int
has the valueTrue
,type(42) == list
has the valueFalse
, andtype(['a', 1, [2, 3]])
has the valuelist
.Write a function
counter(x, lst)
that counts how many times the value x exists in a given list lst both on the level 1 and on level 2.Example:
counter(1, [1, 2, [1, 2, 1], 1, [1, 2]])
should return 5.def counter(x, lst): res = 0 for l in lst: if type(l) == list: res += l.count(x) elif l == x: res += 1 return res
Methods that affects lists
Previously, we've seen how specific list elements can be changed using the assignment operator (e.g. a[3]='hey'
)
and how elements can be added at the end of a list with the method append
.
There are several other methods that can affect and change lists.
Examples:
Code | The list's new content | Comment |
---|---|---|
a = [21, 11, 42, 17] | ||
a.extend([0, 3]) | [21, 11, 42, 17, 0, 3] | |
a.pop() | [21, 11, 42, 17, 0] | pop returns 3. |
a.pop(1) | [21, 42, 17, 0] | pop returns 11. |
a.insert(2, 47) | [21, 42, 47, 17, 0] | |
a.append(47) | [21, 42, 47, 17, 0, 47] | |
a.remove(47) | [21, 42, 17, 0, 47] | |
a.reverse() | [47, 0, 17, 42, 21] | |
a.sort() | [0, 17, 21, 42, 47] | |
a.clear() | [] | |
a = ['Ola', 'Bo', 'Mi'] | ['Ola', 'Bo', 'Mi'] | |
a.sort() | ['Bo', 'Mi', 'Ola'] | |
a.append(3) | ['Bo', 'Mi', 'Ola', 3] | |
a.sort() | TypeError: '<' not supported between instances of 'int' and 'str' |
The elements must be comparable. |
a = [[2, 2, 1], [2, 1], [2, 3]] | [[2, 2, 1], [2, 1], [2, 3]] | |
a.sort() | [[2, 1], [2, 2, 1], [2, 3]] | Lists are comparable. |
Exercises
-
Assume that
lst = ['a', 'b', 'c']
. What's the result oflst.append([1, 2])
andlst.extend([1, 2])
?Try it out! -
Write a function
extend(lst, x)
that does the same thing aslista.extend(x)
does but without using the methodextend
. Assume thatx
is a list.def extend(lst, x): for z in x: lst.append(z)
-
The method
remove(x)
removes the first occurrence of the valuex
at top level. Write a functionremove_all(lst, x)
that removes all occurrences ofx
at the top level in the listlst
.def remove_all(lst, x): while x in lst: lst.remove(x)
Side note: Since the calllst.remove(x)
always starts looking at the beginning of the list this function will be very slow for large lists.
Another way of creating lists
Often we want to build a new list from an old one. In previous examples and exercises we've seen the patternWe used this pattern in the solution of exercise 3 above, where we also noted that the given solution is inefficient for large lists.
There is a much simpler and more efficient way of doing this! To create a new list that contains all the elements except the ones of value x from an old list we can do this:
This is much more efficient since we're only iterating over the list once.
In exercise 3 (remove_all(lst, x)
) above, we don't want new list though,
we want to change the old one.
To do this, we simply assign the variable keeping the old list a new value:
This way to construct lists is called "list comprehension".
More examples:
Expression | Value |
---|---|
a = [x for x in range(7)] | [0, 1, 2, 3, 4, 6] |
[z*z for z in a] | [0, 1, 4, 9, 16, 25, 36] |
[z*z for z in a if z%2==0] | [0, 4, 16, 36] |
[round(z/3,2) for z in a] | [0.0, 0.33, 0.67, 1.0, 1.33, 1.67, 2.0] |
Tuples
Tuples are similar to lists but they are "immutable", that is they can't be changed. Tuples are written with parentheses rather than square brackets. Indexing works the same way as with lists.
Examples:
Code | Value | Comment |
---|---|---|
t = (10, 11, 12, 13) | ||
t[-1] | 13 | Indexing like in lists. |
t[0:3] | (10, 11, 12) | Slicing like in lists. |
t.index(12) | 2 | Index for first occurrence. |
(42) | 42 | Not a tuple! This is a regular int. |
(42,) | (42,) | A tuple with a singular element must be written using a comma. |
() | () | Tuple with zero elements. |
t[-1] = 42 | 13 | Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: 'tuple' object does not support item assignment |
'x', 'a', 'y' | ('x', 'a', 'y') | Can (sometimes) be written without brackets. See the return statement from the exercise with the quadratic equation! |
sorted(t, reverse=True) | 13, 12, 11, 10 | Standard functions are alright. |
Since tuples are unchangeable only a few of the list methods work:
copy
, count
and index
.
So, what's the point of tuples since they don't seem to add any functionality beyond what lists already can handle (everything we do with tuples can be done with lists, but not the other way around). The motivation behind the tuple's existence is that they need fewer resources (memoyy, time) than lists do. We will see more examples of usages in the coming lessons.
Mandatory exercises
-
Write a function
smooth(a, n)
that takes in a list of integers a and a non-negative integer n. The function should create a return a new list r whereri = (ai-n + ai-n-1 + ai-n-2 + ... + ai-1 + ai + ai+1 + ... + ai+n-1 + ai+n) / (2*n + 1)
that is, the mean value of ai and its 2n surrounding numbers.
If n = 1 the result will be as in the exercise (
smooth(a)
) above, at least for the inner elements.For the points close to the corners with very low indexes and very high indexes the interval [i-n:i+n] might be outside the range of the list. In that case, we can choose one of two strategies(You need to do both of them to pass this assignment):
- Same breadth (2n+1) should be used in the smoothing operation, but in the elements where index goes below 0 the first value (that is, the one at index 0) should be used, and for elements where the index is too big the last value should be used. We can look at it as extending the lists in both directions with the corner elements.
- Only elements that exist in the array should be used, so the mean value is created using fewer elements.
Example: The code
x = [1, 2, 6, 4, 5, 0, 1, 2] print('smooth_a(x, 1): ', smooth_a(x, 1)) print('smooth_a(x, 2): ', smooth_a(x, 2)) print('smooth_b(x, 1): ', smooth_b(x, 1)) print('smooth_b(x, 2): ', smooth_b(x, 2))should give the following printoutsmooth_a(x, 1): [1.3333333333333333, 3.0, 4.0, 5.0, 3.0, 2.0, 1.0, 1.6666666666666667] smooth_a(x, 2): [2.2, 2.8, 3.6, 3.4, 3.2, 2.4, 2.0, 1.4] smooth_b(x, 1): [1.5, 3.0, 4.0, 5.0, 3.0, 2.0, 1.0, 1.5] smooth_b(x, 2): [3.0, 3.25, 3.6, 3.4, 3.2, 2.4, 2.0, 1.0]Try to use list operators and list methods as much as possible! No if statements are necessary!
Make sure your functions work for any length of lists, the teacher will test your code with some other lists. Unit tests are available here. If you managed to set up pytest on your computer, please make sure you pass theses tests before asking for evaluation. Instructions to run the tests can be found inside the test file.
-
Write a function
round_list(a_list, ndigits)
that returns a new list of the numbers ina_list
, but with the numbers rounded up tondigits
decimals. Example: The codeprint('smooth_a(x, 1) rounded: ', round_list(smooth_a(x, 1), 2))should, given the same value ofx
give the printoutsmooth_a(x, 1) rounded: [1.33, 3.0, 4.0, 5.0, 3.0, 2.0, 1.0, 1.67]Tip: Use the functionround
and "list comprehension"!
Question
How many hours did you spend on this lesson?
Go to the next lesson or go back.