Back to Contents

7. Arranging Things

We have already seen how to combine values into a list. Lists are ordered, and mutable (we may alter elements, or insert or delete them). Sometimes we would like compound values with different properties. In this chapter, we look at three such structures: tuples, dictionaries, and sets. We will see how to choose the appropriate structure for the appropriate task: a program and its data structures are intimately linked.

Tuples

A tuple is a fixed-length collection of values, allowing the whole structure to be given a name and to be passed around just like we pass around any other value. There are two differences with lists: tuples are of fixed length, and their elements may not be altered. Here are some tuples:

Python
>>> t = (1, 'one')
>>> t2 = (1, (1, 2), (1, 2, 3))

We now have two tuples: the first, t, of length 2, and the second, t2, of length 3, containing within it other tuples. We can take the tuples apart by assigning names. This is called unpacking:

Python
>>> a, b = t
>>> a
1
>>> b
'one'
>>> c, d, e = t2
>>> c
1
>>> d
(1, 2)
>>> e
(1, 2, 3)

We can pass a tuple to a function as usual, then unpack the values. For example, here is function to add two numbers passed to it as a single tuple of length 2:

Python
>>> def f(x):
...     a, b = x
...     return a + b
... 
>>> pair = (1, 2)
>>> f(pair)
3
>>> f((1, 2))
3
>>> f(1, 2)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: f() takes 1 positional argument but 2 were given

We can also select items from a tuple using indexing and slicing:

Python
>>> t2 = (1, (1, 2), (1, 2, 3))
>>> t2[0]
1
>>> t2[::-1]
((1, 2, 3), (1, 2), 1)

Of course, to do this without knowing the length of the tuple might sometimes be difficult. We can use the usual len function:

Python
>>> t2 = (1, (1, 2), (1, 2, 3))
>>> len(t2)
3
>>> len(t2[1])
2

Tuples are immutable – unlike with lists, we cannot change their elements.

Python
>>> x = (1, 2)
>>> x[0] = 3
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'tuple' object does not support item assignment

Of course, if a tuple contains an mutable value such as a list, we can change parts of that inside value:

Python
>>> l = [1, 2, 3]
>>> t = (l, l)
>>> l[0] = 4
>>> t
([4, 2, 3], [4, 2, 3])

Dictionaries

Many programs make use of a structure known as a dictionary. A real dictionary is used for associating definitions with words; we use “dictionary” more generally to mean associating some unique keys (like words) with values (like definitions). For example, we might like to store the following information about the number of people living in each house in a road:

image

We could represent this using a list of pairs represented as tuples. But then we would have to write various functions for looking up or replacing entries ourselves. Python provides a special type for dictionaries, which preserves automatically the property that every key has only one value associated with it. Let us start with an empty dictionary, which is written {}, and add and update some entries:

Python
>>> d = {}
>>> d[1] = 4
>>> d
{1: 4}
>>> d[2] = 2
>>> d[3] = 2
>>> d[4] = 3
>>> d[5] = 1
>>> d[6] = 2
>>> d
{1: 4, 2: 2, 3: 2, 4: 3, 5: 1, 6: 2}
>>> d[6] = 8
>>> d
{1: 4, 2: 2, 3: 2, 4: 3, 5: 1, 6: 8}

We could, of course, write the whole thing in one go:

Python
>>> d = {1: 4, 2: 2, 3: 2, 4: 3, 5: 1, 6: 2}

We can see that dictionaries are unordered:

Python
>>> {1: 4, 2: 2} == {2: 2, 1: 4}
True

Keys in a dictionary must be immutable. For example, we cannot use a list as a key. We can use the usual tests in and not in to check if a dictionary has a value for a given key:

Python
>>> d = {1: 4, 2: 2, 3: 2, 4: 3, 5: 1, 6: 2}
>>> 1 in d
True
>>> 10 in d
False
>>> 10 not in d
True

Finally, deletion is performed with the usual del statement, providing the key only:

Python
>>> d = {1: 4, 2: 2, 3: 2, 4: 3, 5: 1, 6: 2}
>>> del d[2]
>>> d
{1: 4, 3: 2, 4: 3, 5: 1, 6: 2}

There is an error if the key is not in the dictionary:

>>> del d[7]
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
KeyError: 7

Iterating over dictionaries

How might we iterate over the dictionary entries? We use a for loop, but we specify two names: one for the key, and one for the value, and use the items method:

Python
>>> for k, v in d.items():
...     print(f'{k} is mapped to {v}')
... 
1 is mapped to 4
2 is mapped to 2
3 is mapped to 2
4 is mapped to 3
5 is mapped to 1
6 is mapped to 2

Alternatively, we can use an ordinary for loop, and simply look the value up:

Python
>>> for k in d:
...     print(f'{k} is mapped to {d[k]}')
... 
1 is mapped to 4
2 is mapped to 2
3 is mapped to 2
4 is mapped to 3
5 is mapped to 1
6 is mapped to 2

Should we have key-value pairs already, we can turn a list of them into a dictionary using the dict function:

Python
>>> dict([(1, 'one'), (2, 'two'), (3, 'three')])
{1: 'one', 2: 'two', 3: 'three'}
>>> dict([(1, 'ONE'), (1, 'one'), (2, 'two'), (3, 'three')])
{1: 'one', 2: 'two', 3: 'three'}

Notice that the entry (1, ’ONE’) is overwritten, since the entries are added in order.

Sets

In the questions to chapter 4 we wrote a function setify to remove duplicate items from a list. Python has a built-in type for sets: they are just like dictionaries, but with no values.

Python
>>> s = {1, 2, 3}
>>> s2 = set([1, 2, 3, 2, 1])
>>> s2
{1, 2, 3}
>>> s3 = set('qwertyuiop')
>>> s3
{'w', 'p', 'r', 'e', 'i', 'q', 'o', 'u', 'y', 't'}
>>> empty_set = set()
>>> empty_set
set()

Note that the empty set is built by, and printed as set(). This is to distinguish it from the empty dictionary {}. We can use the usual in and not in tests:

Python
>>> s = set('qwertyuiop')
>>> 'e' in s
True
>>> 'z' not in s
True

To add an item to a set, we use the add method:

Python
>>> s = set([1, 2, 3, 4, 4, 5])
>>> s
{1, 2, 3, 4, 5}
>>> s.add(7)
>>> s
{1, 2, 3, 4, 5, 7}

To remove an item from a set, we use the remove method:

Python
>>> s = set([1, 2, 3, 4, 4, 5])
>>> s
{1, 2, 3, 4, 5}
>>> s.remove(4)
>>> s
{1, 2, 3, 5}

There is an error if the item to remove is not in the set. Finally, there are four operations for manipulating pairs of sets:

image

For example:

Python
>>> a = {1, 2, 3, 4}
>>> b = {1, 2, 5, 6}
>>> a | b
{1, 2, 3, 4, 5, 6}
>>> a & b
{1, 2}
>>> a ^ b
{3, 4, 5, 6}
>>> a - b
{3, 4}

Set operations are useful when we need information from two sources to select what to do next, or which data to operate on next.

Common problems

One must take care to distinguish between parentheses used for multiple arguments to a function, and parentheses used for building a tuple:

Python
>>> def f(a, b): return a + b
... 
>>> x = (1, 2)
>>> f(x)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: f() missing 1 required positional argument: 'b'

Tuple unpacking must be explicit. For example, imagine a function we wished to use by passing two arguments, a number and a pair of numbers:

Python
>>> f(1, (2, 3))
7

We might like to write this, but Python will not let us:

Python
>>> def f(a, (b, c)): return a + b * c
  File "<stdin>", line 1
    def f(a, (b, c)): return a + b * c
             ^
SyntaxError: invalid syntax

Instead, we must explicitly unpack the tuple:

Python
>>> def f(a, pair):
        b, c = pair
        return a + b * c

Dictionaries exhibit, of course, the usual lookup errors when a key is not found:

Python
>>> d = {1 : 2, 2 : 3}
>>> d[3]
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
KeyError: 3

More subtly, it is important to remember that in and not in refer to the keys present in a dictionary, not the values:

Python
>>> d = {1 : 2, 2 : 3}
>>> 3 in d
False

Summary

We have looked at some new data structures: tuples for holding two or more items; and dictionaries which assign keys to values. We concluded with sets, which can be used to store information without duplicates, and quickly to test for membership. We have seen how to build sets from strings.

For a long time now, we have been saying that we would address detection and recovery from errors. In the next chapter, we do just that.

Questions

  1. We can swap the values of variables a and b by using a temporary variable t:

    Python
    >>> a = 1
    >>> b = 2
    >>> t = a
    >>> a = b
    >>> b = t
    >>> a
    2
    >>> b
    1

    Show how to use a tuple to achieve the same result.

  2. Write a function unzip which, given a dictionary, returns a pair of lists, the first containing the keys and the second the corresponding values.

  3. The opposite function zip, combined with the dict function we have already described, can be used to build a dictionary from two lists: one of all the keys, and one of all the values.

    Python
    >>> dict(zip([1, 2], ['one', 'two']))
    {1: 'one', 2: 'two'}

    Write a function to replace both zip and dict in this circumstance.

  4. Write the function union(a, b) which forms the union of two dictionaries. The union of two dictionaries is the dictionary containing all the entries in one or other or both. In the case that a key is contained in both dictionaries, the value in the first should be preferred.

  5. The following, flawed function is intended to remove all items equal to zero from a list:

    Python
    >>> def remove_zeroes(l):
    ...     for x in range(0, len(l)):
    ...         if l[x] == 0: del l[x]
    ... 
    >>> 
    >>> l = [1, 0, 0, 0, 1]
    >>> remove_zeroes(l)
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
      File "<stdin>", line 3, in remove_zeroes
    IndexError: list index out of range
    >>> l
    [1, 0, 1]

    Why does it fail? Write a correct version.

  6. We can write dictionary comprehensions, much like list comprehensions. For example:

    Python
    >>> {n: n ** 2 for n in range(10)}
    {0: 0, 1: 1, 2: 4, 3: 9, 4: 16, 5: 25, 6: 36, 7: 49, 8: 64, 9: 81}
    >>> {n: n ** 2 for n in range(10) if n ** 2 % 2 == 0}
    {0: 0, 2: 4, 4: 16, 6: 36, 8: 64}

    Write a dictionary comprehension to ‘reverse’ a dictionary, that is to make the keys in the original values and the values in the original keys. Why might the new dictionary have a different size from the original?

  7. Use sets to write a function which returns the ‘letter set’ of a list of words. That is to say, the list of all letters used in those words. Now write a function to return a set of all letters not used by them.

  8. Imagine Python did not have built-in support for sets. Show how we could use dictionaries to represent sets. Write the four set operations | - ^ & for this new representation of sets.

  9. Write the set operation & using set comprehensions. Set comprehensions look a little like the dictionary comprehensions of question 6. We can use two for sections to cycle over all pairs of set members i.e. for x in a for y in b

  10. Write a function to add the numbers in a tuple. For example, sum_all(1, (1, 2), 3) should yield 7. You will need to distinguish between integers and tuples by using the test type(x) == int, which is True if the type of x is int.