## Announcements

• Lab 2 available now.

• Quiz 1 Friday at start of class.

• Wednesday Ninja session good time to review.

## Monday

### 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("Spring is on its way!")
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.

#### Exercise: practice if statements

Practice `if/else` statements by writing a block of code (in `cs21/inclasss/w03/voting.py`) that determines if a person’s age makes them eligible to vote (18 or older on election day).

Some potential output might look like:

```$python voting.py Enter your age on election day: 20 You are eligible to vote$ python voting.py
Enter your age on election day: 18
You are eligible to vote

$python voting.py Enter your age on election day: 2 You can't vote this year You will need to wait 16 years``` #### Exercise: 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("Spring jacket")`````` ``````#Block 2 if temp >= 60: print("No coat is needed") elif temp >= 40: print("Spring jacket")`````` ``````#Block3 if temp >= 40: if temp >= 60: print("No coat is needed") else: print("Spring jacket")`````` ### 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 Python’s precedence rules evaluate operators in this order: 1. Evaluate anything inside of () 2. Evaluate all relational operators 3. Apply any not operators 4. Evaluate and operators 5. Evaluate or operators. 6. If tied, evaluate left to right. For example, suppose `b = 5` and `c = 10` and a program encounters this line: ``not True or b < 10 and c != 5`` Python first evaluates `b < 10` (`True`) and `c != 5` (True). Thus, we can simplify the line to: ``not True or True and True`` Next, Python evaluates `not True` (`False`), leaving: ``False or True and True`` Next, it evaluates the `True and True` clause, which is also `True`. All that’s left is: ``False or True`` Finally, Python evaluates the `or`, whose result is `True`. ## Wednesday ### review from last time ``````""" Kevin's voting example """ def main(): age = int(input("Age on election day? ")) if age >= 18: print("You are eligible to vote!") else: print("You can't vote this year.") howlong = 18 - age print("You will need to wait",howlong,"years") main()`````` ### single letter count ```$ python3 lettercount.py
phrase: we love computer science
letter: e
Number of e's: 5```

This one combines the accumulator with the `if` statement!

``````phrase = input("phrase: ")
letter = input("letter: ")

count = 0
for ch in phrase:
if ch == letter:
count += 1       # same as count = count + 1

print("Number of",letter,"'s:",count)``````

How can we change the above to count all letters in the alphabet?

``````for letter in "abcdefghijklmnopqrstuvwxyz":
#
#``````

Note how, if we do that, we now have a `for` loop inside another `for` loop. This is called nested `for` loops, and is perfectly fine.

What do you think the following code would print?

``````for i in range(5):
for ch in "hello":
print(i,ch)
print("-"*20)``````

### Substrings, `in` operator

A substring of a string is a portion of the string that appears contiguously. For example, "blue" is a substring of "blueberries". Python has some commands for accessing substrings.

The most relevant for us right now is the `in` operator (not to be confused with the `in` in `for i in range(..)`). The `in` (membership) operator takes a substring called the pattern and another string commonly called the target, and returns `True` if and only if the pattern appears as a substring in the target.

Sometimes, the pattern can be a single letter, but in general, the pattern can be any string.

>>> 'a' in "apples" True >>> 'b' in "apples" False >>> "vark" in "Aardvark" True >>> "bbrries" in "blueberries" False

You can also use the `in` operator on lists:

```>>> names = ["jeff","lauri","kevin","andy"]
>>> "JEFF" in names
False
>>> "jeff" in names
True
>>> L = [1,2,3,4,5]
>>> 99 in L
False
>>> 3 in L
True```

Note that the `in` operator is just checking each item in the list, as we could do with a `for` loop:

``````FOUND = False
for name in names:
if name=="jeff":
FOUND = True
print(FOUND)``````

The above uses a boolean flag variable (`FOUND`), but does exactly the same thing as `"jeff" in names`.

### rock, paper, scissors

```$python3 rps.py rock, paper, or scissors? rock I chose paper >> I win!!``` How would we write the code to show who wins, based on this table (assuming we have two variables, `comp` and `user`, for the computer’s choice and the user’s choice): `comp` `user` winner "rock" "rock" draw "rock" "paper" user "rock" "scissors" computer "paper" "rock" computer "paper" "paper" draw "paper" "scissors" user "scissors" "rock" user "scissors" "paper" computer "scissors" "scissors" draw ### `and`, `or`, and `not` We could write out all of the above conditions using the `and` logical operator: ``````if comp=="rock" and user=="rock": print("draw") elif comp=="rock" and user=="paper": print("user wins") elif comp=="rock" and user=="scissors": print(">> I win!!") ...etc...`````` Is there a smarter way we could write that, so we don’t have to type out all nine possible combinations? ## Friday ### review of rps Here’s one way we could write the rock, paper, scissors decide-who-won part: ``````if comp == user: print(">> draw") elif comp == "paper" and user == "scissors": print(">> you win!") elif comp == "scissors" and user == "rock": print(">> you win!") elif comp == "rock" and user == "paper": print(">> you win!") else: print(">> I win!!")`````` ### pseudo-random numbers The next step would be to have the computer make a random choice, instead of always choosing paper. python has a random library that you can `import` into your programs and use to generate random numbers or choices. The actual numbers are pseudo-random, meaning they are not really random. For our purposes (simple games), they are random enough. #### syntax First import the random library: `from random import *` Then use one of the various functions in the library. The most commonly-used functions are: ```choice(seq) -- choose one from a sequence randrange(start,stop) -- chose a random number from [start,stop-1] shuffle(list) -- shuffles a list random() -- returns a random float from [0,1)``` #### examples To simulate flipping a coin, you could use any of these: ```flip = choice("HT") flip = choice(["heads","tails"]) flip = randrange(2) # assume 0 is heads, 1 is tails flip = random() # assume < 0.5 is heads``` For example: ```>>> from random import * >>> for i in range(10): ... flip = choice(["heads","tails"]) ... print(flip) ... tails heads tails heads heads heads heads tails tails tails``` To simulate rolling 6-sided dice: `result = randrange(1,7)` To shuffle a list: ```>>> L = list("ABCDEFG") >>> print(L) ['A', 'B', 'C', 'D', 'E', 'F', 'G'] >>> shuffle(L) >>> print(L) ['E', 'B', 'C', 'G', 'F', 'A', 'D'] >>> shuffle(L) >>> print(L) ['G', 'B', 'D', 'A', 'F', 'E', 'C']``` So how would you use `random` to have the computer pick rock, paper, or scissors? ### `while` loops A `while` loop should be used anytime we want our programs to loop, but don’t know ahead of time how many iterations are needed. Examples include: • games, where we don’t know how many turns will occur • getting input from the user, where we might have to give them more than one chance (in case they enter invalid data) • simulations, where they might continue running until some condition occurs We’ve talked the first day about the HiQ game. It’s possible the game could end after 31 moves (with one peg left!), or it could end earlier (if the user gets stuck with multiple pegs). In pseudo-code, this would be: ```while game is not over: # player takes a turn # update/display the new board``` #### syntax The syntax is exactly the same as the `if` statement: ```while some condition is True: # do this indented code block # could be 1 or more lines # then go back to the top and recheck the condition``` However, as long as the condition is True, it will keep looping and repeating the code block. The `if` statement will do the code block once if the condition is `True`. The `while` loop will recheck the condition after the code block, and repeat it if the condition is still `True`. #### example Here’s a program that rolls two 6-sided dice, and keeps rolling until the sum of the dice equals a user-entered number: ``````from random import * def main(): utotal = int(input("Number 2-12: ")) dtotal = 0 while dtotal != utotal: dice1 = randrange(1,7) dice2 = randrange(1,7) dtotal = dice1 + dice2 print("%d %d (%d)" % (dice1,dice2,dtotal)) main()`````` And here’s a run of the program, where the user enters 6: ```Number 2-12: 6 5 4 (9) 6 6 (12) 4 5 (9) 6 2 (8) 3 4 (7) 6 2 (8) 6 4 (10) 2 1 (3) 6 5 (11) 5 6 (11) 3 3 (6)``` #### your turn! Write a program called `updown.py` that plays the "up/down" game! The computer initially picks a random level between 5 and 10. If the user enters `u`, we move up a level. If the user enters `d`, we move down a level. And we keep going as long as level > 0. Here’s an example of the running program: ```At level: 4 char (u/d): d At level: 3 char (u/d): d At level: 2 char (u/d): u At level: 3 char (u/d): d At level: 2 char (u/d): d At level: 1 char (u/d): d``` Technically, this game could go on for a long time, if the user enters `u` a lot. ### Comparing Strings We can compare string values just as we can compare ints. That is, we can use any relational operator on a pair of a strings. `"Aardvark" < "Baboon"` Strings are compared lexicographically (i.e., based on their sorted dictionary order). So, the above expression is `True` because Aardvark appears earlier in the dictionary. Drilling down, 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 As, then each p, then the ls, and finally stops at the next position since e and i are different. Since e comes first in the alphabet, it returns `True`. What if we had: `"apple" < "APPLE"` What does Python do? To drill down even further, 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: ```$ python3
Python 3.5.2 (default, Nov 23 2017, 16:37:01)
[GCC 5.4.0 20160609] on linux
>>> ord('A')
65
>>> ord('B')
66
>>> ord('Z')
90
>>> ord('!')
33```

So to answer our question above, we need to compare the Unicode value of 'a' to 'A'. 'A' is a small Unicode value, so the expression is `False`.

We can also convert in the other direction - from a number to a character using the chr() function:

```>>> chr(58)
':'
>>> chr(100)
'd'
>>> chr(75)
'K'```