Session 10 · Sets: the rose garden and the botanical garden#

📖 Based on the blog post Task: The Botanical Garden and Rose Garden, Python Sets.

The big idea#

A set is a collection of unique items with no order. Adding something that’s already there does nothing. Sets are perfect for removing duplicates, checking membership fast, and comparing groups: what’s common, what’s different.

Everyday example: two gardens. Which flowers grow in both? Which only in the rose garden? Which in either? Those are set questions.

Rose garden and botanical garden Set operations compare two collections of unique items: union (|) is everything, intersection (&) is what's in both, difference (-) is what's only in the first. rose_garden botanical_garden white rose pink rose red rose sunflower tulip a | b union a & b intersection a - b difference a ^ b in one only
Set operations compare two collections of unique items: union (|) is everything, intersection (&) is what's in both, difference (-) is what's only in the first.

Creating sets#

rose_garden = {"red rose", "white rose", "red rose"}     # duplicate is dropped
print(len(rose_garden))
print(set(["rose", "tulip", "rose", "daisy"]) == {"rose", "tulip", "daisy"})
empty = set()            # NOT {} — that's an empty dictionary
print(type(empty), type({}))
2
True
<class 'set'> <class 'dict'>

A set has no positions: rose_garden[0] is a TypeError. The printed order can vary between runs, so the examples below use sorted() to show results in a fixed order.

Adding and removing#

garden = {"red rose", "white rose"}
garden.add("pink rose")
garden.add("red rose")          # already there: nothing happens
garden.remove("white rose")     # KeyError if missing
garden.discard("blue rose")     # no error if missing
print(sorted(garden))
['pink rose', 'red rose']

Comparing gardens#

rose = {"red rose", "white rose", "pink rose"}
botanical = {"sunflower", "tulip", "red rose"}

print(sorted(rose | botanical))   # union: in either
print(sorted(rose & botanical))   # intersection: in both
print(sorted(rose - botanical))   # difference: only in rose
print(sorted(rose ^ botanical))   # symmetric difference: in one, not both
['pink rose', 'red rose', 'sunflower', 'tulip', 'white rose']
['red rose']
['pink rose', 'white rose']
['pink rose', 'sunflower', 'tulip', 'white rose']

Each operator also has a method name: union(), intersection(), difference(), symmetric_difference().

Subsets and supersets#

small = {"red rose", "white rose"}
rose = {"red rose", "white rose", "pink rose"}
print(small <= rose, small.issubset(rose))       # every small flower is in rose
print(rose >= small, rose.issuperset(small))
True True
True True

Why sets are fast#

x in my_list checks items one by one. x in my_set jumps straight to the answer, like a dictionary key lookup. For big collections that’s the difference between seconds and microseconds.

frozenset#

A set that can’t change. Use it when you need a set as a dictionary key, or as a constant.

immutable_garden = frozenset({"orchid", "daisy", "red rose"})
try:
    immutable_garden.add("lily")
except AttributeError as e:
    print("AttributeError:", e)
AttributeError: 'frozenset' object has no attribute 'add'

Common mistakes#

  • {} is an empty dict, not a set. Use set().

  • Expecting order or indexes. Sets have neither; convert with sorted(s) or list(s).

  • remove() vs discard(): remove raises KeyError for a missing item, discard doesn’t.

  • Lists inside sets: {[1, 2]} raises TypeError (unhashable). Use tuples.

Hands-on exercises#

The tasks from Task: The Botanical Garden and Rose Garden. After tasks 1–3 the rose garden is {"red rose", "white rose", "pink rose"}; the later solutions start from that.

Task 1. Create rose_garden with red, white and yellow roses and print it.

Solution
rose_garden = {"red rose", "white rose", "yellow rose"}
print(rose_garden)

Task 2. Add “pink rose”.

Solution
rose_garden = {"red rose", "white rose", "yellow rose"}
rose_garden.add("pink rose")
print(rose_garden)

Task 3. Remove “yellow rose” with remove().

Solution
rose_garden = {"red rose", "white rose", "yellow rose", "pink rose"}
rose_garden.remove("yellow rose")
print(rose_garden)

Task 4. Create botanical_garden = {"sunflower", "tulip", "red rose"} and print the union.

Solution
rose_garden = {"red rose", "white rose", "pink rose"}
botanical_garden = {"sunflower", "tulip", "red rose"}
print(rose_garden | botanical_garden)

Task 5. Print the intersection.

Solution
rose_garden = {"red rose", "white rose", "pink rose"}
botanical_garden = {"sunflower", "tulip", "red rose"}
print(rose_garden & botanical_garden)       # {'red rose'}

Task 6. Print what’s only in the rose garden.

Solution
rose_garden = {"red rose", "white rose", "pink rose"}
botanical_garden = {"sunflower", "tulip", "red rose"}
print(rose_garden - botanical_garden)       # white rose, pink rose

Task 7. Print the symmetric difference.

Solution
rose_garden = {"red rose", "white rose", "pink rose"}
botanical_garden = {"sunflower", "tulip", "red rose"}
print(rose_garden ^ botanical_garden)       # everything except red rose

Task 8. Is small_garden = {"red rose", "white rose"} a subset of rose_garden?

Solution
rose_garden = {"red rose", "white rose", "pink rose"}
small_garden = {"red rose", "white rose"}
print(small_garden.issubset(rose_garden))   # True

Task 9. Is rose_garden a superset of small_garden?

Solution
rose_garden = {"red rose", "white rose", "pink rose"}
small_garden = {"red rose", "white rose"}
print(rose_garden.issuperset(small_garden)) # True

Task 10. Print the number of roses.

Solution
rose_garden = {"red rose", "white rose", "pink rose"}
print(len(rose_garden))      # 3

Task 11. discard() “pink rose”, then try to discard “blue rose”.

Solution
rose_garden = {"red rose", "white rose", "pink rose"}
rose_garden.discard("pink rose")
rose_garden.discard("blue rose")       # not there: no error, nothing happens
print(rose_garden)

Task 12. clear() the rose garden.

Solution
rose_garden = {"red rose", "white rose"}
rose_garden.clear()
print(rose_garden)           # set()

Task 13. Copy botanical_garden, add “lily” to the copy, print both.

Solution
botanical_garden = {"sunflower", "tulip", "red rose"}
garden_copy = botanical_garden.copy()
garden_copy.add("lily")
print(botanical_garden)      # unchanged
print(garden_copy)           # has lily

Task 14. Create a frozenset and try to add or remove an element.

Answer
immutable_garden = frozenset({"orchid", "daisy", "red rose"})
print(hasattr(immutable_garden, "add"), hasattr(immutable_garden, "remove"))   # False False

Calling immutable_garden.add("lily") raises AttributeError: a frozenset has no methods that change it.

Task 15. Loop over botanical_garden.

Solution
botanical_garden = {"sunflower", "tulip", "red rose"}
for flower in sorted(botanical_garden):     # sorted() for a predictable order
    print(flower)

Task 16. Use a set comprehension for the even numbers from 1 to 10.

Solution
even_numbers = {n for n in range(1, 11) if n % 2 == 0}
print(sorted(even_numbers))    # [2, 4, 6, 8, 10]

Task 17. Remove duplicates from ["rose", "tulip", "rose", "daisy", "tulip"].

Solution
flowers = ["rose", "tulip", "rose", "daisy", "tulip"]
print(set(flowers))            # {'rose', 'tulip', 'daisy'} in some order

Task 18. Is “sunflower” in botanical_garden?

Solution
botanical_garden = {"sunflower", "tulip", "red rose"}
print("sunflower" in botanical_garden)     # True

Task 19. Keep only the botanical flowers that are also in the rose garden with intersection_update().

Solution
rose_garden = {"red rose", "white rose", "pink rose"}
botanical_garden = {"sunflower", "tulip", "red rose"}
botanical_garden.intersection_update(rose_garden)
print(botanical_garden)        # {'red rose'}

The _update methods change the set in place instead of returning a new one.

Task 20. Remove everything in small_garden from botanical_garden with difference_update().

Solution
botanical_garden = {"sunflower", "tulip", "red rose"}
small_garden = {"red rose", "white rose"}
botanical_garden.difference_update(small_garden)
print(sorted(botanical_garden))   # ['sunflower', 'tulip']