2.1. Conditionals and recursion#

2.1.1. Floor division and modulus#

Floor Division and Modulus are two mathematical operations in Python that are often used with integers. They serve different purposes and can be useful in various programming scenarios [Downey, 2015, Python Software Foundation, 2023].

2.1.1.1. Floor Division (//)#

Floor division (also known as integer division) divides two numbers and returns the largest integer that is less than or equal to the quotient.

# Floor division
result = 10 // 3
print(result)  # Output: 3
3

In this example, 10 // 3 results in 3, as it gives the largest integer value that’s less than or equal to the quotient of the division.

2.1.1.2. Modulus (%)#

The modulus operation calculates the remainder when one number is divided by another.

# Modulus
remainder = 10 % 3
print(remainder)  # Output: 1
1

In this example, 10 % 3 results in 1, as the remainder when dividing 10 by 3 is 1.

These operations can be particularly useful in scenarios where you need to split values into equal parts, determine if a number is divisible by another, or deal with periodic patterns, such as days of the week or cycles.

For example, floor division can be used to divide a range of numbers into equal parts, and modulus can help check for divisibility:

# Divide a range of numbers into equal parts
total = 100
parts = 7
equal_part_size = total // parts
print(equal_part_size)  # Output: 14
14
# Check if a number is divisible by another
num = 25
divisor = 5
is_divisible = num % divisor == 0
print(is_divisible)  # Output: True (25 is divisible by 5)
True

2.1.2. Boolean expressions#

In Python, Boolean expressions are expressions that evaluate to either True or False. These expressions are used in conditional statements, loops, and logical operations. The two primary Boolean values in Python are True and False. Let’s explore some examples of Boolean expressions in Python [Downey, 2015, Python Software Foundation, 2023]:

2.1.2.1. Comparison Operators#

Comparison operators are used to compare two values and evaluate a Boolean result (True or False).

Example:

x = 5
y = 10

result1 = x < y  # True (5 is less than 10)
print(result1)

result2 = x >= y  # False (5 is not greater than or equal to 10)
print(result2)

result3 = x == y  # False (5 is not equal to 10)
print(result3)
True
False
False

2.1.2.2. Logical Operators#

Logical operators are used to combine multiple Boolean expressions and evaluate to a final Boolean result.

  • and: Returns True if both expressions are True.

  • or: Returns True if at least one expression is True.

  • not: Returns the opposite Boolean value (negation) of the expression.

A

B

A AND B

A OR B

NOT A

True

True

True

True

False

True

False

False

True

False

False

True

False

True

True

False

False

False

False

True

In this table:

  • “A” and “B” represent the input values for the logical operations.

  • “A AND B” denotes the result of the logical AND operation between “A” and “B”.

  • “A OR B” represents the result of the logical OR operation between “A” and “B”.

  • “NOT A” indicates the result of the logical NOT operation on “A”.

Example:

a = True
b = False

result1 = a and b  # False (both a and b are not True)
print(result1)
result2 = a or b   # True (at least one of a and b is True)
print(result2)
result3 = not a    # False (the negation of True is False)
print(result3)
False
True
False

Note

Logical operators are often used in combination with comparison operators to create more complex conditions in if statements, while loops, and other control flow structures. They allow you to make decisions based on the evaluation of multiple conditions.

2.1.2.3. Membership Operators#

Membership operators are used to check if a value exists in a sequence (e.g., list, tuple, string) [Python Software Foundation, 2023].

  • in: Returns True if the value is present in the sequence.

  • not in: Returns True if the value is not present in the sequence.

Example:

numbers = [1, 2, 3, 4, 5]

result1 = 3 in numbers  # True (3 is present in the list)
print(result1)
result2 = 6 not in numbers  # True (6 is not present in the list)
print(result2)
True
True

2.1.2.4. Identity Operators#

Identity operators are used to compare the memory location of two objects [Downey, 2015, Python Software Foundation, 2023].

  • is: Returns True if both variables refer to the same object.

  • is not: Returns True if both variables refer to different objects.

Example:

x = [1, 2, 3]
y = x
z = [1, 2, 3]

result1 = x is y  # True (x and y refer to the same object)
print(result1)
result2 = x is z  # False (x and z refer to different objects)
print(result2)
True
False

Boolean expressions play a crucial role in control flow and decision-making in Python programming, enabling you to create conditional statements and loops that execute based on the evaluation of these expressions.

2.1.3. Conditional execution#

In Python, conditional execution allows you to execute specific blocks of code based on certain conditions. This is typically achieved using the if, elif (optional), and else (optional) statements. The general syntax for conditional execution in Python is as follows [Downey, 2015, Python Software Foundation, 2023]:

if condition:
    # Code block to be executed if the condition is True
elif condition2:  # Optional (elif stands for "else if")
    # Code block to be executed if condition2 is True (only if the preceding 'if' is False)
else:  # Optional
    # Code block to be executed if none of the above conditions are True

Let’s look at some examples to better understand conditional execution:

2.1.3.1. Using if statement only#

x = 10

if x > 5:
    print("x is greater than 5.")
x is greater than 5.

In this example, if the value of x is greater than 5, the code block under the if statement will be executed, and “x is greater than 5.” will be printed.

Start
|
|-- [x > 5]
|    |
|    |-- [Print "x is greater than 5."]
|
|-- [Outside the if structure]
|    |
|    |-- [End of the program]
|
End

2.1.3.2. Using if and else statements#

x = 3

if x > 5:
    print("x is greater than 5.")
else:
    print("x is not greater than 5.")
x is not greater than 5.

In this example, if the value of x is greater than 5, the code block under the if statement will be executed. Otherwise, the code block under the else statement will be executed.

Start
|
|-- [x > 5]
|    |
|    |-- [Print "x is greater than 5."]
|
|-- [Else]
|    |
|    |-- [Print "x is not greater than 5."]
|
|-- [Outside the if-else structure]
|    |
|    |-- [End of the program]
|
End

2.1.3.3. Using if, elif, and else statements#

Example:

x = 5

if x > 5:
    print("x is greater than 5.")
elif x == 5:
    print("x is equal to 5.")
else:
    print("x is less than 5.")
x is equal to 5.

In this example, if the value of x is greater than 5, the first if block will be executed. If x is equal to 5, the elif block will be executed. If both conditions are False, the else block will be executed.

Start
|
|-- [x > 5]
|    |
|    |-- [Print "x is greater than 5."]
|
|-- [x == 5]
|    |
|    |-- [Print "x is equal to 5."]
|
|-- [Else]
|    |
|    |-- [Print "x is less than 5."]
|
|-- [Outside the if-elif-else structure]
|    |
|    |-- [End of the program]
|
End

Conditional execution is fundamental for making decisions in programming. It allows your code to respond to different situations and execute appropriate actions accordingly. You can have nested if-elif-else structures to handle more complex decision-making scenarios.

Example: Consider an illustrative case of nested conditionals that ascertain a student’s grade based on their score. This process is demonstrated using the following link: https://conted.ucalgary.ca/info/grades.jsp

def get_letter_grade(score):
    if score >= 95:
        return "A+ - Outstanding"
    elif score >= 90:
        return "A - Excellent\nSuperior performance, showing comprehensive understanding of subject matter."
    elif score >= 85:
        return "A- - Approaching Excellent"
    elif score >= 80:
        return "B+ - Exceeding Good"
    elif score >= 75:
        return "B - Good\nClearly above average performance with knowledge of subject matter generally complete."
    elif score >= 70:
        return "B- - Approaching Good"
    elif score >= 67:
        return "C+ - Exceeding Satisfactory"
    elif score >= 64:
        return "C - Satisfactory (minimal pass)\nBasic understanding of subject matter. Minimum required in all courses to meet certificate program requirements."
    elif score >= 60:
        return "C- - Approaching Satisfactory\nReceipt of a C- or less is not sufficient for certificate program requirements."
    elif score >= 55:
        return "D+ - Marginal Performance"
    elif score >= 50:
        return "D - Minimal Performance"
    else:
        return "F - Fail"

score = 85
print(get_letter_grade(score))
A- - Approaching Excellent

This code essentially acts as a grading scale and provides descriptive information about the grade based on the input score. Here’s a brief explanation of the code:

  • The get_letter_grade function takes a single parameter, score, which is expected to be a numerical value representing a student’s score.

  • The function uses a series of conditional statements (if, elif, else) to determine the appropriate letter grade based on the value of the score.

  • Each conditional branch specifies a certain score range along with a corresponding letter grade and a brief description of that grade. For example, if the score is 95 or above, the function returns “A+ - Outstanding”. If the score is between 90 and 94, it returns “A - Excellent” along with a more detailed description of excellent performance.

  • The code example at the bottom sets the score variable to 85 and then calls the get_letter_grade function with this score. The result is printed using the print function.

  • In this specific example, with a score of 85, the function returns “A- - Approaching Excellent”.

Start
|
|-- [score >= 95]
|    |
|    |-- [Return "A+ - Outstanding"]
|
|-- [score >= 90]
|    |
|    |-- [Return "A - Excellent"]
|    |    |
|    |    |-- [Additional description: "Superior performance, showing comprehensive understanding of subject matter."]
|
|-- [score >= 85]
|    |
|    |-- [Return "A- - Approaching Excellent"]
|
|-- [score >= 80]
|    |
|    |-- [Return "B+ - Exceeding Good"]
|
|-- [score >= 75]
|    |
|    |-- [Return "B - Good"]
|    |    |
|    |    |-- [Additional description: "Clearly above average performance with knowledge of subject matter generally complete."]
|
|-- [score >= 70]
|    |
|    |-- [Return "B- - Approaching Good"]
|
|-- [score >= 67]
|    |
|    |-- [Return "C+ - Exceeding Satisfactory"]
|
|-- [score >= 64]
|    |
|    |-- [Return "C - Satisfactory (minimal pass)"]
|    |    |
|    |    |-- [Additional description: "Basic understanding of subject matter. Minimum required in all courses to meet certificate program requirements."]
|
|-- [score >= 60]
|    |
|    |-- [Return "C- - Approaching Satisfactory"]
|    
|-- [score >= 55]
|    |
|    |-- [Return "D+ - Marginal Performance"]
|
|-- [score >= 50]
|    |
|    |-- [Return "D - Minimal Performance"]
|
|-- [Else]
|    |
|    |-- [Return "F - Fail"]
|
|-- [Outside the if-elif-else structure]
|    |
|    |-- [Print the result of get_letter_grade(score)]
|
End

2.1.4. Nested conditionals#

In Python, nested conditionals refer to the practice of placing conditional statements (if, elif, else) inside other conditional statements. This allows you to create more complex decision-making structures and hierarchically handle multiple conditions. The syntax for nested conditionals is as follows:

if condition1:
    # Code block to be executed if condition1 is True
    if nested_condition1:
        # Code block to be executed if both condition1 and nested_condition1 are True
    elif nested_condition2:
        # Code block to be executed if condition1 is True and nested_condition2 is True
    else:
        # Code block to be executed if condition1 is True but none of the nested conditions are True
elif condition2:
    # Code block to be executed if condition1 is False and condition2 is True
    if nested_condition3:
        # Code block to be executed if condition2 is True and nested_condition3 is True
    else:
        # Code block to be executed if condition2 is True but nested_condition3 is False
else:
    # Code block to be executed if both condition1 and condition2 are False

Start
|
|--> [Condition1]
|      |
|      |--> [Nested_Condition1]
|      |      |
|      |      |--> [Code block if both Condition1 and Nested_Condition1 are True]
|      |
|      |--> [Nested_Condition2]
|      |      |
|      |      |--> [Code block if Condition1 is True and Nested_Condition2 is True]
|      |
|      |--> [Else]
|             |
|             |--> [Code block if Condition1 is True but none of the nested conditions are True]
|
|--> [Condition2]
|      |
|      |--> [Code block if Condition1 is False and Condition2 is True]
|      |
|      |--> [Nested_Condition3]
|      |      |
|      |      |--> [Code block if Condition2 is True and Nested_Condition3 is True]
|      |
|      |--> [Else]
|             |
|             |--> [Code block if Condition2 is True but Nested_Condition3 is False]
|
|--> [Else]
       |
       |--> [Code block if both Condition1 and Condition2 are False]
       
End

Example: Here’s an example of nested conditional statements as per your provided structure:

# Assign values to variables
x = 10
y = 5

# Check if x is greater than y
if x > y:
    print("x is greater than y")  # Print this if x is greater than y
    
    # Check if x is greater than 15
    if x > 15:
        print("x is also greater than 15")  # Print this if x is greater than 15
    # Check if x is greater than 12 but not greater than 15
    elif x > 12:
        print("x is greater than 12 but not greater than 15")  # Print this if x is between 12 and 15
    else:
        print("x is greater than y, but none of the nested conditions matched")  # Print this if none of the above conditions in this block match
        
# Check if x is less than y
elif x < y:
    print("x is less than y")  # Print this if x is less than y
    
    # Check if y is less than 8
    if y < 8:
        print("y is less than 8")  # Print this if y is less than 8
    else:
        print("y is not less than 8")  # Print this if y is 8 or greater
        
# If neither of the above conditions are met
else:
    print("x and y are equal")  # Print this if x and y have the same value
x is greater than y
x is greater than y, but none of the nested conditions matched

In this example:

  • We first compare x and y using the outermost if, elif, and else structure.

  • If x is greater than y, we enter the nested if, elif, and else structure inside that block.

  • Depending on the value of x, we print different messages within the nested structure.

  • If x is less than y, we enter the nested structure under the elif block for the case where x is less than y.

  • Finally, if neither of the outer conditions is met, we execute the code in the else block.

This demonstrates the nesting of conditional statements, where the behavior of the program depends on multiple levels of conditions and nested conditions.

Start
|
|-- [Condition1: x > y]
|    |
|    |-- [Code block: "x is greater than y"]
|    |
|    |-- [Nested_Condition1: x > 15]
|    |    |
|    |    |-- [Code block: "x is also greater than 15"]
|    |
|    |-- [Nested_Condition2: x > 12]
|    |    |
|    |    |-- [Code block: "x is greater than 12 but not greater than 15"]
|    |
|    |-- [Else: "x is greater than y, but none of the nested conditions matched"]
|
|-- [Condition2: x < y]
|    |
|    |-- [Code block: "x is less than y"]
|    |
|    |-- [Nested_Condition3: y < 8]
|    |    |
|    |    |-- [Code block: "y is less than 8"]
|    |
|    |-- [Else: "y is not less than 8"]
|
|-- [Else: "x and y are equal"]
|
|-- [Code block: "End of the program"]
|
End

2.1.5. Recursion#

Recursion is a powerful programming concept in which a function calls itself to solve a problem. It is a fundamental technique used in many algorithms and data structures. Recursive functions break down a complex problem into smaller subproblems, solve each subproblem, and combine their results to obtain the final solution. A recursive function typically consists of two main components:

  1. Base case: A condition that determines when the function should stop calling itself and return a result directly. It prevents the function from calling itself indefinitely and causing infinite recursion.

  2. Recursive case: The part of the function where it calls itself with modified arguments to solve a smaller version of the original problem.

Here’s a simple example of a recursive function to calculate the factorial of a number:

def factorial(n):
    if n == 0:
        return 1  # Base case: 0! is 1
    else:
        return n * factorial(n - 1)  # Recursive case: n! = n * (n-1)!

Let’s break it down step by step:

  1. The factorial function takes one argument, n, which represents the number for which we want to calculate the factorial.

  2. Inside the function, there’s an if statement that checks if n is equal to 0. This is known as the base case of the recursion. In mathematics, 0 factorial (0!) is defined to be 1. So, if n is 0, the function immediately returns 1, and the recursion stops. This is essential because it provides a base condition to end the recursive calls.

  3. If n is not 0, the else block is executed. Here’s what happens in the else block:

    a. n * factorial(n - 1): This is the recursive case. It calculates n times the factorial of (n - 1). This is based on the fact that the factorial of n is defined as n! = n * (n - 1)!.

    b. The factorial(n - 1) part is a recursive call to the factorial function with n - 1 as its argument. This means that the function will be called again with a smaller value of n. This process continues until n reaches 0 (the base case), at which point the recursion stops.

    c. The intermediate results are accumulated as each recursive call returns. For example, if you call factorial(4), it calculates 4 * factorial(3), then 3 * factorial(2), then 2 * factorial(1), and finally 1 * factorial(0). When factorial(0) is reached, it returns 1, and the results are propagated back up the recursion stack to calculate the final result.

  4. The function returns the result of the recursive calculation, which is the factorial of the original n provided as an argument.

Here’s a sample breakdown of how factorial(4) would be calculated step by step:

factorial(4) = 4 * factorial(3)
             = 4 * (3 * factorial(2))
             = 4 * (3 * (2 * factorial(1)))
             = 4 * (3 * (2 * (1 * factorial(0))))
             = 4 * (3 * (2 * (1 * 1)))
             = 4 * (3 * (2 * 1))
             = 4 * (3 * 2)
             = 4 * 6
             = 24

So, factorial(4) returns 24, which is the factorial of 4. This recursive function is a classic example of how recursion can be used to solve problems where the solution depends on smaller instances of the same problem.

Let’s call the factorial function with an example:

result = factorial(5)
print(result)  # Output: 120 (5! = 5 * 4 * 3 * 2 * 1 = 120)
120

Note

Recursion is a powerful technique, but it can also lead to performance issues and stack overflow errors if not used correctly or if the base case is not properly defined. It’s essential to ensure that the recursion stops at some point and that the problem size reduces with each recursive call.

2.1.6. Stack diagrams for recursive functions#

Stack diagrams are a visual representation of the call stack, which is a data structure used to manage function calls in a program. When a function is called, a new frame (also known as an activation record) is created on the call stack to store the function’s local variables and execution context. When the function returns, its frame is removed from the stack.

For recursive functions, the call stack grows as the function calls itself multiple times. Each recursive call creates a new frame on top of the previous one. When the base case is reached, the function calls start returning, and the frames are removed from the stack one by one.

Let’s consider the same example of the factorial function from the previous explanation and visualize its stack diagram:

def factorial(n):
    if n == 0:
        return 1  # Base case: 0! is 1
    else:
        return n * factorial(n - 1)  # Recursive case: n! = n * (n-1)!

result = factorial(5)
print(result)
120

The stack diagram for the factorial(5) function call will look like this:

|-- factorial(5)
|   |-- factorial(4)
|   |   |-- factorial(3)
|   |   |   |-- factorial(2)
|   |   |   |   |-- factorial(1)
|   |   |   |   |   |-- factorial(0)
|   |   |   |   |   |-- return 1
|   |   |   |   |-- return 1 * 1 = 1
|   |   |   |-- return 2 * 1 = 2
|   |   |-- return 3 * 2 = 6
|   |-- return 4 * 6 = 24
|-- return 5 * 24 = 120

../_images/StackedDiagram_Factorial5.jpg

Fig. 2.1 Visual representation 5!.#

Each entry in the stack diagram represents a frame for a specific recursive call. The function factorial(5) calls factorial(4), which in turn calls factorial(3), and so on until the base case factorial(0) is reached. Then, the function returns its result to the previous frame, and the process continues until the final result of factorial(5) is calculated.

Stack diagrams provide a clear visual representation of the function calls and the flow of execution in recursive functions. They are a helpful tool for understanding how recursion works and how the call stack manages function calls in a program.

2.1.7. Infinite recursion#

Infinite recursion occurs when a recursive function calls itself indefinitely without reaching a base case to stop the recursion. As a result, the call stack continues to grow with each recursive call, eventually leading to a “RecursionError” or “maximum recursion depth exceeded” error in Python.

Here’s an example of a recursive function with no base case, leading to infinite recursion:

def infinite_recursion():
    print("This function is calling itself indefinitely!")
    infinite_recursion()

infinite_recursion()

If you run the code above, you will see that the function keeps printing the message “This function is calling itself indefinitely!” repeatedly until a “RecursionError” occurs.

To prevent infinite recursion, it is crucial to define a base case in the recursive function. The base case serves as a termination condition that stops the recursion and allows the function to start returning values instead of making more recursive calls. Without a base case, the function will keep calling itself without an end.

Let’s modify the previous example to include a base case:

def not_a_infinite_recursion(counter):
    print(f"This function is calling itself {counter} times.")
    if counter > 1:
        not_a_infinite_recursion(counter - 1)

not_a_infinite_recursion(5)
This function is calling itself 5 times.
This function is calling itself 4 times.
This function is calling itself 3 times.
This function is calling itself 2 times.
This function is calling itself 1 times.

It’s essential to be careful while using recursion and ensure that the base case is properly defined to avoid infinite recursion and potential stack overflow errors. Recursive functions should reduce the problem size with each recursive call, eventually leading to the termination condition (base case).

2.1.8. Keyboard input#

In Python, you can take keyboard input from the user using the built-in input() function. The input() function reads a line of text entered by the user and returns it as a string. You can then store this input in a variable or use it directly in your program.

Here’s a simple example of how to use the input() function to take keyboard input from the user:

# Taking user input and storing it in a variable
name = input("Enter your name: ")
print("Hello, " + name + "!")

# Taking user input and using it directly
age = int(input("Enter your age: "))  # We use int() to convert the input to an integer
print("You will be " + str(age + 1) + " years old next year.")

In the first example, the program prompts the user to enter their name, and the input is stored in the variable name. The program then prints a greeting message using the user’s name.

In the second example, the program prompts the user to enter their age, and the input is read as a string by default. We use the int() function to convert the input to an integer so that we can perform arithmetic operations on it. The program then prints a message about the user’s age next year.

Keep in mind that input() always returns a string. If you need to perform calculations or comparisons on the input as numbers, you should explicitly convert it to the appropriate data type (e.g., int, float) using int() or float().

Note

When using input(), the program will wait for the user to enter text and press the “Enter” key before moving on to the next line of code.