Note 2: Conditionals and Iteration

Reading: Think Python Chapters 5 and 7

Conditionals

Comparison Expressions

Is 6 greater than 5? Obviously yes.

In Python, comparisons between values are accomplished with expressions using comparison operators.

6 > 5
True

These expressions return special values True and False. Be careful: these are not the same as strings "True" and "False". You can access them directly by typing True or False without any quotation marks.

True and False are Boolean variables, or “Bools” for short. There are only two Bools: True and False. Ambiguity can exist in society and philosophy, but it is not permitted in Python.

There are six comparison operators:

  • > (greater than)
  • >= (greater than or equal to)
  • < (less than)
  • <= (less than or equal to)
  • == (equal to)
  • != (not equal to)
Equality and Assignment

Take care with the difference between = and ==.

  • = (single equals) is assignment:
    • The expression on the right of the = is evaluated
    • The result is assigned to the variable on the left
  • == (double equals) is equality comparison:
    • Expressions on the left and right of == are compared
    • If they are equal, the comparison returns True
    • Otherwise, the comparison returns False

Comparison between numbers is straightforward:

7 < 7
False
7 <= 6 + 1
True
x = 3 + 1
x == 4.0
True

Floats are equal to ints when they are numerically exactly equal.

x == 4.000001
False
x = 5 + 2
x != 7
False

Equality can be used between almost any two values:

3 == "3"
False

The above expression is False because the string "3" is not numerically equal to the integer 3!

a = "finesse"
a == "finesse"
True

Strings are equal when they are the same.

x = print("finesse")
finesse

String comparison checks for alphabetical order if all letters are lowercase or all letters are uppercase. c comes before e in the alphabet, so the position of c is “less than” the position of e.

s = 'espresso'
s > 'cappuccino'
True

Equality can be used to check if something is None.

x == None
True

Remember, True and False are not strings:

True == "True"
False

Some comparisons make no sense, such as between strings and numbers. If you try these, you’ll get an error.

Conditional Execution

Comparison expressions enable us to execute code only if certain conditions are met.

We use the keyword if, a colon, and an indented code block. Similar to function definition syntax, the if statement controls execution of the indented block.

After the keyword if is a conditional expression. If the expression evaluates to True, the controlled block runs:

if_true.py
if True:
    print("this is controlled by the if statement")

print("this is outside the if statement")
this is controlled by the if statement
this is outside the if statement

If the expression does not evaluate to True, the controlled block doesn’t run:

if_false.py
if False:
    print("this is controlled by the if statement")

print("this is outside the if statement")
this is outside the if statement

The simple True and False above are just to illustrate how if statements work. Any valid conditional expression can come after the if keyword:

if_statement.py
x = 3
if (x > 2 + 1):
    print("this is controlled by the if statement")

print("this is outside the if statement")
this is outside the if statement

The parentheses are optional, but they help organize the code. Another example:

two_ifs.py
x = 2
if (x > 3):
    print("x is greater than three")

if (x < 3):
    print("x is less than three")

Trace through the execution. With x = 2, line 3 will not run (because x > 3 is False), and line 6 does run (because x < 3 is True).

Try editing the code and changing the value of x. What happens with x set to 0. Why?

else and elif

The if keyword can be grouped with other keywords to executed code when the expression after the if is False:

if_else.py
x = 2
if (x > 3):
    print("x is greater than three")
else:
    print("x is three or less")

With an if-else group, the code controlled by the else executes if the conditional expression for the if statement is not True. Exactly one of the two will execute.

Trace through the execution:

It’s common to want to perform another conditional check after the first check. Conditionals can be nested in Python:

if_else.py
x = 2
if (x > 3):
    print("x is greater than three")
else:
    if (x >= 1):
        print("x is between one and three")
    else:
        print("x is less than one")

Instead of nesting, however, we can use the conditional keyword elif (“else if”):

if_else.py
x = 2
if (x > 3):
    print("x is greater than three")
elif (x > = 1)
    print("x is between one and three")
else:
    print("x is less than one")

Rules for if-elif-else statements:

  • The if statement must come first
    • if includes a conditional expression
    • The if will run if the expression is True
  • elif statements come next
    • elif statements are optional (you can have zero)
    • Each elif includes a conditional expression
    • Each elif will be checked if the statement before it was False
    • An elif runs if its expression is True
  • An else (optionally) is last
    • else does not have a conditional expression
    • If every statement before the else was False, the else runs

Each statement and expression is evaluated in order. As soon as one expression is True (or we the else is reached), the code block it controls will run, and the group of statements is exited.

Conditionals and Returns

A function can have multiple return statements, but remember that the function ends the first time any return statement is reached.

An example. This function returns True if the input is an integer with multiple digits, otherwise it returns False.

multiple_return.py
def multiple_digits(x):
    if type(x) != int:
        return False
    elif x >= 10:
        return True
    elif x <= -10:
        return True
    else:
        return False

i = multiple_digits(5)
j = multiple_digits('lemon')
k = multiple_digits(-12)

Note that this program has no printed output, because there are no calls to print.

Combining Conditionals

Conditional expressions can be combined and modified with three additional logic operators: and, or, and not.

  • The and operator will evaluate to True if both sides of the and are True.
A B A and B
True True True
True False False
False True False
False False False
  • The or operator will evaluate to True if either (or both) sides of the or are True.
A B A or B
True True True
True False True
False True True
False False False
  • The not operator reverses any boolean coming after it.

We can look at examples of these expressions using the interpreter:

(4 < 5) and (2 < 0)
False
x = 2
(x == 2) or (x == 3)
True
not (3 < 4)
False

These evaluate to bools - which means they can be used with conditionals!

logic_conditions.py
x = 2
if (x == 2) or (x == 3):
    print("x is either 2 or 3")

Iteration

The while Loop

We have used if statements to execute a block of code once if a condition is met.

The while loop extends this functionality and executes a block of code multiple times based on a condition being met.

The syntax is similar: the keyword while, followed by a condition, followed by a colon.

while_loop.py
x = 0
while x < 4:
    x = x + 1
    print("x is: " + str(x))
x is: 1
x is: 2
x is: 3
x is: 4

Unlike the if statement, which executes code once and then moves on, the while loop continues to execute as long as the condition is met:

  • The conditional expression is evaluated. If True, the controlled block runs.
  • After the block finishes running, the conditional expression is evaluated again.

This makes it very important to change something about the conditional expression during the loop. In the example above, x is increased, and the condition is checking if x is less than some value. By increasing x, we guarantee that the loop will eventually end.

This loop will run “forever:”

infinite_loop.py
x = 0
while x < 4:
    x = x - 1
    print("x is: " + str(x))

In practice, infinite loops will usually get stopped by your computer, or you will run out of memory, or get some other kind of error. You may need to manually terminate the loop, either by pressing Control+C or clicking a red “stop” button in your editor.

Because we subtract from x each iteration, x will always be less than 4.

Loops make performing some operation over a large number of values very simple. We could sum integers from 0 through 10:

infinite_loop.py
cumulative_sum = 0
x = 0
last_value = 10
while x <= last_value:
    cumulative_sum = cumulative_sum + x
    x = x + 1

To perform the same computation on integers from 0 to one million, just change last_value to 1000000.

Shortcuts

Python includes a few shortcuts that make our lives easier when performing common tasks. They’re optional, but useful.

  • The += operator simultaneously adds and assigns
    • x = x + 1 can be rewritten x += 1
  • There are also -=, *=, and \= operators
  • Underscores can be used in numbers to keep track of large values
    • 1_000_000 is the same as 1000000, but it’s easier for humans to read
    • 1,000,000 means something in Python, but it does not mean “one million”
      • Don’t worry about it for now
    • You can type something like 23_45 (the same as 2345), but that might be harder to read, not easier

Here’s the simple loop rewritten with +=:

plus_equals_loop.py
x = 0
while x < 4:
    x += 1
    print("x is: " + str(x))

All of these shortcuts work with numbers; the += shortcut will work with strings as well.

k = 'idea'
k += 's'
print(k)
ideas

The for Loop

Basic while loop syntax consists of three parts:

  • Initializing a new variable to control the loop
  • The while statement with a condition related to that variable
  • Something inside the loop that changes the loop-control variable
    • This prevents an infinite loop
basic_while.py
i = 0           # initialize the loop-control variable
while i < 5:    # the `while` statement
    i += 1      # changing the loop control variable
    print(i)    # do something with the loop!

There are other ways to use while loops, but this way is very common.

The case of needing to iterate over an ordered collection of numbers is so common that Python has a specific way to do this: the for loop. The syntax is very simple:

  • The keyword for followed by a space
  • The name of the loop-control variable
  • The keyword in
  • The range function, which creates a sequence

A sequence?

for i in range(5):
  • range(n) creates the sequence \(0, 1, 2 ,..., n-1\)
  • Think of the sequence as starting at 0 and stopping before the argument
    • range(5) creates sequence 0, 1, 2, 3, 4
  • The for loop iterates through the sequence
    • The loop-control variable takes each value in the sequence
    • The controlled block executes once for each value in the sequence

Trace through this for loop. It does the exact same thing as the while loop we just saw:

basic_for.py
for i in range(5): 
    print(i)

The range collection does not need to start at 0. If you pass two arguments to range, the first argument is the starting value, and the second is the stop-before value.

  • range(2, 6) starts at 2 and stops before 6
    • Yields the sequence 2, 3, 4, 5
  • range(-2, 4) starts at -2 and stops before 4
    • Yields the sequence -2, -1, 0, 1, 2, 3
  • range(0, 5) is the same as range(5)
    • With one argument, starting at 0 is implied

If you pass three arguments to range, the first is the starting value, the second is the stop-before value, the third is the spacing:

  • range(1, 6, 2) starts at 1, stops before 6, and has a spacing of 2
    • Yields the sequence 1, 3, 5
  • range(10, 5, -1) starts at 10, stops before 5 and has a spacing of -1
    • It will go down, not up
    • Yields the sequence 10, 9, 8, 7, 6
  • range(0, 5, 1) is the same as range(5)
    • With one argument, starting at 0 and incrementing by 1 are implied

Choosing while vs. for

  • for loops are simpler to write
  • Anything that can be done with a for loop can be done with a while loop
    • The reverse of this is not true
  • If the sequence you are looping through is fixed and regular, use a for loop
  • If not, use a while loop

Printing

The print function can take multiple arguments to perform more ‘advanced’ printing. Print statements are very useful for writing and debugging loops.

  • When given multiple positional arguments, the arguments are each printed on one line, separated by spaces
x = 2
print("x is", 2, "!")
x is 2 !

The arguments can be any type (they do not need to be converted to strings).

By default, at the end of a print statement, the output advances to the next line. This “end” behavior can be overridden with a keyword argument, end=.

keyword_argument.py
print("Hello", end="")
print(" world!")
Hello world!

This example combines many of these concepts:

running_sum.py
running_sum = 0
for val in range(4): 
    running_sum += val
    print("iteration ", end="")
    print(val + 1, end=" - ")
    print("running sum is:", running_sum)

Nesting

Just like conditionals, loops can be nested within each other:

  • Any code can go inside the body of a loop, including another loop.
  • Because the body of a loop is established by indenting, a loop inside of a loop will indent twice.
  • This is called “nesting.”
i = 0

while i < 5:
    j = 0
    while j < 3:
        print(i)
        print(j)
        j += 1
    i += 1
  • Notice how the second loop is indented an additional level inside of the first loop
  • Lines 4-9 are in the body of the first loop
  • Lines 6-8 are the body of the second loop
  • The print statements illustrate how i and j change over the course of the loop

  • Remember that an indented block is associated with a loop
  • The inner indented block is associated with the inner loop
  • The inner loop will run multiple times
  • Only after the inner loop finishes will the outer indented loop be complete
  • The outer loop will then run again

Practice

Practice Problem 2.1

Practice Problem 2.1

Write a function two_four that takes one argument, a number, and prints whether or not the number is between 2 and 4 (inclusive of 2 and 4):

  • two_four(5) results in printed output “5 is not between 2 and 4”
  • two_four(3.1) results in printed output “3.1 is between 2 and 4”
practice1_4_1.py
def two_four(input_number):
    # check if the number is greater than 4
        # if so, print something
    # if not, check if it is greater than or equal to 2
        # if so, print something
    # otherwise:
    #    print something

Hint: The comments in the code above outline the form of code that implements the function.

Practice Problem 2.2

Practice Problem 2.2

Implement the function two_four with the exact same behavior, using a different set of conditional statements.

Practice Problem 2.3

Practice Problem 2.3

Write a function that takes an argument, which could be a number or a string.

  • If the argument is a string, return the string "It's a string."

  • If the argument is a number, check if the number is positive.

    • If the number is positive, return string "It's a positive number."
    • If the number is negative, return string "It's a negative number."
    • If the number is zero, return the integer 0.

You can use the type function to inspect the type of variables.

type(2.3)
float

The output can be used in a conditional expression:

type('this is a string') == str
True

Practice Problem 2.4

Practice Problem 2.4

Write a function that takes as argument a number and rounds that number to the closest int.

Do this without using Python’s built-in round function.

Return the result.

Practice Problem 2.5

Practice Problem 2.5

Write a program that uses a while loop to sum the integers between 1 and 20 (including 1 and 20).

(You should get 210 as the result).

After you have it working, rewrite the program to use a for loop instead.

Practice Problem 2.2

Practice Problem 2.2

Write a function sum_below that takes an integer argument and returns the sum of all integers between 1 and the argument, not including the argument.

  • sum_below(20) would return 190
  • sum_below(15) would return 105

You can assume the argument will always be a positive integer.

Practice Problem 2.6

Practice Problem 2.6

Write a function sum_skip that takes two integer arguments. Your function should sum all integers less (but including) than the first argument, skipping the second argument.

  • sum_skip(5, 4) would return 11 (\(1 + 2 + 3 + 5\), because 4 was skipped)
  • sum_skip(6, 3) would return 18 (\(1 + 2 + 4 + 5 + 6\), because 3 was skipped)
  • sum_skip(4, 6) would return 10 (\(1 + 2 + 3 + 4\), nothing is skipped because 6 is not part of the sum)

You can assume the argument will always be a positive integer.

Note: There is an “easy” way to solve this, by subtracting the second argument from the sum. Try solving this problem a different way.

Practice Problem 2.7

Practice Problem 2.7

Write a function round_three that takes an argument, a number, and rounds up to the nearest multiple of three, returning that value.

  • round_three(4.2) returns 6
  • round_three(3) returns 3
  • round_three(-2) returns 0

Hint: You can check if a number is divisible by three by using the % operator: if x % y is equal to zero, x is evenly divisible by y.

6 % 3 == 0
True
5 % 3 == 0
False

Homework

  • Homework problems should always be your individual work. Please review the collaboration policy and ask the course staff if you have questions. Remember: Put comments at the start of each problem to tell us how you worked on it.

  • Double check your file names and printed output. These need to be exact matches for you to get credit.

Homework Problem 2.1

Homework Problem 2.1 (20 pts)

Write a function smart_root that takes one argument, a number.

If the number is not negative, return the number’s square root, rounded down to the nearest int.

If the number is negative, return string 'The number is negative'

Examples:

  • smart_root(4) returns int '2'
  • smart_root(2) returns int '1'
  • smart_root(1) returns int '1'
  • smart_root(-1) returns string 'The number is negative'

Submit as smart_root.py.

Homework Problem 2.2

Homework Problem 2.2 (10 pts)

Write a function check_square that takes one argument, a positive integer:

  • If the integer is a perfect square, return string 'Perfect square!'

  • Otherwise, return string 'Not a perfect square.'

  • check_square(4) returns string 'Perfect square!'

  • check_square(9) returns string 'Perfect square!'

  • check_square(10) returns string 'Not a perfect square.'

(A perfect square is a number that is the product of an integer multiplied by itself.)

Submit as check_square.py

  1. math.sqrt always returns a float
  2. Calling the math.sqrt function on a perfect square will return a float that is equal to an integer
    • math.sqrt(9) returns 3.0
  3. Calling math.sqrt on a number that isn’t a perfect square returns a float not equal to an integer
    • math.sqrt(10) returns 3.1622776601683795

Homework Problem 2.3

Homework Problem 2.3 (10 pts)

The function polarity is intended to check if a number is positive, negative, or zero, and return the result in the following format:

  • polarity(4) returns string 'Positive.'
  • polarity(-2) returns string 'Negative.'
  • polarity(0) returns string 'Zero.'

Here is the draft function. It does not quite work. Fix it!

polarity.py
# initial comments here
def polarity(v):
    if v >= 0:
        return "Positive."
    elif v < 0:
        print("Negative.")
    else:
        return 0

Submit as polarity.py.

Homework Problem 2.4

Homework Problem 2.4 (10 pts)

Write a function positioner that takes as argument a number. Your function should compare this number to three other numbers: 3, 13, and 31, and return the result of the comparison as a string. The returned string should have the numbers in order, separated by spaces. Here are examples of the format:

  • positioner(2) returns string '2 3 13 31'
  • positioner(1) returns string '1 3 13 31'
  • positioner(15) returns string '3 13 15 31'
  • positioner(200) returns string '3 13 31 200'
  • positioner(13) returns string '3 13 13 31'

The input argument is printed in numerical order along with 3, 13, and 31. There are spaces between each pair of numbers; there is no space at the end of the sequence.

Submit as positioner.py.

Homework Problem 2.5

Homework Problem 2.5 (10 pts)

Write a function closer_to_zero that takes as argument a number.

  • If the number is positive, subtract one from it.
  • If the number is negative, add one to it.
  • If the number is zero, leave it unchanged.
  • At the end, return the number.

Examples:

  • closer_to_zero(4) returns 3
  • closer_to_zero(-2) returns -1
  • closer_to_zero(0) returns 0

Submit as closer_to_zero.py.

Homework Problem 2.6

Homework Problem 2.6 (15 pts)

Write a function sequence_skip that takes two arguments, both integers.

  • The function should return a string that consists of every non-negative integer less than the first argument
    • These should be in sequential order, increasing by one
    • Exclude the second argument.
    • There should be a space between each integer (but no leading or trailing spaces).

Type Hint: Your function should return a string.

  • sequence_skip(5, 2) returns string '0 1 3 4'
  • sequence_skip(4, 3) returns string '0 1 2'
  • sequence_skip(6, 1) returns string '0 2 3 4 5'
  • sequence_skip(4, 5) returns string '0 1 2 3'

Submit as sequence_skip.py.

Homework Problem 2.7

Homework Problem 2.7 (15 pts)

Write a function sequence_string that takes one argument, an integer, and returns a string.

  • The string should consist of integers in sequential order, increasing by one,
    • These integers start with the argument
    • These integers end with the next multiple of five (inclusive).
    • There should be a space between each integer (but no leading or trailing spaces).
  • sequence_string(2) returns string '2 3 4 5'
  • sequence_string(7) returns string '7 8 9 10'
  • sequence_string(15) returns string '15'

Submit as sequence_string.py

Homework Problem 2.8

Homework Problem 2.8 (20 pts)

Write a function greatest_factor that takes one argument, an integer, and returns the largest integer, smaller than the argument, that the argument divides into evenly. (The return value does not need to be prime.) The argument will always be greater than one.

  • greatest_factor(9) returns integer 3
  • greatest_factor(20) returns integer 10
  • greatest_factor(24) returns integer 12
  • greatest_factor(7) returns integer 1

Submit as greatest_factor.py

For greatest_factor, use a loop to check numbers smaller than the argument:

  • Initialize a variable before the loop
  • Loop through numbers smaller than the argument
    • Check if the argument divides evenly into each of these numbers
    • Use the variable you initialized to “remember” the largest one