download notebook

Fifteen-minute Friday: Flow control

files needed = None

Fifteen-minute Fridays are brief, nongraded workbooks that provide some extra practice and introduce new topics for self-guided study. This week we are working on

  1. if-elif-else
  2. The in operator
  3. The not operator
  4. Optional: Floating point numbers

The solutions to the practice problems are at the end of the notebook.

More on conditionals: elif

We covered the basics of comparisons and conditional statements in class. Let dig a bit deeper.

We have seen code of the form

if statement:
    'stuff to do if statement is true'
else:
    'stuff to do if statement is false'

What if our statement may have more than just two answers? We can use the elif (as in else if) construction.

status = 'fresh'

if status == 'fresh':
    class_rank = 0
elif status == 'soph':
    class_rank = 1
elif status == 'jun':
    class_rank = 2
else:
    class_rank = 3

print(class_rank)

Try changing status to 'jun'. What happens?

Now try changing status to 'super senior'. What happens?

The final else is the default value. If none of the preceding statements evaluate to True then the else clause is executed.

  • You can have as many elifs as you like, but if you find yourself writing lots of elifs you should spend a few minutes considering other ways to implement your algorithm.
  • You can leave off the final else, which would mean there is no default value. You should be careful, though, because now class_rank is not defined. If you try to use it later, you will get an error.
  • Once an if or elif is found to be true, the rest of the code block is skipped. In the example above, if status=='fresh' then none of the elifs are checked and neither is the else. This is the advantage of using elif statements rather than many if-else statements. Using multiple if statements would lead to all four conditions being checked separately, even if we are satisfied with the first check.

Looking in collections: in

in gives us an easy way to see if a value is contained in a collection.

names = ['kim', 'greg', 'bucky', 'barry', 'abe', 'hector']

if 'kim' in names:
    print('Found him!')
jobs = {'kim':'prof', 'greg':'TA', 'bucky':'mascot', 'barry':'football guy', 'abe':'president', 'hector':'biochemist'}

employee = 'kim'

if employee in jobs:
    print('Found him! His job is {0}.'.format(jobs['kim']))
else:
    print('That person is not here.')

Go back and try

employee = 'Abe'

Boolean logic: not

We have seen how to evaluate statements that return bool types. Things like

x = 10
y = 3
b = (x == y)

print(b)

The not operator turns False into True and True into False. In mathematical terms, this is called negation.

print(not True)
print(not False)
# The expression in parentheses is True. The `not` flips the sign.  
b = not (3 > 2)

print(b)

We can use not in any boolean statement. We can, for example, combine it with in. Suppose we want to know if someone is missing from the data.

jobs = {'kim':'prof', 'greg':'TA', 'bucky':'mascot', 'barry':'football guy', 'abe':'president', 'hector':'biochemist'}

employee = 'elroy'

if employee not in jobs:
    print('Employee not found.')

Practice: Flow control

  1. Write code that controls a car at a stoplight. The variable light can take values 'green', 'yellow', or 'red'. Check the value of light and print out 'go', 'prepare to stop' and 'stop' as appropriate.

  2. Without running any code, evaluate whether these statements are True or False.

    1. 3 > 2 > -1
    2. (3 > 2) > -1
    3. 'bill' != 'Bill'
    4. not ('bill' != 'Bill')
    5. (3<2) or not (2>3)
  3. Below is the text of the Gettysburg Address. Write code that checks whether each phrase in

phrases = ['last full measure', 'of the people, by the people', 'four score and seven']

is in the Address. If the phrase is in the Address, then print out: Abe said '{phrase}.' If not, then print out: Abe did not say '{phrase}'.

Where you replace {phrase} with the phrase you are checking. Notice that the phrase is printed out with single quotation marks around it.

[Hint: A string is a collection, like a list or a dict. ]

getty = """
    Four score and seven years ago our fathers brought forth on this continent, a new nation, 
    conceived in Liberty, and dedicated to the proposition that all men are created equal.
    Now we are engaged in a great civil war, testing whether that nation, or any nation so conceived
    and so dedicated, can long endure. We are met on a great battle-field of that war. We have come to 
    dedicate a portion of that field, as a final resting place for those who here gave their lives 
    that that nation might live. It is altogether fitting and proper that we should do this.
    But, in a larger sense, we can not dedicate we can not consecrate we can not hallow—this ground. 
    The brave men, living and dead, who struggled here, have consecrated it, far above our poor power 
    to add or detract. The world will little note, nor long remember what we say here, but it can never 
    forget what they did here. It is for us the living, rather, to be dedicated here to the unfinished work 
    which they who fought here have thus far so nobly advanced. It is rather for us to be here dedicated to the 
    great task remaining before us that from these honored dead we take increased devotion to that cause for 
    which they gave the last full measure of devotion that we here highly resolve that 
    these dead shall not have died in vain that this nation, under God, shall have a new birth of 
    freedom and that government of the people, by the people, for the people, shall not perish from the earth.
    """
  1. Did Abe say 'four score and seven?' How could you change your code to deal with this?

Optional: Floating point numbers

Optional material is provided to give you a look into more advanced programming concepts. These are the kinds of things you would learn about in a CS course, but we will generally not need for our purposes. You should read through this material and complete the exercises, but I will not test you on this material.

A computer can only represent floating-point numbers (i.e., non-integers) that can be written as integers divided by a power of 2: e.g., \(\frac{1}{2}, \frac{3}{2^2}, \frac{5}{2^{16}}\). We call these representable numbers. Any number that cannot be expressed this way is approximated by the computer.

How well a number is approximated depends on how much space in memory we are willing to assign to each number. Since memory is finite, we always have an imperfect approximation. The default float in python uses 64 bits of memory to store a number. Not that long ago, the default might have been 32 bits. The first part of this python doc is a good place to start if you want to know more.

Approximation issues crop up most often when comparing values. Let's take a look.

# Here we are comparing a float and an int. The value '1' as a float does not need to be approximated. It is 2^0. 

float_1 = 1.0
print(float_1, type(float_1))

int_1 = 1
print(int_1, type(int_1))

print(int_1 == float_1)
# This should be true, right?

print(0.1 + 0.2 == 0.3) 

What?

We can understand the problem when we ask python to show us the fraction it is using to approximate each side of the equation. The function float.as_integer_ratio() will give us this information.

print('Take 0.1+0.2 as an example.')
print('The numerator and denominator are:', float.as_integer_ratio(0.1+0.2))
print('The approximation is 1351079888211149/4503599627370496 =', 1351079888211149/4503599627370496)
print('Take 0.3 as an example.')
print('The numerator and denominator are:', float.as_integer_ratio(0.3))
print('The approximation is 5404319552844595/18014398509481984 =', 5404319552844595/18014398509481984)

So even though 0.1+0.2 == 0.3 should be true, once it is approximated on a computer, approximation error can cause us problems.

Even adding 0.1 ten times creates a problem — it won't be exactly equal to 1.0 in the end.

# Summation issues:
float_5 = 0.1

# This is not a good way to code this! Once we learn about loops, we will never write something like this again.
float_6 = float_5 + float_5 + float_5 + float_5 + float_5 + float_5 + float_5 + float_5 + float_5 + float_5 

print(float_6)

One final question you might have is how to deal with this problem. As you may have guessed, python has a function called round(number, precision) to help deal with errors created by floating point arithmetic. Give the function the number to round and how many digits to the right of the decimal (the precision).

When we used str.format() last week with float formatting, the function was rounding for us, too.

print(round(float_6, 5))

float_7 = 1.2346987
print(round(float_7, 3))

print('Rounding 1.2346987 to 4 digits is {0:.4f}.'.format(float_7))

All of this might have you a bit freaked out right now. Will my computer always give me garbage answers? Can I trust it?

Do not worry too much. In practice, we will rarely run into floating-point problems (and python handles a lot of stuff in the background for us). The kind of data we work with is not very susceptible to these problems.

Our goal here was just to give you some background on how floating-point numbers work and to give you a place to start digging in case you find some strange behavior in your code. Most of the time, strange behavior is the result of a bug in our code!

Practice: Floating point numbers

  1. Use the round() function so that comparing
num_1 = 5.34322356
num_2 = 5.343

returns the value True.


  1. The accuracy of floating-point approximation is related to machine epsilon. Machine epsilon is the smallest representable number (call it epsilon) such that 1.0 + epsilon != 1.0 is True.

Machine epsilon depends both on your computer and your code. You can find your computer's machine epsilon using a package named numpy, which contains functions that are useful for numerical computing. That doesn't mean much right now, but it will later.

For now, run the following code, which will give you your machine's precision.

# We are importing a package. We will learn about this soon.
import numpy 

# We are using the function 'finfo' from the imported numpy package
mach_eps = numpy.finfo(float).eps

print(mach_eps)

Assuming you are working in python (it would be weird if you weren't...) your machine epsilon is probably 2.220446049250313e-16, which is equal to \(\frac{1}{2^{52}}\).

Test it out. Is this true?

$$1.0 + 2^{-52} != 1.0$$

  1. Is this true?
    $$1.0 + 2^{-53} != 1.0$$

Why or why not? Try to answer the question without writing any code first. Then check your answer by evaluating the expression in a code cell.








Solutions: Flow control

# Question 1

light = 'red'

if light == 'red':
    print('stop')
elif light == 'yellow':
    print('prepare to stop')
else :
    print('go!')

# Note that this code is not robust to values of light that are not: red, yellow, green. 
# Suppose that a light might malfunction and display any color. If the light displays a color that is not red, yellow,
# or green, the safest thing to do is 'proceed with caution.' How would you change your code?
  1. Without running any code, evaluate whether these statements are True or False.
    1. 3 > 2 > -1 True
    2. (3 > 2) > -1 True
    3. 'bill' != 'Bill' True
    4. not ('bill' != 'Bill') False
    5. (3<2) or not (2>3) True
getty = """
    Four score and seven years ago our fathers brought forth on this continent, a new nation, 
    conceived in Liberty, and dedicated to the proposition that all men are created equal.
    Now we are engaged in a great civil war, testing whether that nation, or any nation so conceived
    and so dedicated, can long endure. We are met on a great battle-field of that war. We have come to 
    dedicate a portion of that field, as a final resting place for those who here gave their lives 
    that that nation might live. It is altogether fitting and proper that we should do this.
    But, in a larger sense, we can not dedicate we can not consecrate we can not hallow—this ground. 
    The brave men, living and dead, who struggled here, have consecrated it, far above our poor power 
    to add or detract. The world will little note, nor long remember what we say here, but it can never 
    forget what they did here. It is for us the living, rather, to be dedicated here to the unfinished work 
    which they who fought here have thus far so nobly advanced. It is rather for us to be here dedicated to the 
    great task remaining before us that from these honored dead we take increased devotion to that cause for 
    which they gave the last full measure of devotion that we here highly resolve that 
    these dead shall not have died in vain that this nation, under God, shall have a new birth of 
    freedom and that government of the people, by the people, for the people, shall not perish from the earth.
    """
# Question 3
phrases = ['last full measure', 'of the people, by the people', 'four score and seven']

for p in phrases:
    if p in getty:
        print('Abe said \'{}.\''.format(p))
    else:
        print('Abe did not say \'{}.\''.format(p))
# Question 4: We need to take care of capitalization. 

phrases = ['last full measure', 'of the people, by the people', 'four score and seven']

getty_clean = getty.lower()

for p in phrases:
    if p in getty_clean:
        print('Abe said \'{}.\''.format(p))
    else:
        print('Abe did not say \'{}.\''.format(p))

Solutions: floating-point

# Question 1

num_1 = 5.34322356
num_2 = 5.343

print(round(float(num_1), 3) == num_2)
# Question 2
1.0 + 2**-52 != 1.0
# Question 3

# Machine epsilon is the smallest representable number such that: 1.0 + eps != 1.0.
# 2**-53 is smaller than eps, so it should be that the left-hand side of the 
# expression is now effectively 1.0.

1.0 + 2**-53 != 1.0