Module 1.1 - Conditionals

Objectives

By the end of this module you will be able to:

  • Evaluate Boolean expressions.
  • Construct Boolean expressions from English descriptions.
  • Mentally execute (trace) code with conditionals: if, if-else, and if-elif-else statements.
  • Write and debug code with conditionals.
  • Write and debug code with conditionals inside loops.
  • Identify new syntactic elements related to the above.

1.1.0 Review

In Module 0.4, we learned how to use if, elif, and else statements to conditionally execute code based on the values of different variables. Review that module now - what we learn here will build on it.

Remember:

  • An if statement evaluates an expression, and if that expression is true, the indented code block after it will run.
  • If the if statement is not true, any elif statements will be evaluated in order.
    • If an elif statement is true, the indented code block after it will run.
  • If an elif statement is not true, the next elif (if there is one) will be evaluated.
  • Finally, if no if or elif statements are true, if there is an else statement, its indented code block will run.

There must be an if, there may be any number (including 0) elif statements, and there may be one or zero else statements.

Exercise 1.1.1

Exercise 1.1.1
y = 12

if y > 10:
  print('y has two or more digits')
else:
  print('y has less than two digits')

In digit_count.py, modify the above program so that it works if y is a negative integer.

1.1.3    Nested Conditionals

Consider this program:

a = 3
b = 4
c = 5

if a < b:
    if a < c:
        print('a is the smallest')
    else:
        print('a is not the smallest')

print('Done')
a is the smallest
Done

This is an example of a nested conditional (nested if).

Examine the indented structure:

many elifs

The execution pathway, illustrated:

many elifs

The execution pathway, described:

  • The outer if statement on Line 5 is true, so the outer if body begins executing on Line 6
  • The inner if statement on Line 6 is true, so the inner if body on Line 7 executes
  • The else on Line 8 does not result in execution because the associated if (on Line 6) was true, so Line 9 is skipped
  • After the inner if statement, the program is still inside the body of the outer if statement, but there is no more code, so the program exits the conditional block and executes Line 14.

Consider this variation:

a = 3
b = 4
c = 5

if a < b:
    if a < c:
        print('a is the smallest')
    else:
        print('a is not the smallest')
    print('We know a is less than b')
else:
    print('We know a is not less than b')

print('Done')
a is the smallest
We know a is less than b
Done

Exercise 1.1.7

Exercise 1.1.7

Type up the above in nestedif.py. Draw (or desribe with line numbers) the flow of execution for the following three cases:

  1. a=3, b=4, c=5
  2. a=3, b=4, c=2
  3. a=6, b=4, c=5

Exercise 1.1.8

Exercise 1.1.8

In smallest_of_three.py, modify the above program so that it prints out, appropriately, one of a is the smallest, b is the smallest or c is the smallest, depending on the actual values of a, b, and c. Try different values of these variables to make sure your program is working correctly.

A numeric variable can be: strictly less, less than or equal to, strictly greater, greater than or equal to, or equal to another variable. Accordingly, the different types of less/greater comparisons are:

a < b      # Strictly less than, as when a=3, b=4
a <= b     # Could be less (a=3, b=5), could be equal (a=3, b=3) 
a > b      # Strictly greater (a=3, b=2)
a >= b     # Could be greater (a=3, b=1), could be equal (a=2, b=2)

1.1.4    Combining Conditions

Consider this program:

x = 5
y = 5
z = 5

if x == y and y == z:
    print('All three are equal')
  • The first thing to point out is the == operator: Because we’ve been using the = (single equals) operator for assigning values to variables, we need something else to test for equality.
  • The equality operator in Python is == as in: if x == y:
  • The difference between = and == is very important to remember. It’s easy to confuse the two.
  • The if statement combines two conditions: if x == y and y == z:
  • The combining occurs with the Boolean operator and
  • We can clarify the parts and combination thereof using parentheses:
if (x == y) and (y == z):

The two parts are often called clauses:

  • First clause: (x == y)
  • Second clause: (y == z)
  • You could have many more clauses.

The and operator works like this:

both clauses must be true

Boolean is pronounced “BOO lee unn”. - A Boolean operator takes expressions and computes to either true or false.


Let’s go back to finding the smallest of three numbers using conditionals:

a = 3
b = 4
c = 5

# Fill in code here ...

Exercise 1.1.9

Exercise 1.1.9

In smallest_of_three2.py, fill in code to identify which of the three variables has the smallest value, depending on the actual values of a, b, and c. Use if statements with multiple clauses. Try different values of these variables to make sure your program is working correctly.

Your printed output should take the form: x is smallest (where x is replaced by a,b, or c, depending on their values)

As the counterpart to the and operator, there is the or operator:

a = -1

if (a <= 0) or (a >= 1):
    print('a is not between 0 and 1')

Exercise 1.1.10

Exercise 1.1.10

Type up the above in boolean2.py. Then try a = 0.5.

We have shown how to write “less than or equal to” using <= . So, now we can add “equals” and “not equals” to the numeric comparisons:

a < b      # Strictly less than
a <= b     # Less than or equal to
a > b      # Strictly greater than
a >= b     # Greater than or equal to
a == b     # Exactly equal
a != b     # Not equal

For the or operator to evaluate to true, at least one of the two expressions must be true. Both being true satisfies this as wel.

Consider:

a = 3
b = 4
if (a < 10) or (b < 10):
    print('At least one of a or b is less than 10')
  • In this case both will evaluate to true, and so the print statement executes.
  • Suppose we made a=3, b=11, the print statement will execute.
  • Suppose we change a=11, b=3, the print statement will execute. But if a=11, b=12, the or fails (both clauses are false), and the print won’t execute. Incidentally, let’s replace or with and in the above case, and see what we get:
a = 3
b = 4
if (a < 10) and (b < 10):
    print('Both of them are less than 10')

In this case, both sub-conditions are satisfied, and so the whole if condition is satisfied, which means the print will execute. But if we change the value of b:

a = 3
b = 11 # we changed the value for b
if (a < 10) and (b < 10):
    print('Both of them are less than 10')

In this case, the second comparison fails, and the print won’t occur. Whereas if we change the and to an or:

a = 3
b = 11
if (a < 10) or (b < 10): # we changed the and to an or
    print('One or both of them is less than 10') 

Here, it’s enough that a is less than 10, and so the print executes even though “b less than 10” fails.

Next, let’s look at the “not equals” operator, written != :

x = 5
y = 6
z = 7

if (x != y) and (x != z):
    print('x is not equal to y and not equal to z')

Exercise 1.1.11

Exercise 1.1.11

Type up the above in boolean3.py, then change z to be 6 (the same as y). What do you observe?

One can combine any number of and statements. For example:

x = 5
y = 6
z = 7

if (x != y) and (x != z) and (y != z):
    print('x, y, z are all different')

We should read != as “not equals” just as we read == as “equals”.


There is another operator called not, which applies to Boolean expressions. One can apply the not operator to groups of clauses using additional parentheses:

x = 8

if not ( (x == 5) or (x == 6) ):
    print('x is neither 5 nor 6')

Here, not reverses the True or False value of whatever it applies to:

  • not True is False
  • not False is True
  • If a is equal to 5, a > 4 is True, so not (a > 4) is False

Consider the expression if not ((x == 5) or (x == 6)):

  • In this case, x is 8. So, neither (x == 5) nor (x == 6) evaluates to True.
  • Therefore, the whole expression ( (x == 5) or (x ==6) ) is False.
  • This causes not ( (x == 5) or (x ==6) ) to evaluate to True.
  • Therefore, the print executes.

Most logical expressions can be evaluated in more than one way. For instance, a > 4 is the same as not (a <= 4). Similarly, a != 5 is the same as not (a = 5).

This means there is often no single “correct” way to represent a logical expression. Take care, and make good use of parentheses: logical expressions should be both:

  • Correct, such that the logic for the computer is what you, the programmer, intended.
  • Readable, such that other humans can read and understand the expression (and so you can read your own code when you revisit it in the future).

Exercise 1.1.12

Exercise 1.1.12

Suppose integer variables a,b,c,d,e have values a=1, b=1, c=3, d=4, e=5. Consider the following three expressions:

( (a <= b) and (c+d > e) and (d > 1) )

( (a > c) or ( (c+1 < e) and (c-b > a) ) )

not ( (b == d-c) and (a > b) or (c < d) )

Try to evaluate each expression by hand. Then, in boolean4.py, write up each of these in an if statement to see if the result matches with your hand-evaluation.

Exercise 1.1.13

Exercise 1.1.13

In boolean5.py, write a program that begins with

a = -3
b = -4

and uses conditionals to print out the absolute difference between the two numbers. In the above case, the difference is 1. In the case of a=3, b=4, the difference is also 1. When a=-3, b=4, the difference is 7.

1.1.5    Conditionals and Loops

Let’s write a program to loop through integers and print only the even numbers:

n = 10
for i in range(1, n+1):
    if i % 2 == 0:
        print(i, 'is even')

Exercise 1.1.14

Exercise 1.1.14

In oddeven.py, modify the above program so that for every number between n and 1, going backwards, the program prints whether it’s even or odd, as in:

10 is even
9 is odd
8 is even
7 is odd
6 is even
5 is odd
4 is even
3 is odd
2 is even
1 is odd

1.1.6    Conditionals and Lists

Suppose we have a list of numbers, representing daily profits (sometimes negative, sometimes positive) and we only want to add up the positive numbers:

earnings = [-5, 2, 3, -9, 12, 4, -30]
total = 0
for k in earnings:
    if k >= 0:
        total += k

print('Total profit =', total)

Trace through the values of total and k.

Exercise 1.1.15

Exercise 1.1.15

Write up the above as total_profit.py and verify the trace.

Exercise 1.1.16

Exercise 1.1.16

Given a list like

A = [-5, 2, 4, -9, 12, 13, -30, -21, -20]

we see that 12,13 and -21,-20 are pairs of consecutive numbers. Write a program called consecutive.py, with loop and a conditional to identify such consecutive pairs and print them. For the above list, the output should be:

Consecutive pair found: 12 13
Consecutive pair found: -21 -20

Next, let’s write a program that asks the user to enter a number that we then check is in a list of numbers:

# The list of numbers:
A = [-5, 2, 4, -9, 12, 13, -30]

# Receive what the user types in (as a string):
user_str = input('Enter an integer: ')

# Convert string to integer:
k = int(user_str)

# Check whether in the list:
if k in A:
    print(k,'is in the list')
else:
    print(k,'is not in the list')

Note: The in operator checks if a value is in a list:

  • k in A is True if k is one of the values in the list A
  • Therefore if k in A: runs if k is in A

Exercise 1.1.17

Exercise 1.1.17

Suppose you are given two lists:

A = [-5, 2, 4, -9, 12, 13, -30, -21, -20]
B = [2, -9, 11, 16, 13]

Notice that some elements of A (like 2) also exist in B. In twolist.py, use the list membership idea to print those elements of A that are also in B. For the above example, the output should be:

2 in A also found in B
-9 in A also found in B
13 in A also found in B

1.1.7    More Examples With Lists

Consider the following program that aims to find duplicates in a list:

A = [2, 9, 2, 6, 4, 3, 3, 2]
for k in A:
    if k in A:
        print('Duplicate found:', k)

Be careful! The in keyword behaves differently in different contexts:

  • With for k in A:, the in keyword tells Python to loop through the different members of A
  • With if k in A, the in keyword checks if values are members of A

The key distinction is that the for loop declaration changes how in behaves. The in operator checks for membership, except when it is used in a for loop.

In the list, we can see that 2 occurs three times, and 3 occurs twice. Both should be listed as duplicates. Is this the case?

Exercise 1.1.18

Exercise 1.1.18

Trace through the iterations in the above program and explain why the above does not work.

Now consider this variation:

A = [2, 9, 2, 6, 4, 3, 3, 2]
for i in range(len(A)-1):
    for j in range(i+1, len(A)):
        if A[i] == A[j]:
            print('Duplicate found:', A[i])

Exercise 1.1.19

Exercise 1.1.19

Trace through the iterations in the above program and explain the output. Why does the inner loop start with i+1?

1.1.8    Some Stats via Programming

Let’s now apply our practice with conditionals to solve some problems in probability and statistics.

For example: Suppose I toss a coin 4 times and observe the face that’s up. What is the probability that I get all “tails” (no toss shows “heads”).

Let’s do this in steps.

First, let’s write a program to toss a coin 4 times

import random

coin = ['heads', 'tails']
for i in range(4):
    toss = random.choice(coin)
    print(toss)

Exercise 1.1.20

Exercise 1.1.20

Type up the above in cointosses.py and run it a few times to see what you get.

Note: We have made a list of strings:

coin = ['heads', 'tails']

Python has a useful way to randomly select a member of a list:

toss = random.choice(coin)

Alternatively, we could have written:

toss = random.choice(['heads','tails'])

and avoided defining coin.

Next, instead of printing the results, let’s count the number of heads observed:

import random

count = 0
for i in range(4):
    toss = random.choice(['heads', 'tails'])
    if toss == 'heads':
        count = count + 1

print('Number of heads', count)

Exercise 1.1.21

Exercise 1.1.21

Type up the above in cointosses2.py and run it a few times to see what you get.

Observe how the string that’s randomly selected from the list is compared against ‘heads’:

if toss == 'heads':
    count = count + 1      

Next, what we need to do is repeat the 4-coin toss many times: - Suppose we call each set of 4 coin tosses a single trial . - If we ran a single trial and obtained 1 heads and 3 tails (count=1), could we make a conclusion about the probability of getting 4 tails? No. - What we need to do is run a large number of trials and record in how many trials we get a run of 4 tails. We’ll use the term “success” to identify a trial in which we get all 4tails. Let’s examine the code:

import random

trials = 10
successes = 0
for i in range(trials):
    # Count number of heads in 4 tosses:
    count = 0
    for i in range(4):
        toss = random.choice(['heads', 'tails'])
        if toss == 'heads':
            count = count + 1

    # If the count is zero, that's a success
    if count == 0:
        successes += 1

# Ratio of successes to trials:
probability = successes / trials
print('probability =', probability)

Exercise 1.1.22

Exercise 1.1.22

Type up the above in cointosses3.py and run it a few times to see what you get. Then increase the number of trials to 100000 and see. The theoretical answer is 0.0625 (approximately 6% chance there’s no heads in 4 tosses).

The closed-form solution treats each coin toss as independent, with a probability of giving tails equal to \(\frac{1}{2}\).

The probability of four tails is given by multiplying the independent probabilities of each toss: \(\frac{1}{2} \cdot \frac{1}{2} \cdot \frac{1}{2} \cdot \frac{1}{2} = \frac{1}{2^4} = \frac{1}{16}\)

This must be multiplied by the number of ways to get four tails (there is only one way): \(\frac{1}{16} \cdot 1 = \frac{1}{16} = 0.0625\)

Exercise 1.1.23

Exercise 1.1.23

In cointosses4.py, write a program to run 100000 trials of the following experiment: toss a coin 10 times and record a success if you get an equal number of heads and tails.

Start with the following code:

import random

trials = 10000
successes = 0
for i in range(trials):
    # Your code goes here

# Ratio of successes to trials:
probability = successes / trials
print('probability =', probability)

Now let’s solve a problem with another random process: dice.

We’ll roll two 6-sided dice and add the numbers face up. We want to ask: what is the probability that we get 7 (when the faces are added)?

Here’s the program:

import random

possible_outcomes = [1,2,3,4,5,6]
trials = 100000
successes = 0
for i in range(trials):
    roll1 = random.choice(possible_outcomes)
    roll2 = random.choice(possible_outcomes)
    if roll1 + roll2 == 7:
        successes += 1

probability = successes / trials
print('probability =', probability)

Exercise 1.1.24

Exercise 1.1.24

Type up the above in dice.py to see the result. Is there a sum of rolls that yields a higher probability than seven?

Let’s also look at how one can get Python to randomly generate real numbers:

import random

trials = 50
total = 0

for i in range(trials):
    x = random.uniform(5, 10)
    print(x)
    total += x

print('mean =', total/trials)

Note: By using x = random. uniform(5, 10) we can generate a random real number between 5 and 10, equally likely to take on any value between 5 and 10.

  • In the above program we are generating many such numbers and calculating their average.

Exercise 1.1.25

Exercise 1.1.25

Type up the above in uniform.py, then increase the number of trials until the average “stabilizes” such that the average does not change by more than a small value, say 0.01, if you increase the trials further.

You can use programming to explore ideas in probability and statistics, and solve real problems as well. - These methods can be used to simulate a variety of real-world problems to get insight on how they work.

1.1.9    Algorithmic Art

Let’s now use what we’ve learned to explore the notion of how computers can be programmed to generate abstract art.

In our first example, we’ll draw lines from one border of a square to another:

both clauses must be true

Let’s describe the main idea via some pseudocode:

In a loop we’ll generate:

Set up an initial x1,y1 and x2,y2
  for i in ...
      Pick a random color
      Draw a line from x1,y1, y2,y2
      Make the current endpoint the start of the next line:
      x1 = x2
      y1 = y2
      Pick a random border
      Now pick a new random point on the next border
  • When we pick a border, we’ll need to figure out the coordinates.

Exercise 1.1.26

Exercise 1.1.26

Download conditional_art.py and drawtool.py.

Try it out and then examine the code to confirm that it follows the pseudocode. Try different values of n. Can you change the choices of randomly chosen colors to improve the result? Does it look good with just two colors?

Contrast this method with modern neural network-based art generation tools, such as DALL-E, Stable Diffusion, or Midjourney. If you cannot access one of these, feel free to use examples from a recent article in the press.

Modern art generators like the ones listed above will generate images from text that you provide. It is possible that these generated images contain disturbing content. Choose your text prompts with care.

Are there any advantages to using a procedural method, such as the one we have written in Python, over a neural network-based probabalistic method such as those used in DALL-E, Stable Diffusion, or Midjourney?

Next, we’ll explore changes to generative art:

  • Think of random “noise” at one end of a spectrum. And highly-structured geometry at the other end generated by a procedural algorithm.
  • The question: can we adjust a “knob” that let’s us generate a mix? And is that more aesthetic?

Exercise 1.1.27

Exercise 1.1.27

Download conditional_art2.py. Try out different values (between 0 and 1) of the structure parameter (on line 19 of conditional_art2.py), for example 0.9 and 1.1.

Is there a value that provides a mix you prefer? Try different values of n.

1.1.10    When Things Go Wrong

In each of the exercises below, first try to identify the error just by reading. Then type up the program to confirm, and after that, fix the error.

Exercise 1.1.28

Exercise 1.1.28
x = 5
y = 6
if x < y
    print('x is less than y')

Identify and fix the error in error1.py .

Exercise 1.1.29

Exercise 1.1.29
x = 5
y = 6
if x !< y:
    print('x is not less than y')

Identify and fix the error in error2.py .

Exercise 1.1.30

Exercise 1.1.30
a = 2.5

# See whether a lies between 0 to 1, or 1 to 2, 
# or something else
if (a > 0) and (a < 1):
    print('between 0 and 1')
else if (a > 1) and (a < 2):
    print('between 1 and 2')
else:
    print('something else')

Identify and fix the error in error4.py .

End-Of-Module Problems

Full credit is 100 pts. There is no extra credit.

Problem 1.1.1 (50 pts)

Problem 1.1.1 (50 pts)

Write a function consecutive_finder that takes as input a list of integers and returns:

  • Integer 1 if the list contains a consecutive pair of integers
  • Integer 0 otherwise.

Examples:

  • consecutive_finder([0, 2, 4, 6]) would return 0.
  • consecutive_finder([2, 4, 6, 8, 9]) would return 1.
  • consecutive_finder([4, 3, 7]) would return 0.

Submit as consecutive_finder.py

Problem 1.1.2 (50 pts)

Problem 1.1.2 (50 pts)

Write a function subset_selector that takes as input a list of integers and returns a new list, consisting of every member of the original list that satisfies all of:

  • Is greater than -100
  • Is less than 1000
  • Is evenly divisible by three (if non-negative)
  • Is even (if negative)

For this problem, 0 is not divisible by three.

Examples:

  • subset_selector([1, 2, 3, 4]) returns [3].
  • subset_selector([-5, -600, 0, -8, 25, 27, 2700]) returns [-8, 27].
  • subset_selector([-10, 10, 11, -11, 12, 13]) returns [-10, 12].

Submit as subset_selector.py.

Problem 1.1.3 (50 pts)

Problem 1.1.3 (50 pts)

Write a function fizzbuzz that takes as input any positive integer and returns:

  • The integer, if the integer is not evenly divisible by 5 or by 3
  • String 'fizz' if the integer is evenly divisible by 3 and not 5
  • String 'buzz' if the integer is evenly divisible by 5 and not 3
  • String 'fizz buzz' if the integer is evenly divisible by 15

Example output:

for k in range(1,20):
    print(fizzbuzz(k), end= " ")

Should print: 1 2 fizz 4 buzz fizz 7 8 fizz buzz 11 fizz 13 14 fizz buzz 16 17 fizz 19

Submit as fizzbuzz_func.py.