Lecture 11 — Decisions Part 2
==============================
Overview — Logic and Decision Making, part 2
--------------------------------------------
- Program structure
- Debugging and "hardening" a simple function
- A bit of design and a bit of a random walk
- More on logic - the key to getting programs right
- Boolean logic
- Nested if statements
- Assigning boolean variables
Part 1: Program Structure
----------------------------
Programming requires four basic skills:
#. Develop a solution.
- Start with small steps, solve them, and then add complexity
#. Structure your code.
- Move repeated code into functions
- Make your code easy to read and modify. Use meaningful variable names
and break complex operations into smaller parts.
- Place values in variables so they are easy to change.
- Include comments for important functions, but do not clutter
your code with unnecessary comments. A classic example of a
completely unnecessary comment is
::
x += 1 # increment x
- Document assumptions about what is passed to a function.
- If your function is meant to return a value, make sure it always
does.
#. Test your code.
- Find all possible cases that your program should work for. As
programs get larger, this is increasingly hard.
- If you cannot check for all inputs, then you must then check your code
for meaningful inputs: regular (expected inputs) and edge cases (inputs
that can break your code).
#. Debug your code.
- Never assume an untested part of your code is bug free.
- Learn syntax well so that you can spot and fix syntax errors
fast.
- Semantic errors are much harder to find and fix. You need
strategies to isolate where the error is.
- Print output before and after crucial steps.
- Look at where your program crashed.
- Fix the first error first, not the biggest error. The first
error may be the cause of bigger errors later in your code.
- Use a debugger.
- Simplify the problem: remove (by commenting out) parts of your
code until you no longer have an error. Look at the last code
removed for a source of at least part of your errors.
- Test parts of your code separately and once you are convinced
they are bug free, concentrate on other parts.
Help with debugging
-------------------
- Consider the following code to add the first ``n`` integers:
::
n = int(input("Enter a positive integer ==> "))
sum = 0
i = 0
while i != n:
sum += i
i += 1
print('Sum is', sum)
- Does it work? For all inputs? Might it run forever? (We'll
ignore the fact that a ``for`` loop would be better here.)
- How might we find such an error?
- Careful reading of the code
- Insert print statements
- Use the Wing IDE debugger.
- We will practice with the Wing IDE debugger in class, using it to
understand the behavior of the program. We will explain the following
picture
.. image:: images/debugger.png
and note the use of
- The hand, bug and stop symbols on the top of the display, and
- The Debug I/O and Stack Data at the bottom of the display.
- Debugging becomes crucial in tracking logic errors as well.
Program organization
---------------------
- Envision your code as having two main parts: the main body and the
functions that help the main code.
- Make sure your functions accomplish one well-defined task. This
makes them both easy to test and useful in many places.
- As we will see in an example below, in Python it is good practice to
separate the functions and the main body with the following
addition to the program structure:
::
if __name__ == "__main__"
# Put the main body of the program below this line
- This will have no apparent effect when a program is run.
However, if a program is imported as a module into another program
(like the utility code we have been giving you), any code within the
above ``if`` block is skipped!
- This allows programs to work both as modules and stand alone
code.
- When the primary purpose of your code is to provide functionality
as a module, it is best to use the code in the main body to test
the module functions.
Part 2: Extended Example of a Random Walk
------------------------------------------
- Many numerical simulations, including many video games, involve random
events.
- Python includes a module to generate numbers at random. For
example,
::
import random
# Print three numbers randomly generated between 0 and 1.
print(random.random())
print(random.random())
print(random.random())
# Print a random integer in the range 0..5
print(random.randint(0,5))
print(random.randint(0,5))
print(random.randint(0,5))
- We’d like to use this to simulate a “random walk”:
- Hypothetically, a person takes a step forward or backward,
completely at random (equally likely to go either way). This
can be repeated over and over again until some stopping point is
reached.
- Suppose the person is on a platform with :math:`N` steps and the person
starts in the middle, this random forward/backward stepping
process is repeated until they fall off (reach step 0 or step
:math:`N+1`)
- "forward" is represented by an increasing step, while
"backward" is represented by a decreasing step
- How many steps does it take to fall off?
- Many variations on this problem appear in physical simulations.
- We can simulate a step in two ways:
#. If ``random.random()`` returns a value less than 0.5 step backward;
otherwise step forward.
#. If ``random.randint(0,1)`` returns 1 then step forward; otherwise,
step backward.
- So, in summary, we want to start a random walk at position :math:`N/2` and
repeatedly take a step forward or backward based on the
output of the random number generator until the walker falls off.
- We will solve this problem together during lecture.
We we start by enumerating some of the needed steps and then
solving them individually before putting together the whole
program.
- Once we see the result we can think of several ways to change
things and explore new questions and ideas. Remember, a program
is never done!
Part 3: Review of Boolean Logic
--------------------------------
- Invented / discovered by George Boole in the 1840’s to reduce
classical logic to an algebra
- This was a crucial mathematical step on the road to computation
and computers
- Values (in Python) are ``True`` and ``False``
- Operators
- Comparisons: ``<, >, <=, >=, == !=``
- Logic: ``and``, ``or``, ``not``
Truth Tables
------------
- Aside to recall the syntax: ``and, or, not`` are lower case!
- If we have two boolean expressions, which we will refer to as ``ex1``
and ``ex2``, and if we combine their “truth” values using ``and`` we
have the following “truth table” to describe the result
+-----------+------------+-----------------+
| ``ex1`` | ``ex2`` | ``ex1 and ex2`` |
+===========+============+=================+
| ``False`` | ``False`` | ``False`` |
+-----------+------------+-----------------+
| ``False`` | ``True`` | ``False`` |
+-----------+------------+-----------------+
| ``True`` | ``False`` | ``False`` |
+-----------+------------+-----------------+
| ``True`` | ``True`` | ``True`` |
+-----------+------------+-----------------+
- If we combine the two expressions using ``or``, we have
+------------+-----------+----------------+
| ``ex1`` | ``ex2`` | ``ex1 or ex2`` |
+============+===========+================+
| ``False`` | ``False`` | ``False`` |
+------------+-----------+----------------+
| ``False`` | ``True`` | ``True`` |
+------------+-----------+----------------+
| ``True`` | ``False`` | ``True`` |
+------------+-----------+----------------+
| ``True`` | ``True`` | ``True`` |
+------------+-----------+----------------+
- Finally, using ``not`` we have
+-----------+-------------+
| ``ex1`` | ``not ex1`` |
+===========+=============+
| ``False`` | ``True`` |
+-----------+-------------+
| ``True`` | ``False`` |
+-----------+-------------+
DeMorgan’s Laws Relating and, or, not
-------------------------------------
- Using ``ex1`` and ``ex2`` once again to represent boolean
expressions, we have
::
not (ex1 and ex2) == (not ex1) or (not ex2)
- And,
::
not (ex1 or ex2) == (not ex1) and (not ex2)
- Also, distribution laws
::
ex1 and (ex2 or ex3) == (ex1 and ex2) or (ex1 and ex3)
ex1 or (ex2 and ex3) == (ex1 or ex2) and (ex1 or ex3)
- We can prove these using truth tables.
Why Do We Care?
---------------
- When we’ve written logical expressions into our programs, it no
longer matters what we intended; it matters what the logic actually
does.
- For complicated boolean expressions, we may need to almost prove that
they are correct
Part 4: Additional Techniques in Logic and Decision Making
----------------------------------------------------------
We will examine:
- Short-ciruiting
- Nested conditionals
- Storing the result of boolean expressions in variables
and then apply them to several problems
Short-Circuited Boolean Expressions
-----------------------------------
- Python only evaluates expressions as far as needed to make a
decision.
- Therefore, in boolean expression of the form
::
ex1 and ex2
``ex2`` will not be evaluated if ``ex1`` evaluates to ``False``.
Think about why.
- Also, in boolean expression of the form
::
ex1 or ex2
``ex2`` will not be evaluated if ``ex1`` evalues to ``True``.
- This “short-circuiting” is common across many programming languages.
Nested If Statements
--------------------
- We can place ``if`` statements inside of other ``if`` statements.
- To illustrate, consider the following where ``ex1``, ``ex2``,
``ex3`` and ``ex4`` are all boolean expressions, and ``blockA``,
``blockB``, ``blockD`` and ``blockE`` are sections of code.
::
if ex1:
if ex2:
blockA
elif ex3:
blockB
elif ex4:
blockD
else:
blockE
- We will examine this example in class and answer the following
questions:
- Under what conditions is each block executed?
- Is it possible that no blocks are executed?
- What is the equivalent non-nested if-elif-else structure?
Storing the Result of a Boolean Expression
------------------------------------------
- Sometimes we store the result of boolean expressions in a variable
for later use:
::
f = float(raw_input("Enter a Fahrenheit temperature: "))
is_below_freezing = f < 32.0
if is_below_freezing:
print "Brrr. It is cold"
- We use this to
- Make code clearer
- Avoid repeated evaluation of the same expression, especially if
the expression requires a lot of computation.
Examples for Lecture
--------------------
We will work on the following examples during class, as time permits.
#. In the following code, for what values of ``x`` and ``y`` does the
code print 1, for what values does the code print 2, and for what
values does the code print nothing at all?
::
if x>5 and y<10:
if x<5 or y>2:
if y>3 or z<3:
print 1
else:
print 2
The moral of the story is that you should be careful to ensure that
your logic and if structures **cover the entire range of
possibilities**!
#. Doctors sometimes assess a patient’s risk of heart disease in terms of a
combination of the BMI (body mask index) and age using the following
table:
+-------------------------+-----------------------+--------------------+
| | Age :math:`\leq 45` | Age :math:`> 45` |
+=========================+=======================+====================+
| BMI :math:`< 22.0` | Low | Medium |
+-------------------------+-----------------------+--------------------+
| BMI :math:`\geq 22.0` | Medium | High |
+-------------------------+-----------------------+--------------------+
Assuming the values for a patient are stored in variables ``age`` and
``bmi``, we can write the following code
::
slim = bmi < 22.0
young = age <= 45
We will work out two different ways of printing *Low*, *Medium* or
*High* according to the table based on the values of the boolean
variables ``slim`` and ``young``.
#. Challenge example: Suppose two rectangles are determined by their
corner points - ``(x0,y0)`` and ``(x1,y1)`` for one rectangle and
``(u0,v0)`` and ``(u1,v1)`` for the other. Write a function that
takes these four tuples as arguments and returns ``True`` when the
two rectangles intersect and ``False`` otherwise.n
Summary of Discussion of If Statements and Logic
------------------------------------------------
- Logic is a crucial component of every program.
- Basic rules of logic, including DeMorgan’s laws, help us to write and
understand boolean expressions.
- It sometimes requires careful, precise thinking, even at the level of
a proof, to ensure logical expressions and if statement structures
are correct.
- Many bugs in supposedly-working programs are caused by conditions
that the programmers did not fully consider.
- If statements can be structured in many ways, sometimes nested several
levels deep.
- Nesting deeply can lead to confusing code, however.
- Warning specifically for Python: you can easily change the meaning of
your program by accidentally changing indentation. It is very
hard to debug these changes.
- Using variables to store boolean values can make code easier to
understand and avoids repeated tests.
- Make sure your logic and resulting expressions cover the universe of
possibilities!