Python programming advanced
Contents
LICENSE
MIT License
Copyright Β© 2018 Oleksii Trekhleb
Copyright Β© 2020 Samuel Huang
Copyright Β© 2023 jerry-git
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the βSoftwareβ), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED βAS ISβ, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
42.8. Python programming advanced#
# set up the env
import pytest
import ipytest
import unittest
ipytest.autoconfig()
42.8.1. Conditionals#
42.8.1.1. if-elif-else#
Fill missing pieces ____
of the following code such that prints make sense.
# Binary search algorithm
def binary_search(list, target):
"""Searches for a target element in a sorted list using binary search.
Args:
lst (list): The sorted list to be searched.
target (any): The element to be searched for.
Returns:
int: The index of the target element if found, -1 otherwise.
"""
# Initialize low and high indices
# Initialize the left and right pointers
left = 0
right = len(list) - 1
# Loop until the left pointer is greater than the right pointer
while left <= right:
# Find the middle index of the current range
mid = (left + right) // 2
# Compare the target with the middle element of the list
if target == ____:
# Return the index of the target if found
return mid
____ target ____:
# Narrow the search range to the left half if the target is smaller than the middle element
____ = mid - 1
else:
# Narrow the search range to the right half if the target is larger than the middle element
____ = mid + 1
# Return -1 if the target is not found in the list
return -1
lst = [1, 2, 3, 4, 5, 6, 7, 8, 9]
assert binary_search(lst, 5) == 4
assert binary_search(lst, 9) == 8
assert binary_search(lst, -1) == -1
assert binary_search(lst, 10) == -1
Check result by executing below... π
%%ipytest -qq
class TestBinarySearch:
def test_existing_element(self):
lst = [1, 3, 5, 7, 9]
target = 5
assert binary_search(lst, target) == 2
def test_non_existing_element(self):
lst = [1, 3, 5, 7, 9]
target = 6
assert binary_search(lst, target) == -1
def test_empty_list(self):
lst = []
target = 5
assert binary_search(lst, target) == -1
def test_single_element_list(self):
lst = [5]
target = 5
assert binary_search(lst, target) == 0
def test_wrong_target_type(self):
lst = [1, 3, 5, 7, 9]
target = "a"
with pytest.raises(TypeError):
binary_search(lst, target)
π©βπ» Hint
Compare the target value with the middle element during each iteration and narrow down the search range based on the comparison result.
42.8.2. For loops#
42.8.2.1. Fill the missing pieces#
Fill the ____
parts in the code below.
def factorial(n):
"""
Calculate the factorial of a number.
Args:
n (int): The number to calculate the factorial of.
Returns:
int: The factorial of the input number.
"""
result = 1
____ ____ in range(1, n + 1):
result ____
return result
assert factorial(3) == 6
Check result by executing below... π
%%ipytest -qq
class TestFactorial:
def test_positive_number(self):
assert factorial(5) == 120
assert factorial(10) == 3628800
assert factorial(0) == 1
def test_float_number(self):
with pytest.raises(TypeError):
factorial(3.14)
def test_string_input(self):
with pytest.raises(TypeError):
factorial("5")
π©βπ» Hint
Use a loop to iterate through the numbers from 1 to n and multiply the result by each number to calculate the factorial.
42.8.2.2. range()#
def calculate_sum(n):
"""
Calculate the sum of numbers from 1 to n.
Args:
n (int): The upper limit.
Returns:
int: The sum of the numbers.
"""
total = 0
for num in ____(1, ____):
total += num
return total
assert calculate_sum(4) == 10
Check result by executing below... π
%%ipytest -qq
class TestCalculateSum:
def test_positive_number(self):
assert calculate_sum(5) == 15
assert calculate_sum(10) == 55
assert calculate_sum(100) == 5050
def test_zero(self):
assert calculate_sum(0) == 0
def test_negative_number(self):
assert calculate_sum(-5) == 0
def test_float_number(self):
with pytest.raises(TypeError):
calculate_sum(3.14)
def test_string_input(self):
with pytest.raises(TypeError):
calculate_sum("5")
π©βπ» Hint
Use a loop to iterate through the numbers starting from 1 up to and including n, and accumulate the sum by adding each number to the total variable.
42.8.2.3. Looping dictionaries#
my_dict = {'hacker': True, 'age': 72, 'name': 'John Doe'}
keys_list = []
values_list = []
# Your solution here:
____
42.8.2.4. Calculate the sum of dict values#
Calculate the sum of the values in magic_dict
by taking only into account numeric values (hint: see isinstance).
magic_dict = dict(val1=44, val2='secret value', val3=55.0, val4=1)
# Your implementation
sum_of_values = ____
assert sum_of_values == 100
42.8.2.5. Create a list of strings based on a list of numbers#
The rules:
If the number is a multiple of five and odd, the string should be
'five odd'
If the number is a multiple of five and even, the string should be
'five even'
If the number is odd, the string is
'odd'
If the number is even, the string is
'even'
numbers = [1, 3, 4, 6, 81, 80, 100, 95]
# Your implementation
my_list = ____
assert my_list == ['odd', 'odd', 'even', 'even', 'odd', 'five even', 'five even', 'five odd']
42.8.3. While loops#
42.8.3.1. Fill the missing pieces#
Fill the ____
parts in the code below.
def count_digits(number):
"""
Count the number of digits in a given number.
Args:
number (int): The input number.
Returns:
int: The count of digits in the number.
Raises:
TypeError: If the input is not an integer.
"""
if not isinstance(number, int):
raise TypeError("Input must be an integer.")
count = 0
____ number != ____:
number //= ____
count += 1
return count
assert count_digits(123) == 3
Check result by executing below... π
%%ipytest -qq
class TestCountDigits:
def test_positive_number(self):
assert count_digits(123) == 3
def test_zero(self):
assert count_digits(0) == 0
def test_negative_number(self):
assert count_digits(-123) == 3
def test_float_number(self):
with pytest.raises(TypeError):
count_digits(3.14)
def test_string_input(self):
with pytest.raises(TypeError):
count_digits("123")
π©βπ» Hint
Use a loop to repeatedly divide the number by 10 and increment the count variable until the number becomes zero.
42.8.4. Break#
42.8.4.1. Fill the missing pieces using break
statement#
def find_prime_factors(number):
"""
Function to find and return the prime factors of a given number.
Args:
number (int): The number to find the prime factors of.
Returns:
list: A list containing the prime factors of the given number.
"""
prime_factors = [] # Initialize an empty list to store the prime factors
divisor = 2 # Start with the smallest prime number as the divisor
while number > 1: # Continue the loop until the number is reduced to 1
if number % divisor == 0: # Check if the current divisor is a factor of the number
prime_factors.append(divisor) # If it is, add it to the list of prime factors
number = number ____ divisor # Divide the number by the divisor to continue finding the remaining factors
else:
divisor ____ 1 # If the divisor is not a factor, increment it by 1
if divisor * divisor > number: # If the square of the divisor is greater than the number
if number > 1: # And the remaining number is greater than 1
____.append(____) # Add the remaining number as a prime factor
____ # Break out of the loop, as no further factors need to be checked
return prime_factors # Return the list of prime factors
assert find_prime_factors(18) == [2, 3, 3]
Check result by executing below... π
%%ipytest -qq
class TestFindPrimeFactors:
def test_positive_number(self):
assert find_prime_factors(10) == [2, 5]
assert find_prime_factors(12) == [2, 2, 3]
assert find_prime_factors(29) == [29]
assert find_prime_factors(56) == [2, 2, 2, 7]
assert find_prime_factors(100) == [2, 2, 5, 5]
def test_zero(self):
assert find_prime_factors(0) == []
def test_negative_number(self):
assert find_prime_factors(-10) == []
assert find_prime_factors(-12) == []
assert find_prime_factors(-29) == []
assert find_prime_factors(-56) == []
assert find_prime_factors(-100) == []
def test_string_input(self):
with pytest.raises(TypeError):
find_prime_factors("123")
π©βπ» Hint
Try using the divisor to decompose the number.
42.8.5. Continue#
42.8.5.1. Fill the missing pieces using continue
statement#
def sieve_of_eratosthenes(n):
"""
Finds all prime numbers less than or equal to n using the sieve of Eratosthenes method.
Args:
n (int): A positive integer to be the upper bound of the prime numbers.
Returns:
list: A list of all prime numbers less than or equal to n.
"""
if n <= 1:
return []
# Initialize a list of booleans from 0 to n, where True means the number is prime
is_prime = [True] * (n + 2)
# Mark 0 and 1 as not prime
is_prime[0] = False
is_prime[1] = False
# Loop from 2 to the square root of n
for i in range(2, int(n ** 0.5) + 1):
# If the current number is marked as prime
if is_prime[i]:
# Loop from i^2 to n with step size i
for j in range(i * i, n + 1, i):
# Mark all multiples of i as not prime
is_prime[j] = ____
# Initialize an empty list to store the prime numbers
primes = []
# Loop from 2 to n
for i in range(2, n + 1):
# If the current number is marked as prime
if ____:
# Append it to the list of prime numbers
primes.____
# Otherwise, continue to the next iteration
else:
____
# Return the list of prime numbers
return primes
assert sieve_of_eratosthenes(10) == [2, 3, 5, 7]
Check result by executing below... π
%%ipytest -qq
class TestSieveOfEratosthenes:
def test_positive_number(self):
assert sieve_of_eratosthenes(10) == [2, 3, 5, 7]
assert sieve_of_eratosthenes(20) == [2, 3, 5, 7, 11, 13, 17, 19]
assert sieve_of_eratosthenes(30) == [2, 3, 5, 7, 11, 13, 17, 19, 23, 29]
assert sieve_of_eratosthenes(50) == [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47]
def test_zero(self):
assert sieve_of_eratosthenes(0) == []
def test_negative_number(self):
assert sieve_of_eratosthenes(-10) == []
def test_float_number(self):
with pytest.raises(TypeError):
sieve_of_eratosthenes(3.14)
def test_string_input(self):
with pytest.raises(TypeError):
sieve_of_eratosthenes("123")
π©βπ» Hint
In the inner loop, mark all multiples of the current prime number as not prime. To do this, set the corresponding indices in the is_prime list to False.
42.8.6. Functions#
42.8.6.1. Fill the missing pieces of the gcd
function#
def gcd(a, b):
"""
Finds the greatest common divisor of two positive integers using the Euclidean algorithm.
Args:
a (int): A positive integer.
b (int): Another positive integer.
Returns:
int: The greatest common divisor of a and b.
"""
if not isinstance(a, int) or not isinstance(b, int):
raise TypeError("Inputs must be integers.")
# Base case: if b is 0, return a
if b == 0:
return ____
# Recursive case: if b is not 0, return the GCD of b and the remainder of a divided by b
else:
return gcd(b, ____)
assert gcd(12, 18) == 6
Check result by executing below... π
%%ipytest -qq
class TestGCD:
def test_positive_numbers(self):
assert gcd(10, 25) == 5
assert gcd(14, 28) == 14
assert gcd(21, 14) == 7
assert gcd(48, 18) == 6
assert gcd(17, 29) == 1
def test_zero(self):
assert gcd(0, 10) == 10
assert gcd(10, 0) == 10
assert gcd(0, 0) == 0
def test_negative_numbers(self):
assert gcd(-10, 25) == 5
assert gcd(14, -28) == 14
assert gcd(-21, -14) == 7
def test_same_number(self):
assert gcd(10, 10) == 10
assert gcd(0, 0) == 0
def test_one_as_input(self):
assert gcd(1, 25) == 1
assert gcd(14, 1) == 1
assert gcd(1, 1) == 1
def test_large_numbers(self):
assert gcd(123456789, 987654321) == 9
assert gcd(2**64, 2**32) == 2**32
def test_float_numbers(self):
with pytest.raises(TypeError):
gcd(3.14, 2.71)
def test_string_input(self):
with pytest.raises(TypeError):
gcd("123", "456")
π©βπ» Hint
Use the Euclidean algorithm recursively, updating the values of a and b.
42.8.6.2. Searching for wanted people#
Implement find_wanted_people
function which takes a list of names (strings) as argument. The function should return a list of names which are present both in WANTED_PEOPLE
and in the name list given as argument to the function.
WANTED_PEOPLE = ['John Doe', 'Clint Eastwood', 'Chuck Norris']
# Your implementation here
____
people_to_check1 = ['Donald Duck', 'Clint Eastwood', 'John Doe', 'Barack Obama']
wanted1 = find_wanted_people(people_to_check1)
assert len(wanted1) == 2
assert 'John Doe' in wanted1
assert 'Clint Eastwood' in wanted1
people_to_check2 = ['Donald Duck', 'Mickey Mouse', 'Zorro', 'Superman', 'Robin Hood']
wanted2 = find_wanted_people(people_to_check2)
assert wanted2 == []
42.8.6.3. Counting average length of words in a sentence#
Create a function average_length_of_words
which takes a string as an argument and returns the average length of the words in the string. You can assume that there is a single space between each word and that the input does not have punctuation. The result should be rounded to one decimal place (hint: see round
).
# Your implementation here
____
assert average_length_of_words('only four lett erwo rdss') == 4
assert average_length_of_words('one two three') == 3.7
assert average_length_of_words('one two three four') == 3.8
assert average_length_of_words('') == 0
42.8.7. Lambda#
42.8.7.1. Fill the missing pieces#
def map_function(nums, func):
"""
Applies a function to each element in a list of numbers and returns a new list of results.
Args:
nums (list): A list of numbers to be processed.
func (function): A function to be applied to each element in the list.
Returns:
list: A new list of numbers where each element is the result of applying the function to the corresponding element in the original list.
"""
# Use the map function and a lambda expression to apply the function to each element in the list and convert the result to a list
return list(map(____ x: ____, ____))
# Define a list of numbers
nums = [1, 2, 3, 4, 5]
# Define a function that squares a number
def square(x):
return ____
# Call the map_function with the list of numbers and the square function
assert map_function(nums, square) == [1, 4, 9, 16, 25]
Check result by executing below... π
%%ipytest -qq
class TestMapFunction:
def test_square_function(self):
assert map_function([1, 2, 3, 4, 5], square) == [1, 4, 9, 16, 25]
assert map_function([0, -1, 2, -3, 4], square) == [0, 1, 4, 9, 16]
assert map_function([], square) == []
def test_negative_numbers(self):
assert map_function([-1, -2, -3], abs) == [1, 2, 3]
assert map_function([-1, -2, -3], square) == [1, 4, 9]
def test_float_numbers(self):
assert map_function([1.5, 2.5, 3.5], int) == [1, 2, 3]
assert map_function([0.5, 1.5, 2.5], round) == [0, 2, 2]
def test_string_numbers(self):
with pytest.raises(TypeError):
map_function(["1", "2", "3"], square)
π©βπ» Hint
Use the map() function with a lambda expression to apply the given function to each element in the list and convert the result to a list.
42.8.8. Classes#
42.8.8.1. Fill the missing pieces of the Stack
class#
Fill ____
pieces of the Stack
implemention in order to pass the assertions.
class Stack:
"""
A class to represent a stack data structure.
Attributes:
items (list): A list of items in the stack.
Methods:
push(item): Adds an item to the top of the stack.
pop(): Removes and returns the top item from the stack.
peek(): Returns the top item from the stack without removing it.
is_empty(): Returns True if the stack is empty, False otherwise.
"""
# Initialize an empty list to store the items
def _____(____):
____.items = []
# Add an item to the top of the stack
def push(self, ____):
self.items.append(____)
# Remove and return the top item from the stack
def pop(self):
return self.items.____
# Check if the stack is empty
def is_empty(self):
return ____ == 0
stack = Stack()
stack.push(1)
assert stack.pop() == 1
assert stack.is_empty() == True
Check result by executing below... π
%%ipytest -qq
class TestStack:
def test_push(self):
stack = Stack()
stack.push(1)
stack.push(2)
stack.push(3)
assert stack.items == [1, 2, 3]
def test_pop(self):
stack = Stack()
stack.push(1)
stack.push(2)
stack.push(3)
assert stack.pop() == 3
assert stack.pop() == 2
assert stack.pop() == 1
assert stack.is_empty()
def test_is_empty(self):
stack = Stack()
assert stack.is_empty()
stack.push(1)
assert not stack.is_empty()
stack.pop()
assert stack.is_empty()
π©βπ» Hint
Initialize the list in init, use list operations to push and pop items, and check the list length to determine if itβs empty.
42.8.8.2. Fill the missing pieces of the Complex
class#
class Complex:
"""
A class to represent a complex number.
Attributes:
real (float): The real part of the complex number.
imag (float): The imaginary part of the complex number.
Methods:
__add__(other): Returns the sum of two complex numbers.
__sub__(other): Returns the difference of two complex numbers.
__mul__(other): Returns the product of two complex numbers.
__truediv__(other): Returns the quotient of two complex numbers.
__abs__(): Returns the absolute value of the complex number.
__eq__(): Returns whether two complex numbers are equal.
conjugate(): Returns the conjugate of the complex number.
"""
# Initialize the real and imaginary parts of the complex number
def ____(self, real, imag):
____ = ____
____ = ____
# Define the string representation of the complex number
def __str__(self):
if self.imag >= 0:
return f"{____} + {____}i"
else:
return f"{____} - {____}i"
# Define the addition of two complex numbers
def __add__(self, other):
return ____
# Define the subtraction of two complex numbers
def __sub__(self, other):
return ____
# Define the multiplication of two complex numbers
def __mul__(self, other):
return ____
# Define the division of two complex numbers
def __truediv__(self, other):
denominator = other.real ____ + other.imag ____
return Complex((self.real * ____ + self.imag * ____) ____ denominator,
(self.imag * ____ - self.real * ____) ____ denominator)
# Define the absolute value of the complex number
def __abs__(self):
return (self.real ____ + self.imag ____) ____
# Define the equality of two complex numbers
def __eq__(self, other):
return self.real == other.real ____ self.imag == other.imag
# Define the conjugate of the complex number
def conjugate(self):
return ____
# Create two complex numbers
z1 = Complex(3, 4)
z2 = Complex(1, -2)
assert z1 + z2 == Complex(4, 2)
assert z1 - z2 == Complex(2, 6)
assert z1 * z2 == Complex(11, -2)
assert z1 / z2 == Complex(-1, 2)
assert abs(z1) == 5
assert abs(z2) == 2.23606797749979
assert z1.conjugate() == Complex(3, -4)
assert z2.conjugate() == Complex(1, 2)
Check result by executing below... π
%%ipytest -qq
class TestComplex:
def test_addition(self):
c1 = Complex(1, 2)
c2 = Complex(3, 4)
result = c1 + c2
assert result.real == 4
assert result.imag == 6
def test_subtraction(self):
c1 = Complex(1, 2)
c2 = Complex(3, 4)
result = c1 - c2
assert result.real == -2
assert result.imag == -2
def test_multiplication(self):
c1 = Complex(1, 2)
c2 = Complex(3, 4)
result = c1 * c2
assert result.real == -5
assert result.imag == 10
def test_division(self):
c1 = Complex(1, 2)
c2 = Complex(3, 4)
result = c1 / c2
assert result.real == 0.44
assert result.imag == 0.08
def test_absolute_value(self):
c = Complex(3, 4)
result = abs(c)
assert result == 5
def test_equality(self):
c1 = Complex(1, 2)
c2 = Complex(1, 2)
c3 = Complex(3, 4)
assert c1 == c2
assert c1 != c3
def test_conjugate(self):
c = Complex(1, 2)
result = c.conjugate()
assert result.real == 1
assert result.imag == -2
π©βπ» Hint
Implement arithmetic operations using appropriate operators and utilize mathematical operations and string formatting for other methods.
42.8.9. Exceptions#
42.8.9.1. Dealing with exceptions#
Fill ____
parts of the implementation below. sum_of_list
function takes a list as argument and calculates the sum of values in the list. If some element in the list can not be converted to a numeric value, it should be ignored from the sum.
def sum_of_list(values):
____ = 0
for val in values:
____:
numeric_val = float(val)
____
____ as e:
____
____ += numeric_val
return ____
list1 = [1, 2, 3]
list2 = ['1', 2.5, '3.0']
list3 = ['', '1']
list4 = []
list5 = ['John', 'Doe', 'was', 'here']
nasty_list = [KeyError(), [], dict()]
assert sum_of_list(list1) == 6
assert sum_of_list(list2) == 6.5
assert sum_of_list(list3) == 1
assert sum_of_list(list4) == 0
assert sum_of_list(list5) == 0
assert sum_of_list(nasty_list) == 0
42.8.9.2. Dealing with exceptions#
def square_root(x):
"""
Finds the square root of a non-negative number using the Newton's method.
Args:
x (float): A non-negative number to be the input.
Returns:
float: The square root of x.
Raises:
ValueError: If x is negative.
"""
# Check if x is negative
if x < 0:
# Raise a ValueError exception with a message
raise ValueError("x must be non-negative")
# Use a try block to attempt the square root calculation
____:
# Initialize a guess value as half of x
guess = x / 2
# Loop until the guess is close enough to the actual square root
while abs(____ - x) > 0.000001:
# Update the guess value using the Newton's formula
guess = (guess + x ____ guess) / 2
# Return the guess value as the square root of x
return guess
# Use an except block to handle possible ZeroDivisionError exceptions
____ ZeroDivisionError as ____:
# Print the exception message
print(____)
return ____
import math
assert math.isclose(square_root(25), 5, rel_tol=0.000001)
import pytest
with pytest.raises(ValueError):
square_root(-9)
Check result by executing below... π
%%ipytest -qq
class TestSquareRoot:
def test_positive_number(self):
result = square_root(16)
assert result == pytest.approx(4)
def test_zero(self):
result = square_root(0)
assert result == 0
def test_negative_number(self):
with pytest.raises(ValueError):
square_root(-16)
def test_large_number(self):
result = square_root(123456789)
assert result == pytest.approx(11111.11109, rel=1e-5)
π©βπ» Hint
Try-except for ZeroDivisionError, initialize guess as x/2, update using Newtonβs formula until desired precision.
42.8.10. Acknowledgments#
Thanks to below awesome open source projects for Python learning, which inspire this chapter.