Starter Files

Download Inside the archive, you will find starter files for the questions in this lab, along with a copy of the OK autograder.

Quick Logistics Review

Using Python

When running a Python file, you can use flags on the command line to inspect your code further. Here are a few that will come in handy. If you want to learn more about other Python flags, take a look at the documentation.

  • Using no flags will run the code in the file you provide and return you to the command line.

  • -i: The -i option runs your Python script first, then opens an interactive session. To exit, type exit() into the interpreter prompt. You can also use the keyboard shortcut Ctrl-D on Linux/Mac machines or Ctrl-Z Enter on Windows.

    If you edit the Python file while running it interactively, you will need to exit and restart the interpreter in order for those changes to take effect.

    python3 -i
  • -m doctest: Runs doctests in a particular file. If there is no output, it means all doctests passed. Doctests are marked by triple quotes (""") and are usually located within functions. DO NOT DELETE DOCTESTS ON YOUR LABS AND HOMEWORKS. The autograder (OK) will not run properly without them.

    python3 -m doctest

Using OK

In CS 88, we use a program called OK for autograding labs, homeworks, and projects. You should have OK in the starter files downloaded at the start of this lab. For more information on using OK commands, learn more here. To use OK to run doctests for a specified function, run the following command:

python3 ok -q <specified function>

By default, only tests that did not pass will show up. You can use the -v option to show all tests, including tests you have passed:

python3 ok -v

Sometimes it's helpful to debug using print statements, but that may also interfere with OK's grading system. If you would like to print and have OK ignore the output, you can use the debug printing feature in OK by writing:

print("DEBUG:", x)

Where x is the content you would like to print.

For more OK commands, visit here.


Consult this section if you need a refresher on the material for this lab. It's okay to skip directly to the questions and refer back here should you get stuck.


Let's compare the different division-related operators in Python:

True Division
(decimal division)
The / Operator
Floor Division
(integer division)
The // Operator
(similar to a remainder)
The % Operator
>>> 1 / 5

>>> 25 / 4

>>> 5 / 0
>>> 1 // 5

>>> 25 // 4

>>> 5 // 0
>>> 1 % 5

>>> 25 % 4

>>> 5 % 0

Note that floor division and modulo both return an integer while true division always returns a floating point number.
For now, the only difference you need to understand between integers and floats is that integers are positive or negative whole numbers, while floats are positive or negative decimals.

One useful technique involving the % operator is to check whether a number x is divisible by another number y:

x % y == 0

For example, in order to check if x is an even number:

x % 2 == 0


If we want to execute a series of statements over and over, we can abstract them away into a function to avoid repeating code.

For example, let's say we want to know the results of multiplying the numbers 1-3 by 3 and then adding 2 to it. Here's one way to do it:

>>> 1 * 3 + 2
>>> 2 * 3 + 2
>>> 3 * 3 + 2

If we wanted to do this with a larger set of numbers, that'd be a lot of repeated code! Let's write a function to capture this operation given any input number.

def foo(x):
    return x * 3 + 2

This function, called foo, takes in a single argument and will return the result of multiplying that argument by 3 and adding 2.

Now we can call this function whenever we want this operation to be done:

>>> foo(1)
>>> foo(2)
>>> foo(1000)

Applying a function to some arguments is done with a call expression.

Call expressions

A call expression applies a function, which may or may not accept arguments. The call expression evaluates to the function's return value. It has a familiar notation borrowed from Math.

The syntax of a function call:

  add   (    2   ,    3   )
   |         |        |
operator  operand  operand

Every call expression requires a set of parentheses delimiting its comma-separated operands.

To evaluate a function call:

  1. Evaluate the operator
  2. Evaluate the operands (from left to right).
  3. Apply the operator to the operands (the values of the operands).

If an operand is a nested call expression, then these two steps are applied to that operand in order to evaluate it.

return and print

Most functions that you define will contain a return statement. The return statement will give the result of some computation back to the caller of the function and exit the function. For example, the function square below takes in a number x and returns its square.

def square(x):
    >>> square(4)
    return x * x

When Python executes a return statement, the function terminates immediately. If Python reaches the end of the function body without executing a return statement, it will automatically return None.

In contrast, the print function is used to display values in the Terminal. This can lead to some confusion between print and return because calling a function in the Python interpreter will print out the function's return value.

However, unlike a return statement, when Python evaluates a print expression, the function does not terminate immediately.

def what_prints():
    print('Hello World!')
    return 'Exiting this function.'
    print('CS 88 is awesome!')

>>> what_prints()
Hello World!
'Exiting this function.'

Notice also that print will display text (Python strings) without the quotes, but return will preserve the quotes.

Another thing to remember is that print is a function itself, so it also has a return value. The return value of print is None.

>>> print(print("Hello World!"))
Hello World!


Boolean Operators

You briefly encountered Boolean operators last week, but here you will see there is a close connection between them and conditionals.

Python supports three boolean operators: and, or, and not:

>>> a = 4
>>> a < 2 and a > 0
>>> a < 2 or a > 0
>>> not (a > 0)
  • and evaluates to True only if both operands evaluate to True. If at least one operand is False, then and evaluates to False.
  • or evaluates to True if at least one operand evaluates to True. If all operands are False, then or evaluates to False.

What do you think the following expression evaluates to? Try it out in the Python interpreter.

>>> True and not False or not True and False

It is difficult to read complex expressions, like the one above, and understand how a program will behave. Using parentheses can make your code easier to understand. Just so you know, Python interprets that expression in the following way:

>>> (True and (not False)) or ((not True) and False)

This is because boolean operators, like arithmetic operators, have an order of operation:

  • not has the highest priority
  • and
  • or has the lowest priority

To make your code more readable, and and or work on more than just booleans (True, False). Other Python values can be considered "false-y," including 0, None, '' (the empty string), etc. (see here for a complete list of false-y values in Python). All other values are considered "truth-y."

Short Circuiting

What do you think will happen if we evaluate the following in Python?

1 / 0

Try it out in Python! You should see a ZeroDivisionError. But what about this expression?

True or 1 / 0

It evaluates to True because Python's and and or operators short-circuit. That is, they don't necessarily evaluate every operand.

Operator Evaluates from left to right until: Example
AND The first "false-y" value False and 1 / 0 evaluates to False
OR The first "truth-y" value True or 1 / 0 evaluates to True

If and and or do not short-circuit, they just return the last value. Another way to remember this is that and and or always return the last thing they evaluate, whether they short circuit or not. Keep in mind that and and or don't always return booleans when using values other than True and False.

If Statements

You can review the syntax of if statements in Section 1.5.4 of Composing Programs.

Tip: We sometimes see code that looks like this:

if x > 3:
    return True
    return False

This can be written more concisely as return x > 3. If your code looks like the code above, see if you can rewrite it more clearly!


There are two main types of loops, the while and for loop. They are both used to run a block of code an arbitrary number of times, however, they have different ways of doing it. Take a look at the differences in the table below.

while Loops for Loops
while <predicate expression>:
    <body statements>
When a while loop is seen:
  1. Evaluate the <predicate expression>. If it's satisfied (the expression evaluates to a True boolean value) go to step 2, otherwise exit the loop.
  2. Execute the <body statements>
  3. Repeat steps 1 and 2 in order until the <predicate expression> is not satisfied (the <predicate expression> evaluates to a false-y value)
For example:
>>> i = 0
>>> while i <= 2:
...     print(i)
...     i = i + 1
  • Normally, the <predicate expression> will rely on one or many of the variables defined in the preceding code.
  • Lastly, the <body statements> will have an "update", which will make the <predicate expression> tend toward a false-y value to prevent an infinite loop.
  • There are some execptions to these previous two rules, but they will rarely be used in this class.
for <variables> in <sequence expression>:
    <body statements>
When a for loop is seen:
  1. Create an indicator that points to the first element in <sequence expression>
  2. Bind the <variables> to the element the indicator is currently pointing to
  3. Execute the <body statements>
  4. Move indicator to the next element and repeat step 2 and 3. If there are no more elements, exit the loop.
For example:
>>> x = [0, 1, 2]
>>> for i in x:
...     print(i)
  • The <body statements> doesn't update the <sequence expression> because step 4 has the update already
for loops require a <sequence expression> which hasn't been explicitly taught yet. For that reason, unless you're very comfortable with for loops, we recommend sticking with while loops until for loops are covered in lecture.

Error Messages

By now, you've probably seen a couple of error messages. They might look intimidating, but error messages are very helpful for debugging code. The following are some common types of errors:

Error Types Descriptions
SyntaxError Contained improper syntax (e.g. missing a colon after an if statement or forgetting to close parentheses/quotes)
IndentationError Contained improper indentation (e.g. inconsistent indentation of a function body)
TypeError Attempted operation on incompatible types (e.g. trying to add a function and a number) or called function with the wrong number of arguments
ZeroDivisionError Attempted division by zero

Using these descriptions of error messages, you should be able to get a better idea of what went wrong with your code. If you run into error messages, try to identify the problem before asking for help. You can often Google unfamiliar error messages to see if others have made similar mistakes to help you debug.

For example:

>>> square(3, 3)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: square() takes 1 positional argument but 2 were given


  • The last line of an error message tells us the type of the error. In the example above, we have a TypeError.
  • The error message tells us what we did wrong — we gave square 2 arguments when it can only take in 1 argument. In general, the last line is the most helpful.
  • The second to last line of the error message tells us on which line the error occurred. This helps us track down he error. In the example above, TypeError occurred at line 1.

Required Questions

What Would Python Display (Part 1)?

Question 1: WWPD: Control

Enter the following command into terminal and then complete the WWPD questions that appear:

python3 ok -q control -u
>>> def xk(c, d):
...     if c == 4:
...         return 6
...     elif d >= 4:
...         return 6 + 7 + c
...     else:
...         return 25
...     return 12
>>> xk(10, 10)
>>> xk(10, 6)
>>> xk(4, 6)
>>> xk(0, 0)
>>> def how_big(x):
...     if x < 0:
...         print('negative')
...     if x > 10:
...         print('huge')
...     elif x > 5:
...         print('big')
...     elif x > 0:
...         print('small')
...     else:
...         print('nothing')
>>> how_big(7)
>>> how_big(12)
>>> how_big(1)
>>> how_big(-1)
negative nothing
>>> n = 3
>>> while n >= 0:
...     n -= 1
...     print(n)
2 1 0 -1

Hint: Make sure your while loop conditions eventually evaluate to a false value, or they'll never stop! Typing Ctrl-C will stop infinite loops in the interpreter.

>>> positive = 28
>>> while positive:
...    print("positive?")
...    positive -= 3
Infinite Loop
>>> positive = -9
>>> negative = -12
>>> while negative:
...    if positive:
...        print(negative)
...    else:
...        print(positive)
...    positive += 3
...    negative += 3
-12 -9 -6 0

Question 2: WWPD: Veritasiness

Enter the following command into terminal and then complete the WWPD questions that appear:

python3 ok -q short_circuiting -u
>>> True and 13
>>> False or 0
>>> not 10
>>> not None
>>> True and 1 / 0 and False
Error (ZeroDivisionError)
>>> True or 1 / 0 or False
>>> True and 0
>>> False or 1
>>> 1 and 3 and 6 and 10 and 15
>>> 0 or False or 2 or 1 / 0
>>> not 0
>>> (1 + 1) and 1
>>> 1/0 or True
>>> (True or False) and False

Coding Practice

Question 3: Fix the Bug

The following snippet of code doesn't work! Figure out what is wrong and fix the bugs.

def both_positive(x, y):
    """Returns True if both x and y are positive.

    >>> both_positive(-1, 1)
    >>> both_positive(1, 1)
return x and y > 0 # You can replace this line!
return x > 0 and y > 0

The original line (return x and y > 0) will check that two things are true:

  1. x
  2. y > 0

When will x be considered True? In Python, any number that is not 0 is considered True. Thus, the first doctest will fail: x = -1 and -1 != 0, and y = 1 > 0, so both clauses are True.

Use OK to test your code:

python3 ok -q both_positive

Question 4: Sum Digits

Write a function that takes in a nonnegative integer and sums its digits. (Using floor division and modulo might be helpful here!)

Hint: Consider how numbers are represented in decimal form, with ones, tens, hundreds, etc. What can we use to floor/mod a value to get the ones/tens/hundreds etc. digit?

def sum_digits(n):
    """Sum all the digits of n.

    >>> sum_digits(5)
    >>> sum_digits(34) # 3 + 4 = 7
    >>> sum_digits(10) # 1 + 0 = 1
    >>> sum_digits(4224) # 4 + 2 + 2 + 4 = 12
    >>> sum_digits(1234567890)
    >>> x = sum_digits(123) # make sure that you are using return rather than print
    >>> x
"*** YOUR CODE HERE ***"
total = 0 while n > 0: total, n = total + n % 10, n // 10 return total

Use OK to test your code:

python3 ok -q sum_digits

Optional Questions

What Would Python Display (Part 2)?

Question 5: WWPD: What If?

Use Ok to test your knowledge with the following "What Would Python Display?" questions:

python3 ok -q what_if -u

Hint: print (unlike return) does not cause the function to exit!

>>> def ab(c, d):
...     if c > 5:
...         print(c)
...     elif c > 7:
...         print(d)
...     print('foo')
>>> ab(10, 20)
10 foo
>>> def bake(cake, make):
...     if cake == 0:
...         cake = cake + 1
...         print(cake)
...     if cake == 1:
...         print(make)
...     else:
...         return cake
...     return make
>>> bake(0, 29)
1 29 29
>>> bake(1, "mashed potatoes")
mashed potatoes 'mashed potatoes'

More Coding Practice

The questions below can be completed in the file which is within the lab01 folder.

Question 6: Triangular numbers

The nth triangular number is defined as the sum of all integers from 1 to n, i.e.

1 + 2 + ... + n

The closed-form formula for the nth triangular number is

(n + 1) * n / 2

Define triangular_sum, which takes an integer n and returns the sum of the first n triangular numbers, while printing each of the triangular numbers between 1 and the nth triangular number.

def triangular_sum(n):
    >>> t_sum = triangular_sum(5)
    >>> t_sum
"*** YOUR CODE HERE ***"
count = 1 t_sum = 0 while count <= n: t_number = count * (count + 1) // 2 print(t_number) t_sum += t_number count += 1 return t_sum

Use OK to test your code:

python3 ok -q triangular_sum

Question 7: Double Eights

Write a function that takes in a number and determines if the digits contain two adjacent 8s.

def double_eights(n):
    """Return true if n has two eights in a row.
    >>> double_eights(8)
    >>> double_eights(88)
    >>> double_eights(2882)
    >>> double_eights(880088)
    >>> double_eights(12345)
    >>> double_eights(80808080)
"*** YOUR CODE HERE ***"
prev_eight = False while n > 0: last_digit = n % 10 if last_digit == 8 and prev_eight: return True elif last_digit == 8: prev_eight = True else: prev_eight = False n = n // 10 return False

Use OK to test your code:

python3 ok -q double_eights

Question 8: Right Triangle

Write a function that takes in 3 sides a, b, and c and checks to see if they can be possible lengths of the sides of a right triangle. Recall Pythagorean's Theorem:

a ** 2 + b ** 2 = c ** 2 # where c is the hypotenuse

Hint: Find the smallest and largest side first

def right_triangle(a, b, c):
    """Determine whether a, b, and c can be sides of a right triangle
    >>> right_triangle(1, 1, 1)
    >>> right_triangle(5, 3, 4)
    >>> right_triangle(8, 10, 6)
"*** YOUR CODE HERE ***"
one_side = min(a, b, c) hypotenuse = max(a, b, c) other_side = a + b + c - one_side - hypotenuse return (one_side ** 2 + other_side ** 2) == hypotenuse ** 2

Use OK to test your code:

python3 ok -q right_triangle


When you are done, submit your file to Gradescope. You only need to upload the following files:

You may submit more than once before the deadline; only the final submission will be graded. It is your responsibility to check that the autograder on Gradescope runs as expected after you upload your submission.