# CS61A Lab 2: Control Flow

### Warm Up: What would Python print?

Predict what Python will print in response to each of these expressions. Then try it and make sure your answer was correct, or if not, that you understand why!

```# Q1
a = 1
b = a + 1
a + b + a * b
______________

# Q2
a == b
______________

# Q3
z, y = 1, 2
print(z)
______________

# Q4
def square(x):
print(x * x)        # Hit enter twice
a = square(b)
______________

# Q5
print(a)
______________

# Q6
def square(y):
return y * y        # Hit enter twice
a = square(b)
print(a)
_______________
```

### Boolean operators

1. What would Python print? Try to figure it out before you type it into the interpreter!

```# Q1
a, b = 10, 6
a > b and a == 0
_______________

# Q2
a > b or a == 0
_______________

# Q3
not a > 0
_______________

# Q4
a != 0
_______________

# Q5
True and False
_______________

# Q6
True and True
_______________

# Q7
True or False
_______________

# Q8
False or False
_______________

# Q9
True and True or True and False
_______________
```

Boolean order of operations: just like with mathematical operators, boolean operators (and, or, and not) have an order of operations, too:

```# highest priority
not
and
or
# lowest priority
```

For example, the following expression will evaluate to True:

```True and not False or not True and False
```

It might be easier to rewrite the expression like this:

```(True and (not False)) or ((not True) and False)
```

If you find writing parentheses to be clearer, it is perfectly acceptable to do so in your code.

Short-circuit operators: in Python, and and or are examples of short-circuit operators. Consider the following line of code:

```10 > 3 or 1 / 0 != 1
```

Generally, operands are evaluated from left to right in Python. The expression 10 > 3 will be evaluated first, then 1 / 0 != 1 will be evaluated. The problem is, evaluating 1 / 0 will cause Python to raise an error! (You can try dividing by 0 in the interpreter)

However, the original line of code will not cause any errors -- in fact, it will evaluate to True. This is made possible due to short-circuiting, which works in the following ways:

• and will evaluate to True only if all the operands are True. For multiple and statements, Python will go left to right until it runs into the first False value -- then it will just immediately evaluate to False.
• or will evaluate to True if at least one of the operands is True. For multiple or statements, Python will go left to right until it runs into the first True value -- then it will immediately evaluate to True.

2. What happens when you type in the following expressions into the interpreter?

```>>> True and False and 1 / 0 == 1

>>> True and 1 / 0 == 1 and False

>>> False or 1 / 0 == 1 or True

>>> True or 1 / 0 == 1

>>> 1 and 2 and 3

>>> 1 and 2 and 0 and 5

>>> 0 or 3 or False

>>> 1 and 2 or 0 and 1 / 0

>>> 2 and (4 - 2 * 2) or 0 and 1 / 0

```

Short-circuiting allows you to write boolean expressions while avoiding errors. Using division by zero as an example:

```x != 0 and 3 / x > 3
```

In the line above, the first operand is used to guard against a ZeroDivisionError that could be caused by the second operand.

### if statements

3. What would Python print?

```a, b = 10, 6

# Q1
if a==b:
a
else:
b
_______________

# Q2
if a == 4:
6
elif b >= 4:
6 + 7 + a
else:
25
________________

# Q3
# ';' lets you type multiple commands on one line
if b != a: a; b
_________________
```

The following are some common mistakes when using if statements:

1. Using '=' instead of '==': remember, = (single equals) is used for assignment, while == (double equals) is used for comparison.

```# bad
if a = b:
print("uh oh!")

# good!
if a == b:
print("yay!")
```

2. Multiple comparisons: for example, trying to check if both x and y are greater than 0.

```# bad
if x and y > 0:
print("uh oh!")

# good!
if x > 0 and y > 0:
print("yay!")
```

3. Redundancy: this is more of a style issue, rather than a practical error

```# bad
result = (x % 2 == 0)
if result == True:    # redundant
print("even")

# good
result = (x % 2 == 0)
if result:
print("even")
```

In the above snippet of code, result is True, so the if statement would really be saying

```if True == True:
```

which is redundant.

Guarded commands

Consider the following function:

```def abs(x):
if x >= 0:
return x
else:
return -x
```

It is syntactically correct to rewrite abs in the following way:

```def abs(x):
if x >= 0:
return x
return -x       # missing else statement!
```

This is possible as a direct consequence of how return works -- when Python sees a return statement, it will immediately terminate the function. In the above example, if x >= 0, Python will never reach the final line. Try to convince yourself that this is indeed the case before moving on.

Keep in mind that guarded commands only work if the function is terminated! For example, the following function will always print "less than zero", because the function is not terminated in the body of the if suite:

```def foo(x):
if x > 0:
print("greater than zero")
print("less than zero")

>>> foo(-3)
less than zero
>>> foo(4)
greater than zero
less than zero
```

In general, using guarded commands will make your code more concise -- however, if you find that it makes your code harder to read, by all means use an else statement.

### while loops

4. What would Python print?

```n = 2
def exp_decay(n):
if n % 2 != 0:
return
while n > 0:
print(n)
n = n // 2 # See exercise 3 for an explanation of what '//' stands for
exp_decay(1024)
__________________
exp_decay(5)
__________________

```

5. Before we write our next function, let's look at the idea of floor division (rounds down to the nearest integer) versus true division (decimal division).

True Division Floor Division
>>> 1 / 4 >>> 1 // 4
0.25 0
>>> 4 / 2 >>> 4 // 2
2.0 2
>>> 5 / 3 >>> 5 // 3
1.666666666667 1

Thus, if we have an operator "%" that gives us the remainder of dividing two numbers, we can see that the following rule applies:

```b * (a // b) + (a % b) = a
```

Now, define a procedure factors(n) which takes in a number, n, and prints out all of the numbers that divide n evenly. For example, a call with n=20 should result as follows (order doesn’t matter):

```>>> factors(20)
20
10
5
4
2
1
```

Helpful Tip: You can use the % to find if something divides evenly into a number. % gives you a remainder, as follows:

```>>> 10 % 5
0
>>> 10 % 4
2
>>> 10 % 7
3
>>> 10 % 2
0
```

### Predicates

A predicate is a function that checks if a given input x satisfies a certain condition. For example, the following function is_even(x) checks if a given number x is even.

```def is_even(x):
if x % 2 == 0:
return True
else:
return False
```

The if statement checks if x is divisible by 2 -- if it is, the predicate returns True.

The current is_even function is syntactically valid, but it exhibits poor style (i.e. it's ugly). The following is a more concise way of writing is_even(x):

```def is_even(x):
return x % 2 == 0
```

Try to understand why these two versions do exactly the same thing. The expression x % 2 == 0 evaluates to either True or False. Think of it this way:

```def is_even(x):
result = (x % 2 == 0)
if result == True:    # this is also bad style, but gets the point across
return result
else:                 # if result is False
return result
```

No matter what happens, we are always going to return result!

```def is_even(x):
result = (x % 2 == 0) # parenthesis used for clarification, not necessary
return result
```

Since result is equivalent to x % 2 == 0, we can just return that expression

```def is_even(x):
return x % 2 == 0
```

6. Define a predicate function is_ascending, which takes an argument n and checks if digits are in ascending order, from right to left. Read the doctests for examples:

```def is_ascending(n):
"""Returns True if the digits of n are in ascending order
from right to left.

>>> is_ascending(321)
True
>>> is_ascending(5553220)
True
>>> is_ascending(4352)
False
>>> is_ascending(3)
True
>>> is_ascending(12345)
False
"""