Python Exercises

Contents

Python Exercises#

Beginner#


Variables and Data Types#

Exercise 1: String Manipulation#

# Create a variable 'name' with your name and 'age' with your age.
# Then print a message: "Hello, [name]! You are [age] years old."

# Your code here

# Assertions
assert isinstance(name, str), "name should be a string"
assert isinstance(age, int), "age should be an integer"
assert f"Hello, {name}! You are {age} years old." in globals()['__builtins__'].output
Solution
name = "Alice"
age = 25
print(f"Hello, {name}! You are {age} years old.")

Exercise 2: String Reversal#

# Write a function 'reverse_string' that takes a string as input and returns the reversed string.

# Your code here

# Assertions
assert reverse_string("hello") == "olleh", "String reversal is incorrect"
assert reverse_string("Python") == "nohtyP", "String reversal is incorrect"
assert reverse_string("") == "", "Should handle empty string"
Solution
def reverse_string(s):
    return s[::-1]

Exercise 3: Palindrome Check#

# Write a function 'is_palindrome' that takes a string as input and returns True if it's a palindrome, False otherwise.
# Ignore spaces and consider the string case-insensitive.

# Your code here

# Assertions
assert is_palindrome("A man a plan a canal Panama") == True, "Should be a palindrome"
assert is_palindrome("race a car") == False, "Should not be a palindrome"
assert is_palindrome("") == True, "Empty string should be considered a palindrome"
Solution
def is_palindrome(s):
    s = ''.join(char.lower() for char in s if char.isalnum())
    return s == s[::-1]

Exercise 4: Basic Arithmetic#

# Calculate the area of a rectangle with length 7 and width 5.
# Store the result in a variable called 'area'.

# Your code here

# Assertions
assert isinstance(area, (int, float)), "area should be a number"
assert area == 35, "The area should be 35"
Solution
length = 7
width = 5
area = length * width

Control Flow#

Exercise 5: If-Else Statement#

# Write a function 'is_even' that takes a number as input and returns True if it's even, False otherwise.

# Your code here

# Assertions
assert is_even(4) == True, "4 should be even"
assert is_even(7) == False, "7 should be odd"
assert is_even(0) == True, "0 should be even"
Solution
def is_even(number):
    return number % 2 == 0

Exercise 6: For Loop#

# Create a list of numbers from 1 to 10 and use a for loop to sum all the numbers.
# Store the result in a variable called 'total'.

# Your code here

# Assertions
assert isinstance(total, int), "total should be an integer"
assert total == 55, "The sum of numbers from 1 to 10 should be 55"

Exercise 7: FizzBuzz#

# Implement the classic FizzBuzz problem. Write a function 'fizzbuzz' that takes a number n
# and returns a list of strings for numbers from 1 to n:
# - For multiples of 3, use "Fizz" instead of the number
# - For multiples of 5, use "Buzz" instead of the number
# - For multiples of both 3 and 5, use "FizzBuzz"
# - For other numbers, use the number itself as a string

# Your code here

# Assertions
assert fizzbuzz(15) == ["1", "2", "Fizz", "4", "Buzz", "Fizz", "7", "8", "Fizz", "Buzz", "11", "Fizz", "13", "14", "FizzBuzz"]
assert fizzbuzz(5) == ["1", "2", "Fizz", "4", "Buzz"]
assert fizzbuzz(3) == ["1", "2", "Fizz"]
Solution
def fizzbuzz(n):
    result = []
    for i in range(1, n + 1):
        if i % 3 == 0 and i % 5 == 0:
            result.append("FizzBuzz")
        elif i % 3 == 0:
            result.append("Fizz")
        elif i % 5 == 0:
            result.append("Buzz")
        else:
            result.append(str(i))
    return result

Functions#

Exercise 8: Basic Function#

# Write a function 'greet' that takes a name as input and returns a greeting message.
# For example, greet("Alice") should return "Hello, Alice!".

# Your code here

# Assertions
assert greet("Bob") == "Hello, Bob!", "Greeting message is incorrect"
assert greet("") == "Hello, !", "Function should work with empty string"
Solution
def greet(name):
    return f"Hello, {name}!"

Lists#

Exercise 9: List Operations#

# Create a list of fruits and perform the following operations:
# 1. Add "apple" to the end of the list
# 2. Insert "banana" at the beginning of the list
# 3. Remove the second item from the list
# Store the final list in a variable called 'fruits'.

# Your code here

# Assertions
assert isinstance(fruits, list), "fruits should be a list"
assert fruits[0] == "banana", "First item should be 'banana'"
assert "apple" in fruits, "'apple' should be in the list"
assert len(fruits) == len(set(fruits)), "All items should be unique"
Solution
fruits = ["orange", "grape", "kiwi"]
fruits.append("apple")
fruits.insert(0, "banana")
fruits.pop(1)

Exercise 10: List Flattening#

# Write a function 'flatten_list' that takes a list of lists and returns a flattened list.

# Your code here

# Assertions
assert flatten_list([[1, 2], [3, 4]]) == [1, 2, 3, 4], "List flattening is incorrect"
assert flatten_list([[1], [2], [3]]) == [1, 2, 3], "List flattening is incorrect"
assert flatten_list([]) == [], "Should handle empty list"
Solution
def flatten_list(nested_list):
    return [item for sublist in nested_list for item in sublist]

Exercise 11: Tuple Unpacking#

# Create a function 'swap_first_last' that takes a tuple as input and returns a new tuple
# with the first and last elements swapped.

# Your code here

# Assertions
assert swap_first_last((1, 2, 3, 4)) == (4, 2, 3, 1), "Tuple swap is incorrect"
assert swap_first_last((1,)) == (1,), "Should handle single-element tuple"
assert swap_first_last(()) == (), "Should handle empty tuple"
Solution
def swap_first_last(tup):
    if len(tup) < 2:
        return tup
    return (tup[-1],) + tup[1:-1] + (tup[0],)

Dictionaries and Sets#

Exercise 12: Dictionary Merge#

# Write a function 'merge_dicts' that takes two dictionaries as input and returns a new dictionary
# containing all key-value pairs from both dictionaries. If a key exists in both dictionaries,
# the value from the second dictionary should be used.

# Your code here

# Assertions
dict1 = {'a': 1, 'b': 2}
dict2 = {'b': 3, 'c': 4}
assert merge_dicts(dict1, dict2) == {'a': 1, 'b': 3, 'c': 4}, "Dictionary merge is incorrect"
assert merge_dicts({}, {'x': 10}) == {'x': 10}, "Should handle empty dictionary"
assert merge_dicts({'y': 20}, {}) == {'y': 20}, "Should handle empty dictionary"
Solution
def merge_dicts(dict1, dict2):
    return {**dict1, **dict2}

Exercise 13: Set Operations#

# Implement a function 'set_operations' that takes two sets as input and returns a dictionary
# containing the result of union, intersection, and difference operations.

# Your code here

# Assertions
set1 = {1, 2, 3, 4}
set2 = {3, 4, 5, 6}
result = set_operations(set1, set2)
assert result['union'] == {1, 2, 3, 4, 5, 6}, "Union operation is incorrect"
assert result['intersection'] == {3, 4}, "Intersection operation is incorrect"
assert result['difference'] == {1, 2}, "Difference operation is incorrect"
Solution
def set_operations(set1, set2):
    return {
        'union': set1 | set2,
        'intersection': set1 & set2,
        'difference': set1 - set2
    }

Basic File I/O#

Exercise 14: File Reading and Writing#

# Write a function 'word_frequency' that reads a text file, counts the frequency of each word,
# and writes the results to a new file. Ignore case and punctuation.

# Your code here

# Test the function (assuming you have a file named 'sample.txt')
word_frequency('sample.txt', 'word_counts.txt')

# Assertions
with open('word_counts.txt', 'r') as f:
    content = f.read()
    assert 'the' in content.lower(), "Common word 'the' should be in the frequency list"
    assert len(content.split('\n')) > 1, "There should be multiple words in the frequency list"
Solution
import re
from collections import Counter

def word_frequency(input_file, output_file):
    with open(input_file, 'r') as f:
        text = f.read().lower()
    words = re.findall(r'\w+', text)
    frequency = Counter(words)
    
    with open(output_file, 'w') as f:
        for word, count in frequency.most_common():
            f.write(f"{word}: {count}\n")

Intermediate#


List Comprehensions#

Exercise 15: Even Numbers#

# Use a list comprehension to create a list of even numbers from 0 to 20 (inclusive).
# Store the result in a variable called 'even_numbers'.

# Your code here

# Assertions
assert isinstance(even_numbers, list), "even_numbers should be a list"
assert all(num % 2 == 0 for num in even_numbers), "All numbers should be even"
assert len(even_numbers) == 11, "There should be 11 even numbers"
Solution
even_numbers = [num for num in range(21) if num % 2 == 0]

Exercise 16: Nested List Comprehension#

# Use a nested list comprehension to create a list of all possible coordinate pairs (x, y)
# where x and y are integers between 0 and 4 (inclusive), excluding pairs where x equals y.

# Your code here

# Assertions
assert len(coordinates) == 20, "There should be 20 coordinate pairs"
assert (0, 0) not in coordinates, "Pairs where x equals y should be excluded"
assert (0, 4) in coordinates, "Pair (0, 4) should be included"
assert all(0 <= x <= 4 and 0 <= y <= 4 for x, y in coordinates), "All coordinates should be between 0 and 4"
Solution
coordinates = [(x, y) for x in range(5) for y in range(5) if x != y]

Dictionaries#

Exercise 17: Dictionary Manipulation#

# Create a dictionary of book titles and their authors.
# Then write a function 'get_author' that takes a book title and returns the author.
# If the book is not in the dictionary, it should return "Unknown".

# Your code here

# Assertions
assert get_author("To Kill a Mockingbird") == "Harper Lee", "Incorrect author for 'To Kill a Mockingbird'"
assert get_author("1984") == "George Orwell", "Incorrect author for '1984'"
assert get_author("Nonexistent Book") == "Unknown", "Should return 'Unknown' for nonexistent books"
Solution
books = {
    "To Kill a Mockingbird": "Harper Lee",
    "1984": "George Orwell",
    "Pride and Prejudice": "Jane Austen"
}

def get_author(title):
    return books.get(title, "Unknown")

Lambda Functions#

Exercise 18: Sorting with Lambda#

# Use a lambda function to sort a list of tuples based on the second element of each tuple.

# Your code here

data = [(1, 5), (3, 2), (2, 8), (4, 1)]
sorted_data = # Your sorting code here

# Assertions
assert sorted_data == [(4, 1), (3, 2), (1, 5), (2, 8)], "Sorting is incorrect"
Solution
data = [(1, 5), (3, 2), (2, 8), (4, 1)]
sorted_data = sorted(data, key=lambda x: x[1])

Map, Filter, and Reduce#

Exercise 19: Map and Filter#


# Use map() and filter() to create a list of squares of even numbers from a given list of integers.

# Your code here

numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
result = # Your code here

# Assertions
assert result == [4, 16, 36, 64, 100], "Map and filter result is incorrect"
Solution
numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
result = list(map(lambda x: x**2, filter(lambda x: x % 2 == 0, numbers)))

Data Structures#

Exercise 20: Implement a Stack#

# Implement a Stack class with push, pop, and is_empty methods using a Python list.

# Your code here

# Assertions
stack = Stack()
assert stack.is_empty() == True, "New stack should be empty"
stack.push(1)
stack.push(2)
assert stack.pop() == 2, "Pop should return the last item pushed"
assert stack.is_empty() == False, "Stack should not be empty"
assert stack.pop() == 1, "Pop should return the remaining item"
assert stack.is_empty() == True, "Stack should be empty after popping all items"
Solution
class Stack:
    def __init__(self):
        self.items = []
    
    def push(self, item):
        self.items.append(item)
    
    def pop(self):
        if not self.is_empty():
            return self.items.pop()
        raise IndexError("pop from empty stack")
    
    def is_empty(self):
        return len(self.items) == 0

Exercise 21: Implement a Queue#

# Implement a Queue class with enqueue, dequeue, and is_empty methods using a Python list.

# Your code here

# Assertions
queue = Queue()
assert queue.is_empty() == True, "New queue should be empty"
queue.enqueue(1)
queue.enqueue(2)
assert queue.dequeue() == 1, "Dequeue should return the first item enqueued"
assert queue.is_empty() == False, "Queue should not be empty"
assert queue.dequeue() == 2, "Dequeue should return the remaining item"
assert queue.is_empty() == True, "Queue should be empty after dequeuing all items"
Solution
class Queue:
    def __init__(self):
        self.items = []
    
    def enqueue(self, item):
        self.items.append(item)
    
    def dequeue(self):
        if not self.is_empty():
            return self.items.pop(0)
        raise IndexError("dequeue from empty queue")
    
    def is_empty(self):
        return len(self.items) == 0

Algorithms#

Object-Oriented Programming#

Exercise 23: Simple Class#

# Create a class 'Rectangle' with attributes 'width' and 'height'.
# Implement methods to calculate the area and perimeter of the rectangle.

# Your code here

# Assertions
rect = Rectangle(5, 3)
assert rect.area() == 15, "Area calculation is incorrect"
assert rect.perimeter() == 16, "Perimeter calculation is incorrect"
Solution
class Rectangle:
    def __init__(self, width, height):
        self.width = width
        self.height = height
    
    def area(self):
        return self.width * self.height
    
    def perimeter(self):
        return 2 * (self.width + self.height)

Exercise 24: Bank Account Class#

# Create a class 'BankAccount' with methods for deposit, withdraw, and check balance.
# Implement a 'MinimumBalanceAccount' that inherits from 'BankAccount' and requires a minimum balance.

# Your code here

# Assertions
account = BankAccount(1000)
account.deposit(500)
assert account.balance == 1500, "Deposit not handled correctly"
account.withdraw(200)
assert account.balance == 1300, "Withdrawal not handled correctly"

min_account = MinimumBalanceAccount(1000, minimum_balance=500)
min_account.withdraw(600)
assert min_account.balance == 1000, "Minimum balance rule not enforced"
Solution
class BankAccount:
    def __init__(self, initial_balance=0):
        self.balance = initial_balance
    
    def deposit(self, amount):
        self.balance += amount
    
    def withdraw(self, amount):
        if amount <= self.balance:
            self.balance -= amount
        else:
            raise ValueError("Insufficient funds")
    
    def check_balance(self):
        return self.balance

class MinimumBalanceAccount(BankAccount):
    def __init__(self, initial_balance, minimum_balance):
        super().__init__(initial_balance)
        self.minimum_balance = minimum_balance
    
    def withdraw(self, amount):
        if self.balance - amount >= self.minimum_balance:
            super().withdraw(amount)
        else:
            raise ValueError("Withdrawal would breach minimum balance")

Inheritance and Polymorphism#

Exercise 25: Shape Hierarchy#

# Create a base class 'Shape' with a method 'area'.
# Create two derived classes 'Circle' and 'Square' that inherit from 'Shape' and implement the 'area' method.

# Your code here

# Assertions
circle = Circle(radius=5)
square = Square(side=4)
assert round(circle.area(), 2) == 78.54, "Circle area calculation is incorrect"
assert square.area() == 16, "Square area calculation is incorrect"
Solution
import math

class Shape:
    def area(self):
        pass

class Circle(Shape):
    def __init__(self, radius):
        self.radius = radius
    
    def area(self):
        return math.pi * self.radius ** 2

class Square(Shape):
    def __init__(self, side):
        self.side = side
    
    def area(self):
        return self.side ** 2

Regular Expressions#

Exercise 26: Email Validation#

# Write a function 'is_valid_email' that uses a regular expression to check if a given string is a valid email address.

import re

# Your code here

# Assertions
assert is_valid_email("user@example.com") == True, "Should be a valid email"
assert is_valid_email("invalid.email@com") == False, "Should be an invalid email"
assert is_valid_email("user@.com") == False, "Should be an invalid email"
Solution
import re

def is_valid_email(email):
    pattern = r'^[\w\.-]+@[\w\.-]+\.\w+$'
    return bool(re.match(pattern, email))

Modules and Packages#

Exercise 27: Custom Module#

# Create a custom module 'geometry' with functions to calculate area and perimeter of different shapes.
# Then, in a separate file, import and use these functions.

# File: geometry.py
# Your code here for the geometry module

# File: main.py
# Your code here to import and use the geometry module

# Assertions (in main.py)
assert abs(geometry.circle_area(5) - 78.54) < 0.01, "Circle area calculation is incorrect"
assert geometry.rectangle_perimeter(4, 5) == 18, "Rectangle perimeter calculation is incorrect"
assert geometry.triangle_area(3, 4, 5) == 6, "Triangle area calculation is incorrect"
Solution
# File: geometry.py
import math

def circle_area(radius):
    return math.pi * radius ** 2

def rectangle_perimeter(length, width):
    return 2 * (length + width)

def triangle_area(a, b, c):
    s = (a + b + c) / 2
    return math.sqrt(s * (s - a) * (s - b) * (s - c))

# File: main.py
import geometry

print(geometry.circle_area(5))
print(geometry.rectangle_perimeter(4, 5))
print(geometry.triangle_area(3, 4, 5))

Error Handling#

Exercise 28: Try-Except#

# Write a function 'safe_divide' that takes two numbers as input and returns their division.
# If the second number is zero, catch the ZeroDivisionError and return "Cannot divide by zero".

# Your code here

# Assertions
assert safe_divide(10, 2) == 5, "10 divided by 2 should be 5"
assert safe_divide(5, 0) == "Cannot divide by zero", "Should handle division by zero"
assert safe_divide(-1, 4) == -0.25, "Should handle negative numbers"
Solution
def safe_divide(a, b):
    try:
        return a / b
    except ZeroDivisionError:
        return "Cannot divide by zero"

File I/O#

Exercise 29: Reading and Writing Files#


# Write a function 'word_count' that reads a text file and returns a dictionary
# with the count of each word in the file. Ignore case and punctuation.

# Your code here

# Assertions
# Assuming a file named 'sample.txt' with content: "Hello world! Hello Python."
assert isinstance(word_count('sample.txt'), dict), "Should return a dictionary"
assert word_count('sample.txt')['hello'] == 2, "Should count 'hello' twice"
assert word_count('sample.txt')['world'] == 1, "Should count 'world' once"
assert word_count('sample.txt')['python'] == 1, "Should count 'python' once"
Solution
import re
from collections import defaultdict

def word_count(filename):
    counts = defaultdict(int)
    with open(filename, 'r') as file:
        text = file.read().lower()
        words = re.findall(r'\w+', text)
        for word in words:
            counts[word] += 1
    return dict(counts)

Advanced#


Decorators#

Exercise 30: Timing Decorator#

# Create a decorator 'timer' that measures the time a function takes to execute.
# The decorator should print: "Function {func_name} took {time} seconds to run."

# Your code here

@timer
def slow_function():
    import time
    time.sleep(2)

# Run the function
slow_function()

# Assertions
# Note: We can't assert exact time, but we can check if the output is printed
assert "Function slow_function took" in globals()['__builtins__'].output
assert "seconds to run" in globals()['__builtins__'].output
Solution
import time
from functools import wraps

def timer(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        start_time = time.time()
        result = func(*args, **kwargs)
        end_time = time.time()
        print(f"Function {func.__name__} took {end_time - start_time:.2f} seconds to run.")
        return result
    return wrapper

Exercise 31: Retry Decorator#

# Create a decorator 'retry' that retries the decorated function a specified number of times if it raises an exception.

# Your code here

@retry(max_attempts=3)
def might_fail():
    import random
    if random.random() < 0.7:
        raise ValueError("Random failure")
    return "Success"

# Assertions
result = might_fail()
assert result == "Success", "Function should eventually succeed"
Solution
import functools

def retry(max_attempts):
    def decorator(func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            for _ in range(max_attempts):
                try:
                    return func(*args, **kwargs)
                except Exception as e:
                    last_exception = e
            raise last_exception
        return wrapper
    return decorator

Generators and Iterators#

Exercise 32: Fibonacci Generator#

# Create a generator function 'fibonacci' that yields the Fibonacci sequence.
# The function should take a parameter 'n' for the number of Fibonacci numbers to generate.

# Your code here

# Assertions
fib = fibonacci(10)
assert list(fib) == [0, 1, 1, 2, 3, 5, 8, 13, 21, 34], "Incorrect Fibonacci sequence"
Solution
def fibonacci(n):
    a, b = 0, 1
    for _ in range(n):
        yield a
        a, b = b, a + b

Web Scraping#

Exercise 33: Web Scraping with BeautifulSoup#

# Implement a function 'scrape_headlines' that uses BeautifulSoup to scrape the top headlines
# from a news website (e.g., https://news.ycombinator.com/).

import requests
from bs4 import BeautifulSoup

# Your code here

# Assertions
headlines = scrape_headlines()
assert len(headlines) > 0, "Should have scraped at least one headline"
assert all(isinstance(h, str) for h in headlines), "All headlines should be strings"
Solution
import requests
from bs4 import BeautifulSoup

def scrape_headlines():
    url = "https://news.ycombinator.com/"
    response = requests.get(url)
    soup = BeautifulSoup(response.text, 'html.parser')
    headlines = soup.find_all('a', class_='storylink')
    return [headline.text for headline in headlines]

Dynamic Programming#

Exercise 34: Longest Common Subsequence#


# Implement a function to find the length of the Longest Common Subsequence (LCS)
# between two strings using dynamic programming.

# Your code here

# Assertions
assert lcs_length("ABCDGH", "AEDFHR") == 3, "LCS should be 'ADH' with length 3"
assert lcs_length("AGGTAB", "GXTXAYB") == 4, "LCS should be 'GTAB' with length 4"
assert lcs_length("ABCBDAB", "BDCABA") == 4, "LCS should be 'BCBA' with length 4"
assert lcs_length("", "ABC") == 0, "LCS with empty string should be 0"
assert lcs_length("ABC", "ABC") == 3, "LCS of identical strings should be their length"
Solution
def lcs_length(X, Y):
    m, n = len(X), len(Y)
    L = [[0] * (n + 1) for _ in range(m + 1)]
    
    for i in range(1, m + 1):
        for j in range(1, n + 1):
            if X[i-1] == Y[j-1]:
                L[i][j] = L[i-1][j-1] + 1
            else:
                L[i][j] = max(L[i-1][j], L[i][j-1])
    
    return L[m][n]

Graph Algorithms#

Logical Problems#

Exercise 36: Tower of Hanoi#

# Implement a function to solve the Tower of Hanoi puzzle.
# The function should print the steps to move n disks from the source peg to the destination peg.

# Your code here

# Test the function (no assertions, as it prints the steps)
tower_of_hanoi(3, 'A', 'C', 'B')
Solution
def tower_of_hanoi(n, source, destination, auxiliary):
    if n == 1:
        print(f"Move disk 1 from {source} to {destination}")
        return
    tower_of_hanoi(n-1, source, auxiliary, destination)
    print(f"Move disk {n} from {source} to {destination}")
    tower_of_hanoi(n-1, auxiliary, destination, source)

Exercise 37: N-Queens Problem#

# Implement a function to solve the N-Queens problem.
# The function should return a list of all possible solutions for placing N queens on an NxN chessboard.

# Your code here

# Assertions
solutions_4 = solve_n_queens(4)
assert len(solutions_4) == 2, "There should be 2 solutions for 4-Queens"
assert len(solve_n_queens(1)) == 1, "There should be 1 solution for 1-Queen"
assert len(solve_n_queens(0)) == 0, "There should be no solutions for 0-Queens"
Solution
def solve_n_queens(n):
    def is_safe(board, row, col):
        for i in range(col):
            if board[row][i] == 'Q':
                return False
        for i, j in zip(range(row, -1, -1), range(col, -1, -1)):
            if board[i][j] == 'Q':
                return False
        for i, j in zip(range(row, n, 1), range(col, -1, -1)):
            if board[i][j] == 'Q':
                return False
        return True

    def solve(board, col):
        if col >= n:
            solutions.append([''.join(row) for row in board])
            return
        for i in range(n):
            if is_safe(board, i, col):
                board[i][col] = 'Q'
                solve(board, col + 1)
                board[i][col] = '.'

    solutions = []
    board = [['.' for _ in range(n)] for _ in range(n)]
    solve(board, 0)
    return solutions

Exercise 38: Custom Iterator for Prime Numbers#


# Implement a custom iterator class that generates prime numbers up to a specified limit.

# Your code here

# Assertions
primes = PrimeIterator(20)
assert list(primes) == [2, 3, 5, 7, 11, 13, 17, 19], "Should generate correct prime numbers up to 20"

primes = PrimeIterator(10)
assert list(primes) == [2, 3, 5, 7], "Should generate correct prime numbers up to 10"

primes = PrimeIterator(2)
assert list(primes) == [2], "Should generate only 2 for limit 2"
Solution
class PrimeIterator:
    def __init__(self, limit):
        self.limit = limit
        self.current = 2

    def __iter__(self):
        return self

    def __next__(self):
        while self.current < self.limit:
            if self.is_prime(self.current):
                prime = self.current
                self.current += 1
                return prime
            self.current += 1
        raise StopIteration

    def is_prime(self, n):
        if n < 2:
            return False
        for i in range(2, int(n ** 0.5) + 1):
            if n % i == 0:
                return False
        return True

Exercise 39: Asynchronous Web Crawler#

# Implement an asynchronous web crawler that fetches the content of multiple URLs concurrently.
# The crawler should respect a maximum number of concurrent requests.

import aiohttp
import asyncio

# Your code here

# Assertions
urls = [
    "http://example.com",
    "http://example.org",
    "http://example.net",
    "http://example.edu",
    "http://example.io"
]

async def test_crawler():
    results = await async_crawl(urls, max_concurrent=3)
    assert len(results) == len(urls), "Should fetch all URLs"
    assert all(isinstance(content, str) for content in results.values()), "All results should be strings"

asyncio.run(test_crawler())
Solution
import aiohttp
import asyncio

async def fetch(session, url):
    async with session.get(url) as response:
        return await response.text()

async def async_crawl(urls, max_concurrent):
    semaphore = asyncio.Semaphore(max_concurrent)
    async with aiohttp.ClientSession() as session:
        async def bounded_fetch(url):
            async with semaphore:
                return url, await fetch(session, url)
        
        tasks = [asyncio.create_task(bounded_fetch(url)) for url in urls]
        results = await asyncio.gather(*tasks)
    
    return dict(results)