Making Decisions in our programs

Read time: 30 minutes (7531 words)

See also

Reading:

Text: Chapter 4

Programming involves lots of decisions and loops, we need to examine how Python handles these common concepts!

Decision making

Decisions are at the heart of most interesting programs. You examine data and decide what to do based on what your program finds.

Basics of making decisions

Computers can only ask simple questions. Oddly enough, those questions must be in the form of simple True or False questions. Nothing else is allowed. Most of our more interesting statement types use the answers to such questions to decide how to operate.

The foundation of all question asking in computers is a simple comparison of two data items. The two items must be of the same type to make any sense! With that done, many of the comparisons involve numerical comparisons. For example, if we subtract item one from item two what result might we get?

negative item one was larger than item two
zero item one was identical to item two
positive item one was smaller than item two

So, if we can check these simple conditions, we can form basic logical comparisons that will result in a True or False answer. We have come up with a set of standard comparisons, and Python has them as well!

Comparison operators

Here are the basic operators, they should be easy enough to figure out. (They are similar to those used in many programming languages).

> greater than
>= greater or equal
< less than
<= less or equal
== equal to
!= not equal to
not invert the comparison

We can also use another operator to reverse the boolean value of these expressions:

test = 5 > 10           # results in False
test = not (5 > 10)     # results in True

What can be checked?

It should be obvious that we can compare numbers with these operators. Can we check anything else?

vara = 'Good'
varb = 'Bad'
if vara == 'Good':
    print('We have a good string')
if varb != 'Good':
    print('We have a bad string')

Strings can be compared with the relational operators, and the result is based on alphabetic order:

>>> vara = 'junk'
>>> varb = 'liver'
>>> vara < varb
True

The if-then-else statement

There are places where these questions are asked inside other statements. The most common is probably the if-then`else statement:

if question:
    print "answer was True"
else:
    print "Answer was False"

Warning

In this statement, indenting is how we show what code we want to execute in each part. Python is especially picky about indenting. This is a good habit to acquire for other languages where the indenting is not required, but encourages!

In the spot where question is found, we can either place an expression that evaluates to True or False, or place a variable that has a boolean value in it!

>>> vara = 5
>>> varb = 10
>>> vara > varb
False
>>> varb > vara
True
>>> vara == varb
False
>>> answer = vara > varb
>>> if answer:
...     print "I got a True"
... else:
...     print "I got a False"
...
I got a False
>>> if vara > varb:
...     print "I got a True"
... else:
...     print "I got a False"
...
I got a False
>>>

This allows us to set up questions as we wish. Which one do you choose? Which ever one that makes you program the clearest!

The else part is optional

In many cases, you only need to do something if the value of your expression is true. In this case the else part can be left off:

if income > bills:
    print('Put something in savings!')

Combining logical values

There are a few more operators available that let you ask more complex questions. These involve the logical operations. The most common are and and or which work as you might suspect based on your knowledge of English.

If one thing is true and another thing is false what can you say about both of them? The answer can be figured out using a simple scheme known as a truth table.

Truth tables

A truth table lists all possible inputs on for the variables, and show the result of the operator.

Here are the rules for the and operator:

A B A and B
False False False
True False False
False True False
True True True

And, here are the rules for the or operator:

A B A or B
False False False
True False True
False True True
True True True

So we see that A and B is only True if both A and B are True, and A or B is True if either A or B is True (or both are True). These can be used to create interesting questions.

>>> varA = True
>>> varB = False
>>> varA and varB
False
>>> varA or varB
True
>>> (6 > 3) or (6 < 3)
True
>>> varA = 10
>>> varA > 0 and varA < 20
True
>>>

We can use parentheses to build up even bigger questions, but I will leave that for your experimentation!

Short circuits

These are usually bad, especially if you are wiring a house. In programming, they are a good thing.

What is the value of 10 / 0? Let’s see:

>>> 10 / 0
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ZeroDivisionError: division by zero

Looks like a problem. What if we want to divide by a variable, but only if the variable is not zero?

The and and or operators stop processing as soon as they know what the answer will be.

  • For the and, if the first part ends up False, stop
  • For the or, if the first part is True, stop

This can be handy:

if x != 0 and 10/x:
    # do something

In this statement, if x has a value of zero, the first part of the logical expression ends up False, so we do not evaluate the second part, and we avoid the error! .

Let’s work through an example!

The dreaded grade calculator

You are in school, so you just know that someone is going to assign a letter grade for your courses. Let’s see if we can build a simple program to figure out what grade to assign.

What would a student do?

Probably this:

# Student's view of grading

letter_grade = 'F'
grade = input('Enter the final average: ')
grade = int(grade)  # need to convert the string to an integer
if grade >= 0:
    letter_grade = 'A'

print('You got an ',letter_grade, 'in this course!')

Looks pretty good, I suspect you are thinking. (Actually, most of you would eliminate the test and just assign the grade. Sorry, that is not allowed!)

So, how will this work?

If you enter any grade with a numerical value of zero of higher, you get an A, otherwise you flunked the course. (How could that happen!)

Watch for common mistakes

What is wrong with this test?

if grade >= 0 or grade <= 100:
    # grade is legal

Think about it. The first test allows any number greater than or equal to zero to be legal, the second on lets any number less than or equal to 100 be legal as well. If we combine these two tests with an or, any number ends up being legal. That is not what we want.

Keep it simple!

We could try this one, which is way too complicated! It sets up a logical variable that is used to decide if the entered grade is legal.

is_legal = True
if grade < 0:
    is_legal = False
if grade > 100
    is_legal = False
if not is_legal:
    print('Illegal grade')

That not reverses the value of what comes next (a logical value).

Stopping the code if the value is illegal

At this point, we know the grade is legal. But how do we keep from computing the letter grade if the average is illegal?

Well, we could wrap up the calculation in an else statement (indenting correctly, of course. There is another way:

import sys

grade = 200
if grade < 0 or grade > 100:
    print('Average of', grade, 'is illegal!')
    sys.exit()

Now we need to figure out the letter grade the “real way!

if grade >= 90:
    letter_grade = 'A'

This will work fine. We already know the grade is not above 100, so any grade from 90-100 will get an ‘A’. What about everything else?

Try this:

if grade >= 80:
    letter_grade = 'B'

If we add this code after the first test, what will happen? You should see that this is wrong. If the grade is 95, this test is still going to be satisfied, so we will end up with a ‘B’. The solution is to only do this test if the first one fails:

if grade >= 90:
    letter_grade = 'A'
else:
    if grade >= 80:
        letter_grade

I suspect you can see how to complete this code.

Notice anything annoying? If we keep adding tests for the other grades, the code will be sliding off to the right, and that looks bad. Python has a variation for this situation called the elif clause:

if grade >= 90:
    letter_grade = 'A'
elif grade >= 80:
    letter_grade = 'B'
...
else:
    letter_grade = 'F'

This looks much better.

Hmmm, Santa just stopped by!

Santa says that if you are on the “nice” list (meaning you have an attendance average above 75%, you get the next letter grade if you are within two points of the boundary. How would we add this feature into the code?

Well, we could go into our code and change all the number, but what if Santa decides to change the number from 2 to 3 (or even 1 on a bad day)? It would be better to use a santa_factor to change the code. We could set this at the top of the program, or even read it in. Here is what we might do:

if grade >= (90 - santa_factor):
    letter_grade = 'A'

That looks much better.

Note

Programming pops up in interesting ways, and understanding how to form complex “questions” like this can make things easier. In my real gradebook, with is in Excel, I place a “formula” that converts your numerical average into a letter grade using exactly the kind of code you see above. Sure my gradebook could be a Python program, but Excel is the “right tool for this job”. One thing you might be surprised by, though, is that I set up that gradebook using Python. I have a program that reads a list of students from one data file, a list of assignments I will grade from another data file, and generates the spreadshet I need with all those magic “formulas” in place. All I need to do is come up with the scores for everything.