""" ================================================================================ File: 04_dictionaries.py Topic: Python Dictionaries - Key-Value Pair Collections ================================================================================ This file demonstrates Python dictionaries, which store data as key-value pairs. Dictionaries are extremely versatile and provide fast access to values using their associated keys. Key Concepts: - Creating and accessing dictionaries - Adding, modifying, and removing key-value pairs - Dictionary methods - Iterating over dictionaries - Nested dictionaries - Dictionary comprehensions ================================================================================ """ # ----------------------------------------------------------------------------- # 1. Creating Dictionaries # ----------------------------------------------------------------------------- # Dictionaries use curly braces with key: value syntax print("--- Creating Dictionaries ---") # Empty dictionary empty_dict = {} also_empty = dict() print(f"Empty dict: {empty_dict}") # Dictionary with elements person = { "name": "Baraa", "age": 25, "city": "Gaza", "is_developer": True } print(f"Person: {person}") # Using dict() constructor from_pairs = dict([("a", 1), ("b", 2), ("c", 3)]) from_kwargs = dict(x=10, y=20, z=30) print(f"From pairs: {from_pairs}") print(f"From kwargs: {from_kwargs}") # Keys can be any immutable type mixed_keys = { "string_key": "value1", 42: "value2", (1, 2): "value3", # Tuple as key True: "value4" } print(f"Mixed keys: {mixed_keys}") # ----------------------------------------------------------------------------- # 2. Accessing Values # ----------------------------------------------------------------------------- print("\n--- Accessing Values ---") student = { "name": "Ali", "age": 20, "grades": [85, 90, 78], "active": True } # Using square brackets print(f"Name: {student['name']}") print(f"Grades: {student['grades']}") # Using get() - safer, returns None if key doesn't exist print(f"Age: {student.get('age')}") print(f"Email: {student.get('email')}") # Returns None print(f"Email with default: {student.get('email', 'Not provided')}") # Accessing nested values print(f"First grade: {student['grades'][0]}") # ----------------------------------------------------------------------------- # 3. Modifying Dictionaries # ----------------------------------------------------------------------------- print("\n--- Modifying Dictionaries ---") config = {"theme": "dark", "font_size": 14} print(f"Original: {config}") # Update existing key config["font_size"] = 16 print(f"After update: {config}") # Add new key config["language"] = "English" print(f"After adding key: {config}") # Update multiple keys at once config.update({"theme": "light", "auto_save": True}) print(f"After update(): {config}") # Using setdefault() - only adds if key doesn't exist config.setdefault("font_size", 20) # Won't change (key exists) config.setdefault("new_key", "default_value") # Will add print(f"After setdefault(): {config}") # ----------------------------------------------------------------------------- # 4. Removing Items # ----------------------------------------------------------------------------- print("\n--- Removing Items ---") data = {"a": 1, "b": 2, "c": 3, "d": 4, "e": 5} print(f"Original: {data}") # pop() - Remove and return value removed = data.pop("c") print(f"Popped 'c': {removed}, Dict: {data}") # pop() with default - no error if key missing removed = data.pop("z", "Not found") print(f"Popped 'z': {removed}") # popitem() - Remove and return last inserted pair last_item = data.popitem() print(f"Popitem: {last_item}, Dict: {data}") # del - Delete specific key del data["b"] print(f"After del 'b': {data}") # clear() - Remove all items temp = {"x": 1, "y": 2} temp.clear() print(f"After clear(): {temp}") # ----------------------------------------------------------------------------- # 5. Dictionary Methods # ----------------------------------------------------------------------------- print("\n--- Dictionary Methods ---") info = {"name": "Sara", "age": 28, "job": "Engineer"} # keys() - Get all keys print(f"Keys: {list(info.keys())}") # values() - Get all values print(f"Values: {list(info.values())}") # items() - Get all key-value pairs print(f"Items: {list(info.items())}") # Check if key exists print(f"\n'name' in info: {'name' in info}") print(f"'email' in info: {'email' in info}") # Copy original = {"a": 1, "b": 2} copied = original.copy() copied["c"] = 3 print(f"\nOriginal: {original}") print(f"Copy: {copied}") # fromkeys() - Create dict with same value for all keys keys = ["x", "y", "z"] default_dict = dict.fromkeys(keys, 0) print(f"From keys: {default_dict}") # ----------------------------------------------------------------------------- # 6. Iterating Over Dictionaries # ----------------------------------------------------------------------------- print("\n--- Iterating Over Dictionaries ---") scores = {"Alice": 95, "Bob": 87, "Charlie": 92} # Iterate over keys (default) print("Keys:") for key in scores: print(f" {key}") # Iterate over values print("\nValues:") for value in scores.values(): print(f" {value}") # Iterate over key-value pairs print("\nKey-Value pairs:") for name, score in scores.items(): print(f" {name}: {score}") # With enumerate (if you need index) print("\nWith index:") for idx, (name, score) in enumerate(scores.items(), 1): print(f" {idx}. {name} scored {score}") # ----------------------------------------------------------------------------- # 7. Nested Dictionaries # ----------------------------------------------------------------------------- print("\n--- Nested Dictionaries ---") # Dictionary containing dictionaries company = { "engineering": { "lead": "Alice", "members": ["Bob", "Charlie"], "budget": 50000 }, "marketing": { "lead": "David", "members": ["Eve", "Frank"], "budget": 30000 }, "hr": { "lead": "Grace", "members": ["Henry"], "budget": 20000 } } print("Company structure:") for dept, details in company.items(): print(f"\n {dept.title()} Department:") print(f" Lead: {details['lead']}") print(f" Members: {details['members']}") print(f" Budget: ${details['budget']:,}") # Access nested values print(f"\nEngineering lead: {company['engineering']['lead']}") print(f"Marketing members: {company['marketing']['members']}") # Modify nested value company['hr']['budget'] = 25000 print(f"Updated HR budget: {company['hr']['budget']}") # ----------------------------------------------------------------------------- # 8. Dictionary Comprehensions # ----------------------------------------------------------------------------- print("\n--- Dictionary Comprehensions ---") # Basic dictionary comprehension squares = {x: x**2 for x in range(1, 6)} print(f"Squares: {squares}") # With condition even_squares = {x: x**2 for x in range(1, 11) if x % 2 == 0} print(f"Even squares: {even_squares}") # Transform existing dictionary original = {"a": 1, "b": 2, "c": 3} doubled = {k: v * 2 for k, v in original.items()} print(f"Doubled values: {doubled}") # Swap keys and values flipped = {v: k for k, v in original.items()} print(f"Flipped: {flipped}") # Filter dictionary scores = {"Alice": 95, "Bob": 67, "Charlie": 82, "David": 55} passing = {name: score for name, score in scores.items() if score >= 70} print(f"Passing students: {passing}") # From two lists keys = ["name", "age", "city"] values = ["John", 30, "NYC"] combined = {k: v for k, v in zip(keys, values)} print(f"Combined: {combined}") # ----------------------------------------------------------------------------- # 9. Merging Dictionaries # ----------------------------------------------------------------------------- print("\n--- Merging Dictionaries ---") dict1 = {"a": 1, "b": 2} dict2 = {"c": 3, "d": 4} dict3 = {"b": 99, "e": 5} # Note: 'b' also exists in dict1 # Method 1: update() - modifies in place merged = dict1.copy() merged.update(dict2) print(f"Using update(): {merged}") # Method 2: ** unpacking (Python 3.5+) merged = {**dict1, **dict2} print(f"Using ** unpacking: {merged}") # Method 3: | operator (Python 3.9+) merged = dict1 | dict2 print(f"Using | operator: {merged}") # Later values overwrite earlier ones merged = {**dict1, **dict3} # 'b' will be 99 print(f"With overlap: {merged}") # ----------------------------------------------------------------------------- # 10. Practical Examples # ----------------------------------------------------------------------------- print("\n--- Practical Examples ---") # 1. Word frequency counter text = "the quick brown fox jumps over the lazy dog the fox" words = text.split() frequency = {} for word in words: frequency[word] = frequency.get(word, 0) + 1 print(f"Word frequency: {frequency}") # 2. Grouping items students = [ {"name": "Alice", "grade": "A"}, {"name": "Bob", "grade": "B"}, {"name": "Charlie", "grade": "A"}, {"name": "David", "grade": "B"}, {"name": "Eve", "grade": "A"} ] by_grade = {} for student in students: grade = student["grade"] if grade not in by_grade: by_grade[grade] = [] by_grade[grade].append(student["name"]) print(f"\nStudents by grade: {by_grade}") # 3. Using dict as simple cache/memo cache = {} def fibonacci(n): if n in cache: return cache[n] if n <= 1: return n result = fibonacci(n-1) + fibonacci(n-2) cache[n] = result return result print(f"\nFibonacci(10): {fibonacci(10)}") print(f"Cache: {cache}") # 4. Configuration settings default_config = {"theme": "dark", "font": "Arial", "size": 12} user_config = {"theme": "light", "size": 14} final_config = {**default_config, **user_config} print(f"\nFinal config: {final_config}")