""" ================================================================================ File: 03_sets.py Topic: Python Sets - Unordered Collections of Unique Elements ================================================================================ This file demonstrates Python sets, which are unordered collections that store only unique elements. Sets are highly optimized for membership testing and mathematical set operations. Key Concepts: - Creating sets - Adding and removing elements - Set operations (union, intersection, difference) - Frozen sets (immutable sets) - Use cases for sets ================================================================================ """ # ----------------------------------------------------------------------------- # 1. Creating Sets # ----------------------------------------------------------------------------- # Sets are created with curly braces {} or set() print("--- Creating Sets ---") # Using curly braces fruits = {"apple", "banana", "cherry"} print(f"Fruits set: {fruits}") # Duplicates are automatically removed numbers = {1, 2, 2, 3, 3, 3, 4} print(f"Numbers (duplicates removed): {numbers}") # Empty set - must use set(), not {} empty_set = set() # {} creates an empty dictionary! print(f"Empty set: {empty_set}, type: {type(empty_set)}") print(f"Empty dict: {{}}, type: {type({})}") # From other iterables from_list = set([1, 2, 3, 4, 5]) from_string = set("hello") # Unique characters from_tuple = set((10, 20, 30)) print(f"\nFrom list: {from_list}") print(f"From string 'hello': {from_string}") print(f"From tuple: {from_tuple}") # ----------------------------------------------------------------------------- # 2. Set Characteristics # ----------------------------------------------------------------------------- # Unordered, no duplicates, no indexing print("\n--- Set Characteristics ---") colors = {"red", "green", "blue"} # Sets are unordered - no indexing # colors[0] # This would raise an error! # Check membership (very fast - O(1)) print(f"'red' in colors: {'red' in colors}") print(f"'yellow' in colors: {'yellow' in colors}") # Length print(f"Number of colors: {len(colors)}") # Sets can only contain immutable (hashable) elements valid_set = {1, "hello", (1, 2), 3.14} # OK # invalid_set = {1, [2, 3]} # Error! Lists are not hashable # ----------------------------------------------------------------------------- # 3. Adding and Removing Elements # ----------------------------------------------------------------------------- print("\n--- Adding and Removing Elements ---") languages = {"Python", "Java"} print(f"Initial: {languages}") # add() - Add single element languages.add("JavaScript") print(f"After add('JavaScript'): {languages}") # Adding duplicate has no effect languages.add("Python") print(f"After add('Python'): {languages}") # update() - Add multiple elements languages.update(["C++", "Rust"]) print(f"After update(['C++', 'Rust']): {languages}") # remove() - Remove element (raises error if not found) languages.remove("Java") print(f"After remove('Java'): {languages}") # discard() - Remove element (no error if not found) languages.discard("Go") # No error even though "Go" isn't in set print(f"After discard('Go'): {languages}") # pop() - Remove and return an arbitrary element popped = languages.pop() print(f"Popped: {popped}, Remaining: {languages}") # clear() - Remove all elements temp_set = {1, 2, 3} temp_set.clear() print(f"After clear(): {temp_set}") # ----------------------------------------------------------------------------- # 4. Set Operations - Mathematical Operations # ----------------------------------------------------------------------------- print("\n--- Set Operations ---") a = {1, 2, 3, 4, 5} b = {4, 5, 6, 7, 8} print(f"Set A: {a}") print(f"Set B: {b}") # Union - Elements in A or B or both union = a | b # or a.union(b) print(f"\nUnion (A | B): {union}") # Intersection - Elements in both A and B intersection = a & b # or a.intersection(b) print(f"Intersection (A & B): {intersection}") # Difference - Elements in A but not in B difference = a - b # or a.difference(b) print(f"Difference (A - B): {difference}") difference_ba = b - a print(f"Difference (B - A): {difference_ba}") # Symmetric Difference - Elements in A or B but not both sym_diff = a ^ b # or a.symmetric_difference(b) print(f"Symmetric Difference (A ^ B): {sym_diff}") # ----------------------------------------------------------------------------- # 5. Set Comparison Operations # ----------------------------------------------------------------------------- print("\n--- Set Comparisons ---") numbers = {1, 2, 3, 4, 5} subset = {2, 3} same = {1, 2, 3, 4, 5} different = {6, 7, 8} print(f"numbers: {numbers}") print(f"subset: {subset}") # issubset() - Is subset contained in numbers? print(f"\nsubset.issubset(numbers): {subset.issubset(numbers)}") print(f"subset <= numbers: {subset <= numbers}") # issuperset() - Does numbers contain subset? print(f"numbers.issuperset(subset): {numbers.issuperset(subset)}") print(f"numbers >= subset: {numbers >= subset}") # isdisjoint() - Do they share any elements? print(f"\nnumbers.isdisjoint(different): {numbers.isdisjoint(different)}") print(f"numbers.isdisjoint(subset): {numbers.isdisjoint(subset)}") # Equality print(f"\nnumbers == same: {numbers == same}") print(f"numbers == subset: {numbers == subset}") # ----------------------------------------------------------------------------- # 6. Update Operations (Modify in Place) # ----------------------------------------------------------------------------- print("\n--- Update Operations ---") # These modify the set in place instead of creating new ones x = {1, 2, 3} y = {3, 4, 5} # update() - Union in place (|=) x_copy = x.copy() x_copy.update(y) # or x_copy |= y print(f"After update (union): {x_copy}") # intersection_update() - Intersection in place (&=) x_copy = x.copy() x_copy.intersection_update(y) # or x_copy &= y print(f"After intersection_update: {x_copy}") # difference_update() - Difference in place (-=) x_copy = x.copy() x_copy.difference_update(y) # or x_copy -= y print(f"After difference_update: {x_copy}") # symmetric_difference_update() - Symmetric difference in place (^=) x_copy = x.copy() x_copy.symmetric_difference_update(y) # or x_copy ^= y print(f"After symmetric_difference_update: {x_copy}") # ----------------------------------------------------------------------------- # 7. Frozen Sets (Immutable Sets) # ----------------------------------------------------------------------------- print("\n--- Frozen Sets ---") # Frozen sets are immutable versions of sets frozen = frozenset([1, 2, 3, 4, 5]) print(f"Frozen set: {frozen}") # Can perform operations but not modify print(f"3 in frozen: {3 in frozen}") print(f"len(frozen): {len(frozen)}") # These would raise errors: # frozen.add(6) # frozen.remove(1) # Frozen sets can be used as dictionary keys or set elements nested = {frozenset([1, 2]), frozenset([3, 4])} print(f"Set of frozensets: {nested}") # ----------------------------------------------------------------------------- # 8. Set Comprehensions # ----------------------------------------------------------------------------- print("\n--- Set Comprehensions ---") # Basic set comprehension squares = {x**2 for x in range(1, 6)} print(f"Squares: {squares}") # With condition even_squares = {x**2 for x in range(1, 11) if x % 2 == 0} print(f"Even squares: {even_squares}") # From string - unique vowels text = "Hello, World!" vowels = {char.lower() for char in text if char.lower() in "aeiou"} print(f"Unique vowels in '{text}': {vowels}") # ----------------------------------------------------------------------------- # 9. Practical Use Cases # ----------------------------------------------------------------------------- print("\n--- Practical Use Cases ---") # 1. Remove duplicates from a list my_list = [1, 2, 2, 3, 3, 3, 4, 4, 4, 4] unique_list = list(set(my_list)) print(f"Remove duplicates: {my_list} → {unique_list}") # 2. Find common elements list1 = [1, 2, 3, 4, 5] list2 = [4, 5, 6, 7, 8] common = set(list1) & set(list2) print(f"Common elements: {common}") # 3. Find unique elements across lists all_elements = set(list1) | set(list2) print(f"All unique elements: {all_elements}") # 4. Check if all required items exist required_skills = {"Python", "SQL", "Git"} candidate_skills = {"Python", "SQL", "Git", "Docker", "AWS"} has_all_required = required_skills.issubset(candidate_skills) print(f"\nCandidate has all required skills: {has_all_required}") # 5. Find missing items available_items = {"apple", "banana", "orange"} shopping_list = {"apple", "milk", "bread", "banana"} need_to_buy = shopping_list - available_items print(f"Need to buy: {need_to_buy}") # 6. Count unique words text = "the quick brown fox jumps over the lazy dog" unique_words = set(text.split()) print(f"\nUnique words in text: {len(unique_words)}") print(f"Words: {unique_words}") # ----------------------------------------------------------------------------- # 10. Performance: Sets vs Lists for Membership Testing # ----------------------------------------------------------------------------- print("\n--- Performance Note ---") # Sets use hash tables - O(1) lookup # Lists use linear search - O(n) lookup big_list = list(range(10000)) big_set = set(range(10000)) # For membership testing: # 9999 in big_list # Slow - checks up to 10000 elements # 9999 in big_set # Fast - direct hash lookup print("For membership testing, sets are MUCH faster than lists!") print("Use sets when you frequently check if items exist in a collection.")