Operations Link to heading

These are some of the operations you can perform in Python.

2+3 #addition
2-3 #Subtraction
2*3 #Multiplication
2/3 #Division
2**3 #Exponentiation
2//3 #Floor Division
2%3 #Modulus

5
-1
6
0.6666666666666666
8
0
2

Value types Link to heading

In python there are different types of values, such as integers, floats, and strings.

type(42)        # Integer
type(3.14)      # Float
type("Hello")   # String
type(True)      # Boolean
type([1, 2, 3]) # List
type((1, 2, 3)) # Tuple
type({1, 2, 3}) # Set
type({"key": "value"}) # Dictionary

<class 'int'>
<class 'float'>
<class 'str'>
<class 'bool'>
<class 'list'>
<class 'tuple'>
<class 'set'>
<class 'dict'>

Rounding of numbers Link to heading

Sometimes the python rounds up and sometimes down


round(40.5)  # Rounds to 41
round(41.5)  # Rounds to 42
round(42.5)  # Rounds to 43

40
42
42

It turns out that Python uses “round half to even” strategy, also known as “bankers’ rounding”. This means that when the number is exactly halfway between two integers, it rounds to the nearest even integer.

Arithmetic operations Link to heading

The following questions give you a chance to practice writing arithmetic expressions.

  1. How many seconds are there in 42 minutes 42 seconds?
  2. How many miles are there in 10 kilometers? Hint: there are 1.61 kilometers in a mile.
  3. If you run a 10 kilometer race in 42 minutes 42 seconds, what is your average pace in seconds per mile?
  4. What is your average pace in minutes and seconds per mile?
  5. What is your average speed in miles per hour?
# 1. Seconds in 42 minutes 42 seconds
seconds_in_42_minutes_42_seconds = 42 * 60 + 42
seconds_in_42_minutes_42_seconds

# 2. Miles in 10 kilometers
miles_in_10_kilometers = 10 / 1.61
miles_in_10_kilometers

# 3. Average pace in seconds per mile
average_pace_seconds_per_mile = (42 * 60 + 42) / miles_in_10_kilometers
average_pace_seconds_per_mile

# 4. Average pace in minutes and seconds per mile
average_pace_minutes = average_pace_seconds_per_mile // 60
average_pace_seconds = average_pace_seconds_per_mile % 60
average_pace_minutes, average_pace_seconds

# 5. Average speed in miles per hour
average_speed_miles_per_hour = miles_in_10_kilometers / ((42 * 60 + 42) / 3600)
average_speed_miles_per_hour

2562
6.211180124223602
412.482
(6.0, 52.48200000000003)
8.727653570337614

Functions Link to heading

Why Functions? Allen Downey says it best:

There are several reasons:

  1. Creating a new function gives you an opportunity to name a group of statements, which makes your program easier to read and debug.

  2. Functions can make a program smaller by eliminating repetitive code. Later, if you make a change, you only have to make it in one place.

  3. Dividing a long program into functions allows you to debug the parts one at a time and then assemble them into a working whole.

  4. Well-designed functions are often useful for many programs. Once you write and debug one, you can reuse it.

Write a function named print_right that takes a string named text as a parameter and prints the string with enough leading spaces that the last letter of the string is in the 40th column of the display.

def print_right(text):
    # Calculate the number of leading spaces needed
    leading_spaces = 40 - len(text)
    # Print the text with leading spaces
    print(' ' * leading_spaces + text)
    
print_right("Monty")
print_right("Python's")
print_right("Flying Circus")

                                   Monty
                                Python's
                           Flying Circus

Write a function called triangle that takes a string and an integer and draws a pyramid with the given height, made up using copies of the string. Here’s an example of a pyramid with 5 levels, using the string ‘L’

def triangle(text, height):
    for i in range(height+1):
        # Print the level with leading spaces
        print(text * i)
        
triangle("L", 5)

L
LL
LLL
LLLL
LLLLL

Write a function called rectangle that takes a string and two integers and draws a rectangle with the given width and height, made up using copies of the string. Here’s an example of a rectangle with width 5 and height 4, made up of the string ‘H’

def rectangle(text, width, height):
    for i in range(height):
        # Print the rectangle row
        print(text * width)
        
rectangle("H", 5, 4)

HHHHH
HHHHH
HHHHH
HHHHH

The song “99 Bottles of Beer” starts with this verse:

99 bottles of beer on the wall 99 bottles of beer Take one down, pass it around 98 bottles of beer on the wall

Then the second verse is the same, except that it starts with 98 bottles and ends with 97. The song continues – for a very long time – until there are 0 bottles of beer.

Write a function called bottle_verse that takes a number as a parameter and displays the verse that starts with the given number of bottles.

Hint: Consider starting with a function that can print the first, second, or last line of the verse, and then use it to write bottle_verse.

Use this function call to display the first verse.

def bottle_verse(bottles):
    if bottles > 0:
        print(f"{bottles} bottles of beer on the wall")
        print(f"{bottles} bottles of beer")
        print("Take one down, pass it around")
        print(f"{bottles - 1} bottles of beer on the wall\n")
    else:
        print("No more bottles of beer on the wall")

bottle_verse(99)

99 bottles of beer on the wall
99 bottles of beer
Take one down, pass it around
98 bottles of beer on the wall

Conditional Functions Link to heading

If you are given three sticks, you may or may not be able to arrange them in a triangle. For example, if one of the sticks is 12 inches long and the other two are one inch long, you will not be able to get the short sticks to meet in the middle. For any three lengths, there is a test to see if it is possible to form a triangle:

If any of the three lengths is greater than the sum of the other two, then you cannot form a triangle. Otherwise, you can. (If the sum of two lengths equals the third, they form what is called a “degenerate” triangle.)

Write a function named is_triangle that takes three integers as arguments, and that prints either “Yes” or “No”, depending on whether you can or cannot form a triangle from sticks with the given lengths. Hint: Use a chained conditional.

def is_triangle(a, b, c):
    if a + b > c and a + c > b and b + c > a:
        print("Yes")
    else:
        print("No")
        
is_triangle(3, 4, 5)        

Use incremental development to write a function called hypot that returns the length of the hypotenuse of a right triangle given the lengths of the other two legs as arguments. Note: There’s a function in the math module called hypot that does the same thing, but you should not use it for this exercise! Even if you can write the function correctly on the first try, start with a function that always returns 0 and practice making small changes, testing as you go. When you are done, the function should only return a value – it should not display anything.

import math
def hypot(a, b):
    return math.sqrt(a**2 + b**2)

# Example usage
print(hypot(3, 4))  # Should return 5.0

5.0

Write a boolean function, is_between(x, y, z), that returns True if x < y < z or if z < y < x, and False otherwise.

def is_between(x, y, z):
    return (x < y < z) or (z < y < x)
  
# Example usage
print(is_between(1, 2, 3))  # True

True

Here is the Ackermann function, which is a classic example of a recursive function. It is defined as follows:

def ackermann(m, n):
    if m == 0:
        return n + 1
    elif m > 0 and n == 0:
        return ackermann(m - 1, 1)
    elif m> 0 and n > 0:
        return ackermann(m - 1, ackermann(m, n - 1))
    else:
        raise ValueError("m must be a non-negative integer less than or equal to 3")

# Example usage
ackermann(2, 3)  # Should return 9

9

The greatest common divisor (GCD) of a and b is the largest number that divides both of them with no remainder.

One way to find the GCD of two numbers is based on the observation that if r is the remainder when a is divided by b, then the GCD of a and b is the same as the GCD of b and r. gcd(a, b) = gcd(b, a % b). As a base case we can use gcd(a, 0) = a.

Write a function called gcd that takes parameters a and b and returns their greatest common divisor.

def gcd(a, b):
    if b == 0:
        return a
    else:
        return gcd(b, a % b)
      
# Example usage
print(gcd(48, 18))  # Should return 6

6

Write a function named uses_none that takes a word and a string of forbidden letters, and returns True if the word does not use any of the forbidden letters.

Here’s an outline of the function that includes two doctests. Fill in the function so it passes these tests, and add at least one more doctest.

def uses_none(word, forbidden):
    """
    >>> uses_none("hello", "xyz")
    True
    >>> uses_none("hello", "aeiou")
    False
    >>> uses_none("python", "xyz")
    True
    """
    for letter in forbidden:
        if letter in word:
            return False
    return True
  
# Example usage
uses_none("hello", "xyz")  # Should return True

True

Testing function

from doctest import run_docstring_examples

def run_doctests(func):
    run_docstring_examples(func, globals(), name=func.__name__)
    
run_doctests(uses_none)


**********************************************************************
File "__main__", line 7, in uses_none
Failed example:
    uses_none("python", "xyz")
Expected:
    True
Got:
    False

Write a function called uses_all that takes a word and a string of letters, and that returns True if the word contains all of the letters in the string at least once.

Here’s an outline of the function that includes two doctests. Fill in the function so it passes these tests, and add at least one more doctest.

def uses_all(word, required):
    """
    >>> uses_all("hello", "aeiou")
    False
    >>> uses_all("hello", "hlo")
    True
    >>> uses_all("python", "pyth")
    True
    """
    for letter in required:
        if letter not in word:
            return False
    return True

# example usage
uses_all("hello", "aeiou")  # Should return False

False

String and Regular Expressions Link to heading

See if you can write a function that does the same thing as the shell command !head. It should take as arguments the name of a file to read, the number of lines to read, and the name of the file to write the lines into. If the third parameter is None, it should display the lines rather than write them to a file.

def head(file_name, num_lines, output_file=None):
    with open(file_name, 'r') as file:
        lines = [next(file) for _ in range(num_lines)]
    
    if output_file:
        with open(output_file, 'w') as out_file:
            out_file.writelines(lines)
    else:
        for line in lines:
            print(line, end='')
            
# Example usage
head('example.txt', 5, 'output.txt')

“Wordle” is an online word game where the objective is to guess a five-letter word in six or fewer attempts. Each attempt has to be recognized as a word, not including proper nouns. After each attempt, you get information about which of the letters you guessed appear in the target word, and which ones are in the correct position.

For example, suppose the target word is MOWER and you guess TRIED. You would learn that E is in the word and in the correct position, R is in the word but not in the correct position, and T, I, and D are not in the word.

As a different example, suppose you have guessed the words SPADE and CLERK, and you’ve learned that E is in the word, but not in either of those positions, and none of the other letters appear in the word. Of the words in the word list, how many could be the target word? Write a function called check_word that takes a five-letter word and checks whether it could be the target word, given these guesses.

def check_word(word, guesses):
    """
    >>> check_word("MOWER", "TRIED")
    "E is in the word and in the correct position, R is in the word but not in the correct position, and T, I, and D are not in the word."
    >>> check_word("CLERK", "SPADE")
    "E is in the word but not in the correct position, and none of the other letters are in the word."
    >>> check_word("MOWER", "BOOKS")  
    "None of the letters are in the word"
    """
    correct_position = []
    wrong_position = []
    not_in_word = []
    for i, letter in enumerate(guesses):
        if letter in word:
            if word[i] == letter:
                correct_position.append(letter)
            else:
                wrong_position.append(letter)
        else:
            not_in_word.append(letter)
    
    result = []
    if correct_position:
        result.append(f"{', '.join(correct_position)} is in the word and in the correct position")
    if wrong_position:
        result.append(f"{', '.join(wrong_position)} is in the word but not in the correct position")
    if not_in_word:
        result.append(f"{', '.join(not_in_word)} are not in the word")
    if not result:
        return "None of the letters are in the word"
    return ', '.join(result)
  

# Example usage
print(check_word("MOWER", "TRIED"))  

# E is in the word and in the correct position, R is in the word but not in the correct position, T, I, D are not in the word

The Count of Monte Cristo is a novel by Alexandre Dumas that is considered a classic. Nevertheless, in the introduction of an English translation of the book, the writer Umberto Eco confesses that he found the book to be “one of the most badly written novels of all time”.

In particular, he says it is “shameless in its repetition of the same adjective,” and mentions in particular the number of times “its characters either shudder or turn pale.”

To see whether his objection is valid, let’s count the number number of lines that contain the word pale in any form, including pale, pales, paled, and paleness, as well as the related word pallor. Use a single regular expression that matches any of these words. As an additional challenge, make sure that it doesn’t match any other words, like impale – you might want to ask a virtual assistant for help.

import re

def count_pale_words(file_name):
    with open(file_name, 'r') as file:
        text = file.read()
    
    # Regular expression to match variations of "pale"
    pattern = r'\b(pale|pales|paled|paleness|pallor)\b'
    
    matches = re.findall(pattern, text, re.IGNORECASE)
    return len(matches)

Lists Link to heading

Two words are anagrams if you can rearrange the letters from one to spell the other. For example, tops is an anagram of stop. One way to check whether two words are anagrams is to sort the letters in both words. If the lists of sorted letters are the same, the words are anagrams. Write a function called is_anagram that takes two strings and returns True if they are anagrams. Using your function and the word list, find all the anagrams of takes.

def is_anagram(word1, word2):
    return sorted(word1) == sorted(word2)
  
# Example usage
print(is_anagram("tops", "stop"))  
# Should return True

Write a function called reverse_sentence that takes as an argument a string that contains any number of words separated by spaces. It should return a new string that contains the same words in reverse order. For example, if the argument is “Reverse this sentence”, the result should be “Sentence this reverse”.

Hint: You can use the capitalize methods to capitalize the first word and convert the other words to lowercase.

def reverse_sentence(sentence):
    words = sentence.split()
    reversed_words = words[::-1]
    return ' '.join(reversed_words).capitalize()
  
# Example usage
print(reverse_sentence("Reverse this sentence"))  
# Should return "Sentence this reverse"

Dictionary Link to heading

a dictionary is a collection of key-value pairs. You can use dictionaries to count the occurrences of items in a list, similar to the value_counts method in pandas.

Write a function called find_repeats that takes a dictionary that maps from each key to a counter, like the result from value_counts. It should loop through the dictionary and return a list of keys that have counts greater than 1.

def find_repeats(count_dict):
  """
  >>> find_repeats({'a': 2, 'b': 1, 'c': 3})
  ['a', 'c']
  """ 
  return [key for key, count in count_dict.items() if count > 1]

# Example usage
print(find_repeats({'a': 2, 'b': 1, 'c': 3}))  
# Should return ['a', 'c']

Tuples Link to heading

Tuples are immutable sequences in Python. They can be used to store a collection of items, similar to lists, but they cannot be modified after creation.

An example of a tuple is:

my_tuple = (1, 2, 3, "hello", True)

Write a function called find_max that takes a tuple of numbers and returns the maximum value in the tuple.

def find_max(numbers):
    """
    >>> find_max((1, 2, 3, 4, 5))
    5
    >>> find_max((10, 20, 30, 40))
    40
    >>> find_max((5, 3, 8, 1))
    8
    """
    return max(numbers)
  
# Example usage
print(find_max((1, 2, 3, 4, 5)))

Write a function that creates a tuple of the first n Fibonacci numbers, where n is a parameter. The Fibonacci sequence starts with 0 and 1, and each subsequent number is the sum of the previous two.

def fibonacci_tuple(n):
    """
    >>> fibonacci_tuple(5)
    (0, 1, 1, 2, 3)
    >>> fibonacci_tuple(7)
    (0, 1, 1, 2, 3, 5, 8)
    >>> fibonacci_tuple(0)
    ()
    """
    fib = []
    a, b = 0, 1
    for _ in range(n):
        fib.append(a)
        a, b = b, a + b
    return tuple(fib)
  
# Example usage
print(fibonacci_tuple(5))  # Should return (0, 1, 1, 2, 3)

Classes, Functions, Methods and Objects Link to heading

Classes are a way to define new types in Python. They allow you to create objects that have attributes and methods.

Methods are functions that are defined inside a class and can access the attributes of the class. Objects are instances of classes.

Objects are created from classes, and they can have their own attributes and methods.

Write a class called Circle that has an attribute radius and a method area that returns the area of the circle. The area of a circle is given by the formula A = π * r^2, where r is the radius.

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

# Example usage
circle = Circle(5)
print(circle.area())  # Should return 78.53981633974483

Write a class called Rectangle that has attributes width and height, and a method area that returns the area of the rectangle. The area of a rectangle is given by the formula A = w * h, where w is the width and h is the height.

class Rectangle:
    def __init__(self, width, height):
        self.width = width
        self.height = height
    
    def area(self):
        return self.width * self.height

# Example usage
rectangle = Rectangle(4, 5)
print(rectangle.area())  # Should return 20

Write a class that represents a bank account. The class should have attributes for the account holder’s name and balance, and methods to deposit and withdraw money. The deposit method should add the amount to the balance, and the withdraw method should subtract the amount from the balance if there are sufficient funds.

class BankAccount:
    def __init__(self, account_holder, initial_balance=0):
        self.account_holder = account_holder
        self.balance = initial_balance
    
    def deposit(self, amount):
        self.balance += amount
        print(f"Deposited {amount}. New balance: {self.balance}")
    
    def withdraw(self, amount):
        if amount > self.balance:
            print("Insufficient funds")
        else:
            self.balance -= amount
            print(f"Withdrew {amount}. New balance: {self.balance}")
            
# Example usage
account = BankAccount("Alice", 100)
account.deposit(50)  # Should print "Deposited 50. New balance: 150"
account.withdraw(30)  # Should print "Withdrew 30. New balance: 120"

END