3.4.5. Sets¶

In this section we introduce the Python type `set`. As the name suggests, Python sets are intended to model the standrad mathematical notion of a set. Like lists, sets are containers that can contain any kind of object. Like lists, they are mutable (that is, you can add things to and remove things from a set); Unlike lists, sets are unordered, so you don’t add or remove things by referencing a particular index; you use the `add` or `remove` method, as shown below.

Just as with other containers, the `in` operator is a valid test for containment:

```>>> X = set([1,2,3])
>>> 2 in X
True
>>> 4 in X
False
>>> X[0]
Traceback (most recent call last):
...
TypeError: 'set' object does not support indexing
```

Sets are used when we are trying to keep track of a collection of things, don’t care about order, and especially when we don’t want duplicates. Like mathematical sets, Python sets can be unioned and intersected with other sets:

```>>> S = set([1,2,3])
>>> T = set([1,2,5])
>>> S.union(T)
set([1, 2, 3, 5])
>>> S.intersection(T)
set([1, 2])
```

These operations build new sets, and do not change `S` and `T`:

```>>> R = S.union(T)
>>> R
set([1, 2, 3, 5])
>>> S
set([1, 2, 3])
>>> T
set([1, 2, 5])
```

There are versions of union and intersection that do change `S` and `T`:

```>>> S.union_update(T)
>>> S
set([1, 2, 3, 5])
>>> S.intersection_update(T)
>>> S
set([1, 2])
```

Like other Python types, the name of the type can be used as a function to create instances of the type:

```>>> L = [1,2,3]
>>> S = set(L)
```

Here the `set` function converts a list into a set. As with other Python containers, calling the function with no arguments produces an empty container, in this, the empty set:

```>>> S = set()
>>> S
set([])
```

Python also provides more compact, and perhaps more familiar notation for sets as well. So in the following code snippet, the first two lines create sets and the next two are equivalent to the `union` and `intersection` methods introduced above:

```>>> S = {1,2,3}
>>> T = {1,2,5}
>>> S | T
set([1, 2, 3, 5])
>>> S & T
set([1, 2])
```

Note that whether curly braces produce a dictionary or a set depends on what’s inside the curly braces:

```>>> {('a',0),('b',1)}
set([('b', 1), ('a', 0)])
```

Here we have a set of tuples. On the other hand:

```>>> X = {'a':0 ,'b':1}
>>> type(X)
<type 'dict'>
```

3.4.5.1. Immutable sets¶

The immutable version of a list is a tuple. Mutability is the only reason we have a distinction between lists and tuples. But sets are lists with the order taken away. Is there a motivation for a comparable distinction between mutable and immutable sets?

There is, and Python has implemented it. Immutable sets are called frozensets. Frozensets have all the same operations as sets, except for the ones that change the sets:

```>>> S = frozenset([1,2,3])
>>> Ss = set([1,2,3])
>>> T = frozenset([4,2,5])
>>> S.union(T)
frozenset([1, 2, 3, 4, 5])
>>> S.update(T)
...
AttributeError: 'frozenset' object has no attribute 'update'
>>> Ss.update(T)
>>> Ss
set([1, 2, 3, 4, 5])
>>> S.issuperset(T)
False
>>> S.pop()
....
AttributeError: 'frozenset' object has no attribute 'pop'
>>> Ss.pop()
1
```

All the set methods with ‘update’ in the name raise errors for frozensets (`difference_update`, `union_update`, `intersection_update`, `symmetric_difference_update`, and `update`), and ditto for `pop` and `remove`.