Beginner

What are sets and why use them.


In more technical termes, sets are defined as :

  • Mutable.
  • Unordered.
  • Not subscriptable / Not slicable.
  • Does not accepts duplicate elements.

Sets are a bit harder to work with because they don't allow duplicate elements and being unordered prevent you to use the really common collection[0] to select element into it.

The real power with sets is in all the operations that can be done with sets like union, intersection and so on. It can be extremely useful in cases where you have to get every elements that are in a collection but not in another.

ToC

Beginner

Declaring a set.


There are two ways to declare a set, they are equivalent.

ToC

In [1]:
s = {}
s = set()

They can be initialized directly with values.

In [2]:
s = {1, 2, 3, 4}
s = set([1, 2, 3, 4])

Beginner

Accessing elements in a set.


Sets are not ordered. Hence you cannot acces values by index or slice a set, since these notions do not exist regarding sets. They are mainly handled with operations on one or more sets.

ToC

Beginner

Adding elements to a set.


There is one simple way to add elements to a set, the set.add method.

ToC

In [3]:
s1 = {1, 2}
s1.add(3)

print(s1)
{1, 2, 3}

The set.update method can also be used and can handle collections.

In [4]:
s1.update([4, 5])

print(s1)
{1, 2, 3, 4, 5}

Unions can be considered a way to add elements to a dictionary.

The difference is it requires two dictionaries and creates a third one.

Beginner

Modifying elements in a set.


Since sets are unordered, modifying a specific items does not make much sense.

It is more frequent to delete elements and add new ones.

ToC

Beginner

Deleting elements from a set.


Unlike accessing elements, sets provide several interesting ways to remove elements.

ToC

In [5]:
s1 = {1, 2, 3, 4, 5, 6, 7, 8, 9}

The set.remove method removes an element from the set and raises a KeyError if the element is not contained in the set.

In [6]:
print("Removing an item by value :")
s1.remove(2)

print('\nState of the set :')
print(s1)
Removing an item by value :

State of the set :
{1, 3, 4, 5, 6, 7, 8, 9}

The set.discard method removes an element from the set if it is present.

In [7]:
print("Discarding an item by value :")
s1.discard(2)

print('\nState of the set :')
print(s1)
Discarding an item by value :

State of the set :
{1, 3, 4, 5, 6, 7, 8, 9}

The set.pop method removes and return an arbitrary element from the set. Raises KeyError if the set is empty.

In [8]:
print('Item return by pop :')
print(s1.pop())

print('\nState of the set :')
print(s1)
Item return by pop :
1

State of the set :
{3, 4, 5, 6, 7, 8, 9}

The set.clear method removes all elements from the set.

In [9]:
print("Clearing the set.")
s1.clear()

print('\nState of the set :')
print(s1)
Clearing the set.

State of the set :
set()

Beginner

Operations on sets.


Sets allow to use powerfull operation.

First we can declare a bunch of sets to illustrate all these operations.

ToC

In [10]:
# Sets of various length.
a = {1, 2, 3, 4}
b = {3, 4, 5, 6}
c = {5, 6, 7, 8}
d = {2, 3}
e = {0, 1, 2, 3, 4, 5}

Unions.

In [11]:
set.union(a, b)

# Or ...
a.union(b)

# Or ...
a | b

a = {1, 2}
b = {4, 5}
In [12]:
print(f"Union of {a} and {b}")
print("\nResults :")
print(f"{a | b}")
Union of {1, 2} and {4, 5}

Results :
{1, 2, 4, 5}

Intersections.

In [13]:
set.intersection(a, b)

# Or ...
a.intersection(b)

# Or ...
a & b
Out[13]:
set()
In [14]:
print(f"Intersection of {a} and {b}")
print("\nResults :")
print(f"{a & b}")
Intersection of {1, 2} and {4, 5}

Results :
set()

Subsets.

In [15]:
set.issubset(a, b)

# Or ...
a.issubset(b)

# Or ...
a < b
Out[15]:
False
In [16]:
print(f"Is {a} a subset of {b}")
print("\nResults :")
print(f"{a < b}")
Is {1, 2} a subset of {4, 5}

Results :
False

This operator is not symetrical.

In [17]:
print(f"{a} is a subset of {e}")
print(f"> a < e == {a < e}.")
{1, 2} is a subset of {0, 1, 2, 3, 4, 5}
> a < e == True.
In [18]:
print(f"\n but {e} is a subset of {a}")
print(f"> e < a == {e < a}.")
 but {0, 1, 2, 3, 4, 5} is a subset of {1, 2}
> e < a == False.

Supersets.

Same as subsets.

In [19]:
set.issuperset(a, b)

# Or ...
a.issuperset(b)

# Or ...
a > b
Out[19]:
False

Difference.

In [20]:
set.difference(a, b)

# Or ...
a.difference(b)

# Or ...
a - b
Out[20]:
{1, 2}
In [21]:
print(f"Difference of {a} minus {b}")
print("\nResults :")
print(f"{a - b}")
Difference of {1, 2} minus {4, 5}

Results :
{1, 2}

Symetric difference.

In [22]:
set.symmetric_difference(a, b)

# Or ...
a.symmetric_difference(b)

# Or ...
a ^ b
Out[22]:
{1, 2, 4, 5}
In [23]:
print(f"Symetric difference of {a} and {b}")
print("\nResults :")
print(f"{a ^ b}")
Symetric difference of {1, 2} and {4, 5}

Results :
{1, 2, 4, 5}

Checking if sets are disjoint.

In [24]:
print('Is a disjointed from b :')
print(a.isdisjoint(b))
Is a disjointed from b :
True
In [25]:
print('Is a disjointed from c :')
print(a.isdisjoint(c))
Is a disjointed from c :
True

Advanced

What is the difference between adding an element to a dictionary and using the union operator ?


Let's create a set.

In [26]:
s1 = {1, 2, 3, 4}

Now let's add an element to this set and see if it is still the same object.

In [27]:
print(f"Address of the set before adding : {hex(id(s1))}.")

s1.add(5)

print(f"Address of the set after adding  : {hex(id(s1))}.")
Address of the set before adding : 0x7f6af87003c0.
Address of the set after adding  : 0x7f6af87003c0.

Now let's see what happens if we add an element with the union operator.

In [28]:
s2 = {9, 10, 11}

print(f"Address of the set before adding : {hex(id(s1))}.")

s1 = s1 | s2

print(f"Address of the set after adding  : {hex(id(s1))}.")
Address of the set before adding : 0x7f6af87003c0.
Address of the set after adding  : 0x7f6af8700040.

s1 is not the same object anymore.

Note this does not happen with the |= operator as it implicitely call the set.update method.

Beginner

Copying a set.


Copying a set works in the exact same way as it does for lists.

ToC

In [29]:
# A basic set.
s1 = {1, 2}

# Creating copies.
s2 = s1
s3 = s1.copy()

print(f"Address of s1 : {hex(id(s1))}")
print(f"Address of s2 : {hex(id(s2))}")
print(f"Address of s3 : {hex(id(s3))}")
Address of s1 : 0x7f6af87003c0
Address of s2 : 0x7f6af87003c0
Address of s3 : 0x7f6af8700820

Nested sets requiere the copy.deepcopy function from the copy library, same as lists.

Beginner

Iterating through a set.


Iterating through a set works in the exact same way as it does with lists.

Sets being unordered collection, iterating through them can yield unepexted results.

ToC

In [30]:
# A simple set.
s1 = {1, 4, 3, 2}

lst_cumsum = []
for ind, elt in enumerate(s1):
    try:
        lst_cumsum.append(lst_cumsum[ind - 1] + elt)
    except IndexError:
        lst_cumsum.append(elt)

print(lst_cumsum)
[1, 3, 6, 10]

Given the set {1, 4, 3, 2}. One might expect the cumulative sum to be :

[1, 5, 8 10]

But the result is :

[1, 3, 6, 10]

Beginner

Logic tests with sets.


Just like with lists, a set can be tested and is Truthy if not empty.

ToC

In [31]:
s1 = {1, 2, 3}

while s1:
    elem = s1.pop()
    print(f"Random element in our set : {elem}")
Random element in our set : 1
Random element in our set : 2
Random element in our set : 3

Beginner

Unpacking a set.


Same as before, sets being unordered, unpacking is possible but can have unexpected effects.

When iterating through a collection, the order may not matter but with unpacking it is almost never the case, therefore unpacking a set should be avoided.

ToC

In [32]:
a, b, c = {1, 3, 2}
print("Expected values : a -> 1, b -> 3, c -> 2")
print(f"Unpacked values : a -> {a}, b -> {b}, c -> {c}")
Expected values : a -> 1, b -> 3, c -> 2
Unpacked values : a -> 1, b -> 2, c -> 3

Intermediate

Set comprehension.


Set comprehension is possible, just the same as list comprehension.

Set comprehension can be used when the order of the result does not matter and when we do not want to know duplicated results.

ToC

In [33]:
# A list of number.
s1 = {i ** 2 for i in [0, 1, 3, 2, 5, 4]}