python-by-example/04_data_structures/04_dictionaries.py
2025-12-30 08:50:00 +02:00

352 lines
9.7 KiB
Python

"""
================================================================================
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}")