Python Practice Problems with Solutions

Python Practice Problems with Solutions

Welcome, Python enthusiast! Whether you're just starting or looking to sharpen your coding skills, practice problems are one of the best ways to reinforce your learning. In this article, I've compiled a variety of Python problems ranging from beginner to intermediate level, complete with solutions and explanations. Let's dive right in!

Basic Problems for Beginners

Let's start with some fundamental problems that will help you get comfortable with Python syntax and basic operations. These exercises focus on variables, data types, loops, and conditional statements.

Problem: Write a function that checks if a number is even or odd.

def check_even_odd(number):
    if number % 2 == 0:
        return "Even"
    else:
        return "Odd"

# Test the function
print(check_even_odd(4))  # Output: Even
print(check_even_odd(7))  # Output: Odd

Problem: Create a function that returns the factorial of a number.

def factorial(n):
    result = 1
    for i in range(1, n + 1):
        result *= i
    return result

# Test the function
print(factorial(5))  # Output: 120

Here's a simple table showing factorial values for numbers 1 through 5:

Number Factorial
1 1
2 2
3 6
4 24
5 120

Problem: Write a program that prints all prime numbers between 1 and 100.

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

primes = [num for num in range(1, 101) if is_prime(num)]
print(primes)

When working with these basic problems, remember these key points: - Always test your functions with different inputs - Pay attention to edge cases (like 0 or negative numbers) - Use meaningful variable names - Comment your code to explain your logic

String Manipulation Problems

Strings are fundamental in Python, and mastering string operations is crucial for many programming tasks. Let's explore some common string problems.

Problem: Write a function that reverses a string.

def reverse_string(s):
    return s[::-1]

# Test the function
print(reverse_string("hello"))  # Output: olleh

Problem: Create a function that checks if a string is a palindrome.

def is_palindrome(s):
    s = s.lower().replace(" ", "")
    return s == s[::-1]

# Test the function
print(is_palindrome("madam"))    # Output: True
print(is_palindrome("python"))   # Output: False

Problem: Write a function that counts the occurrences of each character in a string.

def count_characters(s):
    char_count = {}
    for char in s:
        char_count[char] = char_count.get(char, 0) + 1
    return char_count

# Test the function
print(count_characters("hello"))  # Output: {'h': 1, 'e': 1, 'l': 2, 'o': 1}

Here's what you should keep in mind when working with strings: - Python strings are immutable - Use string methods like lower(), upper(), strip() effectively - String slicing is a powerful feature - Regular expressions can be useful for complex patterns

String Operation Method Example Result
Convert to uppercase "hello".upper() "HELLO"
Replace substring "hello".replace("l", "x") "hexxo"
Split string "a,b,c".split(",") ["a", "b", "c"]

List and Dictionary Problems

Lists and dictionaries are among the most commonly used data structures in Python. Let's practice some problems that will help you master them.

Problem: Write a function that removes duplicates from a list.

def remove_duplicates(lst):
    return list(set(lst))

# Test the function
print(remove_duplicates([1, 2, 2, 3, 4, 4, 5]))  # Output: [1, 2, 3, 4, 5]

Problem: Create a function that finds the most frequent element in a list.

def most_frequent(lst):
    from collections import Counter
    return Counter(lst).most_common(1)[0][0]

# Test the function
print(most_frequent([1, 2, 2, 3, 3, 3, 4]))  # Output: 3

Problem: Write a function that merges two dictionaries.

def merge_dicts(dict1, dict2):
    return {**dict1, **dict2}

# Test the function
dict_a = {'a': 1, 'b': 2}
dict_b = {'c': 3, 'd': 4}
print(merge_dicts(dict_a, dict_b))  # Output: {'a': 1, 'b': 2, 'c': 3, 'd': 4}

When working with lists and dictionaries, consider these best practices: - Use list comprehensions for concise code - Understand time complexity of different operations - Use built-in methods like sort(), reverse(), and get() - Consider using collections module for advanced data structures

File Handling Problems

Working with files is an essential skill for any Python developer. Let's look at some common file operations.

Problem: Write a function that counts the number of lines in a file.

def count_lines(filename):
    with open(filename, 'r') as file:
        return len(file.readlines())

# Usage example
print(count_lines('sample.txt'))

Problem: Create a function that reads a CSV file and returns the data as a list of dictionaries.

import csv

def read_csv_to_dict(filename):
    with open(filename, 'r') as file:
        reader = csv.DictReader(file)
        return list(reader)

# Usage example
data = read_csv_to_dict('data.csv')

Problem: Write a function that finds the longest word in a text file.

def longest_word_in_file(filename):
    with open(filename, 'r') as file:
        words = file.read().split()
        return max(words, key=len)

# Usage example
print(longest_word_in_file('text.txt'))

Here are some important file handling concepts: - Always use context managers (with statements) - Handle exceptions when working with files - Close files properly to avoid resource leaks - Use appropriate file modes ('r', 'w', 'a', etc.)

File Mode Description Use Case
'r' Read mode Reading existing files
'w' Write mode Creating new files or overwriting existing ones
'a' Append mode Adding to existing files
'r+' Read and write Both reading and writing to existing files

Object-Oriented Programming Problems

Object-oriented programming is a fundamental paradigm in Python. Let's practice some OOP concepts.

Problem: Create a BankAccount class with deposit and withdraw methods.

class BankAccount:
    def __init__(self, balance=0):
        self.balance = balance

    def deposit(self, amount):
        self.balance += amount
        return self.balance

    def withdraw(self, amount):
        if amount > self.balance:
            raise ValueError("Insufficient funds")
        self.balance -= amount
        return self.balance

# Test the class
account = BankAccount(100)
print(account.deposit(50))    # Output: 150
print(account.withdraw(30))   # Output: 120

Problem: Create a Rectangle class with area and perimeter methods.

class Rectangle:
    def __init__(self, length, width):
        self.length = length
        self.width = width

    def area(self):
        return self.length * self.width

    def perimeter(self):
        return 2 * (self.length + self.width)

# Test the class
rect = Rectangle(5, 3)
print(rect.area())        # Output: 15
print(rect.perimeter())   # Output: 16

Problem: Implement inheritance with a base Animal class and derived classes.

class Animal:
    def __init__(self, name):
        self.name = name

    def speak(self):
        raise NotImplementedError("Subclass must implement this method")

class Dog(Animal):
    def speak(self):
        return "Woof!"

class Cat(Animal):
    def speak(self):
        return "Meow!"

# Test the classes
dog = Dog("Buddy")
cat = Cat("Whiskers")
print(dog.speak())  # Output: Woof!
print(cat.speak())  # Output: Meow!

When working with OOP in Python, remember: - Use encapsulation to protect data - Inheritance allows code reuse - Polymorphism enables flexible code - Understand the difference between class and instance variables

Algorithmic Problems

Now let's tackle some algorithmic problems that will help you think like a programmer and improve your problem-solving skills.

Problem: Implement binary search on a sorted list.

def binary_search(arr, target):
    low, high = 0, len(arr) - 1

    while low <= high:
        mid = (low + high) // 2
        if arr[mid] == target:
            return mid
        elif arr[mid] < target:
            low = mid + 1
        else:
            high = mid - 1

    return -1

# Test the function
sorted_list = [1, 3, 5, 7, 9, 11, 13]
print(binary_search(sorted_list, 9))  # Output: 4
print(binary_search(sorted_list, 6))  # Output: -1

Problem: Write a function to sort a list using bubble sort.

def bubble_sort(arr):
    n = len(arr)
    for i in range(n):
        for j in range(0, n - i - 1):
            if arr[j] > arr[j + 1]:
                arr[j], arr[j + 1] = arr[j + 1], arr[j]
    return arr

# Test the function
unsorted_list = [64, 34, 25, 12, 22, 11, 90]
print(bubble_sort(unsorted_list))  # Output: [11, 12, 22, 25, 34, 64, 90]

Problem: Implement the Fibonacci sequence using recursion and memoization.

def fibonacci(n, memo={}):
    if n in memo:
        return memo[n]
    if n <= 2:
        return 1
    memo[n] = fibonacci(n - 1, memo) + fibonacci(n - 2, memo)
    return memo[n]

# Test the function
print(fibonacci(10))  # Output: 55

Here's a comparison of different sorting algorithms:

Algorithm Time Complexity (Best) Time Complexity (Worst) Space Complexity
Bubble Sort O(n) O(n²) O(1)
Selection Sort O(n²) O(n²) O(1)
Insertion Sort O(n) O(n²) O(1)
Merge Sort O(n log n) O(n log n) O(n)
Quick Sort O(n log n) O(n²) O(log n)

When working with algorithms: - Understand time and space complexity - Choose the right algorithm for your use case - Test with different input sizes - Consider edge cases and worst-case scenarios

Real-World Application Problems

Let's look at some problems that simulate real-world scenarios you might encounter as a Python developer.

Problem: Create a simple weather data analyzer.

def analyze_weather_data(temperatures):
    analysis = {
        'max_temp': max(temperatures),
        'min_temp': min(temperatures),
        'avg_temp': sum(temperatures) / len(temperatures),
        'temperature_range': max(temperatures) - min(temperatures)
    }
    return analysis

# Test the function
temps = [22, 24, 19, 21, 25, 23, 20]
print(analyze_weather_data(temps))

Problem: Build a simple expense tracker.

class ExpenseTracker:
    def __init__(self):
        self.expenses = []

    def add_expense(self, amount, category, description):
        self.expenses.append({
            'amount': amount,
            'category': category,
            'description': description
        })

    def total_expenses(self):
        return sum(expense['amount'] for expense in self.expenses)

    def expenses_by_category(self):
        categories = {}
        for expense in self.expenses:
            categories[expense['category']] = categories.get(expense['category'], 0) + expense['amount']
        return categories

# Test the class
tracker = ExpenseTracker()
tracker.add_expense(15.50, 'Food', 'Lunch')
tracker.add_expense(30.00, 'Transport', 'Bus fare')
tracker.add_expense(8.75, 'Food', 'Coffee')
print(tracker.total_expenses())           # Output: 54.25
print(tracker.expenses_by_category())     # Output: {'Food': 24.25, 'Transport': 30.00}

Problem: Create a password strength checker.

def check_password_strength(password):
    strength = 0
    feedback = []

    if len(password) >= 8:
        strength += 1
    else:
        feedback.append("Password should be at least 8 characters long")

    if any(char.isdigit() for char in password):
        strength += 1
    else:
        feedback.append("Password should contain at least one digit")

    if any(char.isupper() for char in password):
        strength += 1
    else:
        feedback.append("Password should contain at least one uppercase letter")

    if any(char.islower() for char in password):
        strength += 1
    else:
        feedback.append("Password should contain at least one lowercase letter")

    if any(not char.isalnum() for char in password):
        strength += 1
    else:
        feedback.append("Password should contain at least one special character")

    return strength, feedback

# Test the function
strength, feedback = check_password_strength("Weak1")
print(f"Strength: {strength}/5")
print("Feedback:", feedback)

When working on real-world problems: - Consider user experience and error handling - Write modular, maintainable code - Add documentation and comments - Test with realistic data

Debugging and Optimization Problems

Let's practice some problems that will help you improve your debugging skills and write more efficient code.

Problem: Identify and fix the bug in this code.

# Buggy code
def calculate_average(numbers):
    total = 0
    for number in numbers:
        total += number
    return total / len(numbers)

# Fixed code
def calculate_average(numbers):
    if not numbers:
        return 0  # Handle empty list
    total = 0
    for number in numbers:
        total += number
    return total / len(numbers)

# Test the function
print(calculate_average([1, 2, 3, 4, 5]))  # Output: 3.0
print(calculate_average([]))               # Output: 0 (instead of crashing)

Problem: Optimize this inefficient code.

# Inefficient version
def find_duplicates(lst):
    duplicates = []
    for i in range(len(lst)):
        for j in range(i + 1, len(lst)):
            if lst[i] == lst[j] and lst[i] not in duplicates:
                duplicates.append(lst[i])
    return duplicates

# Optimized version
def find_duplicates(lst):
    seen = set()
    duplicates = set()
    for item in lst:
        if item in seen:
            duplicates.add(item)
        else:
            seen.add(item)
    return list(duplicates)

# Test both functions
test_list = [1, 2, 3, 2, 4, 5, 3, 6, 7, 8, 1]
print(find_duplicates(test_list))  # Output: [1, 2, 3]

Problem: Refactor this code to make it more readable and maintainable.

# Original code
def p(s):
    l = 0
    r = len(s) - 1
    while l < r:
        if s[l] != s[r]:
            return False
        l += 1
        r -= 1
    return True

# Refactored code
def is_palindrome(string):
    left = 0
    right = len(string) - 1

    while left < right:
        if string[left] != string[right]:
            return False
        left += 1
        right -= 1

    return True

# Test the function
print(is_palindrome("racecar"))  # Output: True
print(is_palindrome("python"))   # Output: False

Here are some debugging and optimization tips: - Use meaningful variable names - Add comments to explain complex logic - Test edge cases and boundary conditions - Use Python's built-in functions and libraries - Profile your code to identify bottlenecks

Optimization Technique When to Use Example
Memoization Repeated calculations Fibonacci sequence
Early termination Search algorithms Binary search
Using sets for membership tests Checking for duplicates Finding unique elements
List comprehensions Creating new lists Filtering or transforming data

Remember, the key to mastering Python is consistent practice. Work through these problems, understand the solutions, and then try to solve them in different ways. Don't just copy the solutions - type them out yourself, experiment with modifications, and challenge yourself to come up with alternative approaches.

Happy coding, and keep practicing! The more problems you solve, the more comfortable you'll become with Python programming.