.. _decision-structure: ################################ Making Decisions in our programs ################################ .. wordcount:: .. include:: /references.inc .. vim:ft=rst spell: .. seealso:: 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? .. csv-table:: 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). .. csv-table:: ">", 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: .. code-block:: python 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? .. code-block:: python 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: .. code-block:: python >>> 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: .. code-block:: python 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! .. code-block:: python >>> 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: .. code-block:: python 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: .. csv-table:: 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: .. csv-table:: 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. .. code-block:: python >>> 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: .. code-block:: python >>> 10 / 0 Traceback (most recent call last): File "", line 1, in 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: .. code-block:: python 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: .. code-block:: python # 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!) Only allowing legal grades ========================== Not very realistic, I am afraid. We need to do the job right. Let's try to restrict legal grades to a value between 0 and 100: .. code-block:: python # Grading program with range checking grade = input('Enter the final average: ') grade = int(grade) if grade < 0 or grade > 100: print('Average of ', grade, ' is illegal, try again!') Notice how we checked the grade to see if it is in the range of legal values. We could do this other ways. Try these and make sure you believe they are right: .. code-block:: python if grade >= 0 and grade <= 100: # grade is legal Watch for common mistakes ========================= What is wrong with this test? .. code-block:: python 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. .. code-block:: python 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: .. code-block:: python 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! .. code-block:: python 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: .. code-block:: python 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: .. code-block:: python 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: .. code-block:: python 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: .. code-block:: python 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.