## Week 3: Boolean types and branching structures

### More String Operations

You’ve seen several instances of strings already this semester, and you’ve likely used string concatenation to build up a string. There are many other useful string operations. Here are some highlights:

1. length. To get the length of a string, use the `len` command e.g. `len("cs21") = 4`

2. indexing. Access a single character in a string using its position indexing from zero. For example, if `name="Punxsutawney"`, then `name[1]` is the string value `"u"`.

3. concatenation. Concatenate with the `+` operator. `"hello" + "world"` produces `"helloworld"`

### Accumulator Pattern - Strings

Just as we saw with numbers, you can use a `for` loop and the accumulator pattern to build strings. To start with an empty string, initialize a variable with two quote marks that have nothing inside them (not even a space):

``````result = ""
orig = "hello"

for ch in orig:
result = result + ch

print (result)``````

### Boolean Logic and Relational Operators

Our programs in the first week were entirely sequential. Each statement was processed immediately after the preceding line. In week two, we added the `for` loop to allow us to repeat a task a fixed number of times. This week we will introduce a new type, the Boolean type and show how to use it with branching or decision structures to optionally run code based on various conditions. Booleans and conditionals represent another computational tool we will use throughout the semester to design algorithms for problems.

The Boolean or `bool` type can only hold two possible values: `True` or `False`. Note in Python, both of these values begin with an upper case letter and the values do not have quotes around them. The value `"True"` (with quotes) is a string, not a Boolean.

One way to generate a Boolean value is to use one of the relational operators listed below. For example, the operator `<` compares two variables or expressions `left < right`. If the value of `left` is smaller than `right`, the expression ```left < right``` evaluates to `True`, otherwise, the answer is `False`.

Python’s relational operators are:

Operator Meaning

<

less than

<=

less than or equal to

>

greater than

>=

greater than or equal to

==

equal to

!=

not equal to

Note that to check if two expressions are equal, you must use the `==`, e.g., `x == 7`. Using `x = 7` in Python has a different semantic meaning — it performs a variable assignment and stores the value of 7 in the container labeled x.

#### Exercise: practice relational operators

What are the `bool` values that result from the following expressions? Assume `x = 10`. First, try to predict the value, then you can check your answers in an interactive Python shell by typing `python3` in the terminal.

``````x < 10
x >= 10
x != 15
(x + 15) <= 20
(x % 2) == 1``````

Note: `%` is the mod or remainder operator. `x % y` returns the remainder when `x` is divided by `y` using integer division.

### Branching with if

Programmers use branching, or conditional statements, to run different code based on the state of the program. The simplest form of branching is an `if` statement:

``````if <condition>:
<body>``````

Here, `<condition>` should be a statement that evaluates to a Boolean value. The code inside the `<body>` only runs if the condition is `True`. Here’s an example program that warns you only if the temperature is below freezing:

``````def main():
temp = int(input("Enter temperature: "))
if temp < 32:
print("Freezing temperatures; be sure to wear a coat!")
print("Have a great day!")

main()``````

Note the use of the `:` as we saw at the end of `for` loops and the `main()` function. Like those constructs, the `<body>` of an `if` must be indented to indicate that it should execute together as part of the `if` statement.

### Other Branching Structures

In addition to the basic `if` statement, Python supports two additional variants: `if/else` and `if/elif/else`. The general form of the `if/else` is:

``````if <condition>:
<body>
else:
<else-body>``````

Again, if the `<condition>` evaluates to `True`, Python executes the code in `<body>`. However, if the condition is `False`, Python executes the code in `<else-body>` instead. Regardless of the value of `<condition>`, exactly one of `<body>` or `<else-body>` will run, but not both. It is possible to have an `if` with no `else`, but any `else` must be paired with a matching `if` statement.

We could modify the program above to print a different message if `temp` is above freezing. Regardless of the `temp` value, the program will always print `Have a great day!` since this message is printed outside the body of either the `if` or the `else` as noted by the indentation.

``````def main():
if temp < 32:
print("Freezing temperatures; be sure to wear a coat!")
else:
print("Enjoy the Fall weather")
print("Have a great day!")

main()``````

The final, most complex branching variant is the `if/elif/else`:

``````if <cond-1>:
<body-1>
elif <cond-2>:
<body-2>
elif <cond-3>:
<body-3>
...
else:
<else-body>``````

All of these statements work together as one large decision block. Python will first evaluate `<cond-1>` and if it’s `True`, it will execute `<body-1>` then skip over the remaining bodies in the block. If `<cond-1>` is `False`, Python will next evaluate `<cond-2>`. If that is `True`, it will execute `<body-2>` and then skip over all the remaining bodies in the block. We can continue to add more `elif` conditions and bodies, but each condition will only be evaluated if all the other previous conditions were `False`. Finally if all the condition checks evaluate to `False`, Python executes the `<else-body>`, if there is one. You can have an `if/elif/elif/…​` with no final `else`.

In summary, a decision block has a mandatory `if <condition>:` at the beginning, and optional `else:` at the end, and zero or more `elif <condition-k>:` statements in the middle.

### Logical Operators

In many programs, it’s convenient to ask compound questions or require multiple conditions be `True` before executing some code. In these cases, we can join to questions together using a logical operator:

Operator Meaning

and

both Boolean expressions must be true

or

at least one of the two Boolean expressions must be true

not

negates the Boolean value

Below is a truth table, where `x` and `y` represent Boolean values or expressions. For example, `x` could be `age >= 18` and `y` could be ```status == "Yes"```. Each row should be read as follows: for the given Boolean values of `x` and `y`, what is the result of `x and y`, `x or y`, and `not x`:

x y x and y x or y not x

True

True

True

True

False

True

False

False

True

False

False

True

False

True

True

False

False

False

False

True

### De Morgan’s Laws

If you want to check if a Boolean expression `C` is false, you can use `not C` to evaluate to `True` when `C` is false. For more complex expressions using `and` or `or`, we can apply the following rules known as De Morgan’s laws.

`not (A and B)` ←→ `(not A) or (not B)`

`not (A or B)` ←→ `(not A) and (not B)`

### Exercise: Logic Tests

For this exercise, use the program `logic_tests.py` to test your understanding of logical operators. You don’t need to write any code for this exercise, just run the program and follow the prompts.

`\$ python3 logic_tests.py`

For each expression, can you think of values for `x`, `y`, and `z` where the expression would be true and separate values where the expression would be false.

### Code Tracing

Code tracing is when you run through code in your head and try to determine the result. I have provided three blocks (the last purposefully being harder than the other two). What will each of these blocks do? Do they give different results, or are some of them equivalent in terms of what they print?

``````#Block 1
if temp >= 60:
print("No coat is needed")
if temp >= 40:
print("Fall jacket")``````
``````#Block 2
if temp >= 60:
print("No coat is needed")
elif temp >= 40:
print("Fall jacket")``````
``````#Block3
if temp >= 40:
if temp >= 60:
print("No coat is needed")
else:
print("Fall jacket")``````

### Comparing Strings

We can compare string values just as we can compare integer and float values. That is, we can use any relational operator on a pair of a strings.

``"Aardvark" < "Baboon"``

Strings in python3 are compared lexicographically, i.e., based on their sorted dictionary order. So, the above expression is `True` because `Aardvark` appears earlier in the dictionary than `Baboon`.

Python actually compares the two strings character-by-character until it finds a difference. So, it will first compare `A` to `B`. It finds that they are different, and so it returns True. If the expression is:

``"Apple" < "Applied"``

Python first compares the `A` s, then each `p`, then the `l` s , and finally stops at the next position since `e` and `i` are different. Since `e` comes before `i` in the alphabet, the expression returns True.

``"apple" < "APPLE"``

What does Python do here? Internally, everything in the computer is represented numerically in binary (0s and 1s). So, even text is really represented as a series of numbers (positive integers, specifically). The encoding, or conversion, is known as Unicode. We can find the conversion using the `ord()` function. The `ord()` functions takes a string or length one (a character) and returns an integer value.

 The actual integer value is not important for you to know. One nice property of the `ord()` function is that the lowercase letters `a` through `z` return sequential values, as do the upper case letters `A` through `Z` and the digits `0` through `9`. This can be helpful for manipulating individual letters/digits.
```\$ python3
>>> ord('C')-ord('A')
2```

This indicates `C` is two characters away from `A`.

```\$ python3
>>> ord('p')-ord('a')
15
>>> ord('a')-ord('A')
32```

So to answer our question above, comparing `apple` to `Apple` we need to compare the Unicode value of `a` to `A`. `A` has a smaller Unicode value than `a`, so the expression is `False`.

We can also convert in the other direction - from a number to a character using the `chr()` function. The `chr()` takes a non-negative integer as input and returns a string of length one (a character). We typically use this in conjunction with `ord()` to avoid working with the numbers directly, as the number have no intrinsic meaning.

```>>> ord_a = ord('a')
>>> type(ord_a)
<class 'int'>
>>> chr(ord_a+2)
'c'
>>> chr(ord_a+10)
'k'
>>> chr(ord_a+25)
'z'
>>> chr(ord_a)
'a'```

#### Exercise: lowercase

Can you think of a way to use string comparision, `ord()`, and/or `char()` to tell if a character is a lowercase letter?

``````\$ python3 lowercase.py
Enter a character: a
a is a lowercase letter
\$ python3 lowercase.py
Enter a character: d
d is a lowercase letter
\$ python3 lowercase.py
Enter a character: D
D is NOT a lowercase letter
\$ python3 lowercase.py
Enter a character: \$
\$ is NOT a lowercase letter``````