FreeCampus Python

Sets: Uniqueness and Group Comparisons

Use sets for unique membership and group comparisons, choose deliberate mutation methods, and represent immutable hashable groups with frozenset.
python-foundations collections-iteration sets frozenset
Open in Colab
  • Level: Python Foundations · Unit 3
  • Estimated time: 3–4.5 hours
  • You will learn: Create, change, compare, and freeze unique-value groups while respecting missing-item, ordering, indexing, and hashability boundaries.
  • Practice in: Google Colab, JupyterLab, or a local editor

1. Keep one copy of every discovered skill

An expedition log may repeat a skill whenever a crew member demonstrates it. A readiness check cares only whether each skill is available at least once:

skill_log = ["mapping", "repair", "mapping", "translation", "repair"]
available_skills = set(skill_log)

print(len(skill_log))
print(len(available_skills))
print("repair" in available_skills)

The set has three members because equal duplicates collapse. A set models unique membership, not occurrence count or positional order. Preserve the original list when arrival order or duplicate evidence still matters.

Set literals use braces:

required_skills = {"mapping", "translation", "repair"}

An empty set is the important exception:

empty_set = set()
empty_dictionary = {}

print(type(empty_set).__name__)
print(type(empty_dictionary).__name__)

{} already denotes an empty dictionary, so use set() when no members exist.

This lesson answers:

  • Which operations mutate a set, and how do missing removals differ?
  • How do union, intersection, difference, and symmetric difference answer distinct questions?
  • What do subset, superset, and disjoint relationships prove?
  • When does an immutable frozenset fit where a mutable set cannot?

2. Membership matters; positions do not

Sets support len() and membership:

visited = {"dock", "garden", "vault"}

assert len(visited) == 3
assert "garden" in visited
assert "tower" not in visited

They do not support numeric indexing:

visited = {"dock", "garden", "vault"}
print(visited[0])

Python raises TypeError: 'set' object is not subscriptable. There is no promised “first” member for index zero to select.

Printing or traversing a set can show members in an order that looks stable in one session, but code must not treat it as a positional or display-order contract. Create an explicitly sorted list when presentation needs a predictable order:

visited = {"dock", "garden", "vault"}
display_names = sorted(visited)
print(display_names)

Lesson 6 develops sorting policies. The result here is a list; the set remains unchanged.

NoteMembership is a natural set question

Sets are designed for repeated membership checks without scanning a positional sequence in the ordinary case. Keep a list as well if you need both original order and membership behavior. One collection does not need to satisfy every requirement.

3. Add one member or update from many values

.add() treats its argument as one member:

skills = {"mapping", "repair"}
result = skills.add("translation")

print("translation" in skills)
print(result)

The set changes and the method returns None.

Adding an existing equal member has no effect on length:

before = len(skills)
skills.add("mapping")
after = len(skills)

assert before == after

.update() consumes members from one or more iterables:

skills = {"mapping"}
skills.update(["repair", "translation"], {"navigation"})

assert skills == {"mapping", "repair", "translation", "navigation"}

A string supplies individual characters, just as it did for list.extend():

letters = {"N"}
letters.update("OVA")
assert letters == {"N", "O", "V", "A"}

Use .add("NOVA") if the complete word should be one member.

4. Choose removal behavior from the missing-item rule

.remove() requires the member:

skills = {"mapping", "repair", "translation"}
skills.remove("repair")

assert "repair" not in skills

Removing an absent member raises KeyError:

skills.remove("flying")

.discard() makes absence a harmless no-op:

skills = {"mapping", "translation"}
result = skills.discard("flying")

assert skills == {"mapping", "translation"}
assert result is None

Use remove() when absence violates the task’s promise and discard() when “make sure it is absent” is the complete requirement.

.pop() removes and returns an arbitrary member:

tokens = {"sun", "moon", "star"}
removed_token = tokens.pop()

assert removed_token not in tokens
assert len(tokens) == 2

Do not assert which token is removed. Set pop() does not mean “remove the last item.” Calling it on an empty set raises KeyError.

.clear() removes every member and returns None.

Checkpoint: creation and mutation

5. Set operations answer four different group questions

Suppose two crews have these skills:

nova_skills = {"mapping", "repair", "translation"}
mira_skills = {"navigation", "repair", "first aid"}

Union: present in either group

combined = nova_skills | mira_skills
same_result = nova_skills.union(mira_skills)

assert combined == same_result
assert combined == {
    "mapping",
    "repair",
    "translation",
    "navigation",
    "first aid",
}

Union combines membership and keeps one copy of overlap.

Intersection: present in both groups

shared = nova_skills & mira_skills
assert shared == {"repair"}

The named spelling is nova_skills.intersection(mira_skills).

Difference: present only on the left

nova_only = nova_skills - mira_skills
mira_only = mira_skills - nova_skills

assert nova_only == {"mapping", "translation"}
assert mira_only == {"navigation", "first aid"}

Difference is directional. Swapping operands changes the question.

Symmetric difference: present in exactly one group

not_shared = nova_skills ^ mira_skills
assert not_shared == nova_only | mira_only

The shared "repair" member is excluded. The named method is .symmetric_difference().

All four expressions above create new sets; neither source set changes. In-place forms such as |=, &=, -=, and ^= mutate the left set and should be used only when that state change is intended.

6. Compare readiness with subset relationships

An expedition is ready if all required skills are included in its available skills:

required = {"mapping", "repair"}
available = {"mapping", "repair", "translation"}

print(required <= available)
print(required < available)
print(available >= required)
print(available > required)
  • <= means subset, including equality;
  • < means proper subset, requiring at least one additional member on the right;
  • >= means superset, including equality; and
  • > means proper superset.

Equality ignores insertion history and display order:

assert {"map", "rope"} == {"rope", "map"}

.isdisjoint() asks whether two groups share no members:

hazards = {"flood", "storm"}
current_conditions = {"clear", "cold"}

assert hazards.isdisjoint(current_conditions)

This expresses “no overlap” more directly than constructing an intersection only to compare it with an empty set.

Checkpoint: group operations and relationships

7. Set members must be hashable

Strings, numbers, and suitable tuples can be members:

known_locations = {(12, 7), (4, 9)}
assert (12, 7) in known_locations

A list cannot:

invalid_locations = {[12, 7], [4, 9]}

Python raises TypeError: unhashable type: 'list'. A mutable list could change in a way that invalidates the set’s membership organization. A tuple works only if its own relevant contents are hashable.

8. frozenset represents an immutable set value

Construct a frozen set from any iterable:

required_skills = frozenset(["mapping", "repair", "translation"])

print(type(required_skills).__name__)
print("repair" in required_skills)

It supports non-mutating set operations and comparisons:

available = {"mapping", "repair", "translation", "navigation"}

assert required_skills <= available
assert required_skills & available == required_skills

It has no .add(), .remove(), or .update() methods:

required_skills.add("navigation")

The resulting AttributeError confirms the immutable interface.

Because a frozenset is hashable when its members are hashable, it can itself be a set member or dictionary key:

mission_by_requirements = {
    frozenset({"mapping", "repair"}): "river survey",
    frozenset({"translation", "navigation"}): "signal hunt",
}

mission_key = frozenset({"repair", "mapping"})
print(mission_by_requirements[mission_key])

Set equality ignores order, so the differently written key finds the same requirements group.

A set can contain frozen sets:

approved_teams = {
    frozenset({"Nova", "Mira"}),
    frozenset({"Sol", "Ivo"}),
}

assert frozenset({"Mira", "Nova"}) in approved_teams

Choose frozenset because the value is conceptually an immutable membership group or must be hashable—not merely because its name sounds safer.

Checkpoint: hashability and frozenset

9. Solve the expedition-skills puzzle

Three explorers report skills with duplicates. Build stable evidence and answer the readiness questions without relying on set display order.

nova_log = ["mapping", "repair", "mapping", "translation"]
mira_log = ["navigation", "repair", "first aid", "repair"]

required = frozenset({"mapping", "repair", "translation"})
nova_skills = None
mira_skills = None
shared_skills = None
all_skills = None
nova_missing = None
mira_missing = None
nova_ready = None
mira_ready = None
team_signature = None
display_skills = None

Your artifact must:

  1. convert each log to a unique skill set without changing either log;
  2. find shared and combined membership;
  3. find required skills missing from each explorer;
  4. calculate readiness with a subset relationship;
  5. create an immutable team_signature from the combined skills; and
  6. create a predictable alphabetical list only for display.
assert nova_log == ["mapping", "repair", "mapping", "translation"]
assert mira_log == ["navigation", "repair", "first aid", "repair"]
assert nova_skills == {"mapping", "repair", "translation"}
assert mira_skills == {"navigation", "repair", "first aid"}
assert shared_skills == {"repair"}
assert all_skills == {
    "mapping",
    "repair",
    "translation",
    "navigation",
    "first aid",
}
assert nova_missing == set()
assert mira_missing == {"mapping", "translation"}
assert nova_ready is True
assert mira_ready is False
assert type(team_signature) is frozenset
assert team_signature == all_skills
assert display_skills == [
    "first aid",
    "mapping",
    "navigation",
    "repair",
    "translation",
]

Boundary variation: add two more "repair" entries and one "navigation" entry to nova_log. Predict which assertions change before rerunning. The unique set should gain only navigation; duplicate repair records remain preserved in the log but not the set.

Hint: translate each sentence into one group question

Convert logs with set(...). Shared means intersection, combined means union, missing means required - available, and ready means required <= available. Pass the combined set to frozenset() and to sorted() for two different final representations.

Show one complete solution after attempting the puzzle
nova_log = ["mapping", "repair", "mapping", "translation"]
mira_log = ["navigation", "repair", "first aid", "repair"]
required = frozenset({"mapping", "repair", "translation"})

nova_skills = set(nova_log)
mira_skills = set(mira_log)
shared_skills = nova_skills & mira_skills
all_skills = nova_skills | mira_skills
nova_missing = required - nova_skills
mira_missing = required - mira_skills
nova_ready = required <= nova_skills
mira_ready = required <= mira_skills
team_signature = frozenset(all_skills)
display_skills = sorted(all_skills)

The logs preserve order and duplicates as evidence. Sets answer membership questions. The frozen set is a stable value that could become a mapping key, and the sorted list exists only to guarantee display order.

10. Explain the group model

  1. Why does set(log) lose information even when it correctly deduplicates?
  2. When should missing removal raise KeyError, and when is a no-op better?
  3. Why is a - b different from b - a?
  4. What fact does required <= available state?
  5. Why can a frozenset be a dictionary key while an ordinary set cannot?

Key points

TipKey points
  • Sets retain unique hashable members and do not promise numeric positions or a stable presentation order.
  • .add() adds one member; .update() consumes members from iterables.
  • .remove() requires a member, .discard() accepts absence, and .pop() removes an arbitrary member.
  • Union, intersection, difference, and symmetric difference answer distinct group questions without mutating their operands.
  • Subset, superset, and disjoint comparisons state readiness and overlap rules.
  • frozenset provides immutable set membership and can be hashable itself.
  • Keep an ordered source list when duplicates or arrival order remain meaningful.

References

Back to top