Copyright © 2006 David Schmidt

Chapter 3A:
Conditional control: if


3A.1 The conditional command
    3A.1.1 Definition of the conditional command
    3A.1.2 Semantics of the conditional command
    3A.1.3 Design: Using a conditional command to interact with a program's user
    3A.1.4 Foundations: Knowledge generated by the conditional command
3A.2 Nested conditional commands
3A.3 Otherwise...else
    3A.3.1 Foundations: Knowledge generated from an if-else-command
3A.4 Semantics of Conditionals
3A.5 Relational Operations
3A.6 Compound conditionals
3A.7 Design: Writing algorithms with flowcharts
    3A.7.1 Case Study: Time conversion
    3A.7.2 Boolean-valued Variables
    3A.7.3 The exit command
3A.8 Testing programs with conditionals
3A.9 Summary


Real-life instructions often ask you questions before they tell you what to do. For example,

Such questions are not idle --- they are inserted at key points in the instructions to consider the range of situations that might arise and what action to take in response.

Computer programs ask questions of their data in a similar way. For example, the computer program that dispenses money from a cash machine contains a command like this:

IF  the person's account balance is less than the amount of money
      that the person wishes to withdraw,
THEN  display an error message and return the person's bank card
Here, a question (called a ``test'' or a ``condition,'' prefixed by IF) triggers a command: When the answer is ``yes'' (called ''True''), then the the bank card is returned.

A Mario-Brothers computer game contains complex commands like this one, which examine the state of the game, instant by instant, and update the game accordingly:

Move Mario one step, in the direction the human indicated with the joy-stick.

IF  Mario has stepped on a gold piece:
THEN 
   give Mario one more energy point.
ELSE IF
   Mario has stepped on a bomb:
   THEN 
      subtract one energy point.
      IF Mario's energy points are now zero:
      THEN 
         print "Game Over".
ELSE IF Mario has ...
 ...
The example shows how one question can lead to other questions. This style of asking questions is the secret to how a computer program exhibits human-like intelligence. To master the style, we must understand the linguistics, the foundations, and the design applications of the conditional (''if'') command.


3A.1 The conditional command

We use a conditional command to ask a key question that guides a program to taking a correct strategy. For example, we know that a square root of a number, num, is a number, q, such that q * q equals num. (E.g., a square root of 9 is 3.) Clearly, there is no square root for a negative number.

The Python language has a prebuilt ``mini-program'' (a function), called sqrt, that lives in a file (module) named math. We use it like this:

$ python
>>> import math
>>> math.sqrt(3)
1.7320508075688772
(The import math command makes the module available, so that we can use its sqrt mini-program.)

If we try to use sqrt with a negative number, it is a disaster:

>>> math.sqrt(-3)
Traceback (most recent call last):
  File "", line 1, in ?
ValueError: math domain error
>>> 
We wish to prevent such an error.

Here is a small Python script, SQ.py, that asks the human to type an integer for calculating its square root:

=============================================

# SQ.py  computes a square root of a nonnegative int.
# input: a nonnegative int
# output: a nonnegative square root

import math

num = int(raw_input("Type an int: "))

if num >= 0 :
    answer = math.sqrt(num)
    print answer

raw_input("\npress Enter to finish")

===============================================
The conditional command asks if the input, num, has a value greater-or-equal to zero. If the answer is yes (True), then a square root is printed.

Try the script:

$python SQ.py
Type an int: 3
1.73205080757

press Enter to finish
The answer prints --- the program asked the question (the condition or test), calculated an answer of True, and calculated the square root.

Try the script with a negative input:

$python SQ.py
Type an int: -3

press Enter to finish
No answer prints, because the test computed to False --- the command to compute the square root with sqrt was skipped.

The philosophy behind the conditional command is this:

When one cannot assert a property, one uses an if-command to learn if the property is True and then do appropriate computation.

In the example, we cannot guarantee that the input number is nonnegative, so we ask and only then do we undertake the planned computation.

In the previous chapter, the above example might have been handled like this, using the assert command:

====================================================

# SQassert  uses an assert command to ensure that sqrt works properly.
import math

num = int(raw_input("Type an int: "))
assert num >= 0
answer = math.sqrt(num)
print answer

raw_input("\npress Enter to finish")

==================================================
The above program behaves like this:
$ python SQassert.py
Type an int: -3
Traceback (most recent call last):
  File "SQassert.py", line 7, in ?
    assert num >= 0
AssertionError
The assert command prevented sqrt from malfunctioning, but at the cost of quitting the program prematurely and printing a technical error message. In contrast, the conditional command seen earlier guides the computer around the trouble spot to a proper conclusion.

You will see the difference if you save both SQ.py and SQassert.py and start each by double-clicking on their respective icons. Supply a negative number as input to each; what happens? Now, say that you are writing a computer program that controls a cash machine --- if there is bad input to the machine, do you want the machine to avoid the bad computation or do you want it to stop functioning on the spot?


3A.1.1 Definition of the conditional command

This is the grammatical syntax of the conditional command:
if CONDITION :
    COMMANDs
where CONDITION is an expression that computes to True or False. A condition typically has the form, EXPRESSSION OP EXPRESSION, where a comparison operation, OP, can be
>, <, >=, <=, ==, !=
Read >= as ``greater than or equals to''; read == as ``equals'' (this is not an assignment!); and read != as ``not equals''. An example is:
if (num - 1) >= 0 :
    print "the num is positive"
You must have at least one space between the keyword, if, and the CONDITION, but spaces are not required between the CONDITION and the colon.

The COMMANDs are one or more commands, one command per line. The commands must be indented, say, by 4 spaces or one tab. Here is an example:

if num == 0 :
    print num,
    print "is zero"
print "Have a nice day"
The two print commands are executed when the condition computes to True, and after the conditional command finishes, the last command, print "Have a nice day", is always executed --- the indentation indicates this. (Notice that num == 0 asks whether the value of num equals 0.)

Inconsistent indentation within an if-command causes an error --- this example is wrong

if num == 0 :
    print num,
      print "is zero"
print "Have a nice day"
because the indentation of the two commands inside the if-command is inconsistent.


3A.1.2 Semantics of the conditional command

For
if CONDITION :
    COMMANDs
the computer does these steps:
  1. Compute the answer of CONDITION.
  2. If the answer is True, then execute the COMMANDs, one by one.
  3. If the answer is False, then ignore the COMMANDs (do not execute them).


3A.1.3 Design: Using a conditional command to interact with a program's user

Often a program gains knowledge by asking its user a question; the user's answer is inspected by a conditional command, and a course of action is taken.

Here is an example. In his text, Dawson uses a built-in Python function, randrange, to generate a random number:

import Random   # the built-in function lives in module/file  Random
 ...
die_roll = Random.randrange(1,7)   
This generates a random integer in the range of 1 to 6 (!) and assigns it to die_roll.

Perhaps we build a program that plays a dice game, and the program asks the user whether she wishes to roll a die one more time. We write this:

import Random
 ...
total_score = ...  # the player's score so far

question = "Do you want to throw the die again (type y or n)? "
answer = raw_input(question)

if answer == "y" :
    die_roll = Random.randrange(1,7)
    total_score = total_score + die_roll

print "Your score is", total_score
The if-command compares the user's answer to the string, "y"; if the comparison computes to True, then a die is ``thrown'' and added to the total score. If the comparison computes to False, no action is taken. Finally, the score is printed.

This little example illustrates a standard design pattern: When a program needs more information to continue its computation, it asks the user a question and uses an if-command to analyze the answer.


3A.1.4 Foundations: Knowledge generated by the conditional command

We saw that assignment commands generate algebraic knowledge; so do conditionals. Let's reexamine the square-root example:
======================================

import math

num = int(raw_input("Type an int: "))

if num >= 0 :
    # assert: num >= 0  (that is, num is nonnegative and has a square root)
    answer = math.sqrt(num)
    # assert:  answer is a number
    print answer

raw_input("Press Enter to finish")

=======================================
The test of the conditional command generates new knowledge --- when the test computes to True, then the knowledge gained ensures that the square-root calculation will proceed correctly. When the test computes to False, the knowledge gained prevents an erroneous attempt to compute square root.

We see this clearly when we draw the program as a circuit-like flowchart:


At the end of the flowchart, we do not have knowledge whether num is nonnegative or not --- this is because the two ``paths'' in the ``circuit'' join together.

Here is a second example, where we must ask whether the input integer is even valued before we print an answer:

================================================

answer = -1
# assert : answer is (an) odd (number)

num = int(raw_input("Type an int:")

if (num % 2) == 0 :  # is num an even number (has a remainder of zero) ?
    # assert:  num is even  (if the computer reaches this position,
    #                             it is because the test computed to True)
    answer = num + 1
    # assert: num is even  and  answer = num + 1
    # implies:  answer is odd 

# assert: answer is odd
print answer
raw_input("Press Enter to finish")

====================================================
The knowledge generated by the test ensures that answer is always odd-valued at the end of the program:

The technical explanation is that, at the conclusion of the if-command, we assert
assert: (num is even and answer is odd) or (answer is odd)
because one of the two outcomes surely must occur. From this fact we conclude, regardless of the outcome, that answer is odd.


3A.2 Nested conditional commands

It is perfectly acceptable to insert one conditional command inside another. For example, we might make the square-root program verify that the input text typed by the human is indeed a number and not a word. (When some people type numbers, they ``goof'' and type the letter O for the digit 0. Another goof is typing a small l for digit 1.)

The Python method, isdigit, helps us check if a string contains all digits. For example,

>>> s = "123"
>>> print s.isdigit()
True
>>> t = "ab2"
>>> print t.isdigit()
False
Here is how we use this operation to guard against badly typed, nonnumeric input:
======================================

import math
text = raw_input("Type an int: "))

if text.isdigit() :
    # assert: text holds only digits
    num = int(text)
    if num >= 0 :
        # assert: num >= 0
        print math.sqrt(num)
    
raw_input("\npress Enter to finish")

=======================================
We placed a conditional command inside a conditional command to ask whether the input is numeric and if it is, then nonnegative.


3A.3 Otherwise...else

Sometimes we also want to take action when a condition computes to False (``no''). For example, it might be nice to improve the square root program so that it informs its user when a negative number is typed:
===========================================================
# SQ2.py  computes a square root of a nonnegative int.
# input: a nonnegative int
# output: a nonnegative square root or an error message

import math

num = int(raw_input("Type an int: "))

if num >= 0 :
    # assert: num is nonnegative; it has a square root:
    print math.sqrt(num)
else :
    # assert: num is < 0 (negative)
    print "Sorry --- cannot compute square root of a negative number"

print  # a blank line
raw_input("press Enter to finish")
=============================================================
Try it:
$ python sq2.py
Type an int: -3
Sorry --- cannot compute square root of a negative number
The experiment shows that when the condition is False, the command labelled by else executes.

The syntax format of the if-else-command is

if CONDITION :
    TRUE_COMMANDs
else :
    FALSE_COMMANDs
where TRUE_COMMANDs and FALSE_COMMANDs are sequences of one or more commands. The two groups of commands must be indented by the same amount of spaces/tabs.

Some people like to visualize an IF-ELSE conditional command as a ``fork in the road'' in their program; they draw a picture (a flowchart) of it like this:


                  |
                  v
                CONDITION?
               /    \
        True  /      \ False
             v        v
   TRUE_COMMANDS   FALSE_COMMANDS
               \     /
                \_ _/
                  |
                  v
The two paths fork apart and join together again, displaying the two possible ``flows of control.'' The picture also inspires this terminology: We call the TRUE_COMMANDs the then-arm and FALSE_COMMANDs the else-arm. (Many programming languages write a conditional as if CONDITION then COMMANDs else COMMANDsS; the keyword, then, is omitted from Python's conditional but the terminology is preserved.)

When you design a conditional command, you might find it helpful to draw the flowchart first and write the Python coding second. Here is another example, which ensures that we can print a square root for every integer (because we use the integer's absolute value):

============================================

# SQalways.py  computes a square root for the absolute value of an int.
# asssumed input: an integer
# guaranteed output: the sqaure root for the input's absolute value.
import math
num = int(raw_input("Type an int: "))

if num >= 0 :
    absolute_value = num
else :
    absolute_value = -num

print math.sqrt(absolute_value)
raw_input("\npress Enter to finish")

===============================================

Perhaps a friendlier version of the above program is one that asks its user if the square root of a negative number is desired. Nested conditional commands can do this:

============================================

# SQ3.py  computes a square root of an int.
#   If the int is negative, the user has the option of computing
#   the square root of the number's negation
# input: an int
# output: a nonnegative square root or nothing

import math

num = int(raw_input("Type an int: "))

if num >= 0 :
    # assert: num is nonnegative
    print math.sqrt(num)
else :
    # assert: num is < 0 (negative)
    response = raw_input(
         "Do you want the square root of the negation? (y or n)? " )
    if response == "y" :
        print math.sqrt(-num)
    else :
        print "OK --- bye!"

print  # a blank line
raw_input("press Enter to finish")

====================================================


3A.3.1 Foundations: Knowledge generated from an if-else-command

We gain knowledge when a conditional's test computes to False as well as when it computes to True. Here is the square root example with detailed assertions that illustrate the knowledge gained:
==========================================

# SQalways.py  computes a square root for the absolute value of an int.
# asssumed input: an integer
# guaranteed output: the sqaure root for the input's absolute value.

num = int(raw_input("Type an int: ")) 

if num >= 0 :
    # assert: num >= 0
    absolute_value = num
    # assert:  num >= 0  and  absolute_value = num
    # implies:  absolute_value >= 0
else :
    # assert: not(num >= 0)
    # implies:  num < 0
    absolute_value = -num
    # assert:  num < 0  and  absolute_value = -num
    # implies  absolute_value >= 0

#! assert in both cases:  absolute_value >= 0
print math.sqrt(absolute_value)
# assert: a number is printed

raw_input("\npress Enter to finish")

===========================================

Both the then- and else-arms exploit the answer that the test might calculate to achieve the same fact --- that absolute_value >= 0. Because this fact holds in all cases, we can assert it when the conditional command finishes, at the position labelled by #!. This ensures that a square root is always correctly computed.

Here is a flowchart drawing of the above example:


The pattern of knowledge production for an if-else command looks like this:

if TEST :
    # assert: TEST
    THEN_COMMANDS
    # assert: GOAL
else :
    # assert: not TEST
    ELSE_COMMANDs
    # assert: GOAL
# assert, in both cases: GOAL
That is,

We write the THEN_COMMANDs to operate correctly when we know that TEST holds True, and we write the ELSE_COMMANDs to operate correctly when we know that TEST holds False.
In both cases, we work towards achieving the same GOAL knowledge, which can be used by the rest of the program! An intelligent application of the if-command is made with this philosophy in mind.

Here is the same idea, drawn flowchart-style:

 
                  |
                  v
                TEST ?
               /    \ 
        True  /      \ False
             v        v
   assert TEST      assert not TEST
   THEN_COMMANDS    ELSE_COMMANDS
   assert GOAL      assert GOAL
               \     /
                \_ _/
                  |
              assert GOAL
                  |
                  v


3A.4 Semantics of Conditionals

We now confirm our intuitions of the execution semantics of the conditional command. Here is a small program, annotated with line numbers:
=====================

num = int( raw_input("Type an int: "))  # Line 1
                                        #      2
if num < 0 :                            #      3
    print "is negative"                 #      4
else :                                  #      5
    print "is nonnegative"              #      6
                                        #      7
============================
When the program starts at Line 1, the instruction counter (``i.c.'') holds 1, and there are no variables in the program's namespace. Here is a picture of the computer's configuration:

From now on, we show just the instruction counter and the namespace, since the program stays the same. The instructor counter and the namespace will be typed like this:
      i.c.: 1      namespace:  (empty)
The first command asks the user to enter an integer; say that 42 is typed. This causes 42 to be extracted and saved in the newly creadted variable, num:
      i.c.: 3      namespace:  num: 42
At Line 3, the expression, num < 0 is computed. Of course, the comparison, 42 < 0 computes to False. This makes the instruction counter reset to the first instruction of the conditional's else-arm:
      i.c.: 6      namespace:  num: 42
At instruction 6, the message, is nonnegative, prints, and the instruction counter is set to the command following the conditional:
      i.c.: 7      namespace:  num: 42

Another test case

Now, let's repeat the execution with a different test case. We again start with the instruction counter at 1 and an empty namespace:

      i.c.: 1      namespace:  (empty)
Say that the user types -3 as the input. At the start of the conditional, Line 3, we have this configuration:
      i.c.: 3      namespace:  num: -3
At Line 3, the expression, num < 0 is computed, and the comparison, -3 < 0 computes to True. This makes the instruction counter reset to the first instruction of the conditional's then-arm:
      i.c.: 4      namespace:  num:-3 
At instruction 4, the message, is negative, prints, and the instruction counter is again set to the command following the conditional:
      i.c.: 7      namespace:  num: -3


3A.5 Relational Operations

Sometimes we wish to ask two questions within one test. In the square root example, another way to ask whether the input number is nonnegative is to ask if it is zero or positive. We do it with a relational operation, which computes on boolean (True-False) arguments and produces boolean answers.

Here is the example:

===================================================

import math
num = int(raw_input("Type an int: "))

if (num == 0) or (num > 0) :  # num is zero or positive ?
    print math.sqrt(num)

=====================================================

The or operation is called (logical disjunction): if either (or both!) of its argments computes to True, then the result of the question is True as well.

More precisely stated, the test calculates to True if the first argument, num == 0 , computes to True. If num == 0 computes to False, then the second argument is computed---if num > 0 computes to True then the test computes to True. If both phrases compute to False, then so does the test.

We have used and (logical conjunction) in program assertions; we can also use it in tests. Here is a Python coding of the example where we limit the program to computing square roots in the range of 0 to 10:

===================================================

import math
num = int(raw_input("Type an int: "))

if (num >= 0) and (num <= 10) :  # num in range of 0 to 10?
    print math.sqrt(num)
else :
    print "Sorry --- the number must fall between 0 and 10"

=====================================================
Both expressions of the conjunction must be True for the test to compute to True: The first argument, num >= 0, is computed; if it is False, the else-arm is immediately taken; if it is True, then num <= 10 is computed, and its result determines which arm executes.

We can also use not (negation), which ``flips'' the answer of a test:

if not(num < 0) :  #again, is  num  nonnegative ?
    print math.sqrt(num)
Of course, this test asks the same question as does num >= 0.

Table 5 summarizes the relational operations and their semantics.

TABLE 5: logical operations===========================================
Operator Semantics
E1 and E2 conjunction (``and''):
True and True => True
True and False => False
False and E2 => False
E1 or E2 disjunction (``or''):
False or False => False
False or True => True
True or E2 => True
not E negation(``not''):
not True => False
not False => True
ENDTABLE================================================================
Finally, we can write arbitrarily complex expressions with the relational operations. Here is an example that checks whether a number is either 99 or a nonnegative, even-valued int:
n = int(raw_input("Type an int: "))
if (n == 99)  or  (not(n < 0)  and  (n%2 == 0)) :
    print "it's either 99 or it's a nonnegative, even int"
Notice how extra parentheses are used to indicate how the comparisons should be grouped. (The not operator is always applied to the first argument that follows it, here n < 0.) It is safe, however, to omit parentheses when a sequence of the same relational operation is used, e.g.,
if (n == 2) or (n == 3) or (n == 5) or (n > 99) :
    ...
There is no need to group the arguments for the multiple or operations, since the computation will proceed left to right in any case.

Exercises

  1. Calculate the answers for each of the following expressions; assume that x = 2 and y = 3.5.
    1. (x > 1) and ((2*x) <= y)
    2. not(x == 1)
    3. (x >= 0 and x <= 1) or (1 <= y)
    4. x > 0 and x < 10 and (y == 3)
  2. Given this program,
    # Withdrawal
    #  does subtraction
    # assumed inputs: arg1 - a nonnegative int
    #                 arg2 - an int less-or-equal-to than arg1
    # guaranteed output:  arg1 - arg2
    
    int arg1 = int(raw_input("Type balance: "))
    int arg2 = int(raw_input("Type withdrawal amount: "))
    
    if  arg1 > 0  and  arg1 >= arg2 :
        print  "New balance is", (arg1 - arg2)
    else :
        print "Transaction voided"
    
    what results are returned for these argument pairs: 3 and 2 ? 2 and 3 ? 3 and 3 ? -4 and 5 ? 4 and -5 ?

    Say that the Withdrawal program is embedded into a cash dispenser. Improve the program so that it behaves ``correctly'' for all five test cases just mentioned.

  3. Rewrite ConvertToSeconds from the previous exercise set so that it uses only one conditional command.
  4. Write this program so that it contains only one conditional command.
    # SmallPrime
    #   says whether its argument is a prime number less than 10
    # assumed input: an int
    # guaranteed output: a message stating whether the int is a prime
    #   number less than 10  (NOTE: the only primes less than 10 are
    #   2, 3, 5, and 7.)
    


3A.6 Compound conditionals

Sometimes one wants to use knowledge where there are multiple (more than two) cases to consider. For example, we wish to determine whether an input number is either negative, or zero, or positive. Here is Python's way of asking multiple questions within a single conditional:

=================================

# PrintSign
#  prints the sign of a number
# asssumed input: an integer
# guaranteed output: a message as to whether or not the
#  number is negative, zero, or positive 

num = int( raw_input("Type an int: "))

if num < 0 :
    print "is negative"
elif num == 0 :
    print "is zero"
else : # assert:  num > 0  must be true
    print "is positive"

raw_input("\n\npress Enter to finish")

======================================
Try it:
$ python PrintSign.py
Type an int: 16
is positive
Again:
$ python PrintSign.py
Type an int: 0
is zero
And again:
$ python PrintSign.py
Type an int: -99
is negative
Read the elif as ``else-if'', which means that if the previous condition was False, then we ask another question.

The compound conditional looks like this:

if TEST1 :
    COMMANDs1     # do this if  TEST1  is True
elif TEST2 :      # ask this when  TEST1 is False
    COMMANDs2     # do this if TEST2  is True
elif TEST3 :      # ask this when TEST1 and TEST2 are False
    COMMANDs3     #   do this if TEST3 is True
  ...             # ... continue this way through all the TESTs....
else :
    COMMANDsLAST  # do this when _all_ TESTs are False
The semantics of the command is described by the comments inserted into the syntax description: The conditions are computed one by one, until a condition computes to True. At that point, the corresponding set of commands are executed. If all the conditions compute to False, the commands labelled by else are computed.

Here is a flowchart drawing of the compound conditional:

      |
      v    False          False
    TEST1? ------> TEST2? ------> TEST3? ... ---+
      |              |              |             |
 True |          True|          True|        False|
      v              v              v             v
 COMMANDS1      COMMANDS2       COMMANDS3      COMMANDS_LAST
      |              |              |             |
      +--------------+--------------+---- ... ----+
                            |
                            V
Finally, it is crucial to understand the knowledge that is generated by asking the series of questions/tests; it looks like this:
if TEST1 :
    # assert: TEST1
    COMMANDs1
elif TEST2 :
    # assert: not TEST1  and  TEST2
    COMMANDs2
elif TEST3 :
    # assert: not TEST1  and  not TEST2  and  TEST3
    COMMANDs3
   ...          # ... continue this way through all the TESTs....
else :
    # assert not TEST1  and  not TEST2  and  not TEST3  ...
    COMMANDsLAST 
The more questions that are asked, the more knowledge that is gained for the benefit of the COMMANDs that are finally selected to compute.

Here is a second example, where a nested conditional asks about the possible cases that arrive within an input request. The example is a program that can convert temperatures from Celsius to Fahrenheit and from Fahrenheit to Celsius, for example:

$python ConvertTemps.py
Is the temperature Celsius or Fahrenheit (C or F)? C
Please type the temperature: 22
22 degrees Celsius is  71.6 degrees Fahrenheit
There are three possible cases for the first input from the human:
  1. the input is "C"
  2. the input is "F"
  3. the input is something else
A computation must be planned for each of the three cases. It is helpful to graph the cases within a flowchart:
    read the base of the temperature
    read the temperature
          |   
          V   
    base = "C" ? --> base == "F" ? --> otherwise:
          |            |                 |
          V            V                 V
     convert the     convert the       print error message
     Celsius temp    Fahrenheit          |
     to Fahrenheit   temp to Celsius     |
          |            |                 |
          +------------+-----------------+
                       |
                       V
Here is the program, which uses a compound conditional to code the flowchart:
FIGURE==========================================================

# ConvertTemps  converts between Celsius and Fahrenheit temperatures.
# assumed input:  a base ("C" for Celsius and "F" for Fahrenheit)
#                 the temperature to convert, an int
# guaranteed output: the temperature converted to the other base

base = raw_input("Is the temperature Celsius or Fahrenheit (C or F)? ")
temp = int(raw_input("Please type the temperature: "))

if base == "C" :
    # assert: base == "C"
    output_temp = ((9.0 / 5.0) * temp ) + 32
    print temp, "degrees Celsius is ", output_temp, "degrees Fahrenheit"
elif base == "F" :
    # assert: base == "F"
    output_temp = ((5 * temp) - 160) / 9.0
    print temp, "degrees Fahrenheit is ", output_temp, "degrees Celsius"
else:  # assert:  base != "C"  and  base != "F"
    print "error in the temperature base; no conversion performed"

ENDFIGURE======================================================
The ``goal'' of each of the three plans of actions is to compute the form of conversion desired by the human. In the third case, when the base is erroneous, no conversion is appropriate.

Exercises

Use conditional commands to write these programs. Insert asserts within the then- and else- arms that indicate what knowledge is gained by the answers to the conditionals' tests:
  1. # WithdrawCash
    #   subtracts its second argument from its first, provided that
    #   the result is nonnegative
    # assumed inputs: arg1 - the first input, must be a nonnegative float
    #                 arg2 - the second argument, a float
    # guaranteed output: (arg1 - arg2), if  arg1 >= arg2;
    
  2. # Divide
    #  does integer division on its arguments
    # assumed inputs: arg1 - the dividend, a float
    #                 arg2 - the divisor, a float that is not zero
    # guaranteed output:  arg1 / arg2  
    


3A.7 Design: Writing algorithms with flowcharts

Remember: To teach (program) a computer to solve a problem, we must
  1. learn really well how to solve the problem ourselves;
  2. write down an algorithm that explains how we solved the problem;
  3. translate the algorithm into a Python program
Some problems are solved by asking a lot of questions about the problem's input data, and an algorithm written in English is not the best way to write solutions to such ``question-problems.'' (Have you read the awful explanations in the IRS tax manuals? They are failed attempts at using English to write solutions to question-problems.)

Flowcharts are a better language for solutions to question-problems, because they are a nice mix of graphics and English. Following is an example.



3A.7.1 Case Study: Time conversion

In practice, two main uses of conditionals are

Here is a problem that employs both uses of conditionals.

We desire a program, TwelveHourClock, which converts a time from a twenty-four hour clock into the corresponding value on a twelve-hour clock. Here is a typical behavior:

Type time (24-hour clock): 16:35
The time is 4:35 p.m.
and here is another:
Type time (24-hour clock): 00:05
The time is 12:05 a.m. 
The program must be intelligent enough to understand that the hours 0 to 11 are ``a.m.'' hours and that 12 to 23 are ''p.m.'' Also, the ''p.m.'' hours are rewritten as 12, 1, 2, ..., 11, and the very first ``a.m.'' hour is also 12.

Take a moment to practice these conversions yourself: How do you convert 16:35 to 4:35 p.m.? 00:05 to 12:05 a.m.? 09:00 to 9:00 a.m.? What is the pattern/strategy you use? Can you write it down? It's time to try:

Our first attempt at the algorithm might go like this:

  1. Read the input and extract the hours and minutes, both integers.
  2. If either hours or minutes are nonsensical, then print an error message and quit. Otherwise, convert the time:
  3. Compute whether the time is in the morning (a.m.) time or afternoon (p.m.)
  4. Compute the correct hour, remembering that hours 13..23 are reduced by 12 and that the 0 hour is written as 12.
  5. Compute the correct minutes.
  6. Print the results, remembering that a minute value of 0..9 should be written with a leading 0 (e.g., 2 minutes is written 02)
Each step asks some questions and takes action based on the answers, and we might refine each step with its own little flowchart. For example, Step 4, which converts the hours, has three cases that cover all possible values for the hours:
hours == 0 ?      hours > 12 ?    hours between 1 and 12 ?
    |                |                       |
    V                V                       V
new_hours = 12    new_hours = hours - 12    new_hours = hours
    |                 |                      |
    +-----------------+----------------------+
                      |
 assert: new_hours is correct hours in 12-hour-clock time
                      |
                      V
The flowchart helps us organize our thinking: The hours value might be any of 0, 1, 2, ..., 12, 13, ..., 23, and our grouping of the 24 values into 3 cases helps us plan the strategies for computation.

You are welcome to write little flowcharts for Steps 3 and 6, which also have some interest. Figure 7 shows the program that we code from the flowcharts.

FIGURE 7: program that uses conditionals to convert time==================

# TwelveHourClock 
#   converts a 24-hour-clock time into a 12-hour-clock time.
# assumed inputs: hour - the hour time, in the range 0..23
#                 minute - the minutes time, in the range 0..59
# guaranteed output: the 12-hour-clock time

# Step 1:
input = raw_input("Type time (24-hour clock): ")

hours_and_minutes = input.split(":")    # split breaks a string into pieces
hour_text = hours_and_minutes[0]   # get the first piece (see Chap. 4)
minute_text = hours_and_minutes[1] # get the second piece (see Chap. 4)

if  hour_text.isdigit()  and  minute_text.isdigit() :
    # assert: we can convert the inputs into ints
    hours = int(hour_text)
    minutes = int(minute_text)

    # Step 2:
    if  hours < 0  or  hours > 23  or  minutes < 0  or  minutes > 59 :
        print "Error: hours or minutes are out of range"
    else :
        # assert:  hours in 0..23  and  minutes in 0..59

        # Step 3:
        if hours < 12 :
            ampm = "a.m."
        else :
            ampm = "p.m."
        # assert in both cases:  a.m./p.m. distinction correct

        # Step 4:
        if hours > 12 :
            new_hours = hours - 12
        elif hours == 0 :
            new_hours = 12
        else :
            new_hours = hours
        # assert in all cases:  hours correctly computed

        # Step 5:  minutes is already correctly computed

        # Step 6: print
        import string   # find  string  module to use here:
        print  str(new_hours) + ":" + string.zfill(minutes, 2) + ampm
else :
    print "Error: input are not numbers"

raw_input("\npress Enter to finish")


ENDFIGURE============================================================
Within Step 1, there is a new Python trick, which will be completely understood in a couple of chapters:

Since the input to the program is a string, which is in fact two numbers separated by a colon, we must ``split'' the string into its two pieces. Python has a special method, named split, that we use:

input = raw_input(...)
hours_and_minutes = input.split(":")
The second command splits the string, input into two pieces, discarding the colon in the middle, and saving the two string-pieces in variable hours_and_minutes

We extract the two pieces (strings) like this:

hour_text = hours_and_minutes[0]
minute_text = hours_and_minutes[1]
(the details are given in Chapter 5), and we make the two pieces into integers:
hours = int(hour_text)
minutes = int(minute_text) 
The split is trick is useful for splitting apart hours-minutes, dollars-cents, word-word-word, etc.

The remainder of the program makes careful use of the questions we ask of the values of the hours and minutes. When the answer is printed, we use a helper function, string.zfill, which ensures that we print the minutes amounts as a two-digit integer.

Exercises

  1. Test TwelveHourClock with each of these times: 9,45; 23,59; 0,01; 50,50; -12,-12; 24,0.
  2. Write a program that meets this specification:
    # TranslateGrade
    #   converts a numerical score to a letter grade.
    # assumed input: score - a numerical score that must be in the range 0..100
    # guaranteed output: a letter grade based on this scale:
    #   100..90 = "A"; 89..80 = "B"; 79..70 = "C"; 69..60 = "D"; 59..0 = "F" 
    
  3. Improve the MakeChange3 program so that
    1. answers of zero coins are not displayed. For example, the change for 0 dollars and 7 cents should display only
      Nickels = 1
      Pennies = 2
      
    2. if only one of a kind of coin is needed to make change, then a singular (and not plural) noun is used for the label, e.g., for 0 dollars and 46 cents, the application prints
      1 quarter
      2 dimes
      1 penny
      


3A.7.2 Boolean-valued Variables

The program in Figure 7 is a bit hard to read --- we must remember a long history of tests to know when a command in the program's middle might execute --- so we might ``unnest'' the conditionals in the Figure with a boolean variable, which is a variable that holds as its value either True or False.

Boolean variables are good for remembering a history of knowledge gained from conditionals' tests.

In the time-conversion program, we must ask several questions about the inputs before we proceed to time conversion. If any of the answers indicate that the inputs are improper, we must skip the time conversion. See Figure 8, which rewrites the time-conversion program with a boolean variable, OK, that remembers whether the inputs are acceptable.

FIGURE 8: unnesting conditional statements=============================

# MakeChange2
#   calculates change in coins for a dollars, cents amount
# assumed input: dollars - a nonnegative integer
#                cents - an integer between 0 and 99
# guaranteed output: the coins for the amount

# Step 1:
input = raw_input("Type time (24-hour clock): ")

hours_and_minutes = input.split(":")    # split breaks a string into pieces
hour_text = hours_and_minutes[0]   # get the first piece (see Chap. 4)
minute_text = hours_and_minutes[1] # get the second piece (see Chap. 4)

OK = True  # asserts whether the inputs are acceptable for time conversion

# Step 2:
if  (not hour_text.isdigit())  or  not(minute_text.isdigit()) :
    print "Error: input are not numbers"
    OK = False  

if OK :  
    # assert: we can convert the inputs into ints
    hours = int(hour_text)
    minutes = int(minute_text)
    if  hours < 0  or  hours > 23  or  minutes < 0  or  minutes > 59 :
        print "Error in inputs"
        OK = False

if OK :
    # assert:  hours in 0..23  and  minutes in 0..59, so OK to convert time!
    # Step 3:
    if hours < 12 :
        ampm = "a.m."
    else :
        ampm = "p.m."
    # assert in both cases:  a.m./p.m. distinction correct

    # Step 4:
    if hours > 12 :
        new_hours = hours - 12
    elif hours == 0 : 
        new_hours = 12
    else : 
        new_hours = hours
    # assert in all cases:  hours correctly computed

    # Step 5:  minutes is already correctly computed

    # Step 6: print
    import string   # find  string  module to use here:
    print  str(new_hours) + ":" + string.zfill(minutes, 2) + ampm
    
raw_input("\npress Enter to finish")

ENDFIGURE=============================================================

The boolean variable is set to False if any test uncovers a bad input. The subsequent commands consult OK to determine whether the computation may proceed. By using OK, we need not write so many nested conditional commands.


3A.7.3 The exit command

The boolean-variable technique, just seen above, works best when a program must be written so that large parts of it can be skipped when a condition ``goes False.'' Another technique is to stop the program dead and not finish it. This can be done with the sys.exit command. For example, the time conversion program can be rewritten like this:
================================================

import sys   # finds the module with ``system'' functions, like  exit

# Step 1:
input = raw_input("Type time (24-hour clock): ")

hours_and_minutes = input.split(":")    # split breaks a string into pieces
hour_text = hours_and_minutes[0]   # get the first piece (see Chap. 4)
minute_text = hours_and_minutes[1] # get the second piece (see Chap. 4)

# Step 2:
if  (not hour_text.isdigit())  or  not(minute_text.isdigit()) :
    print "Error: input are not numbers"
    sys.exit()  # STOP DEAD, HERE!
       
# assert: program is still computing, so we can convert the inputs into ints
hours = int(hour_text)
minutes = int(minute_text)
if  hours < 0  or  hours > 23  or  minutes < 0  or  minutes > 59 :
    print "Error in inputs"
    sys.exit()

# assert:  program is still computing,
#          so hours in 0..23  and  minutes in 0..59, so OK to convert time!
# Step 3:
if hours < 12 :
    ampm = "a.m."
else :
    ampm = "p.m."
# assert in both cases:  a.m./p.m. distinction correct

# Step 4:
if hours > 12 :
    new_hours = hours - 12
elif hours == 0 : 
    new_hours = 12
else : 
    new_hours = hours
# assert in all cases:  hours correctly computed

# Step 5:  minutes is already correctly computed

# Step 6: print
import string   # find  string  module to use here:
print  str(new_hours) + ":" + string.zfill(minutes, 2) + ampm

raw_input("\npress Enter to finish")

=====================================================
The sys.exit command is actually a separate, small program (``function'') that is prewritten and saved in a package named sys. We must import sys to start the exit program.

Use the exit command with caution --- it stops your program and the Python interpreter, and nothing can happen after it executes. (Demo the above program by saving it and double-clicking on its icon --- what happens when you provide bad input?)

Truly, sys.exit is a ``last resort'' when planning a computation strategy --- use it when you are truly lost and cannot recover from a disastrous situation. (The above use of sys.exit is overkill.)

Exercises

  1. Write this method; use nested conditionals
    # ConvertToSeconds 
    #   converts an hours, minutes amount into the equivalent time in seconds.
    # assumed inputs: hours - the hours, a nonnegative integer
    #                 minutes - the minutes, an integer in the range 0..59
    # guaranteed output: the time in seconds
    
  2. Next, rewrite the program with no nested conditionals---use a boolean variable to remember the status of the conversion.


3A.8 Testing programs with conditionals

In the previous chapter, we noted that we should test a program to see if it behaves the way we want. This means we should try the program with the test-case behaviors we started from when we designed the program. (Testing the test-case behaviors is sometimes called ``black box'' testing, because the program is treated like it is resting inside a black box and we do not look inside it to see what happens --- we only watch what the program produces as output.)

But programs that contain conditional statements should undergo a second round of testing where, for every if-command in the program, there is a test case that makes the command's then-arm execute and there is another test case that makes the command's else-arm execute. This ensures that every command inside the program is tested at least once! (Testing the conditions this way is sometimes called ``white box'' or ''clear box'' testing, because we look inside the program to see what happens with the test cases.)

For example, consider this structure of conditionals, which is checking the inputs to a banking machine:

if  dollars < 0 :
   ...
else :
   ...
   if cents < 0 :
      ...
   elif cents > 99 :
      ...
   else
      ...
This asks questions about the input values of dollars and cents. We must test this program with at least the following test cases: (Actually, only four --- not six --- test cases are required. Why?) The flowchart for the commands indicates all the necessary combinations of input values for dollars and cents:

Use a command window to test your program!

As we noted in the previous chapter, it is easy to start a Python program --- double-click on its icon. But if the program contains an error, then the error message will flash onto the display and disappear almost immediately!

It is better to test a new Python program by starting it within a command window:

  1. Start a command-prompt window.
  2. cd to the folder where your program lives.
  3. Start your program in the command-prompt window, e.g., if your program is named P.py, type python P.py.

This starts your program and makes the program show its work in the opened command-prompt window. If there is an error, the error message will appear in the window.

This technique is important, because it is easy to forget to insert colons and uniform indentation into conditional commands. When the Python interpreter notes that you have forgotten to do this, it will issue an error message, and you will certainly want to read it!


3A.9 Summary

Here is a summary of the new Python constructions:

The new COMMAND is the CONDIIONAL, which can have these forms of syntax:

That is, a conditional consists of one or more CONDITION--COMMANDs pairings, optionally finished by an else : COMMANDs. The semantics of the conditional goes like this:
  1. The CONDITIONs are computed one by one until a condition is located that computes to True.
  2. The COMMANDs that follow the condition's colon are executed.
  3. If all the conditions compute to False, then the COMMANDs that follow the else : are executed. (If there is no else, then no commands at all are executed.)
It is important that all the COMMANDs are indented exactly the same amount, e.g., 4 spaces or one tab.

A CONDITION is an expression that computes to a boolean (True-False) value. CONDITIONs can be built in these ways:

The semantics of a CONDITION is that it is evaluated from left to right until its result is definitely True or False. (See Table 5 earlier in these notes for the computation rules.)

Boolean variables can be used to simplify the structure of a program that must ask many questions to choose its course of action. The style looks like this:

ok = True  # remembers if the computation has proceeded correctly so far

... # compute
if BAD_CONDITION :
    ok = False

if ok :  # if no BAD_CONDITION so far, proceed:
    ... # compute 
    if BAD_CONDITION :
        ok = False 
    else :
        ... # compute

if ok :  # if no BAD_CONDITION so far, proceed:
    ... # compute 
If a program must be stopped immediately, use the exit command:
import sys
sys.exit()

The following special functions were introduced in this chapter: