7 // 3 # Floor division results in the quotient

7 % 3 # Modulus returns the remainder

"""
Explanation: Chapter 5: Conditionals and recursion

Contents
- Modulus operator
- Boolean expressions
- Logical operators
- Conditional execution
- Alternative execution
- Chained conditionals
- Nested conditionals
- Recursion
- Infinite recursion
- Keyboard input
- Debugging
- Exercises

This notebook is based on "Think Python, 2Ed" by Allen B. Downey <br>
https://greenteapress.com/wp/think-python-2e/

Modulus operator

The modulus operator is used with integers
It returns the remainder of a division operation
You will find that you use it more than you think you will
End of explanation
"""


5 == 5

5 == 4

"""
Explanation: Boolean expressions

A boolean (or bool in Python) is a True or False value
A boolean expression is an expression that results in either a True or False
We have already seen a simple operator that produces a boolean: ==
End of explanation
"""


True and True

True and False

True or True

True or False

False or False

"""
Explanation: True and False are not strings, nor are the equivalent to strings
They belong to the bool datatype
There are other relational operators besides ==:
!= Not equal
&gt; Greater than
&lt; Less than
&gt;= Greater than or equal to
&lt;= Less than or equal to
Remember that = is an assignment operator and == is a relational operator that tests equality

Logical operators

There are 3 logical operators:
and
or
not
They mean the same thing in Python that they do in English
End of explanation
"""


True and (True or False)

"""
Explanation: When combining multiple expresssions, don't be afraid to use parenthesis
They help make the code more readable and ensure it works as intended
End of explanation
"""


x = 1
if( x > 0 ):
    print( 'x is positive' )

"""
Explanation: All operands in a boolean expression should be boolean expressions or boolean variables/values
However, Python can bend the rules
For example, nonzero numbers are considered True
But try to avoid this behavior

Conditional execution

When writing programs, we often need the ability to change the behavior of a program depending on the situation
Conditional statements give us this ability
The simplest version is the if statement
End of explanation
"""


if( 0 == x % 2 ):
    print( 'x is even' )
else:
    print( 'x is odd' )

"""
Explanation: The boolean expression after the if is called the condition
If it is True, then the indented portion is executed
It has a similar form as a function definition or a loop
These are often called compound statements

Alternative execution

Another form of the if statement is alternative execution
If there are two possibilities, you can add an else statement
End of explanation
"""


y = 0
if( x < y ):
    print( 'x is less than y' )
elif( x > y ):
    print( 'x is greater than y' )
else:
    print( 'x and y are equal' )

"""
Explanation: The possible paths the code can follow are referred to as branches

Chained conditionals

If you have more than two possibilities, you can build a chained conditional
This is done by adding one or more elif statements
End of explanation
"""


# TODO - calculate a tax rate, tip or GPA example

"""
Explanation: elif is an abbreviation for "else if"
Using chained conditionals, only one branch will be taken
Each condition is checked in order from first to last
When a condition is met, the branch is executed and the remainder are ignored, even if others would be true as well
There isn't a limit on the number of elif statements, butdon't go overboard
An else statement isn't required so don't use one if it doesn't make sense
End of explanation
"""


if( x == y ):
    print( 'x and y are equal' )
else:
    if( x < y ):
        print( 'x is less than y' )
    else:
        print( 'x is greater than y' )

"""
Explanation: Nested conditionals

One conditional can be nested within another
End of explanation
"""


# Using nested conditionals
if( 0 < x ):
    if( x < 10 ):
        print( 'x is a positive single-digit number' )

# Using logical operators - this is easier to read
if( (0 < x) and (x > 10) ):
    print( 'x is a positive single-digit number' )

"""
Explanation: The outer conditional contains two (2) branches
The second branch contains another conditional with two (2) branches of its own
Note the indentation of the statements
Nested conditionals can be difficult to read quickly
Avoid them when possible
Note that you can use logical operators to simplify conditionals
End of explanation
"""


def countdown( n ):
    if( n <= 0 ):
        print( 'Blastoff!' )
    else:
        print( n )
        # Recursive call
        countdown( n - 1 )

countdown( 5 )

"""
Explanation: Recursion

We have seen how one function can call another
It is also legal for a function to call itself
Such functions are called recursive and use a process called recursion
End of explanation
"""


def countdown_unsafe( n ):
    if( n == 0 ):
        print( 'Blastoff!' )
    else:
        print( n )
        # Recursive call
        countdown( n - 1 )

"""
Explanation: This problem-solving approach is called divide and conquer
It breaks a problem in to smaller chunks that are easier to solve
In this case, the smaller problem is identical in nature to the previous one, just smaller
The function calls itself until n &lt;= 0
This is called the base case
Recursive functions need at least one base case to function properly, but you can have more
You can always convert a recursive function into an iterative function that uses a loop
You can also do the reverse and go from an iterative function to a recursive function
For some problems it is easier to write a recursive function than an iterative one

Infinite recursion

If you forget a base case or have an error in your program so that you never reach a base case, the program will never stop running
The recursive function keeps calling itself, but the problem is never smaller
This is called infinite recursion and is generally considered bad
Due to the way programs work inside a computer, they don't really run forever
You usually get a runtime error like a stack overflow or max recursion depth exceeded
The exact error depends on the programming language
Take care to write your base case conditions carefully to avoid these situations
Consider the countdown function from above
End of explanation
"""


# Uncomment this to see what will happen
# countdown_unsafe( 3.5 )

"""
Explanation: It has been modified so that the conditional is an equality test
What would happen if a non-integer number was passed to the function?
End of explanation
"""


def calculate_gpa( a_hours, b_hours, c_hours, d_hours, f_hours ):
    # INSERT YOUR CODE HERE
    return 0

"""
Explanation: Using &lt;= acts as a failsafe if an incorrect value is passed to the function
Always assume the worst can happen and write your code accordingly
Think of it as defensive programming (just like defensive driving)

Keyboard input

Up to this point we have primarily relied upon hard-coded input for functions
Python provides the input function that gets input from the keyboard
It returns the user's input as a string value
You can pass a prompt as an argument to the function
If you want the user's input to start below the prompt add a \n newline character
If you need the user's input as something other than a string, convert it
Work some examples
You can even use the eval function to evaluate it as if it where a Python expression

Debugging

Note that when Python indicates that an error was discovered on a particular line, that is only where the problem was detected
It does not mean that is the line where the problem actually occurs
You may have to look at previous lines of code to find the actual error

Exercises

You are given the number of hours in a semester broken down by grade.  For example, of Bob's total 16 hours, he got an A in 6 hours (2 classes), a B in 7 hours (2 classes and a lab), and a C in 3 hours (1 class).  Write a function calculate_gpa that takes the number of hours for each letter grade A through F and computes the GPA.  Test your function with at least three different sets of letter grade hours.
End of explanation
"""


def calculate_fibonacci( n ):
    # INSERT YOUR CODE HERE
    return 0

"""
Explanation: Write a function called calculate_fibonacci that takes a parameter n, denoting the $n$-th fibonacci number, and returns the fibonacci number.
End of explanation
"""


