Programming with Python | Chapter 7: Data Structures – Dictionaries and Sets
Chapter Objectives
- Understand and create dictionaries (
dict
) for storing key-value pairs in Python. - Access, add, modify, and remove items from dictionaries using keys.
- Iterate over dictionary keys, values, and key-value pairs.
- Use common dictionary methods like
.keys()
,.values()
,.items()
,.get()
, and.update()
. - Understand the properties of dictionary keys (unique, immutable).
- Understand and create sets (
set
) for storing unordered collections of unique items. - Perform common set operations: adding/removing elements, union, intersection, difference, symmetric difference.
- Use common set methods like
.add()
,.remove()
,.discard()
,.pop()
,.union()
,.intersection()
, etc. - Recognize appropriate use cases for dictionaries and sets.
Introduction
Chapters 5 introduced lists and tuples, which are ordered sequences accessed by numerical index. This chapter introduces two non-sequential built-in data structures: dictionaries and sets. Dictionaries store data as key-value pairs, allowing you to look up a value using a unique key (like looking up a word in a real dictionary). Sets store an unordered collection of unique items, useful for tasks like removing duplicates or performing mathematical set operations (like finding common elements between two groups).
Theory & Explanation
Dictionaries (dict
): Key-Value Mappings
A dictionary is an unordered collection of items where each item consists of a unique key and an associated value. Dictionaries are enclosed in curly braces {}
. They are incredibly useful for associating related pieces of data.
- Key-Value Pairs: Data is stored as
key: value
. - Keys must be unique: You cannot have duplicate keys in a dictionary.
- Keys must be immutable: Keys must be of an immutable type (like strings, numbers, or tuples containing only immutable elements). Lists cannot be used as keys.
- Values can be anything: Values can be of any data type (numbers, strings, lists, other dictionaries, etc.) and can be duplicated.
- Unordered (Historically): Before Python 3.7, dictionaries were unordered. From Python 3.7 onwards, dictionaries remember the insertion order, but you still access items by key, not by numerical index.
- Mutable: Dictionaries can be changed after creation (add, remove, modify key-value pairs).
Creating Dictionaries:
empty_dict = {}
student = {
"name": "Alice",
"age": 21,
"major": "Computer Science",
"courses": ["CS101", "MA203", "PH101"] # Value can be a list
}
# Using the dict() constructor
config = dict(host="localhost", port=8080, debug=True)
print(student)
print(config)
print(type(student)) # <class 'dict'>
Accessing Values:
You access the value associated with a key using square bracket notation dictionary[key]
. If the key doesn’t exist, this raises a KeyError
.
Dictionary Key-Value Pairs
Each unique, immutable key points to its associated value.
print(f"Student Name: {student['name']}") # Alice
print(f"Student Age: {student['age']}") # 21
# print(student['gpa']) # This would raise a KeyError
# Using the .get() method (safer access)
# .get(key, default=None) returns the value for key if it exists,
# otherwise returns the default value (None if not specified). No KeyError!
major = student.get("major")
gpa = student.get("gpa", "N/A") # Provide a default value if 'gpa' key is missing
print(f"Student Major: {major}") # Computer Science
print(f"Student GPA: {gpa}") # N/A
graph TD subgraph Accessing 'my_dict' A[Start] --> B{Key exists in my_dict?}; B -- Yes --> C["Use my_dict[key]"]; C --> D[Return value]; B -- No --> E["Use my_dict[key]"]; E --> F[Raise KeyError!]; G[Start] --> H{"Use my_dict.get(key, default)"}; H --> I{Key exists in my_dict?}; I -- Yes --> J[Return value]; I -- No --> K["Return default value (e.g., None)"]; J --> L[End]; K --> L; end style F fill:#f99,stroke:#f00,stroke-width:2px style K fill:#cfc,stroke:#080,stroke-width:1px
Adding and Modifying Items:
You can add a new key-value pair or modify the value of an existing key using assignment with square bracket notation.
# Add a new key-value pair
student["email"] = "alice@example.com"
print(student)
# Modify an existing value
student["age"] = 22
print(student)
graph TD A["Start: Assign dict[key] = value"] --> B{Does 'key' already exist in dict?}; B -- Yes --> C[Update existing value associated with 'key' to new 'value']; B -- No --> D[Add new pair: 'key': 'value' to dict]; C --> E[End]; D --> E;
Removing Items:
del dictionary[key]
: Removes the key-value pair with the specified key. RaisesKeyError
if the key doesn’t exist..pop(key, default=None)
: Removes the item with the specified key and returns its value. If the key is not found, it returns thedefault
value (or raisesKeyError
if no default is given)..popitem()
: Removes and returns an arbitrary (key, value) pair (in LIFO order for Python 3.7+). RaisesKeyError
if the dictionary is empty.
# Remove 'major' using del
del student["major"]
print(student)
# Remove 'age' using pop and get its value
removed_age = student.pop("age")
print(f"Removed age: {removed_age}")
print(student)
# Remove an arbitrary item (last inserted in 3.7+)
key, value = student.popitem()
print(f"Removed item: {key}: {value}")
print(student)
Iterating Over Dictionaries:
There are several ways to loop through dictionaries:
student = {"name": "Bob", "id": 123, "dept": "Physics"}
# 1. Iterate over keys (default behavior)
print("\nIterating over keys:")
for k in student: # Equivalent to 'for k in student.keys():'
print(f"Key: {k}, Value: {student[k]}")
# 2. Iterate over values
print("\nIterating over values:")
for v in student.values():
print(f"Value: {v}")
# 3. Iterate over key-value pairs (items)
print("\nIterating over items (key-value pairs):")
for k, v in student.items(): # .items() returns view objects containing (key, value) tuples
print(f"Key: {k}, Value: {v}")
Other Common Dictionary Methods:
Common Dictionary Methods & Operations
Method / Operation | Description | Example (d = {'a': 1, 'b': 2} ) |
Return Value / Result |
---|---|---|---|
d[key] |
Accesses the value associated with key . |
d['a'] |
1 (Raises KeyError if key not found) |
d.get(key, default) |
Accesses value for key . Returns default if key not found (None if default omitted). |
d.get('b') d.get('c', 0) |
2 0 (No KeyError) |
d[key] = value |
Adds a new key-value pair or updates the value if key exists. |
d['c'] = 3 d['a'] = 5 |
d becomes {'a': 5, 'b': 2, 'c': 3} |
del d[key] |
Removes the item with the specified key . |
del d['a'] |
d becomes {'b': 2} (Raises KeyError if key not found) |
d.pop(key, default) |
Removes item with key and returns its value. Returns default if key not found (raises KeyError if no default). |
d.pop('b') d.pop('c', 0) |
Returns 2 , d becomes {'a': 1} Returns 0 (if ‘c’ not in d) |
d.popitem() |
Removes and returns an arbitrary (key, value) pair (LIFO in Python 3.7+). | d.popitem() |
Returns ('b', 2) , d becomes {'a': 1} (Raises KeyError if empty) |
len(d) |
Returns the number of key-value pairs (items). | len(d) |
2 |
key in d |
Checks if key exists in the dictionary’s keys. |
'a' in d 'z' in d |
True False |
d.keys() |
Returns a view object containing the dictionary’s keys. | d.keys() |
dict_keys(['a', 'b']) |
d.values() |
Returns a view object containing the dictionary’s values. | d.values() |
dict_values([1, 2]) |
d.items() |
Returns a view object containing the dictionary’s (key, value) tuple pairs. | d.items() |
dict_items([('a', 1), ('b', 2)]) |
d.update(other_dict) |
Updates d with key-value pairs from other_dict , overwriting existing keys. |
d.update({'b': 3, 'c': 4}) |
d becomes {'a': 1, 'b': 3, 'c': 4} |
d.clear() |
Removes all items from the dictionary. | d.clear() |
d becomes {} |
Dictionaries are mutable mappings optimized for key-based lookups.
Sets (set
): Unordered Collections of Unique Items
A set is an unordered collection of unique, immutable items. Sets are enclosed in curly braces {}
like dictionaries, but they contain individual items separated by commas, not key-value pairs.
- Unordered: Items have no specific index or position.
- Unique: Sets automatically discard duplicate items.
- Mutable: You can add or remove items from a set after creation.
- Items must be immutable: Set elements must be of immutable types (numbers, strings, tuples). You cannot have lists or dictionaries as set elements.
Creating Sets:
empty_set = set() # IMPORTANT: {} creates an empty dictionary, not a set!
numbers = {1, 2, 3, 4, 4, 5} # Duplicates are automatically removed
mixed_set = {1.0, "hello", (1, 2)} # Can contain different immutable types
chars = set("hello world") # Creates a set from characters in the string
print(empty_set)
print(numbers) # {1, 2, 3, 4, 5}
print(mixed_set)
print(chars) # {'l', 'o', 'h', 'd', ' ', 'w', 'r', 'e'} (order may vary)
print(type(numbers)) # <class 'set'>
Adding and Removing Items:
.add(element)
: Adds a single element to the set. If the element is already present, the set remains unchanged..update(iterable)
: Adds all elements from an iterable (like a list, tuple, or another set) to the set..remove(element)
: Removes the specified element. RaisesKeyError
if the element is not found..discard(element)
: Removes the specified element if it is present. Does not raise an error if the element is not found (safer than.remove()
)..pop()
: Removes and returns an arbitrary element from the set (since sets are unordered). RaisesKeyError
if the set is empty..clear()
: Removes all elements from the set.
colors = {"red", "green", "blue"}
print(f"Initial colors: {colors}")
colors.add("yellow")
print(f"After add: {colors}")
colors.update(["orange", "purple", "red"]) # 'red' is ignored as it's a duplicate
print(f"After update: {colors}")
colors.remove("green")
print(f"After remove 'green': {colors}")
# colors.remove("black") # This would raise KeyError
colors.discard("blue")
print(f"After discard 'blue': {colors}")
colors.discard("black") # No error raised
print(f"After discard 'black': {colors}")
removed_color = colors.pop()
print(f"Popped color: {removed_color}")
print(f"After pop: {colors}")
Set Operations:
Sets support powerful mathematical operations.
set_a = {1, 2, 3, 4}
set_b = {3, 4, 5, 6}
# Union
union_set = set_a | set_b
print(f"Union: {union_set}") # {1, 2, 3, 4, 5, 6}
# Intersection
intersection_set = set_a & set_b
print(f"Intersection: {intersection_set}") # {3, 4}
# Difference (A - B)
difference_set = set_a - set_b
print(f"Difference (A - B): {difference_set}") # {1, 2}
# Symmetric Difference
sym_diff_set = set_a ^ set_b
print(f"Symmetric Difference: {sym_diff_set}") # {1, 2, 5, 6}
Union Operation:
graph TD A[Set A] --> C{Combine All Elements}; B[Set B] --> C; C --> D{Remove Duplicates}; D --> E[Result: Union Set]; style E fill:#ccf,stroke:#33a,stroke-width:1px
Intersection Operation
graph TD A[Set A] --> C{Find Common Elements}; B[Set B] --> C; C --> D[Result: Intersection Set]; style D fill:#cfc,stroke:#080,stroke-width:1px
Set Intersection (A & B)
The intersection contains only elements present in both sets.
Difference Operation:
graph TD A[Set A] --> C{Keep elements from A...}; B[Set B] --> D{...that are NOT in B}; C --> D; D --> E["Result: Difference Set (A - B)"]; style E fill:#fcc,stroke:#a33,stroke-width:1px
Symmetric Difference Operation:
graph TD A[Set A] --> C{"Find elements ONLY in A (A - B)"}; B[Set B] --> D{"Find elements ONLY in B (B - A)"}; C --> E{Combine Unique Elements}; D --> E; E --> F[Result: Symmetric Difference Set]; style F fill:#ffc,stroke:#aa3,stroke-width:1px
Other Common Set Methods/Operations:
Common Set Methods & Operations
Method / Operator | Description | Example (s={1, 2}, s2={2, 3} ) |
Result / Return Value |
---|---|---|---|
s.add(elem) |
Adds elem to the set. No effect if already present. |
s.add(3) s.add(2) |
s becomes {1, 2, 3} s remains {1, 2} |
s.update(iterable) |
Adds all elements from iterable to the set. |
s.update([3, 4]) |
s becomes {1, 2, 3, 4} |
s.remove(elem) |
Removes elem from the set. |
s.remove(2) |
s becomes {1} (Raises KeyError if elem not found) |
s.discard(elem) |
Removes elem if present. Does nothing if not found. |
s.discard(1) s.discard(5) |
s becomes {2} s remains {1, 2} (No KeyError) |
s.pop() |
Removes and returns an arbitrary element. | s.pop() |
Returns 1 or 2 , s has one element left.(Raises KeyError if empty) |
s.clear() |
Removes all elements from the set. | s.clear() |
s becomes set() |
len(s) |
Returns the number of elements in the set. | len(s) |
2 |
elem in s |
Checks if elem is present in the set. |
1 in s 5 in s |
True False |
s | s2 s.union(s2) |
Returns a new set with elements from both sets. | s | s2 |
{1, 2, 3} |
s & s2 s.intersection(s2) |
Returns a new set with elements common to both sets. | s & s2 |
{2} |
s - s2 s.difference(s2) |
Returns a new set with elements in s but not in s2 . |
s - s2 |
{1} |
s ^ s2 s.symmetric_difference(s2) |
Returns a new set with elements in either set, but not both. | s ^ s2 |
{1, 3} |
s1 <= s2 s1.issubset(s2) |
Checks if s1 is a subset of s2 . |
{1}.issubset(s) |
True |
s1 >= s2 s1.issuperset(s2) |
Checks if s1 is a superset of s2 . |
s.issuperset({1}) |
True |
s1.isdisjoint(s2) |
Checks if s1 and s2 have no elements in common. |
s.isdisjoint({4, 5}) |
True |
Sets are mutable, unordered collections of unique, immutable items, useful for membership testing and mathematical set operations.
Why Use Sets?
- Removing Duplicates: Quickly get unique items from a list:
unique_items = set(my_list)
. - Membership Testing: Checking if an item exists in a set (
item in my_set
) is very fast (much faster than checking in a list for large collections). - Set Operations: Performing unions, intersections, differences efficiently.
Code Examples
set
Animation (Uniqueness)
Current Set Contents:
Example 1: Dictionary for Word Counting
# word_counter.py
text = "this is a sample text this text has sample words"
word_counts = {} # Initialize an empty dictionary
words = text.lower().split() # Convert to lowercase and split into words
for word in words:
if word in word_counts:
word_counts[word] += 1 # Increment count if word exists
else:
word_counts[word] = 1 # Add word with count 1 if new
print("Word Counts:")
# Print sorted by word
for word, count in sorted(word_counts.items()): # .items() gets pairs, sorted() sorts them by key
print(f"- {word}: {count}")
# Find the most frequent word
most_frequent_word = ""
max_count = 0
for word, count in word_counts.items():
if count > max_count:
max_count = count
most_frequent_word = word
print(f"\nMost frequent word: '{most_frequent_word}' (appeared {max_count} times)")
Explanation:
- We initialize an empty dictionary
word_counts
. - The text is converted to lowercase and split into a list of words.
- The
for
loop iterates through thewords
list. - Inside the loop, we check if the
word
is already a key inword_counts
.- If yes, we increment its associated value (count).
- If no, we add the
word
as a new key with a value of 1.
- Finally, we iterate through the
.items()
of the completed dictionary to print the counts and find the most frequent word.sorted(word_counts.items())
sorts the key-value pairs alphabetically by key before printing.
Example 2: Using Sets for Finding Common Elements
# common_elements.py
group_a_skills = {"Python", "Git", "HTML", "CSS", "SQL"}
group_b_skills = {"Java", "Git", "SQL", "Testing", "Docker"}
# Find skills common to both groups
common_skills = group_a_skills.intersection(group_b_skills)
# Alternative: common_skills = group_a_skills & group_b_skills
print(f"Common skills: {common_skills}")
# Find skills unique to group A
unique_to_a = group_a_skills.difference(group_b_skills)
# Alternative: unique_to_a = group_a_skills - group_b_skills
print(f"Skills unique to Group A: {unique_to_a}")
# Find skills unique to group B
unique_to_b = group_b_skills.difference(group_a_skills)
# Alternative: unique_to_b = group_b_skills - group_a_skills
print(f"Skills unique to Group B: {unique_to_b}")
# Find all unique skills across both groups
all_skills = group_a_skills.union(group_b_skills)
# Alternative: all_skills = group_a_skills | group_b_skills
print(f"All unique skills: {all_skills}")
# Check if one group's skills are a subset of another (example)
required_skills = {"Python", "Git"}
print(f"Group A has all required skills? {required_skills.issubset(group_a_skills)}") # True
Explanation:
- Two sets,
group_a_skills
andgroup_b_skills
, are created. - Set methods (
.intersection()
,.difference()
,.union()
) and operators (&
,-
,|
) are used to find common skills, unique skills, and all skills combined. .issubset()
checks if one set is contained within another.
Common Mistakes or Pitfalls
KeyError
(Dictionaries): Accessing a dictionary key that doesn't exist usingmy_dict[key]
. Usemy_dict.get(key)
or check withif key in my_dict:
first.- Using Mutable Keys (Dictionaries): Trying to use a list or another mutable type as a dictionary key will raise a
TypeError
. Keys must be immutable. {}
Creates Empty Dictionary: Using{}
creates an empty dictionary, not an empty set. Useset()
to create an empty set.- Sets are Unordered: Relying on the order of elements in a set. While insertion order might be preserved in some Python versions for some operations, it's not guaranteed and should not be relied upon.
KeyError
vs No Error (Sets): Usingmy_set.remove(element)
for an element that might not exist raisesKeyError
. Usemy_set.discard(element)
if you don't want an error in that case.- Modifying While Iterating: Modifying dictionaries or sets while iterating over them directly can sometimes lead to
RuntimeError
or unexpected behavior. It's often safer to iterate over a copy of the keys/items or collect changes and apply them after the loop.
Chapter Summary
Dictionaries (dict)
store unordered (insertion order preserved since 3.7) collections of key-value pairs.- Created with
{key: value, ...}
ordict()
. - Accessed via
my_dict[key]
(raisesKeyError
) ormy_dict.get(key, default)
. - Keys must be unique and immutable; values can be anything.
- Mutable: items can be added, modified (
my_dict[key] = value
), or removed (del
,.pop()
,.popitem()
). - Iterate using
.keys()
,.values()
,.items()
.
- Created with
Sets (set)
store unordered collections of unique, immutable items.- Created with
{item1, item2, ...}
(if not empty) orset()
. - Automatically handle uniqueness. Items must be immutable.
- Mutable: items can be added (
.add()
,.update()
) or removed (.remove()
,.discard()
,.pop()
). - Support fast membership testing (
item in my_set
). - Support mathematical operations: union (
|
), intersection (&
), difference (-
), symmetric difference (^
).
- Created with
Exercises & Mini Projects
Exercises
- Dictionary Creation and Access: Create a dictionary representing a book with keys "title", "author", and "year". Print the author's name. Try accessing the "ISBN" key using
.get()
with a default value. - Dictionary Modification: Start with the book dictionary from Exercise 1. Add a new key "genre" with an appropriate value. Change the "year" to a different value. Remove the "author" key using
.pop()
. Print the final dictionary. - Set Creation and Uniqueness: Create a list with duplicate numbers, e.g.,
[1, 2, 5, 2, 3, 4, 5, 1]
. Create a set from this list to get the unique numbers. Print the resulting set. - Set Operations: Create two sets:
set1 = {10, 20, 30, 40}
andset2 = {30, 40, 50, 60}
. Calculate and print their union, intersection, and the difference (set1 - set2
). - Dictionary Iteration: Create a dictionary mapping country names (strings) to their capitals (strings). Iterate through the dictionary using
.items()
and print each country and its capital in the format "The capital of [Country] is [Capital]."
Mini Project: Contact Book
Goal: Create a simple command-line contact book using a dictionary.
Steps:
- Initialize an empty dictionary called
contacts
. This dictionary will store names (keys) and phone numbers (values), both as strings. - Use a
while True:
loop for the main menu (similar to the To-Do List project). - Provide options:
- 1: Add/Update Contact
- 2: View Contact
- 3: Delete Contact
- 4: View All Contacts
- 5: Exit
- Implement the choices using
if-elif-else
:- Choice 1 (Add/Update):
- Ask for the contact's name (key).
- Ask for the contact's phone number (value).
- Add or update the entry in the
contacts
dictionary:contacts[name] = phone_number
. Print a confirmation.
- Choice 2 (View):
- Ask for the name to view.
- Use
.get(name)
to retrieve the phone number. - If
.get()
returns a value (notNone
), print the name and number. - If
.get()
returnsNone
(or your chosen default), print "Contact not found."
- Choice 3 (Delete):
- Ask for the name to delete.
- Check if the name exists as a key (
if name in contacts:
). - If it exists, use
del contacts[name]
orcontacts.pop(name)
to remove it. Print a confirmation. - If it doesn't exist, print "Contact not found."
- Choice 4 (View All):
- Check if the dictionary is empty (
if not contacts:
). If so, print "Contact book is empty." - If not empty, print "--- All Contacts ---".
- Iterate through the dictionary using
.items()
and print each name and phone number.
- Check if the dictionary is empty (
- Choice 5 (Exit):
- Print "Exiting contact book."
- Use
break
to exit thewhile True:
loop.
- Else (Invalid Choice):
- Print an error message.
- Choice 1 (Add/Update):
Additional Sources: