Set Operations: Union, Intersection and Difference

Four operations answer four everyday questions: what is in either, what is in both, what is only in the first, and what is in exactly one.

The four operations

a = {1, 2, 3, 4}
b = {3, 4, 5, 6}

print(a | b)      # {1, 2, 3, 4, 5, 6}   union - in either
print(a & b)      # {3, 4}               intersection - in both
print(a - b)      # {1, 2}               difference - in a only
print(b - a)      # {5, 6}               difference - in b only
print(a ^ b)      # {1, 2, 5, 6}         symmetric difference - in exactly one
        a                 b
   .---------.       .---------.
   |  1   2  |  3  4 |  5   6  |
   '---------'       '---------'

   a | b  ->  1 2 3 4 5 6      everything
   a & b  ->  3 4              the overlap
   a - b  ->  1 2              left only
   a ^ b  ->  1 2 5 6          everything except the overlap

Operators and methods

OperationOperatorMethodMethod accepts
Uniona | ba.union(b)Any iterable
Intersectiona & ba.intersection(b)Any iterable
Differencea - ba.difference(b)Any iterable
Symmetric differencea ^ ba.symmetric_difference(b)Any iterable
a = {1, 2, 3}

print(a.union([3, 4]))            # {1, 2, 3, 4} - a list is accepted
# print(a | [3, 4])               # TypeError - the operator needs a set

print(a.intersection([2, 3], (3, 9)))   # {3} - several iterables at once

The operators require both sides to be sets. The methods accept any iterable, which is often more convenient.

Updating in place

a = {1, 2, 3}

a |= {4}                          # or a.update({4})
print(a)                          # {1, 2, 3, 4}

a &= {2, 3, 4, 9}                 # or a.intersection_update(...)
print(a)                          # {2, 3, 4}

a -= {4}                          # or a.difference_update({4})
print(a)                          # {2, 3}

a ^= {3, 5}                       # or a.symmetric_difference_update(...)
print(a)                          # {2, 5}

Comparing sets

small = {1, 2}
large = {1, 2, 3, 4}
other = {9}

print(small <= large)                    # True  - subset
print(small.issubset(large))             # True
print(small < large)                     # True  - proper subset (not equal)

print(large >= small)                    # True  - superset
print(large.issuperset(small))           # True

print(small.isdisjoint(other))           # True  - no elements in common
print({1, 2} == {2, 1})                  # True  - order is irrelevant

Practical uses

What changed between two versions

before = {"index.html", "style.css", "app.js"}
after = {"index.html", "style.css", "app.js", "logo.svg"}

print("added:  ", after - before)         # {'logo.svg'}
print("removed:", before - after)         # set()
print("kept:   ", before & after)

Common and unique interests

meera = {"python", "sql", "design"}
arun = {"python", "javascript", "design"}

print("both:      ", meera & arun)        # {'python', 'design'}
print("either:    ", meera | arun)
print("only meera:", meera - arun)        # {'sql'}
print("not shared:", meera ^ arun)        # {'sql', 'javascript'}

Validating required fields

required = {"name", "email", "phone"}
submitted = {"name", "email"}

missing = required - submitted
extra = submitted - required

if missing:
    print("missing fields:", ", ".join(sorted(missing)))
if extra:
    print("unexpected fields:", ", ".join(sorted(extra)))

Permission checks

user_roles = {"editor", "reviewer"}
allowed_roles = {"admin", "editor"}

if user_roles & allowed_roles:
    print("access granted")

if user_roles.isdisjoint(allowed_roles):
    print("no overlap at all")

if {"admin"} <= user_roles:
    print("is an administrator")

Deduplicating across sources

list_a = ["a@x.com", "b@x.com"]
list_b = ["b@x.com", "c@x.com"]

all_addresses = set(list_a) | set(list_b)
duplicates = set(list_a) & set(list_b)

print(sorted(all_addresses))     # ['a@x.com', 'b@x.com', 'c@x.com']
print(duplicates)                # {'b@x.com'}

Finding the common words in several documents

docs = [
    "the quick brown fox",
    "the lazy brown dog",
    "the brown bear",
]

word_sets = [set(doc.split()) for doc in docs]
common = set.intersection(*word_sets)
print(sorted(common))            # ['brown', 'the']

anywhere = set.union(*word_sets)
print(len(anywhere))

Cost

OperationRoughly
x in s, add, discardConstant
a | b, a ^ bProportional to the total size
a & b, a - bProportional to the smaller set

Common mistakes

  • Using | with a list on one side. Use the method form.
  • Confusing a - b with b - a; difference is not symmetric.
  • Expecting ^ to mean "in both". It means the opposite.
  • Using < and thinking it compares sizes. It tests subset.
  • Forgetting that these operators mean bitwise arithmetic on integers.
  • Expecting the result of a set operation to preserve any order.

Best practices

  • Use operators when both sides are sets, methods when one side is a list or a generator.
  • Use isdisjoint rather than testing whether the intersection is empty; it can stop early.
  • Name the result of a set operation: missing = required - submitted reads by itself.
  • Sort before displaying, always.

Practice

  1. Given three lists of student names per subject, find those enrolled in all three and those in exactly one.
  2. Report which required configuration keys are missing and which are unrecognised.
  3. Explain the difference between a - b, b - a and a ^ b with a concrete example.
  4. Use set.intersection(*sets) to find words common to five sentences.
  5. Explain why {1, 2} < {1, 2} is False but {1, 2} <= {1, 2} is True.

Conclusion

Union, intersection, difference and symmetric difference answer four questions you will ask constantly: everything, the overlap, what is only here, and what is not shared. Naming the result of each operation turns set code into something that reads like the requirement it implements.

Written by Lorens Mishra

Default administrator account created by the installer.

Continue reading

All Python notes →

Discussion

0 comments
Sign in to join the discussion.

No comments yet. Be the first to say something.