Section 3.4 Logic for Flow Control
In this section we’ll take what we’ve learned about logic and apply it to computing in such a way that it helps control which commands are run when a routine is called. We’ll find that the logical connectors
and
, or
, not
, and if
are quite useful in code. We’ll start this section by using them to write some simple routines.Once we’ve seen
if
, and
, or
, and not
in action, We’ll introduce three other logical operators often found in code. The else
keyword can be combined with an if
statement to provide alternate code to be run in the case that the conditions on the if
statement are not satisfied. We can even combine else if
to define alternate sets of conditions in series, resulting in the running of different code in each scenario. We’ll also introduce two looping structures, the for
loop and the while
loop. These looping structures allow the same code to be run over and over again. In the case of the for
loop a predetermined number of iterations will be run, but in the case of the while
loop the code will be run until a termination condition is satisfied.Subsection 3.4.1 Conditional Statements in Code
Earlier in this chapter, we were exposed to conditional statements of the form \(P\implies Q\text{.}\) We read these statements If \(P\) is true, then \(Q\) is true. The idea with code is quite similar -- the interpretation of If \(P\text{,}\) then \(Q\) in code, however, is If \(P\) is true, then do \(Q\). Seeing this in action will help.
Let’s write a simple snippet of code which will test whether an input value is even, and will report back
"The input is even!"
if that is the case. We’ll see an implementation in pseudocode below, and then I’ll provide a working version in Python so that you can run the code and experiment with it.def is_even(myNumber): if myNumber is divisible by 2: print("The input is even!") return None
The first line in the pseudocode
def
ines the function, including its name and any inputs (parameters) it requires. The second line contains the if
statement and its condition. That is, this line defines the If \(P\) portion of our conditional statement. The line ends with a colon, and all indented lines below this one define what will be done if the condition is true. That is, the indented lines define the then \(Q\) portion of our conditional statement. Different programming languages have slightly different syntax. For example, the colon and indentation are used in Python, but curly braces surround the code to be run when the condition is true in C++, R, and others. The final line in the pseudocode definition of the function is a return
statement. It describes what the result of the function is and how it will be passed back to the computing environment. The function we’ve written has nothing to return, which we describe as return
ing None
.The following is a live implementation of the
is_even()
function in Python. Please consider experimenting with the code. At the very least, try evaluating is_even()
for other input values.The Python implementation of the
is_even()
function looks quite similar to the pseudocode we used to describe the function initially. The major difference is how the condition on the if
statement appears. The expression myNumber % 2
computes the remainder when myNumber
is divided by 2
. The ==
is a test for whether the value on the left of the operator is identically the same as the value on the right of the operator. All together, myNumber % 2 == 0
evaluates to true
if the remainder when dividing the input myNumber
by 2
is 0
, and it evaluates to false
otherwise. This means that our call to print()
will only be executed if the myNumber
input parameter is even.If you played around with the function, you’ll have likely noticed that nothing is printed out if you pass a value to the
myNumber
parameter which is not even. We can add an else
statement to print out that the input is odd if the original conditional is not satisfied. We’ll do that in pseudocode and follow it up with a live implementation below.def is_even(myNumber): if myNumber is divisible by 2: print("The input is even!") else: print("The input is odd!") return None
Hopefully you noticed that we’ve constructed a function which no longer behaves correctly. If you didn’t notice this, try passing the value 0.5 to the
myNumber
input parameter. The function prints out that "The
input is odd"
, but that’s not correct. This brings up an important point about code and computing in general. Computers are great at following instructions, but they aren’t smart. The computer has no way to know that it is providing us with wrong information -- instead, it is only following the instructions that we’ve written for it. We can create a better (and correct) version of our function if we use else if
followed by an else
statement to catch scenarios where the user has input a non-integer. Below is what that might look like using pseudocode as well as in a live Python implementation.def is_even(myNumber): if myNumber is divisible by 2: print("The input is even!") else if myNumber has a remainder of 1 under division by 2: print("The input is odd!") else: print("The input was not an integer!") return None
Note that in the working implementation, the combination
else if
is shortened to elif
. This is a common feature of many languages, since it helps to avoid nested if
statements within else
statements. The result is more compact and readable code.Subsection 3.4.2 Conditionals with Compound Conditions
In each iteration of the
is_even()
function explored in the previous subsection, we tested simple conditions for each if
or else if
conditional. We are certainly able to require more complex conditions to be satisfied in order for the code in the body of the conditional to be run. We can combine simple conditions using the and
, or
, and not
logical connectors we’ve learned about throughout this chapter.Let’s see this by building a new function, `is_large_even()` which prints out
"The input is over 1000 and is even!"
if the input value exceeds \(1000\) and is en even number, and prints that "The input is either
small or is not even!"
otherwise. As before, we’ll start with a pseudocode implementation followed by a live implementation in Python below.def is_large_even(myNumber): if (myNumber is even) and (myNumber > 1000): print("The input is over 1000 and is even!") else: print(The input is either small or is not even!) return None
In Python we simply used the keyword
and
to create the compound conditional statement. The keywords or
and not
can be used similarly in Python. In other languages the syntax is for constructing these compound conditionals is similar, but you may need to use symbols in place of the and
/or
/not
keywords. For example, in C++ we write \amp\amp
to mean "and", use ||
in place of "or", and use !
in place of "not". For example, let’s create a new function which will print "true"
if its input value is large and even or is not a multiple of \(3\text{,}\) but will print "false"
otherwise. We show a version of the function in pseudocode and a working Python implementation below.def is_large_even_or_not_mult3(myNumber): if ((myNumber is even) and (myNumber > 1000)) or (myNumber is not a multiple of 3): print("true") else: print("false") return None
You may notice that, in the working implementation,
myNumber % 3 != 0
was used to test whether the input myNumber
is not divisible by \(3\text{.}\) There are, of course, other ways to encode this test -- perhaps something like (not (myNumber % 3 == 0))
. There are always multiple means to an end; we typically prefer code which is more simple, more readable, and more efficient.
Subsection 3.4.3 Looping with for
and while
Now that we’ve seen conditionals as well as our logical connectors in action, let’s see a slightly more advanced feature of code. We’ll explore loops, which can help us re-run the same set of instructions many times. We’ll make use of two types of loop here because they’ll be helpful to us when we return to develop a base-conversion function which will convert between two arbitraty bases (although we’ll build a function that only works as long as those bases are both at most 10).
We’ll start our discussion of loops with the
for
loop. A for
loop will run through a block of code (instructions) for a set number of iterations. For example, perhaps we would like to double every value in a list. Then we can struct a for
loop which will double each value from an input list. The doubling code will run once for each element in the list and then the loop will terminate. Let’s see such a function.def double_list_values(myList): n = length(myList) doubled_list = new list of length n for i in list(0, 1, ..., n - 1): doubled_list element i = 2*(myList element i) return doubled_list
Please experiment with the code above. Try evaluating
double_list_values()
on lists of a variety of lengths. The for
loop in the function definition adapts automatically to input lists of any length. This makes our function quite flexible. You’ll note that when we evaluated our function this time, we stored the result in a new variable and then explicitly printed it out. This is because values returned by a function are not generally printed out in any language. We need to explicitly ask for the returned value to be printed. See this for yourself by simply running double_list_values([1, 2, 3])
-- while nothing is printed out, the values of the input list are doubled.There are a few additional things to notice here about the Python implementation of the
double_list_values()
function. First, a majority of programming languages are 0-indexed. That means for objects like lists or arrays, the first element occupies index 0, the second occupies index 1, etc. This takes some getting used to, but we’ve adopted the 0-indexing convention here since you are almost certain to encounter it in the future. Next, in the working implementation which uses Python, the code n*[0]
initializes a list of length n
, all of whose entries are 0
. In Python, lists are denoted/defined by square brackets. Finally, in Python we can access the item occupying the i
\(^{\text{th}}\) index of a list by using ListName[i]
. Finally, values returned by a function are not printed out unless we explicitly ask for that behavior. You can see this for yourself by simply running double_list_values([1, 2, 3])
. You’ll notice that other languages have slightly different syntax for working with lists.What if there is no way to determine ahead of time the number of iterations that the code within the loop should be run? For example, what if we would like to run a block of code until a condition is no longer true? For example, what if we would like to double an input value until the result exceeds 100? There’s no convenient/simple way to determine ahead of time the number of iterations the loop should run through before terminating. This is exactly a scenario for a
while
loop. Let’s build the function described above.def my_doubling_function(startingValue): val = startingValue while val <= 100: val = 2*val return val
Notice that
my_doubling_function()
does not include a preset number of iterations over which the val = 2*val
code in the while
loop will be run. At each iteration through the loop, val
is checked to see whether it exceeds \(100\text{.}\) As soon as val
exceeds \(100\text{,}\) the while
loop is terminated. Try running the function with several input different input values. Unfortunately, with a while
loop it is possible to find yourself in a scenario where you function loops infinitely, never returning a value, and eventually crashing. Such is the case here if we try evaluating my_doubling_function()
with a negative input value.Subsection 3.4.4 Putting it all together
In this final, subsection, we’ll provide ourselves an opportunity to build a function which explores the Collatz Conjecture. You may have heard of this colloquially as the "\(3x + 1\)" conjecture. Below is a video from the Veritasium YouTube channel discussing the conjecture.
The conjecture is simple enough to state. Create a sequence by choosing any positive integer starting value you like. Obtain the next element of the sequence by either dividing by \(2\) (if the current sequence element is even), or multiplying the current sequence value by \(3\) and adding \(1\text{.}\) The conjecture claims that no matter your choice of starting value, the resulting sequence will eventually attain a value of \(1\text{,}\) which then results in the sequence repeating \(4, 2, 1\) forever. Nobody, not even the best mathematicians in the world, has been able to prove or disprove this claim. We can use code to create the sequence of values created by this procedure for a given starting value. It will require several of the ideas we’ve introduced here.
def collatz_sequence(startVal): my_sequence = empty list current_val = startVal append startVal to my_sequence while current_val is not 1: if current_val is divisible by 2: current_val = current_val / 2 else: current_val = 3*current_val + 1 append current_val to my_sequence return my_sequence
Try computing the
collatz_sequence()
for a variety of different startValue
s. Do you always end up with a sequence terminating at \(1\text{?}\) Below is another function containing a for
loop which has the ability to call our collatz_sequence()
function many times and plot the resulting sequences.Subsection 3.4.5 Conclusion
That’s it for this section. Hopefully you enjoyed applying our newfound knowledge of logic to code. You saw how the use of
if
, else if
, and else
statements combined with conditions can direct the flow of your code. This is powerful because it allows us to run different instructions depending on the state of a parameter within our code. You also saw how we can use looping to run the same set of instructions over and over within a function. We saw that for
loops can be used when we know the number of iterations the loop must run ahead of time. If a loop should run an unspecified number of iterations and terminate only when a stopping condition is satisfied, then we should use a while
loop. If you decide to complete Project I in the next chapter, you’ll have an opportunity to test out what you’ve learned here.