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/Agraph 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:1pxAdding 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. RaisesKeyErrorif 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 thedefaultvalue (or raisesKeyErrorif no default is given)..popitem(): Removes and returns an arbitrary (key, value) pair (in LIFO order for Python 3.7+). RaisesKeyErrorif 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) |
20 (No KeyError) |
d[key] = value |
Adds a new key-value pair or updates the value if key exists. |
d['c'] = 3d['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 |
TrueFalse |
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. RaisesKeyErrorif 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). RaisesKeyErrorif 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:1pxIntersection 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:1pxSet 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:1pxOther 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 s5 in s |
TrueFalse |
s | s2s.union(s2) |
Returns a new set with elements from both sets. | s | s2 |
{1, 2, 3} |
s & s2s.intersection(s2) |
Returns a new set with elements common to both sets. | s & s2 |
{2} |
s - s2s.difference(s2) |
Returns a new set with elements in s but not in s2. |
s - s2 |
{1} |
s ^ s2s.symmetric_difference(s2) |
Returns a new set with elements in either set, but not both. | s ^ s2 |
{1, 3} |
s1 <= s2s1.issubset(s2) |
Checks if s1 is a subset of s2. |
{1}.issubset(s) |
True |
s1 >= s2s1.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
forloop iterates through thewordslist. - Inside the loop, we check if the
wordis already a key inword_counts.- If yes, we increment its associated value (count).
- If no, we add the
wordas 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)}") # TrueExplanation:
- Two sets,
group_a_skillsandgroup_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.
KeyErrorvs 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
RuntimeErroror 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
contactsdictionary: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
breakto exit thewhile True:loop.
- Else (Invalid Choice):
- Print an error message.
- Choice 1 (Add/Update):
Additional Sources:


