Книга: Practical Programming, Fourth Edition
Назад: Chapter 11: Storing Data Using Other Collection Types
Дальше: Storing Data Using Tuples

Storing Data Using Sets

Sets are used to store unordered collections
of unique values

A set is an unordered collection of distinct items. Unordered means that items aren’t stored in any particular order. Something is either in the set or it’s not, but there’s no notion of it being the first, second, or last item. Distinct means that any item appears in a set at most once; there are no duplicates.

Python has a type called set that allows you to store mutable collections of unordered, distinct items. (Remember that a mutable object is one that you can modify.) Let’s create a set containing the vowels:

 >>>​​ ​​vowels​​ ​​=​​ ​​{​​'a'​​,​​ ​​'e'​​,​​ ​​'i'​​,​​ ​​'o'​​,​​ ​​'u'​​}
 >>>​​ ​​vowels
 {'a', 'u', 'o', 'i', 'e'}

Searching through sets and dictionaries is very fast

It resembles a list, except that sets use braces ({}) instead of brackets ([]). Notice that, when displayed in the shell, the set is shown in an unordered manner. Python does some mathematical tricks behind the scenes to make accessing the items very fast, and one of the side effects of this is that the items aren’t in any particular order.

Let’s show that each item is distinct; duplicates are ignored:

 >>>​​ ​​vowels​​ ​​=​​ ​​{​​'a'​​,​​ ​​'e'​​,​​ ​​'a'​​,​​ ​​'a'​​,​​ ​​'i'​​,​​ ​​'o'​​,​​ ​​'u'​​,​​ ​​'u'​​}
 >>>​​ ​​vowels
 {'u', 'o', 'i', 'e', 'a'}

Even though there were three ’a’s and two ’u’s when you created the set, only one of each was kept. Python considers the two sets to be equal:

 >>>​​ ​​{​​'a'​​,​​ ​​'e'​​,​​ ​​'i'​​,​​ ​​'o'​​,​​ ​​'u'​​}​​ ​​==​​ ​​{​​'a'​​,​​ ​​'e'​​,​​ ​​'a'​​,​​ ​​'a'​​,​​ ​​'i'​​,​​ ​​'o'​​,​​ ​​'u'​​,​​ ​​'u'​​}
 True

The reason they are equal is that they contain the same items. Again, order doesn’t matter, and only one of each element is kept.

Variable vowels refers to an object of type set:

 >>>​​ ​​type(vowels)
 <class 'set'>
 >>>​​ ​​type({1,​​ ​​2,​​ ​​3})
 <class 'set'>

In , you’ll learn about a type that also uses the notation {}, which prevents you from using that notation to represent an empty set. Instead, to create an empty set, you need to call the function set with no arguments:

 >>>​​ ​​set()
 set()
 >>>​​ ​​type(set())
 <class 'set'>

The function set is a constructor. It expects either no arguments (to create an empty set) or a single argument that is a collection of values. For example, let’s create a set from a list:

 >>>​​ ​​set([2,​​ ​​3,​​ ​​2,​​ ​​5])
 {2, 3, 5}

Because duplicates aren’t allowed, only one of the 2s appears in the set:

Set

The function set expects at most one argument. You can’t pass several values as separate arguments:

 >>>​​ ​​set(2,​​ ​​3,​​ ​​5)
 Traceback (most recent call last):
  File "<python-input-0>", line 1, in <module>
  set(2, 3, 5)
  ~~~^^^^^^^^^
 TypeError: set expected at most 1 argument, got 3

In addition to lists, there are a couple of other types that can be used as arguments to the function set. One is a set:

 >>>​​ ​​vowels​​ ​​=​​ ​​{​​'a'​​,​​ ​​'e'​​,​​ ​​'a'​​,​​ ​​'a'​​,​​ ​​'i'​​,​​ ​​'o'​​,​​ ​​'u'​​,​​ ​​'u'​​}
 >>>​​ ​​vowels
 {'i', 'a', 'u', 'e', 'o'}
 >>>​​ ​​set(vowels)
 {'i', 'a', 'u', 'e', 'o'}
 >>>​​ ​​set({5,​​ ​​3,​​ ​​1})
 {1, 3, 5}

Another such type is range from . In the following code, a set is created with the values 0 to 4 inclusive:

 >>>​​ ​​set(range(5))
 {0, 1, 2, 3, 4}

In , you will learn about the tuple type, another type of sequence, that can also be used as an argument to the function set.

Set Operations

In mathematics, set operations include union, intersection, add, and remove. In Python, these are implemented as methods:

Method

Description

S.add

Adds item v to a set S—this has no effect if v is already in S

S.clear

Removes all items from set S

S.difference

Returns a set with items that occur in set S but not in set other

S.intersection

Returns a set with items that occur both in sets S and other

S.issubset

Returns True if and only if all of set S’s items are also in set other

S.issuperset

Returns True if and only if set S contains all of set other’s items

S.remove

Removes item v from set S

S.symmetric_difference

Returns a set with items that are in exactly one of sets S and other—any items that are in both sets are not included in the result

S.union

Returns a set with items that are either in set S or other (or in both)

Sets are mutable. The methods add, remove, and clear all modify a set. The letter y is sometimes considered to be a vowel; let’s add it to the set of vowels:

 >>>​​ ​​vowels​​ ​​=​​ ​​{​​'a'​​,​​ ​​'e'​​,​​ ​​'i'​​,​​ ​​'o'​​,​​ ​​'u'​​}
 >>>​​ ​​vowels
 {'o', 'u', 'a', 'e', 'i'}
 >>>​​ ​​vowels.add(​​'y'​​)
 >>>​​ ​​vowels
 {'u', 'y', 'e', 'a', 'o', 'i'}

Other methods, such as intersection and union, return new sets based on their arguments.

In the following code, you’ll see all of these methods in action:

 >>>​​ ​​ten​​ ​​=​​ ​​set(range(10))
 >>>​​ ​​lows​​ ​​=​​ ​​{0,​​ ​​1,​​ ​​2,​​ ​​3,​​ ​​4}
 >>>​​ ​​odds​​ ​​=​​ ​​{1,​​ ​​3,​​ ​​5,​​ ​​7,​​ ​​9}
 >>>​​ ​​lows.add(9)
 >>>​​ ​​lows
 {0, 1, 2, 3, 4, 9}
 >>>​​ ​​lows.difference(odds)
 {0, 2, 4}
 >>>​​ ​​lows.intersection(odds)
 {1, 3, 9}
 >>>​​ ​​lows.issubset(ten)
 True
 >>>​​ ​​lows.issuperset(odds)
 False
 >>>​​ ​​lows.remove(0)
 >>>​​ ​​lows
 {1, 2, 3, 4, 9}
 >>>​​ ​​lows.symmetric_difference(odds)
 {2, 4, 5, 7}
 >>>​​ ​​lows.union(odds)
 {1, 2, 3, 4, 5, 7, 9}
 >>>​​ ​​lows.clear()
 >>>​​ ​​lows
 set()

Many of the tasks performed by methods can also be accomplished using operators. If acids and bases are two sets, for example, then acids | bases creates a new set containing their union (that is, all the elements from both acids and bases). In contrast, acids <= bases tests whether acids is a subset of bases—that is, that all the values in acids are also in bases. Some of the operators that sets support are listed in the following table.

Method Call

Operator

some_set.difference

some_set - other_set

some_set.intersection

some_set & other_set

some_set.issubset

some_set <= other_set

some_set.issuperset

some_set >= other_set

some_set.union

some_set | other_set

some_set.symmetric_difference

some_set ^ other_set

The following code shows the set operations in action.

 >>>​​ ​​lows​​ ​​=​​ ​​set([0,​​ ​​1,​​ ​​2,​​ ​​3,​​ ​​4])
 >>>​​ ​​odds​​ ​​=​​ ​​set([1,​​ ​​3,​​ ​​5,​​ ​​7,​​ ​​9])
 >>>​​ ​​lows​​ ​​-​​ ​​odds​​ ​​# Equivalent to lows.difference(odds)
 {0, 2, 4}
 >>>​​ ​​lows​​ ​​&​​ ​​odds​​ ​​# Equivalent to lows.intersection(odds)
 {1, 3}
 >>>​​ ​​lows​​ ​​<=​​ ​​odds​​ ​​# Equivalent to lows.issubset(odds)
 False
 >>>​​ ​​lows​​ ​​>=​​ ​​odds​​ ​​# Equivalent to lows.issuperset(odds)
 False
 >>>​​ ​​lows​​ ​​|​​ ​​odds​​ ​​# Equivalent to lows.union(odds)
 {0, 1, 2, 3, 4, 5, 7, 9}
 >>>​​ ​​lows​​ ​​^​​ ​​odds​​ ​​# Equivalent to lows.symmetric_difference(odds)
 {0, 2, 4, 5, 7, 9}

Set Example: Arctic Birds

Suppose you have a file used to record observations of birds in the Canadian Arctic, and you want to know which species have been observed. The observations file, observations.txt, has one species per line:

 canada goose
 canada goose
 long-tailed jaeger
 canada goose
 snow goose
 canada goose
 long-tailed jaeger
 canada goose
 northern fulmar

The following program reads each line of the file, strips off the leading and trailing whitespace, and adds the species on that line to the set. Notice the type annotation specifying that the function returns a set of strings.

 from​ ​typing​ ​import​ Set, TextIO
 from​ ​io​ ​import​ StringIO
 
 def​ ​observe_birds​(observations_file: TextIO) -> Set[str]:
 """Return a set of the bird species listed in observations_file, which has
  one bird species per line.
 
  >>> infile = StringIO('bird 1​​\\​​nbird 2​​\\​​nbird 1​​\\​​n')
  >>> birds = observe_birds(infile)
  >>> birds == {'bird 1', 'bird 2'}
  True
  """
  birds_observed = set()
 for​ line ​in​ observations_file:
  bird = line.strip()
  birds_observed.add(bird)
 
 return​ birds_observed
 
 if​ __name__ == ​'__main__'​:
 with​ open(​'observations.txt'​) ​as​ observations_file:
 print​(observe_birds(observations_file))

The resulting set contains four species. Since sets don’t include duplicates, calling the method add with a species already in the set had no effect.

You can loop over the values in a set. In the following code, a for loop is used to print each species:

 >>>​​ ​​for​​ ​​species​​ ​​in​​ ​​birds_observed:
 ...​​ ​​print(species)
 ...
 long-tailed jaeger
 canada goose
 northern fulmar
 snow goose

Looping over a set works precisely like a loop over a list, except that the order in which items are encountered is arbitrary; there is no guarantee that they will come out in the order in which they were added, in alphabetical order, in order by length, or any other order.

Set Contents Must Be Immutable

Sets employ a mathematical technique called hashing, which relies on immutable set values. Mutable values, such as lists, cannot be added to sets because mutable values in general are unhashable.

 >>>​​ ​​S​​ ​​=​​ ​​set()
 >>>​​ ​​L​​ ​​=​​ ​​[1,​​ ​​2,​​ ​​3]
 >>>​​ ​​S.add(L)
 Traceback (most recent call last):
  File "<python-input-2>", line 1, in <module>
  S.add(L)
  ~~~~~^^^
 TypeError: cannot use 'list' as a set element (unhashable type: 'list')

This restriction means that you can’t store a set of sets. Sets themselves can’t be immutable, since you need to add and remove values, so a set can’t contain another one. To solve this problem, Python has another data type called a frozen set. As the name implies, frozen sets are sets that cannot be mutated. An empty frozen set is created using the frozenset constructor; to create a frozen set that contains some values, use frozenset(values), where values is a list, tuple, set, or other collection.

In the next section, you will learn about tuples, which can also be used as items in sets.

Назад: Chapter 11: Storing Data Using Other Collection Types
Дальше: Storing Data Using Tuples