Introduction to Loops
Welcome to our exploration of loops in Python! If conditional statements are the decision-makers in programming, loops are the workhorses—they allow us to execute code repeatedly, automating repetitive tasks that would be impractical to write manually.
Imagine you're planting a garden. If conditional statements are like deciding which plants go where based on sun exposure, loops are like the act of planting 100 seeds one by one. You wouldn't write out 100 separate planting instructions—you'd use a loop to repeat the planting process.
The code for this lesson can be found in the /week2/day2/python_loops.py file in your course repository.
Types of Loops in Python
Python provides two primary loop structures:
forloops: Used for iterating over a sequence (like a list, tuple, dictionary, set, or string)whileloops: Used for executing code repeatedly as long as a condition is true
Think of a for loop as a tour guide taking you to each specific landmark on a predefined route, while a while loop is more like a security guard who keeps patrolling until something changes.
For Loops: Iterating Over Sequences
The for loop in Python is designed for iterating over a sequence. Unlike for loops in some other programming languages that focus on numeric ranges, Python's for loop is more like a "for each" loop.
Basic Syntax
for item in sequence:
# Code to execute for each item
statement1
statement2
# ... more statements
In this pattern:
itemis a variable that takes the value of each element in the sequence, one at a timesequenceis any iterable object (list, tuple, string, etc.)- The indented block of code executes once for each item in the sequence
Example: Iterating Over a List
fruits = ["apple", "banana", "cherry", "date"]
for fruit in fruits:
print(f"I like {fruit}s.")
# Output:
# I like apples.
# I like bananas.
# I like cherrys.
# I like dates.
Notice how the variable fruit takes on each value in the list, one at a time, and the print statement executes for each value.
Loops with Strings
message = "Python"
for character in message:
print(character)
# Output:
# P
# y
# t
# h
# o
# n
Strings in Python are sequences of characters, so a for loop iterates through each character.
Loops with Dictionaries
student = {
"name": "Alice",
"age": 25,
"courses": ["Python", "Data Science", "Web Development"]
}
# Iterating over keys (default)
for key in student:
print(key)
# Output:
# name
# age
# courses
# Iterating over values
for value in student.values():
print(value)
# Output:
# Alice
# 25
# ['Python', 'Data Science', 'Web Development']
# Iterating over key-value pairs
for key, value in student.items():
print(f"{key}: {value}")
# Output:
# name: Alice
# age: 25
# courses: ['Python', 'Data Science', 'Web Development']
When iterating over dictionaries, you can choose to access keys, values, or both together.
Using the range() Function
The range() function generates a sequence of numbers, which is commonly used with for loops when you need to perform an action a specific number of times.
# range(stop) - Generates numbers from 0 to stop-1
for i in range(5):
print(i)
# Output:
# 0
# 1
# 2
# 3
# 4
# range(start, stop) - Generates numbers from start to stop-1
for i in range(2, 7):
print(i)
# Output:
# 2
# 3
# 4
# 5
# 6
# range(start, stop, step) - Generates numbers from start to stop-1 with a step interval
for i in range(1, 10, 2):
print(i)
# Output:
# 1
# 3
# 5
# 7
# 9
Think of range() as a tour planner that creates an itinerary of stops (numbers) for your for loop to visit.
Accessing Index with enumerate()
Sometimes you need both the index position and the value of each item in a sequence. The enumerate() function makes this easy.
fruits = ["apple", "banana", "cherry", "date"]
for index, fruit in enumerate(fruits):
print(f"Index {index}: {fruit}")
# Output:
# Index 0: apple
# Index 1: banana
# Index 2: cherry
# Index 3: date
# You can also specify a starting index
for index, fruit in enumerate(fruits, start=1):
print(f"Fruit #{index}: {fruit}")
# Output:
# Fruit #1: apple
# Fruit #2: banana
# Fruit #3: cherry
# Fruit #4: date
This is like having a tour guide who both shows you each landmark and tells you which stop number you're at.
Real-World Example: Processing Data Records
students = [
{"name": "Alice", "grade": 85, "attendance": 0.92},
{"name": "Bob", "grade": 72, "attendance": 0.85},
{"name": "Charlie", "grade": 90, "attendance": 0.95},
{"name": "Diana", "grade": 65, "attendance": 0.70},
{"name": "Eliza", "grade": 91, "attendance": 0.98}
]
# Calculate class average
total_grade = 0
high_performers = []
for student in students:
total_grade += student["grade"]
# Identify high performers (good grades and attendance)
if student["grade"] >= 85 and student["attendance"] >= 0.9:
high_performers.append(student["name"])
average_grade = total_grade / len(students)
print(f"Class average grade: {average_grade:.2f}")
print(f"High performers: {', '.join(high_performers)}")
# Output:
# Class average grade: 80.60
# High performers: Alice, Charlie, Eliza
This example shows how loops are essential for data processing tasks, allowing us to analyze multiple records with a single block of code.
While Loops: Repeating Based on Conditions
Unlike for loops that iterate over a sequence, while loops continue executing as long as a specified condition remains true. They're perfect for situations where you don't know in advance how many iterations you'll need.
Basic Syntax
while condition:
# Code to execute while the condition is True
statement1
statement2
# ... more statements
In this pattern:
- The
conditionis evaluated before each loop iteration - If the condition is True, the indented block executes
- After execution, flow returns to evaluate the condition again
- When the condition becomes False, the loop terminates
Example: Basic While Loop
count = 1
while count <= 5:
print(f"Count is: {count}")
count += 1 # Without this, we'd have an infinite loop!
# Output:
# Count is: 1
# Count is: 2
# Count is: 3
# Count is: 4
# Count is: 5
Notice the crucial update to count within the loop. Without it, the condition would always be true, creating an infinite loop!
Using While for User Input Validation
valid_input = False
while not valid_input:
age = input("Enter your age (18-100): ")
if age.isdigit():
age = int(age)
if 18 <= age <= 100:
valid_input = True
print(f"Thank you, your age ({age}) has been recorded.")
else:
print("Age must be between 18 and 100.")
else:
print("Please enter a valid number.")
This loop continues asking for input until the user provides a valid age. It's a perfect example of not knowing in advance how many iterations will be needed.
While Loop with a Sentinel Value
print("Enter numbers to sum (enter 'done' to finish)")
sum_total = 0
user_input = ""
while user_input.lower() != 'done':
user_input = input("Enter a number (or 'done'): ")
if user_input.lower() == 'done':
break # Exit the loop early
if user_input.isdigit():
sum_total += int(user_input)
else:
print("That's not a valid number. Try again.")
print(f"The sum of your numbers is: {sum_total}")
This pattern uses a "sentinel value" ('done') to signal when to end the loop. It's commonly used when reading input until a specific termination signal.
Building a Simple Game Loop
import random
health = 100
rounds = 0
game_active = True
print("Welcome to Dragon Battle! Try to defeat the dragon.")
while game_active:
rounds += 1
print(f"\nRound {rounds}")
print(f"Your health: {health}")
action = input("What will you do? (attack/heal/run): ").lower()
if action == "attack":
player_damage = random.randint(10, 25)
dragon_damage = random.randint(5, 20)
print(f"You dealt {player_damage} damage to the dragon!")
print(f"The dragon strikes back and deals {dragon_damage} damage!")
health -= dragon_damage
if player_damage >= 20:
print("Critical hit! The dragon is severely wounded!")
game_active = False
print("Victory! You've defeated the dragon!")
elif action == "heal":
heal_amount = random.randint(10, 20)
dragon_damage = random.randint(5, 10)
health += heal_amount
health -= dragon_damage
print(f"You restored {heal_amount} health!")
print(f"The dragon deals {dragon_damage} damage while you're healing.")
elif action == "run":
escape_chance = random.random()
if escape_chance > 0.5:
print("You escaped successfully!")
game_active = False
else:
dragon_damage = random.randint(10, 25)
health -= dragon_damage
print(f"You failed to escape! The dragon deals {dragon_damage} damage!")
else:
print("Invalid action. You hesitate and lose your turn!")
health -= random.randint(5, 10)
# Check for defeat condition
if health <= 0:
print("Your health has reached zero. The dragon has defeated you!")
game_active = False
print("\nGame Over!")
This example demonstrates a game loop, one of the most common applications of while loops in programming. The loop continues until a win or loss condition is met.
Loop Control: break, continue, pass
Python provides three statements that can alter the flow of a loop: break, continue, and pass.
The break Statement
The break statement immediately terminates the loop and transfers control to the statement following the loop.
# Finding the first even number in a list
numbers = [5, 7, 11, 2, 9, 8, 3]
for num in numbers:
if num % 2 == 0:
print(f"Found an even number: {num}")
break # Exit loop after finding first even number
# Output:
# Found an even number: 2
Think of break as an emergency exit—it allows you to leave the loop immediately when a specific condition is met.
The continue Statement
The continue statement skips the rest of the current iteration and jumps to the next iteration of the loop.
# Printing only odd numbers
for num in range(1, 10):
if num % 2 == 0:
continue # Skip even numbers
print(num)
# Output:
# 1
# 3
# 5
# 7
# 9
Think of continue as a "skip" button—it allows you to bypass certain iterations based on specific conditions.
The pass Statement
The pass statement is a null operation: it does nothing. It's used as a placeholder when a statement is syntactically required but you don't want any action.
# Using pass as a placeholder
for item in some_list:
if condition1:
# Do something
elif condition2:
# Do something else
else:
pass # Nothing to do here, but syntactically needed
Think of pass as a "do nothing" instruction—it's useful for creating syntactically correct code blocks that don't have any implementation yet.
Practical Example: Data Filtering with Control Statements
def process_transactions(transactions):
approved = []
flagged = []
for transaction in transactions:
# Skip processing for zero-amount transactions
if transaction["amount"] == 0:
continue
# Flag suspicious high-value transactions
if transaction["amount"] > 10000:
transaction["status"] = "flagged"
flagged.append(transaction)
continue
# Check for blacklisted merchants
if transaction["merchant"] in blacklisted_merchants:
transaction["status"] = "rejected"
continue
# If all checks pass, approve the transaction
transaction["status"] = "approved"
approved.append(transaction)
return approved, flagged
# Sample usage
transactions = [
{"id": 1, "amount": 75.50, "merchant": "Grocery Store"},
{"id": 2, "amount": 0, "merchant": "Test Transaction"},
{"id": 3, "amount": 12500, "merchant": "Car Dealership"},
{"id": 4, "amount": 199.99, "merchant": "Scam Website"},
{"id": 5, "amount": 850, "merchant": "Online Retailer"}
]
blacklisted_merchants = ["Scam Website", "Fraudulent Store"]
approved, flagged = process_transactions(transactions)
print("Approved transactions:")
for t in approved:
print(f"ID: {t['id']}, Amount: ${t['amount']}, Merchant: {t['merchant']}")
print("\nFlagged transactions:")
for t in flagged:
print(f"ID: {t['id']}, Amount: ${t['amount']}, Merchant: {t['merchant']}")
This example demonstrates how continue can be used to implement sophisticated filtering logic in data processing applications.
Infinite Loops and How to Avoid Them
An infinite loop is a loop that continues indefinitely because its termination condition is never met. While sometimes intentional, infinite loops are often the result of programming errors.
Intentional Infinite Loop
# Simple program that runs until explicitly stopped
while True:
command = input("Enter command (type 'exit' to quit): ")
if command.lower() == 'exit':
print("Exiting program...")
break
# Process other commands
print(f"You entered: {command}")
Here, the while True creates a deliberate infinite loop that continues until the user enters 'exit', triggering the break statement.
Common Causes of Unintentional Infinite Loops
- Forgetting to update the loop variable
# Infinite loop - count never changes! count = 1 while count <= 5: print(f"Count is: {count}") # Missing: count += 1 - Condition that's always true
# Infinite loop - condition is always true! x = 10 while x > 5: print("x is greater than 5") # x is never decreased - Logical errors in condition updates
# Infinite loop - wrong direction! i = 10 while i > 0: print(i) i += 1 # Increasing instead of decreasing!
Safeguards Against Infinite Loops
- Always verify your loop variable update - Ensure it's moving toward the termination condition
- Add a safety counter - Implement a maximum iteration count as a fallback
max_iterations = 1000 iteration = 0 while some_condition: # Loop body iteration += 1 if iteration >= max_iterations: print("Maximum iterations reached. Breaking loop.") break - Use debugging print statements - To track the value of key variables
- Test termination conditions separately - Before implementing the loop
Remember: if you do get stuck in an infinite loop while running a Python script, you can typically terminate it by pressing Ctrl+C (or Cmd+C on Mac).
Nested Loops
A nested loop is a loop inside another loop. The inner loop completes all its iterations for each single iteration of the outer loop.
Basic Structure
for outer_item in outer_sequence:
# Outer loop code
for inner_item in inner_sequence:
# Inner loop code
# This runs completely for each iteration of the outer loop
Think of nested loops like the hands on a clock: for each hour (outer loop), the minute hand makes a complete 60-minute cycle (inner loop).
Example: Multiplication Table
# Generating a multiplication table
for i in range(1, 6): # Outer loop: 1 to 5
for j in range(1, 6): # Inner loop: 1 to 5
product = i * j
print(f"{i} × {j} = {product}", end="\t")
print() # New line after each row
# Output:
# 1 × 1 = 1 1 × 2 = 2 1 × 3 = 3 1 × 4 = 4 1 × 5 = 5
# 2 × 1 = 2 2 × 2 = 4 2 × 3 = 6 2 × 4 = 8 2 × 5 = 10
# 3 × 1 = 3 3 × 2 = 6 3 × 3 = 9 3 × 4 = 12 3 × 5 = 15
# 4 × 1 = 4 4 × 2 = 8 4 × 3 = 12 4 × 4 = 16 4 × 5 = 20
# 5 × 1 = 5 5 × 2 = 10 5 × 3 = 15 5 × 4 = 20 5 × 5 = 25
Here, for each value of i, the inner loop goes through all values of j, creating a complete row of the table.
Pattern Printing with Nested Loops
# Printing a right-angled triangle pattern
rows = 5
for i in range(1, rows + 1):
for j in range(1, i + 1):
print("*", end=" ")
print() # New line after each row
# Output:
# *
# * *
# * * *
# * * * *
# * * * * *
This example demonstrates how nested loops are essential for creating patterns where each row has a different number of elements.
Real-World Example: Processing Multi-dimensional Data
student_grades = [
["Alice", [85, 90, 92, 88]],
["Bob", [75, 82, 79, 84]],
["Charlie", [95, 97, 91, 93]],
["Diana", [70, 65, 72, 69]]
]
print("Student Grade Analysis")
print("-" * 30)
for student, grades in student_grades:
total = 0
highest = 0
lowest = 100
print(f"{student}'s grades:")
for index, grade in enumerate(grades, start=1):
print(f" Test {index}: {grade}")
total += grade
highest = max(highest, grade)
lowest = min(lowest, grade)
average = total / len(grades)
print(f" Average: {average:.2f}")
print(f" Highest: {highest}")
print(f" Lowest: {lowest}")
print()
# Output:
# Student Grade Analysis
# ------------------------------
# Alice's grades:
# Test 1: 85
# Test 2: 90
# Test 3: 92
# Test 4: 88
# Average: 88.75
# Highest: 92
# Lowest: 85
#
# Bob's grades:
# Test 1: 75
# Test 2: 82
# Test 3: 79
# Test 4: 84
# Average: 80.00
# Highest: 84
# Lowest: 75
# ...etc.
This example shows how nested loops are essential for processing multi-dimensional data structures, like records containing lists.
Performance Considerations with Nested Loops
Nested loops multiply the number of iterations. With two loops of sizes n and m, the inner code runs n×m times. This can become performance-intensive with large datasets.
# O(n²) time complexity with nested loops
n = 1000
# This inner code will run 1,000,000 times!
for i in range(n):
for j in range(n):
# Do something
pass
Always consider alternatives when working with large datasets:
- Use list comprehensions for simple transformations
- Use library functions optimized for performance (e.g., NumPy operations)
- Consider if the problem can be solved with a single loop
List Comprehensions: Compact Loop Alternatives
List comprehensions provide a concise way to create lists based on existing sequences. They combine a for loop and a list creation into a single line.
Basic Syntax
[expression for item in iterable if condition]
This creates a new list by applying an expression to each item in an iterable that satisfies an optional condition.
Simple List Comprehension Examples
# Traditional for loop
squares = []
for i in range(1, 11):
squares.append(i ** 2)
# Equivalent list comprehension
squares = [i ** 2 for i in range(1, 11)]
print(squares) # [1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
# With a condition (only even numbers)
even_squares = [i ** 2 for i in range(1, 11) if i % 2 == 0]
print(even_squares) # [4, 16, 36, 64, 100]
List comprehensions aren't just shorter—they're often faster and more readable once you're familiar with the syntax.
More Complex Examples
# Flattening a 2D list
matrix = [[1, 2, 3], [4, a5, 6], [7, 8, 9]]
flattened = [num for row in matrix for num in row]
print(flattened) # [1, 2, 3, 4, 5, 6, 7, 8, 9]
# Creating a dictionary with comprehension
squares_dict = {i: i**2 for i in range(1, 6)}
print(squares_dict) # {1: 1, 2: 4, 3: 9, 4: a16, 5: 25}
# Filtering and transforming strings
words = ["apple", "banana", "cherry", "date", "elderberry"]
long_words_upper = [word.upper() for word in words if len(word) > 5]
print(long_words_upper) # ['BANANA', 'CHERRY', 'ELDERBERRY']
The nested loop syntax in list comprehensions ([item for sublist in list for item in sublist]) follows the same order as actual nested loops.
When to Use List Comprehensions
List comprehensions shine when:
- You need to transform each element in a sequence
- You need to filter elements based on a condition
- The operation is simple enough to express clearly in one line
However, they can become hard to read with complex operations. In such cases, traditional loops often provide better readability.
Real-World Example: Data Cleaning with Comprehensions
raw_data = [
"42",
"N/A",
"37.5",
"",
"error",
"19",
"41.8",
"not recorded"
]
# Extract and convert all valid numeric values
cleaned_data = [float(value) for value in raw_data
if value.replace(".", "", 1).isdigit()]
print(cleaned_data) # [42.0, 37.5, 19.0, 41.8]
# Calculate statistics
if cleaned_data:
average = sum(cleaned_data) / len(cleaned_data)
minimum = min(cleaned_data)
maximum = max(cleaned_data)
print(f"Average: {average:.2f}")
print(f"Minimum: {minimum:.2f}")
print(f"Maximum: {maximum:.2f}")
print(f"Valid readings: {len(cleaned_data)}/{len(raw_data)}")
This example shows how a list comprehension can elegantly handle data cleaning and filtering in a single line.
Common Loop Patterns and Techniques
Certain loop patterns appear frequently in programming. Recognizing these patterns can help you solve problems more efficiently.
Accumulation Pattern
This pattern builds a result by updating a variable in each iteration.
# Sum accumulation
numbers = [1, 2, 3, 4, 5]
total = 0
for num in numbers:
total += num
print(total) # 15
# String accumulation
words = ["Python", "is", "awesome"]
sentence = ""
for word in words:
sentence += word + " "
print(sentence.strip()) # "Python is awesome"
Filtering Pattern
This pattern selects elements from a sequence based on a condition.
# Traditional filtering
numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
evens = []
for num in numbers:
if num % 2 == 0:
evens.append(num)
print(evens) # [2, 4, 6, 8, 10]
# With list comprehension
evens = [num for num in numbers if num % 2 == 0]
Mapping Pattern
This pattern transforms each element in a sequence to create a new sequence.
# Traditional mapping
numbers = [1, 2, 3, 4, 5]
squared = []
for num in numbers:
squared.append(num ** 2)
print(squared) # [1, 4, 9, 16, 25]
# With list comprehension
squared = [num ** 2 for num in numbers]
Search Pattern
This pattern looks for an element in a sequence that meets a specific condition.
numbers = [4, 7, 2, 9, 3, 1, 8]
target = 9
found = False
position = -1
for i, num in enumerate(numbers):
if num == target:
found = True
position = i
break
if found:
print(f"Found {target} at position {position}.")
else:
print(f"{target} not found in the list.")
Parallel Iteration Pattern
This pattern processes multiple sequences simultaneously.
names = ["Alice", "Bob", "Charlie"]
scores = [85, 92, 78]
# Using zip to iterate over multiple sequences together
for name, score in zip(names, scores):
print(f"{name}: {score}")
# Output:
# Alice: 85
# Bob: 92
# Charlie: 78
Counting Pattern
This pattern counts occurrences that meet specific criteria.
text = "Python programming is fun and Python is easy to learn"
words = text.split()
python_count = 0
for word in words:
if word.lower() == "python":
python_count += 1
print(f"'Python' appears {python_count} times in the text.")
Max/Min Pattern
This pattern finds the maximum or minimum value in a sequence.
temperatures = [72, 68, 75, 63, 80, 67, 78]
highest = temperatures[0] # Initialize with first value
lowest = temperatures[0]
for temp in temperatures[1:]: # Start from the second item
if temp > highest:
highest = temp
if temp < lowest:
lowest = temp
print(f"Highest temperature: {highest}°F")
print(f"Lowest temperature: {lowest}°F")
Real-World Example: Multiple Patterns in a Data Analysis Task
sales_data = [
{"date": "2023-01-15", "product": "Laptop", "amount": 1200, "units": 1},
{"date": "2023-01-16", "product": "Mouse", "amount": 25, "units": 5},
{"date": "2023-01-16", "product": "Laptop", "amount": 2400, "units": 2},
{"date": "2023-01-17", "product": "Keyboard", "amount": 80, "units": 2},
{"date": "2023-01-18", "product": "Mouse", "amount": 30, "units": 6},
{"date": "2023-01-19", "product": "Monitor", "amount": 350, "units": 1},
{"date": "2023-01-20", "product": "Laptop", "amount": 1200, "units": 1}
]
# Analysis goals:
# 1. Total sales amount
# 2. Count the number of each product sold
# 3. Find the day with highest sales
# 4. Calculate average sale amount per transaction
# Accumulation for total sales
total_sales = 0
# Counting occurrences for each product
product_counts = {}
# Finding max for day with highest sales
best_day = {"date": "", "amount": 0}
# Prepare for average calculation
transaction_count = len(sales_data)
# Main loop with multiple patterns
for sale in sales_data:
# Accumulation pattern for total
total_sales += sale["amount"]
# Counting pattern for products
product = sale["product"]
if product in product_counts:
product_counts[product] += sale["units"]
else:
product_counts[product] = sale["units"]
# Max pattern for best day
if sale["amount"] > best_day["amount"]:
best_day["date"] = sale["date"]
best_day["amount"] = sale["amount"]
# Calculate average (not in the loop)
average_sale = total_sales / transaction_count
# Output results
print(f"Total sales: ${total_sales}")
print("\nProduct units sold:")
for product, count in product_counts.items():
print(f" {product}: {count} units")
print(f"\nBest sales day: {best_day['date']} (${best_day['amount']})")
print(f"Average sale amount: ${average_sale:.2f}")
This example demonstrates how multiple loop patterns can be combined to perform a complete data analysis task.
Best Practices and Optimizations
Here are some guidelines to write efficient and readable loops in Python:
Choose the Right Loop Type
- Use
forloops when you know the sequence or the number of iterations - Use
whileloops when the number of iterations depends on a condition - Consider list comprehensions for simple transformations and filtering
Loop Efficiency
- Move constant operations outside the loop
# Less efficient for i in range(1000000): result = i * math.pi # math.pi is calculated each time # More efficient pi_value = math.pi # Calculate once for i in range(1000000): result = i * pi_value - Use appropriate data structures
# Inefficient for membership testing in large lists numbers = [1, 2, 3, ..., 10000] if x in numbers: # O(n) operation # Do something # More efficient with sets number_set = set(numbers) if x in number_set: # O(1) operation # Do something - Avoid growing lists or strings inside loops (use list comprehensions or join)
# Inefficient string building result = "" for i in range(1000): result += str(i) # Creates a new string each time # More efficient parts = [str(i) for i in range(1000)] result = "".join(parts) # Creates the string once
Readability Over Cleverness
- Use clear variable names that indicate purpose
- Add comments explaining the loop's purpose
- Break complex loops into smaller functions
- Don't nest loops too deeply (consider refactoring)
Alternative Approaches to Traditional Loops
Python provides several functions that can sometimes replace loops with more readable alternatives:
map(): Apply a function to each item in an iterable# Traditional approach squares = [] for x in range(1, 6): squares.append(x ** 2) # Using map squares = list(map(lambda x: x ** 2, range(1, 6)))filter(): Filter items based on a function# Traditional approach evens = [] for x in range(1, 11): if x % 2 == 0: evens.append(x) # Using filter evens = list(filter(lambda x: x % 2 == 0, range(1, 11)))reduce(): Apply a function cumulatively to itemsfrom functools import reduce # Traditional approach product = 1 for x in range(1, 6): product *= x # Using reduce product = reduce(lambda x, y: x * y, range(1, 6))
Real-World Optimization Example
import time
# Sample task: Count words in a large text that start with each letter
text = "This is a sample text. " * 1000000 # 5 million words
words = text.split()
# Approach 1: Nested loops (inefficient)
def count_first_letters_inefficient(word_list):
start_time = time.time()
alphabet = "abcdefghijklmnopqrstuvwxyz"
counts = {}
for letter in alphabet:
count = 0
for word in word_list:
if word.lower().startswith(letter):
count += 1
counts[letter] = count
end_time = time.time()
return counts, end_time - start_time
# Approach 2: Single pass with dictionary (efficient)
def count_first_letters_efficient(word_list):
start_time = time.time()
counts = {}
for word in word_list:
first_letter = word[0].lower()
if first_letter.isalpha():
counts[first_letter] = counts.get(first_letter, 0) + 1
end_time = time.time()
return counts, end_time - start_time
# Compare performance
inefficient_counts, inefficient_time = count_first_letters_inefficient(words)
efficient_counts, efficient_time = count_first_letters_efficient(words)
print(f"Inefficient approach time: {inefficient_time:.2f} seconds")
print(f"Efficient approach time: {efficient_time:.2f} seconds")
print(f"Speedup factor: {inefficient_time / efficient_time:.2f}x")
This example demonstrates how rewriting a nested loop as a single-pass algorithm with a suitable data structure can dramatically improve performance with large datasets.
Practice Exercises
To solidify your understanding of loops, try these exercises. Solutions will be reviewed in class.
-
Basic For Loop: Write a program that calculates the sum of all even numbers from 1 to 100 using a for loop.
-
While Loop with User Input: Write a guessing game program where the computer randomly selects a number between 1 and 100, and the user keeps guessing until they get it right. Provide "higher" or "lower" hints after each guess.
-
Nested Loops: Print a pyramid pattern of asterisks with a given number of rows:
* *** ***** ******* *********
-
Loop Control: Write a program that prints all prime numbers between 1 and 50.
-
List Comprehension: Convert a list of temperatures in Celsius to Fahrenheit using a list comprehension. (Formula: F = C * 9/5 + 32)
-
Dictionary Loop: Given a string, create a dictionary that counts how many times each character appears in the string.
-
Advanced Challenge: Write a function that checks if a given number is a "perfect number" (equal to the sum of its proper divisors, e.g., 6 = 1+2+3).
Real-World Applications of Loops
Loops are foundational to numerous real-world programming tasks. Here are some practical applications:
- Data Processing: Analyzing large datasets, extracting insights, and transforming formats
- File Operations: Reading and writing files, batch processing
- Web Scraping: Extracting information from multiple web pages
- API Interactions: Processing paginated results from API calls
- Image Processing: Applying filters or transformations to pixels
- Game Development: Creating game loops that update the game state
- Simulations: Modeling physical or statistical processes over time
Example: Web Scraping with Loops
import requests
from bs4 import BeautifulSoup
# Function to extract product details from a page
def extract_products(url):
response = requests.get(url)
soup = BeautifulSoup(response.text, 'html.parser')
products = []
# Find all product containers
product_containers = soup.find_all('div', class_='product-item')
for container in product_containers:
name = container.find('h3', class_='product-name').text.strip()
price = container.find('span', class_='price').text.strip()
rating = container.find('div', class_='rating').get('data-rating')
products.append({
'name': name,
'price': price,
'rating': float(rating)
})
return products
# Main program to scrape multiple pages
base_url = "https://example-store.com/products?page="
all_products = []
max_pages = 5
for page_num in range(1, max_pages + 1):
print(f"Scraping page {page_num}...")
page_url = base_url + str(page_num)
# Get products from this page
page_products = extract_products(page_url)
all_products.extend(page_products)
# Stop if we reach a page with no products
if not page_products:
print(f"No more products found on page {page_num}. Stopping.")
break
# Be polite to the server
time.sleep(1)
print(f"Scraped {len(all_products)} products total.")
# Find the highest rated products
highest_rated = []
highest_rating = 0
for product in all_products:
if product['rating'] > highest_rating:
highest_rated = [product]
highest_rating = product['rating']
elif product['rating'] == highest_rating:
highest_rated.append(product)
print(f"\nHighest rated products (Rating: {highest_rating}):")
for product in highest_rated:
print(f"- {product['name']} ({product['price']})")
This example demonstrates how loops are essential for web scraping tasks, including pagination handling and data processing.
Conclusion and Next Steps
We've covered the fundamental concepts and techniques of Python loops, from basic for and while loops to advanced patterns and optimizations. Loops are one of the core building blocks that allow us to automate repetitive tasks, process data efficiently, and create dynamic programs.
Tomorrow, we'll continue building on these control flow concepts by exploring data structures in Python. You'll learn how to combine loops with lists, dictionaries, and other collections to solve even more complex problems.
Remember to practice these loop concepts by working through the exercises. Mastering loops is essential for becoming proficient in Python and programming in general.
For further exploration, consider reading the official Python documentation on control flow statements: https://docs.python.org/3/tutorial/controlflow.html