Sets

Updated

September 8, 2026

Sets

A set holds unique hashable values. The boring default is a set when the question is membership or overlap (which tables are free, which allergens collided), not when order or duplicates matter.

Mental model

{3, 11, 12} is a set. set() is the empty set — {} is an empty dict. Values must be hashable. A set itself is mutable and not hashable. frozenset is the immutable twin; it can be a dict key or a member of another set.

Operators:

Op Meaning
a \| b union — in either
a & b intersection — in both
a - b difference — in a not b
a ^ b symmetric difference — in one, not both
x in a membership

<= / < are subset. >= / > are superset. Methods (union, intersection, add, discard, remove) exist too. remove raises KeyError if missing; discard does not.

Iteration order is not a contract. Sort when you print.

Worked examples

Case 1: Unique tables on the shift

Save as unique_tables.py.

# unique_tables.py
def main():
    landed = [12, 3, 12, 11, 3]
    tables = set(landed)
    print(sorted(tables))
    print(12 in tables)
    tables.add(4)
    tables.discard(99)
    print(sorted(tables))


if __name__ == "__main__":
    main()

Run:

uv run python unique_tables.py

Output:

[3, 11, 12]
True
[3, 4, 11, 12]

set(landed) dropped duplicate 12 and 3. discard(99) is a no-op. remove(99) would raise.

Case 2: Operators for two shifts

# shift_overlap.py
def main():
    lunch = {3, 11, 12}
    dinner = {12, 14, 15}
    print("either ", sorted(lunch | dinner))
    print("both   ", sorted(lunch & dinner))
    print("lunch only", sorted(lunch - dinner))
    print("one side", sorted(lunch ^ dinner))
    print("lunch subset of either", lunch <= (lunch | dinner))


if __name__ == "__main__":
    main()

Run:

uv run python shift_overlap.py

Output:

either  [3, 11, 12, 14, 15]
both    [12]
lunch only [3, 11]
one side [3, 11, 14, 15]
lunch subset of either True

Read & as “conflict” when both shifts claimed the same table. Read - as “only lunch.”

Case 3: Allergens as a set, frozenset as a key

# allergens.py
def main():
    soup = frozenset({"dairy", "gluten"})
    tea = frozenset()
    cake = frozenset({"dairy", "nuts"})
    by_item = {
        soup: "soup",
        tea: "tea",
        cake: "cake",
    }
    guest = {"dairy"}
    for tags, name in by_item.items():
        hit = set(tags) & guest
        if hit:
            print(name, "hits", sorted(hit))
        else:
            print(name, "ok")


if __name__ == "__main__":
    main()

Run:

uv run python allergens.py

Output:

soup hits ['dairy']
tea ok
cake hits ['dairy']

A plain set cannot be a dict key (TypeError: cannot use 'set' as a dict key). frozenset can. frozenset() is the empty tag set for tea. Converting with set(tags) lets you & against the mutable guest set.

The trap

Building a set of dicts (tickets), or expecting a set to remember insert order in your output.

# set_of_tickets.py
def main():
    tickets = [{"id": 7}, {"id": 8}]
    print(set(tickets))


if __name__ == "__main__":
    main()

Run:

uv run python set_of_tickets.py

Output (the process exits non-zero):

Traceback (most recent call last):
  File "set_of_tickets.py", line 8, in <module>
    main()
  File "set_of_tickets.py", line 4, in main
    print(set(tickets))
TypeError: cannot use 'dict' as a set element (unhashable type: 'dict')

Sets hash their members. Dicts (and lists) do not hash. Store ids: set(t["id"] for t in tickets). If you need unique tickets as whole records, pick a key (id) or use a dict keyed by id.

{1, 2, 3} looks ordered in a lucky print. Do not write tests that == a printed order. sorted(the_set) is the boring print.

The boring rule

  • Set: unique membership. List: sequence. Dict: named fields.
  • Use | & - ^ and in. Sort for display.
  • discard when missing is fine. remove when missing is a bug.
  • frozenset for keys and for set-of-sets.
  • Empty set is set(), never {}.

Try this

  1. In unique_tables.py, call tables.remove(99) instead of discard and read the KeyError.
  2. Add a brunch set and print tables that appear in all three shifts (lunch & dinner & brunch).
  3. Change allergens.py so guest = {"nuts"} and confirm only cake hits.