Skip to main content

Discrete Mathematics for Computing (Draft)

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 defines 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 returning 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 startValues. 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.