Lesson 2: Data Structures#
Introduction#
Data structures are like different types of containers for organizing your data. Just as you might use a filing cabinet for documents, a spice rack for spices, and a bookshelf for books, Python provides different data structures optimized for different purposes.
Why Data Structures Matter:
Efficiency: Choose the right structure for fast access and updates
Organization: Keep related data together logically
Readability: Make code intentions clear
Performance: Some operations are faster with certain structures
What You’ll Learn:
Lists: ordered, mutable sequences
Tuples: ordered, immutable sequences
Sets: unordered, unique elements
Dictionaries: key-value mappings
Comprehensions: elegant data transformation
Collections module: specialized data structures
Performance characteristics
When to use which structure
1. Lists - Ordered Mutable Sequences#
Lists are the most versatile data structure in Python. Think of them as dynamic arrays that can grow or shrink.
Creating Lists#
# Empty list
empty = []
also_empty = list()
# List with items
numbers = [1, 2, 3, 4, 5]
fruits = ["apple", "banana", "cherry"]
# Mixed types (not recommended, but possible)
mixed = [1, "hello", 3.14, True]
# Nested lists
matrix = [
[1, 2, 3],
[4, 5, 6],
[7, 8, 9]
]
# List from string
chars = list("Python") # ['P', 'y', 't', 'h', 'o', 'n']
# List from range
nums = list(range(5)) # [0, 1, 2, 3, 4]
print(f"Numbers: {numbers}")
print(f"Fruits: {fruits}")
print(f"Matrix: {matrix}")
print(f"Chars: {chars}")
Accessing Elements#
fruits = ["apple", "banana", "cherry", "date", "elderberry"]
# Positive indexing (0-based)
print(f"First: {fruits[0]}") # apple
print(f"Second: {fruits[1]}") # banana
# Negative indexing (from the end)
print(f"Last: {fruits[-1]}") # elderberry
print(f"Second to last: {fruits[-2]}") # date
# Out of range causes IndexError
# print(fruits[10]) # Error!
Slicing Lists#
Slicing syntax: list[start:stop:step]
start: inclusive (default 0)stop: exclusive (default end)step: increment (default 1)
numbers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
print(f"First 3: {numbers[0:3]}") # [0, 1, 2]
print(f"First 3 (short): {numbers[:3]}") # [0, 1, 2]
print(f"Last 3: {numbers[-3:]}") # [7, 8, 9]
print(f"Middle: {numbers[3:7]}") # [3, 4, 5, 6]
# With step
print(f"Every 2nd: {numbers[::2]}") # [0, 2, 4, 6, 8]
print(f"Every 3rd: {numbers[::3]}") # [0, 3, 6, 9]
# Reverse
print(f"Reversed: {numbers[::-1]}") # [9, 8, 7, ..., 0]
# Slice assignment
numbers[2:5] = [20, 30, 40]
print(f"After slice assignment: {numbers}")
Modifying Lists#
fruits = ["apple", "banana"]
# Append (add to end)
fruits.append("cherry")
print(f"After append: {fruits}")
# Insert at specific index
fruits.insert(1, "blueberry")
print(f"After insert: {fruits}")
# Extend (add multiple items)
fruits.extend(["date", "elderberry"])
print(f"After extend: {fruits}")
# Remove by value
fruits.remove("banana")
print(f"After remove: {fruits}")
# Pop (remove and return by index)
last = fruits.pop() # removes last item
print(f"Popped: {last}, List: {fruits}")
second = fruits.pop(1) # removes item at index 1
print(f"Popped: {second}, List: {fruits}")
# Clear all items
# fruits.clear()
# print(f"After clear: {fruits}")
List Operations#
# Concatenation
list1 = [1, 2, 3]
list2 = [4, 5, 6]
combined = list1 + list2
print(f"Combined: {combined}")
# Repetition
repeated = [0] * 5
print(f"Repeated: {repeated}")
# Membership testing
print(f"Is 3 in list1? {3 in list1}")
print(f"Is 10 in list1? {10 in list1}")
# Length
print(f"Length of combined: {len(combined)}")
# Count occurrences
nums = [1, 2, 2, 3, 2, 4]
print(f"Count of 2: {nums.count(2)}")
# Find index
print(f"Index of first 2: {nums.index(2)}")
print(f"Index of 2 after position 2: {nums.index(2, 2)}")
Sorting Lists#
numbers = [3, 1, 4, 1, 5, 9, 2, 6]
# sort() modifies the list in place
numbers.sort()
print(f"Sorted: {numbers}")
# Reverse sort
numbers.sort(reverse=True)
print(f"Reverse sorted: {numbers}")
# sorted() returns a new sorted list
original = [3, 1, 4, 1, 5]
new_sorted = sorted(original)
print(f"Original: {original}")
print(f"New sorted: {new_sorted}")
# Sort by custom key
words = ["banana", "pie", "Washington", "book"]
words.sort(key=str.lower) # Case-insensitive sort
print(f"Case-insensitive sort: {words}")
# Sort by length
words.sort(key=len)
print(f"Sorted by length: {words}")
# Reverse a list
numbers = [1, 2, 3, 4, 5]
numbers.reverse()
print(f"Reversed: {numbers}")
List Copying#
Important: Assignment doesn’t copy lists!
# This doesn't create a copy!
original = [1, 2, 3]
reference = original # Both point to the same list
reference.append(4)
print(f"Original: {original}") # [1, 2, 3, 4] - modified!
print(f"Reference: {reference}") # [1, 2, 3, 4]
# Create a shallow copy
original = [1, 2, 3]
copy1 = original.copy()
copy2 = original[:]
copy3 = list(original)
copy1.append(4)
print(f"Original: {original}") # [1, 2, 3] - unchanged
print(f"Copy: {copy1}") # [1, 2, 3, 4]
# Deep copy for nested lists
import copy
nested = [[1, 2], [3, 4]]
deep = copy.deepcopy(nested)
deep[0].append(999)
print(f"Original nested: {nested}") # [[1, 2], [3, 4]]
print(f"Deep copy: {deep}") # [[1, 2, 999], [3, 4]]
2. Tuples - Immutable Sequences#
Tuples are like lists, but they cannot be modified after creation. They’re useful for data that shouldn’t change.
Creating Tuples#
# Empty tuple
empty = ()
also_empty = tuple()
# Tuple with items
coordinates = (10, 20)
rgb = (255, 128, 0)
# Single item tuple (note the comma!)
single = (5,) # With comma: tuple
not_tuple = (5) # Without comma: just an int in parentheses
print(f"Type of (5,): {type(single)}")
print(f"Type of (5): {type(not_tuple)}")
# Tuples without parentheses
point = 10, 20 # Still a tuple!
print(f"Point: {point}, Type: {type(point)}")
# Convert list to tuple
my_list = [1, 2, 3]
my_tuple = tuple(my_list)
print(f"Tuple from list: {my_tuple}")
Tuple Operations#
coords = (10, 20, 30)
# Indexing and slicing (just like lists)
print(f"First: {coords[0]}")
print(f"Last: {coords[-1]}")
print(f"First two: {coords[:2]}")
# Concatenation
tuple1 = (1, 2, 3)
tuple2 = (4, 5, 6)
combined = tuple1 + tuple2
print(f"Combined: {combined}")
# Repetition
repeated = (0,) * 5
print(f"Repeated: {repeated}")
# Membership
print(f"Is 20 in coords? {20 in coords}")
# Length, count, index
print(f"Length: {len(coords)}")
# Tuples are immutable!
# coords[0] = 99 # TypeError: tuple doesn't support item assignment
Tuple Unpacking#
# Basic unpacking
point = (10, 20)
x, y = point
print(f"x: {x}, y: {y}")
# Swap variables (Python magic!)
a, b = 5, 10
a, b = b, a # Swap!
print(f"a: {a}, b: {b}")
# Unpacking with *
numbers = (1, 2, 3, 4, 5)
first, *middle, last = numbers
print(f"First: {first}")
print(f"Middle: {middle}") # List of middle elements
print(f"Last: {last}")
# Unpacking in function returns
def get_stats(nums):
return min(nums), max(nums), sum(nums) / len(nums)
minimum, maximum, average = get_stats([1, 2, 3, 4, 5])
print(f"Min: {minimum}, Max: {maximum}, Avg: {average}")
Named Tuples#
from collections import namedtuple
# Create a named tuple type
Point = namedtuple('Point', ['x', 'y'])
Person = namedtuple('Person', ['name', 'age', 'city'])
# Create instances
p = Point(10, 20)
print(f"Point: {p}")
print(f"X coordinate: {p.x}")
print(f"Y coordinate: {p[1]}") # Can still use index
# Named tuples are more readable than regular tuples
alice = Person(name="Alice", age=30, city="NYC")
print(f"Name: {alice.name}")
print(f"Age: {alice.age}")
print(f"City: {alice.city}")
3. Sets - Unordered Unique Elements#
Sets are unordered collections of unique elements. Perfect for membership testing and removing duplicates.
Creating Sets#
# Using curly braces
fruits = {"apple", "banana", "cherry"}
numbers = {1, 2, 3, 4, 5}
# Empty set (must use set(), not {})
empty = set() # Correct
not_set = {} # This is an empty dictionary!
print(f"Type of set(): {type(empty)}")
print(f"Type of {{}}: {type(not_set)}")
# From list (removes duplicates)
numbers_with_dupes = [1, 2, 2, 3, 3, 3, 4]
unique_numbers = set(numbers_with_dupes)
print(f"Original: {numbers_with_dupes}")
print(f"Unique: {unique_numbers}")
# From string
letters = set("hello")
print(f"Letters: {letters}") # {'h', 'e', 'l', 'o'}
Set Operations#
fruits = {"apple", "banana", "cherry"}
# Add single element
fruits.add("date")
print(f"After add: {fruits}")
# Add multiple elements
fruits.update(["elderberry", "fig"])
print(f"After update: {fruits}")
# Remove element (raises error if not found)
fruits.remove("banana")
print(f"After remove: {fruits}")
# Discard element (no error if not found)
fruits.discard("grape") # No error even though grape isn't in the set
# Pop (remove arbitrary element)
item = fruits.pop()
print(f"Popped: {item}")
# Membership testing (FAST in sets!)
print(f"Is apple in fruits? {'apple' in fruits}")
# Length
print(f"Number of fruits: {len(fruits)}")
Set Mathematics#
a = {1, 2, 3, 4, 5}
b = {4, 5, 6, 7, 8}
# Union (all elements from both sets)
print(f"Union: {a | b}") # {1, 2, 3, 4, 5, 6, 7, 8}
print(f"Union: {a.union(b)}")
# Intersection (common elements)
print(f"Intersection: {a & b}") # {4, 5}
print(f"Intersection: {a.intersection(b)}")
# Difference (in a but not in b)
print(f"Difference (a - b): {a - b}") # {1, 2, 3}
print(f"Difference (b - a): {b - a}") # {6, 7, 8}
# Symmetric difference (in either set, but not both)
print(f"Symmetric diff: {a ^ b}") # {1, 2, 3, 6, 7, 8}
print(f"Symmetric diff: {a.symmetric_difference(b)}")
# Subset and superset
small = {1, 2}
large = {1, 2, 3, 4}
print(f"Is {small} subset of {large}? {small.issubset(large)}")
print(f"Is {large} superset of {small}? {large.issuperset(small)}")
# Disjoint (no common elements)
set1 = {1, 2, 3}
set2 = {4, 5, 6}
print(f"Are they disjoint? {set1.isdisjoint(set2)}")
Frozen Sets (Immutable Sets)#
# Frozen sets are immutable
frozen = frozenset([1, 2, 3, 4])
print(f"Frozen set: {frozen}")
# Can be used as dictionary keys
dict_with_set_keys = {
frozenset([1, 2]): "first",
frozenset([3, 4]): "second"
}
print(f"Dict with frozenset keys: {dict_with_set_keys}")
# Cannot modify frozen sets
# frozen.add(5) # AttributeError!
4. Dictionaries - Key-Value Mappings#
Dictionaries map keys to values, like a real dictionary maps words to definitions.
Creating Dictionaries#
# Using curly braces
student = {
"name": "Alice",
"age": 20,
"major": "CS",
"gpa": 3.8
}
# Using dict() constructor
person = dict(name="Bob", age=25, city="NYC")
# From list of tuples
pairs = [("a", 1), ("b", 2), ("c", 3)]
letter_to_num = dict(pairs)
# Dict comprehension
squares = {x: x**2 for x in range(5)}
print(f"Squares: {squares}")
# Empty dictionary
empty = {}
also_empty = dict()
print(f"Student: {student}")
print(f"Person: {person}")
Accessing Dictionary Values#
student = {"name": "Alice", "age": 20, "major": "CS"}
# Using square brackets (raises KeyError if key doesn't exist)
print(f"Name: {student['name']}")
# print(student['gpa']) # KeyError!
# Using get() (returns None or default if key doesn't exist)
print(f"Age: {student.get('age')}")
print(f"GPA: {student.get('gpa')}") # None
print(f"GPA: {student.get('gpa', 0.0)}") # 0.0 (default)
# Check if key exists
print(f"Has 'name'? {'name' in student}")
print(f"Has 'gpa'? {'gpa' in student}")
Modifying Dictionaries#
student = {"name": "Alice", "age": 20}
# Add or update key-value pair
student["major"] = "CS" # Add new
student["age"] = 21 # Update existing
print(f"After updates: {student}")
# Update multiple items
student.update({"gpa": 3.8, "year": 3})
print(f"After bulk update: {student}")
# Remove items
del student["year"] # Delete by key
print(f"After del: {student}")
gpa = student.pop("gpa") # Remove and return value
print(f"Popped GPA: {gpa}")
print(f"After pop: {student}")
# Pop with default (no error if key missing)
minor = student.pop("minor", "Undeclared")
print(f"Minor: {minor}")
# popitem() removes and returns arbitrary (key, value)
# item = student.popitem()
# Clear all items
# student.clear()
Iterating Over Dictionaries#
grades = {"Alice": 95, "Bob": 87, "Charlie": 92}
# Iterate over keys (default)
print("Keys:")
for name in grades:
print(f" {name}")
# Iterate over keys explicitly
print("\nKeys (explicit):")
for name in grades.keys():
print(f" {name}")
# Iterate over values
print("\nValues:")
for grade in grades.values():
print(f" {grade}")
# Iterate over key-value pairs
print("\nKey-value pairs:")
for name, grade in grades.items():
print(f" {name}: {grade}")
Nested Dictionaries#
# Dictionary of dictionaries
students = {
"alice": {"age": 20, "major": "CS", "gpa": 3.8},
"bob": {"age": 21, "major": "Math", "gpa": 3.6},
"charlie": {"age": 19, "major": "Physics", "gpa": 3.9}
}
# Access nested values
print(f"Alice's major: {students['alice']['major']}")
print(f"Bob's GPA: {students['bob']['gpa']}")
# Iterate over nested dict
for name, info in students.items():
print(f"{name.capitalize()}: {info['major']}, GPA: {info['gpa']}")
Dictionary Methods#
# setdefault() - get value or set default if key doesn't exist
counts = {}
word = "hello"
for char in word:
counts.setdefault(char, 0)
counts[char] += 1
print(f"Character counts: {counts}")
# fromkeys() - create dict with keys from iterable
keys = ['a', 'b', 'c']
default_dict = dict.fromkeys(keys, 0)
print(f"Dict from keys: {default_dict}")
# Merge dictionaries (Python 3.9+)
dict1 = {"a": 1, "b": 2}
dict2 = {"b": 3, "c": 4}
merged = dict1 | dict2 # dict2 values win on conflict
print(f"Merged: {merged}")
5. Comprehensions#
Comprehensions provide concise syntax for creating collections.
List Comprehensions#
# Basic syntax: [expression for item in iterable]
squares = [x**2 for x in range(10)]
print(f"Squares: {squares}")
# With condition: [expression for item in iterable if condition]
even_squares = [x**2 for x in range(10) if x % 2 == 0]
print(f"Even squares: {even_squares}")
# String manipulation
words = ["hello", "world", "python"]
uppercase = [word.upper() for word in words]
print(f"Uppercase: {uppercase}")
# Nested loops
pairs = [(x, y) for x in range(3) for y in range(3)]
print(f"Pairs: {pairs}")
# If-else expression
labels = ["even" if x % 2 == 0 else "odd" for x in range(5)]
print(f"Labels: {labels}")
# Flatten nested list
matrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
flat = [num for row in matrix for num in row]
print(f"Flattened: {flat}")
Dictionary Comprehensions#
# Basic syntax: {key: value for item in iterable}
squares_dict = {x: x**2 for x in range(5)}
print(f"Squares dict: {squares_dict}")
# From lists
names = ["Alice", "Bob", "Charlie"]
lengths = {name: len(name) for name in names}
print(f"Name lengths: {lengths}")
# With condition
even_squares = {x: x**2 for x in range(10) if x % 2 == 0}
print(f"Even squares: {even_squares}")
# Swap keys and values
original = {"a": 1, "b": 2, "c": 3}
swapped = {v: k for k, v in original.items()}
print(f"Original: {original}")
print(f"Swapped: {swapped}")
# Filter dictionary
grades = {"Alice": 95, "Bob": 75, "Charlie": 92, "Dave": 65}
honor_roll = {name: grade for name, grade in grades.items() if grade >= 90}
print(f"Honor roll: {honor_roll}")
Set Comprehensions#
# Basic syntax: {expression for item in iterable}
squares_set = {x**2 for x in range(10)}
print(f"Squares set: {squares_set}")
# Remove duplicates and transform
text = "hello world"
unique_upper = {char.upper() for char in text if char.isalpha()}
print(f"Unique uppercase letters: {unique_upper}")
# With condition
even_squares = {x**2 for x in range(10) if x % 2 == 0}
print(f"Even squares set: {even_squares}")
Generator Expressions#
Similar to list comprehensions but create generators (memory efficient for large datasets).
# Generator expression (uses parentheses)
squares_gen = (x**2 for x in range(10))
print(f"Generator: {squares_gen}")
print(f"Values: {list(squares_gen)}")
# Memory efficient for large datasets
# List comprehension - creates entire list in memory
squares_list = [x**2 for x in range(1000000)] # Uses lots of memory
# Generator - creates values on demand
squares_gen = (x**2 for x in range(1000000)) # Uses minimal memory
# Use in sum()
total = sum(x**2 for x in range(100))
print(f"Sum of squares: {total}")
6. Collections Module#
Python’s collections module provides specialized container types.
Counter - Count Occurrences#
from collections import Counter
# Count elements
words = ["apple", "banana", "apple", "cherry", "banana", "apple"]
counts = Counter(words)
print(f"Counts: {counts}")
# Most common
print(f"Most common 2: {counts.most_common(2)}")
# Count letters
text = "hello world"
letter_counts = Counter(text)
print(f"Letter counts: {letter_counts}")
# Arithmetic operations
c1 = Counter(a=3, b=1)
c2 = Counter(a=1, b=2)
print(f"c1 + c2: {c1 + c2}") # Add counts
print(f"c1 - c2: {c1 - c2}") # Subtract counts
defaultdict - Default Values#
from collections import defaultdict
# Regular dict - need to check if key exists
word_groups = {}
words = ["apple", "apricot", "banana", "blueberry"]
for word in words:
first_letter = word[0]
if first_letter not in word_groups:
word_groups[first_letter] = []
word_groups[first_letter].append(word)
print(f"Regular dict: {word_groups}")
# defaultdict - automatic default values
word_groups = defaultdict(list) # Default value is empty list
for word in words:
word_groups[word[0]].append(word)
print(f"defaultdict: {dict(word_groups)}")
# With int (for counting)
counts = defaultdict(int) # Default value is 0
for char in "hello":
counts[char] += 1
print(f"Counts: {dict(counts)}")
deque - Double-Ended Queue#
from collections import deque
# Create deque
d = deque([1, 2, 3])
print(f"Initial: {d}")
# Add to right
d.append(4)
print(f"After append: {d}")
# Add to left
d.appendleft(0)
print(f"After appendleft: {d}")
# Remove from right
d.pop()
print(f"After pop: {d}")
# Remove from left
d.popleft()
print(f"After popleft: {d}")
# Rotate
d.rotate(1) # Rotate right
print(f"After rotate(1): {d}")
d.rotate(-2) # Rotate left
print(f"After rotate(-2): {d}")
7. Performance Comparison#
Different data structures have different performance characteristics.
import time
# Create large collections
size = 100000
my_list = list(range(size))
my_set = set(range(size))
# Membership testing: set is MUCH faster
target = size - 1 # Last element
# List membership
start = time.time()
result = target in my_list # O(n) - checks every element
list_time = time.time() - start
# Set membership
start = time.time()
result = target in my_set # O(1) - hash lookup
set_time = time.time() - start
print(f"List membership: {list_time:.6f} seconds")
print(f"Set membership: {set_time:.6f} seconds")
print(f"Set is {list_time/set_time:.0f}x faster!")
Time Complexity Summary#
Operation |
List |
Set |
Dict |
|---|---|---|---|
Access by index/key |
O(1) |
N/A |
O(1) |
Search (membership) |
O(n) |
O(1) |
O(1) |
Insert at end |
O(1) |
O(1) |
O(1) |
Insert at beginning |
O(n) |
O(1) |
N/A |
Delete |
O(n) |
O(1) |
O(1) |
Iteration |
O(n) |
O(n) |
O(n) |
8. When to Use Which Structure#
Use Lists when:
You need ordered data
You need to access elements by index
You need to maintain duplicates
Order matters
Use Tuples when:
Data shouldn’t change
You want to use as dictionary keys
Returning multiple values from functions
Memory efficiency matters
Use Sets when:
You need unique elements only
Fast membership testing is important
Set operations (union, intersection, etc.)
Order doesn’t matter
Use Dictionaries when:
You need key-value mapping
Fast lookup by key
Representing structured data
Counting or grouping items
Exercises#
Exercise 1: List Manipulation#
Create a function that:
Takes a list of numbers
Removes duplicates (preserving order)
Returns a new list with only even numbers, squared
Example: [1, 2, 2, 3, 4, 5, 6, 6] → [4, 16, 36]
# Your code here
Exercise 2: Dictionary Operations#
Given this list of students:
students = [
{"name": "Alice", "age": 20, "grade": 95, "major": "CS"},
{"name": "Bob", "age": 21, "grade": 87, "major": "Math"},
{"name": "Charlie", "age": 19, "grade": 92, "major": "CS"},
{"name": "Dave", "age": 20, "grade": 78, "major": "Physics"}
]
Create a dictionary mapping majors to list of student names
Find the average grade for each major
Get names of all CS students with grade >= 90
students = [
{"name": "Alice", "age": 20, "grade": 95, "major": "CS"},
{"name": "Bob", "age": 21, "grade": 87, "major": "Math"},
{"name": "Charlie", "age": 19, "grade": 92, "major": "CS"},
{"name": "Dave", "age": 20, "grade": 78, "major": "Physics"}
]
# Your code here
Exercise 3: Set Operations#
Given two lists of user IDs:
yesterday = [101, 102, 103, 104, 105, 106]
today = [103, 104, 105, 106, 107, 108, 109]
Find:
Users active both days
Users who churned (active yesterday but not today)
New users (active today but not yesterday)
Total unique users across both days
yesterday = [101, 102, 103, 104, 105, 106]
today = [103, 104, 105, 106, 107, 108, 109]
# Your code here
Exercise 4: Comprehensions#
Use comprehensions to:
Create a list of all even numbers from 1 to 50
Create a dict mapping numbers 1-10 to their cubes
Create a set of all vowels in the sentence “The quick brown fox jumps”
Flatten this nested list:
[[1, 2], [3, 4], [5, 6]]
# Your code here
Exercise 5: Word Frequency#
Given a text string:
text = "the quick brown fox jumps over the lazy dog the fox was quick"
Count frequency of each word using Counter
Find the 3 most common words
Create a dict of words grouped by their length using defaultdict
from collections import Counter, defaultdict
text = "the quick brown fox jumps over the lazy dog the fox was quick"
# Your code here
Exercise 6: Data Transformation#
Transform this data structure:
data = [
("Alice", "Math", 95),
("Bob", "Math", 87),
("Alice", "Science", 92),
("Bob", "Science", 85),
("Charlie", "Math", 90)
]
Into a nested dict:
{
"Alice": {"Math": 95, "Science": 92},
"Bob": {"Math": 87, "Science": 85},
"Charlie": {"Math": 90}
}
data = [
("Alice", "Math", 95),
("Bob", "Math", 87),
("Alice", "Science", 92),
("Bob", "Science", 85),
("Charlie", "Math", 90)
]
# Your code here
Self-Check Quiz#
1. What’s the difference between lists and tuples?
Answer
Lists are mutable (can be changed) while tuples are immutable (cannot be changed after creation).2. Can a set contain duplicate elements?
Answer
No, sets automatically remove duplicates and only store unique elements.3. What is the time complexity of checking if an item is in a set?
Answer
O(1) average case - sets use hash tables for fast lookup.4. What’s returned by dict.get('key') if the key doesn’t exist?
Answer
None (or a default value if provided as second argument).5. How do you create an empty set?
Answer
Use `set()` - NOT `{}` which creates an empty dictionary.6. What’s the difference between list.append() and list.extend()?
Answer
append() adds a single element; extend() adds multiple elements from an iterable.7. Can you use a list as a dictionary key?
Answer
No, dictionary keys must be immutable. Use a tuple instead.8. What does list[::-1] do?
Answer
Reverses the list using slice notation with step -1.9. What’s the purpose of defaultdict?
Answer
Provides default values for missing keys, avoiding KeyError and simplifying code.10. When should you use a tuple instead of a list?
Answer
When data shouldn't change, for dictionary keys, for function returns, or when memory efficiency matters.Key Takeaways#
✅ Lists are ordered, mutable sequences - use for ordered collections
✅ Tuples are ordered, immutable sequences - use for unchanging data
✅ Sets are unordered unique elements - use for membership testing and uniqueness
✅ Dictionaries map keys to values - use for key-value relationships
✅ Comprehensions provide concise syntax for creating collections
✅ Collections module provides specialized data structures (Counter, defaultdict, deque)
✅ Performance matters - sets and dicts use O(1) lookup; lists use O(n)
✅ Choose the right structure based on your needs (order, mutability, uniqueness)
✅ Shallow vs deep copy - be careful when copying nested structures
✅ Set operations (union, intersection, difference) are powerful for data analysis
Pro Tips#
💡 Use sets for fast membership testing - x in my_set is much faster than x in my_list
💡 Use Counter for frequency counting - simpler than manual dictionary updates
💡 Use get() with default - avoid KeyError: dict.get('key', default)
💡 Comprehensions over loops - more Pythonic and often faster
💡 Tuple unpacking - elegant way to swap variables: a, b = b, a
💡 Use defaultdict when building dicts incrementally
💡 frozenset for dict keys - when you need a set as a dictionary key
💡 deque for queues - O(1) operations on both ends
💡 Named tuples for clarity - more readable than indices: point.x vs point[0]
💡 Generator expressions for large datasets - save memory
Common Mistakes to Avoid#
❌ Modifying list while iterating - causes unexpected behavior ✅ Iterate over a copy or use list comprehension
❌ Using {} for empty set - creates empty dict!
✅ Use set()
❌ Forgetting tuple needs comma - (5) is not a tuple
✅ Use (5,) for single-element tuple
❌ Direct assignment for copying - creates reference!
✅ Use .copy(), [:], or copy.deepcopy()
❌ Using mutable objects as dict keys - lists can’t be keys ✅ Use immutable types like strings, numbers, tuples
❌ Assuming dict order before Python 3.7 - dicts were unordered ✅ Dicts are ordered in Python 3.7+, but don’t rely on it for older code
Next Steps#
You now understand Python’s core data structures! Next lessons:
Classes and OOP - Create your own custom data types
Iterators and Generators - Advanced iteration techniques
Algorithms - Sorting, searching, and problem-solving
File I/O - Reading and writing data to files
Data Analysis with Pandas - Working with tabular data
Practice Projects:
Build a contact management system (dicts and lists)
Create a word frequency analyzer (Counter)
Implement a simple database using nested dicts
Design a task queue using deque
Master these data structures - they’re the foundation of all Python programming! 🚀