Lists

In our first lab, we encountered several distinct data types: int (integers), float (decimal point numbers), complex (complex numbers), str (strings) and bool (booleans). All of these types represent various categories of individual data: a single number, a single string of letters etc. Often, we are interested in collections of data, for example a set of temperatures at which a given experiment has been performed. Python accommodates this requirement by providing us with various data structures that allow us to collate related data into a single object. Lists are one of these data structures.

List basics

A list is an ordered collection of values. The values that comprise a list are usually referred to as the elements or items in that list. To create a list, we enclose the elements (separated by commas) we wish to include in a set of square brackets:

[1, 2, 3, 4, 5]
[1, 2, 3, 4, 5]

Just like the other data types we have encountered so far, we can store lists in variables with the assignment operator =

some_numbers = [1, 2, 3, 4, 5]
print(some_numbers)
print(type(some_numbers))
[1, 2, 3, 4, 5]
list

where see that there is a list type.

This is an example of a list of int, but lists can contain any type of data

some_strings = ['Welcome', 'to', 'CH12002']
print(some_strings)
['Welcome', 'to', 'CH12002']

We can mix different types together

a_variety = [5, 'five', 5.0, 5.0 + 0j]
print(a_variety)
[5, 'five', 5.0, (5+0j)]

and we can even make a list of lists!

some_numbers = [1, 2, 3, 4, 5]
some_strings = ['Welcome', 'to', 'CH12002']

list_of_lists = [some_numbers, some_strings]
print(list_of_lists)
[[1, 2, 3, 4, 5], ['Welcome', 'to', 'CH12002']]

We refer to a list of lists as a nested list.

List indexing

How can we access a single entry in a list? Consider the following example which lists the names of some first-row transition metals

transition_metals = ['vanadium', 'chromium', 'iron']

If we want the first entry in the list we can obtain it using list indexing as follows

transition_metals[0]
'vanadium'

To index a list we use square brackets [] immediately after the name of the list - no spaces are allowed! Inside the square brackets we put an integer corresponding to the index of the element we want. In programming, we refer to the “entries” of a data structure as its elements.

In Python, we start counting the elements of a data structure from the number zero. The first element of a data structure has index 0, the second 1, and so on. This might be a bit confusing, but you’ll get used to it.

NoteSyntax

The general syntax for list indexing is

name_of_list_variable[index]

where index is an int \(\ge 0\).

What happens if we try and get the 11th index of transition_metals? There are only three elements, so we get an error!

transition_metals[10]
---------------------------------------------------------------------------
IndexError                                Traceback (most recent call last)
Cell In[11], line 1
----> 1 transition_metals[10]

IndexError: list index out of range
TipError

Here we have another new type of error: an IndexError. This one is relatively straightforward, the index we have given is “out of range” and so refers to an element that does not exist. In this case, the index 10 refers to the eleventh element of the list, but transition_metals only has 3 items, so our code doesn’t make any sense!

We can get the length of a list using the len function

len(transition_metals)

3

To finish off this discussion of list indexing, lets see what happens if we use a negative index.

transition_metals[-1]
'iron'

In real life, the “minus first” element doesnt exist, but in Python this takes on a new meaning - the negative sign tells the computer to start counting backwards from the end of the list. So an index of -1 refers to the last element, -2 refers to the second-to-last, and so on.

To index a list of lists we use a new set of brackets [] for each list. Look at the following list

opposites = [['proton', 'electron'], ['positive', 'negative'], ['oxidise', 'reduce']]

this contains three lists, each with two elements. Say I want to get the first element of the second list - this would be

opposites[1][0]

which tells Python to take the second list (with index 1) and get its first element (index 0).

Modifying lists

Replacing elements

List indexing can be used to modify the elements of a list. This is possible because list objects are what we call mutable - they can change or mutate. Let’s replace the third element of transition_metals with manganese

transition_metals = ['vanadium', 'chromium', 'iron']
print(transition_metals)
transition_metals[2] = 'manganese'
print(transition_metals)
['vanadium', 'chromium', 'iron']
['vanadium', 'chromium', 'manganese']
Tip

As we’ve seen, you could use an index of -1 instead.

Adding new elements

We can also make lists longer by appending values. To do this, we use the append method of the list. A method is a function which is attached to a particular object. They’re used somewhat similarly, but the syntax differs mostly in the order in which each command is written. Let’s compare len with append to see the difference

transition_metals = [
    'vanadium', 'chromium', 'managanese'
]
len(transition_metals)
transition_metals.append('iron')
print(transition_metals)
['vanadium', 'chromium', 'managanese', 'iron']

We use the method append by writing .append immediately after the name of the list, and we then feed in our inputs to append - here we just append the string 'iron'.

NoteSyntax

The general syntax for a method is

name_of_object.name_of_method(input_to_method)

As well as appending elements to the end of a list, we can also insert elements at any given index in the list using the insert method

transition_metals.insert(0, 'titanium')
print(transition_metals)
['titanium', 'vanadium', 'chromium', 'managanese', 'iron']

notice that insert has two arguments - the first is the index where the new element will be located, and the second is the value to use for that new element.

Both insert and append allow us to add single elements, whereas the extend method allows us to add mutiple elements to the end of a list

more_transition_metals = ['cobalt', 'nickel']
transition_metals.extend(more_transition_metals)

print(transition_metals)
['titanium', 'vanadium', 'chromium', 'managanese', 'iron', 'cobalt', 'nickel']

where we have providedn a new list to stick onto the end of our current list.

Finally, now that you know about append, insert, and extend, its important to know that you can create an empty list

empty_list = []
print(empty_list)
[]

and can append values to it

empty_list.append('absolutely_nothing!')
print(empty_list)
['absolutely_nothing!']

Exercise

That’s a lot about lists, but there’s more to come!

As a “break”, play around with the following operations, just like you did for strings.

Create a list containing the letters a, b, and c and carry out the following operations

  1. Multiply your list by the integer 3
  2. Multiply your list by the float 3.5
  3. Add the integer 5 to your list using the + operator
  4. Add the list ['d', 'e'] to your list using the + operator
  5. Subtract the integer 10 from your list using the - operator

Some of the above will give an error, but some will give results that look similar to what we’ve just seen for append and extend.

  1. Multiplying a list by an integer N makes a new list with N copies of the element - in this case ['a', 'b', 'c', 'a', 'b', 'c', 'a', 'b', 'c'].

  2. Multiplying a list by an float gives an error - this operation is not allowed.

  3. Adding an integer to a list gives an error - this operation is not allowed.

  4. Adding a list to a list using + gives the same result as extend.

  5. Subtracting anything from a list gives an error - this operation is not allowed.

Removing elements

We now have a range of tools at our disposal for adding elements to a list, but how can we remove them instead? Well, for a start, there’s the remove method

transition_metals.remove('cobalt')
print(transition_metals)
['titanium', 'vanadium', 'chromium', 'managanese', 'iron',  'nickel']

Thiss removes the first element which matches the input argument from the list.

Alternatively, there’s the pop method, which allows you to instead specify the index of the element you want to remove.

pop_example = transition_metals.pop(5)
print(pop_example)
print(transition_metals)
'cobalt'
['titanium', 'vanadium', 'chromium', 'managanese', 'iron',  'nickel']

Notice that the pop method also returns the value of the removed element. In the example above, we have assigned the output of the pop method to a variable pop_example, which when printed, shows us the removed element.

List slicing

We are now familiar with accessing individual elements of a list, but what if we want to obtain multiple elements at once? Python facilitates this task with a process called list slicing.

transition_metals = ['titanium', 'vanadium', 'chromium', 'managanese', 'iron', 'cobalt', 'nickel']
shorter_list = transition_metals[1:5]
print(shorter_list)
['vanadium', 'chromium', 'manganese', 'iron']

We’ve specified two indices 1 and 5 separated by a colon :, and Python has returned a slice containing four elements from our the list - the 2nd, 3rd, 4th, and 5th elements. Interestingly, specifying 5 as the second index doesnt result in the 6th element being included in the slice - to explain why take a look at the box below.

NoteSyntax

The general syntax for slicing is

name_of_list[start_index : end_index]

where the element at end_index is not included in the slice that is returned. Think of this as saying “give me all the elements starting at start_index up to and not including end_index”.

In actual fact, we don’t have to include both the start and end indices - have a go and see what happens using the following example.

transition_metals = ['titanium', 'vanadium', 'chromium', 'managanese', 'iron', 'cobalt', 'nickel']
print(transition_metals[3:])
print(transition_metals[:6])

In the first case, we specify only a start index 3 - this tells python to give us every element between index 3 to the end of the list.

In the second case, we specify only an end index 6 - this tells python to give us every element from the start of the list up to, but not including, index 6.

Finally, we can also slice a list in steps by providing a third index which tells python the step size.

transition_metals = ['titanium', 'vanadium', 'chromium', 'managanese', 'iron', 'cobalt', 'nickel']
transition_metals[1:5:2]
['vanadium', 'managanese']

which gives us every second element from the 2nd up to but not including the 6th - i.e. our slice will contain the 2nd and 4th elements.

Use list indexing with steps to return the every fourth element of the following list.

d_block = ['Scandium', 'Titanium', 'Vanadium', 'Chromium', 'Manganese', 'Iron', 'Cobalt', 'Nickel', 'Copper', 'Zinc', 'Yttrium', 'Zirconium', 'Niobium', 'Molybdenum', 'Technetium', 'Ruthenium', 'Rhodium', 'Palladium', 'Silver', 'Cadmium']
print(d_block[::4])

Exercise

Use your knowledge of lists to solve the following problem

Copy the following block of code into a Jupyter notebook:

colours = [['Red', 'Magenta'], 
           ['Cyan', 'Green'], 
           [['Yellow', 'Blue'], 'White']]
  1. Use list indexing to print Magenta from the colours list.

  2. Again, using list indexing, assign the element Red to a variable called colour_1, the element Green to a variable called colour_2, and the element Yellow to a variable called colour_3, so that the following line of code runs without error and displays a true statement:

print(f'{colour_1} + {colour_2} -> {colour_3}')

The solutions are not to just type out

print('Magenta')
print(f'Red + Green -> Yellow')

you need to use list indexing!