This page is a follow-on from the introduction to lists.
A list can contain any combination of object types:
# A list with an int, float, string, Boolean and null
ls = [0, 1.2, 'Two', True, None]
So, naturally, it can also contain lists:
# Create a list-of-lists
ls_of_ls = [
['A', 'B', 'C'],
['One', 'Two'],
[100, 200, 300, 400, 500]
]
print(ls_of_ls)
## [['A', 'B', 'C'], ['One', 'Two'], [100, 200, 300, 400, 500]]
A list-of-lists is, of course, a list and so can be indexed like one, using integers or slices:
# Index using an integer
print(ls_of_ls[2])
## [100, 200, 300, 400, 500]
# Index using a slice
print(ls_of_ls[2:3])
## [[100, 200, 300, 400, 500]]
As per normal, indexing with an integer returns the value itself (in this case, a list) whereas indexing with a slice returns a list (in this case a list-of-list).
Each sub-list can be accessed individually by iterating through the macro list via a for loop:
ls_of_ls = [
['A', 'B', 'C'],
['One', 'Two'],
[100, 200, 300, 400, 500]
]
# Iterate through the list
for row in ls_of_ls:
print(row)
## ['A', 'B', 'C']
## ['One', 'Two']
## [100, 200, 300, 400, 500]
As an example, let’s take a list of pairs of x-y coordinates:
coordinates = [
[1, 2],
[7, 4],
[9, 9]
]
If there is a ‘target’ located at one of the coordinates:
target_location = [7, 4]
We can find which of the coordinates have hit the target:
for x_y in coordinates:
if x_y == target_location:
print('Hit')
else:
print('Miss')
## Miss
## Hit
## Miss
This is the start of a Battleships-type game.
To add another list to the list-of-lists, use the .append()
method:
ls_of_ls.append(['Jan', 'Feb', 'Mar'])
print(ls_of_ls)
## [['A', 'B', 'C'], ['One', 'Two'], [100, 200, 300, 400, 500], ['Jan', 'Feb', 'Mar']]
To add extra elements to each of the lists individually, use the .append()
method on each sub-list while iterating though the macro list:
for row in ls_of_ls:
row.append('Extra')
print(ls_of_ls)
## [['A', 'B', 'C', 'Extra'], ['One', 'Two', 'Extra'], [100, 200, 300, 400, 500, 'Extra'], ['Jan', 'Feb', 'Mar', 'Extra']]
This can also be used as a rough way of transposing the list-of-lists:
# Initialise the list-of-lists
ls_of_ls = [
[1, 2, 3],
[4, 5, 6],
[7, 8, 9],
]
# Initialise the output lists
first = []
second = []
third = []
# Iterate over the macro list
for row in ls_of_ls:
first.append(row[0])
second.append(row[1])
third.append(row[2])
print(first)
## [1, 4, 7]
print(second)
## [2, 5, 8]
print(third)
## [3, 6, 9]
However, a better way to do this might be to convert it to an array:
If the list-of-lists contains only numbers it can be converted to an array with the array()
function from the numpy
package:
import numpy as np
ar = np.array(ls_of_ls)
print(ar)
## [[1 2 3]
## [4 5 6]
## [7 8 9]]
It can then be transposed and sliced as per any array:
# Transpose the array
ar_transposed = np.transpose(ar)
print(ar_transposed)
## [[1 4 7]
## [2 5 8]
## [3 6 9]]
# Slice the array - extract all columns and the zeroth row
print(ar[:, 0])
## [1 4 7]
As mentioned in the introductory page on lists, the way to find a value in a list is to use the .index()
method:
ls = ['A', 'B', 'C', 'A', 'B', 'C']
# Find the letter C
print(ls.index('C'))
## 2
This has two drawbacks:
Another option is to use the next()
function, as discussed here, although the limitations are similar: while you can indeed search for multiple values at a time only the first occurrence of any match is returned:
ls = ['A', 'B', 'C', 'A', 'B', 'C']
# Find the letter A or C
print(next(index for index, value in enumerate(ls) if (value == 'A') or (value == 'C')))
## 0
In order to find multiple things you need to construct a list of the elements that match your condition(s). Constructing a list via a conditional search like this is called list comprehension and is performed using a similar syntax to the next()
function, except with square brackets (which indicate that a list is being made). This syntax is relatively close to an English sentence so it can be useful to think of it as such:
For the values in a given list, if a value meets a condition, add it to your list
…and here’s how the above English sentence gets translated into Python:
[value for value in given_list if value == condition]
The above Python code is an example of a list comprehension. List comprehensions are very powerful because they combine for loops and conditionals which gives a wide variety of options when constructing a search. Now, here’s how to use this ‘list comprehension syntax’ to find values in a list:
This example finds all numbers in a list that are larger than a certain value:
ls = [1, 2, 3, 4, 5, 6]
# Find all numbers larger than 2
print([value for value in ls if value > 2])
## [3, 4, 5, 6]
The above code translates into English as “for the values in the given list ‘ls’, if a value is greater than 2, add it to the output list”.
To find indexes you need to use the enumerate()
function. This splits each element in a list into its index and its value, which is different from the default behaviour which just returns the value (as demonstrated in the previous example). Once you’ve split the elements with enumerate()
you then need to specify that it’s each index you want to have returned:
ls = ['A', 'B', 'C', 'A', 'B', 'C']
# Find the letter B or C
print([index for index, value in enumerate(ls) if (value == 'B') or (value == 'C')])
## [1, 2, 4, 5]
The above code translates into English as “for each index and value pair in the list ‘ls’, if the value is ‘B’ or ‘C’ then add the index to the output list”.
Similarly, you can find all values that do (or don’t) meet one or more criteria, and get the indexes and values of the elements that match at the same time:
ls = ['Zeroth', 'First', 'Second', 'Third', 'Fourth']
# Return a tuple for each matching element
print([(i, v) for i, v in enumerate(ls) if v != 'Second'])
## [(0, 'Zeroth'), (1, 'First'), (3, 'Third'), (4, 'Fourth')]
The above code translates into English as “for each index and value pair in the list ‘ls’, if the value is not ‘Second’ then add the index and value as a tuple to the output list”.
As list comprehensions are effectively lists constructed using for loops, they can be used to iterate through a list-of-lists to create subsets:
ls_of_ls = [
[1, 2, 3],
[4, 5, 6],
[7, 8, 9]
]
# Extract the first element of each row
print([row[0] for row in ls_of_ls])
## [1, 4, 7]
You can also generate lists of numbers according to rules:
# Return the even numbers from 0 to 50, doubled
print([2 * i for i in range(51) if i % 2 == 0])
## [0, 4, 8, 12, 16, 20, 24, 28, 32, 36, 40, 44, 48, 52, 56, 60, 64, 68, 72, 76, 80, 84, 88, 92, 96, 100]
As mentioned in the first page on lists, when the “+” operation is performed on two lists it concatenates them:
ls_1 = [1, 2, 3]
ls_2 = [4]
ls = ls_1 + ls_2
print(ls)
## [1, 2, 3, 4]
If, instead, you want to mathematically add the element(s) of the second list to those of the first you can do so with a list comprehension:
ls_1 = [1, 2, 3]
ls_2 = [4]
ls = [x + y for x in ls_1 for y in ls_2]
print(ls)
## [5, 6, 7]
This translates into English as “for each value ‘x’ in list 1, and for each value ‘y’ in list 2, add x and y”.
To remove a single value from a list you can use the .remove()
method, but to remove multiple values at a time (ie remove a list from a list) you can use list comprehensions:
ls = ['One', 'Two', 'Three', 'Three']
to_remove = ['Three', 'Four']
ls = [x for x in list(ls) if x not in to_remove]
print(ls)
## ['One', 'Two']
Notice that this has preserved the order of the items in the list, which would not neccessarily have been the case if we had used set subtraction to do this.