Mathematics and Computing
Logic And Algorithms
Spring 2018
Comparisons and Decisions
Algorithms are problem-solving techniques via a sequence of steps of the following form:
Each step changes inputs to outputs (statements)
A subsequence of steps may be repeated (loops)
A decision point may lead to one of two different subsequences of steps (branches)
We introduced statements and loops in the previous notebook, so in this notebook we focus on branches (i.e., decisions), which means we need to explore how logic is implemented in Python.
To begin with, a BooleanStatement is a statement which evaluates as either True or False. Typically, a BooleanStatement is produced by a logical comparison.
For example, let's look at some examples of logical comparisons.
Equality is also an important concept, but one which is NOT defined clearly enough in mathematics. Indeed, in Python, there are 3 ways that equality occurs, only 2 of which are logical.
Assignment: Python uses a single equals to bind a name to an object. ASSIGNMENT (single equals) IS NOT LOGICAL.
Equal Value: Python uses == to logically test if two objects have the same value.
Identical Objects: Python uses the word is to test if two objects are identical.
Giving an object a name (mathematically, assigning a value to a variable) does not produce True or False, so it cannot be used to make decisions.
Let's look at examples of all 3.
For example, suppose we want to determine if an integer is either even or odd. A even number has a remainder of 0 if divided by 2, so that
n % 2 is zero if n is even
This is easily implemented as a BooleanStatement.
All logical comparisons are __BooleanStatement__s, but __BooleanStatement__s can be produced in many, many, many other ways.
We can't possible explore all possible ways, so instead, let's focus on the numpy library for arrays and on the logic associated with mathematical structures like matrices.
First, we execute the usual import/plot configuration structures:
The names np and plt are conventional and are used because they are short and easy to remember.
Moreover, both numpy and matplotlib have commands that are BooleanStatements.
Indeed, almost every module includes BooleanStatement commands needed in that module.
__BooleanStatement__s can be used to form compound comparisons using and, not, and or. A compound condition is a logical combination of one or more __BooleanStatement__s A and B according to the following:
A and B evalues to True only if A and B both evaluate to True. Otherwise, evaluates False.
not A evaluates to True if A evaluates to False, and vice versa.
A or B evaluates to True if either of A or B are True, but is False if both A and B evaluate False.
Let's look at some examples.
Indeed, compound statements are potentially quite long and complicated.
Decision Making in Algorithms
In Python, the simplest form for a branching structure (i.e., a decision structure) is
The statements in the if structure are executed only if the BooleanStatement evaluates to True.
Decision structures (if statements) allow us to implement algorithms in which the sequence of steps depends on the initial input.
For example, the algorithm for Making Change is straightforward. A customer pays at least the price of a product to a cashier, who subsequently __return__s the difference in the form of dollars, quarters, dimes, nickels, and pennies.
The logic for this algorithm is implemented as a series of if statements in the next cell. Try changing the price and the amount paid to see how the logic varies according to the initial inputs.
An else structure can be added for the case where a BooleanStatement is False:
Let's look at an example.
We can also include an unlimited number of elif structures, which test a new BooleanStatement1 for the case where the original BooleanStatement is False:
Let's look at an example.
Typically, these decision structures are combined with loops and other structures in implementing algorithms.
For example, the loop below tests if a given integer x is prime.
Moreover, we can loop over any list or tuple or any other iterable -- such as a numpy array.
In this case, we can use logic and decisions to count or otherwise analyze what is in a list or numpy array.
For example, if create a long numpy array of random integers -- using the randint command -- then we can loop over that array and use a decision statement to count the number of occurences of a given pattern.
Of course, this is a simple example. But how about the question like "how many times is the current roll equal to the previous roll"?
Indeed, many interesting mathematical questions can be answered in much the same way.
Logic with Lists and Dictionaries
Python allows the use of logic and decisions in many useful ways and in many useful contexts. For example, list comprehension is the creation of a list using the structure
Let's look at an example.
Indeed, suppose we wanted to create a numpy array of numbers between 1 and 100 that do not have a factor of 7. This turns out to be quite easy using list comprehension inside of the array command in numpy.
Finally, there is a very useful structure for writing algorithms called a dictionary, where a dictionary is a set of key, value pairs.
We can create dictionaries in many ways in Python, but the method we use most often is as a set of key-value pairs:
The dict command can also be used to create dictionaries.
Because a dictionary is a set of key-value pairs, the order in which they are stored is not preserved (i.e., Python decides).
However, we can access values using keys.
Once a dictionary is created, we can access its keys with the keys command; and we can get its values using the values command.
If we loop over a dictionary, then we are getting the keys one at a time:
Dictionaries can be very useful when logic is involved when designing algorithms.
For example, let's consider how a dictionary can greatly "clean up" the code for the Making Change example, which we repeat below:
Notice that we are repeating the same "if" statement several times, each time for a different type of coin (assuming dollar coins!). So first off, let's create a coin dictionary whose keys are the coin names and whose values are the coin values.
Once we need to make change, we can simply loop over a list of coin names in the order they need to be considered.
Assignment 3
In the exercises below, are the last 4 __ nonzero __ digits of your student number in nondescending order ( ). Also, EnumberLast3NonzeroDigits is the last 3 nonzero digits of your Enumber. For example, if your enumber was E12375609, then and EnumberLast3NonzeroDigits is 569.
1. (0.25 points) Execute the following and describe in the Raw NBconvert cell what list it produced.
The output does describes a list of eleven positive integers all in order with a range between [6 and 16] starting at 16 and ending at 6.
2. (0.25 points) Execute the following and describe in the Raw NBconvert cell what list it produced.
A list of four positive integers which represent the squared product of four odd numbers [13,11,9,7]
3. (0.5 points) Determine if the following BooleanStatement is True or False for your values of a,b,c,d.
4. (0.5 point) Use list comprehension to create the list of numbers between a and c*d which are not divisible by b.
5. (0.5 points) Create a dictionary in which the keys are the first names of 5 people you know and the values are their ages, respectively.
6. (1 point) The next cell generates 10,000 digits (numbers = 0,1,2,3,4,5,6,7,8,9). Count the number of times that a digit is between a and b+1 (including a and excluding b+1).
7. (1 point) Write an algorithm in Python which determines the largest factor of a number x and apply it to EnumberLast3NonzeroDigits
(Hint: suggest you go backwards -- i.e., loop over a descending list such as in #1 and #2; n is a factor of x if x divided by n has a remainder of 0 -- and if n is not equal to x )