Midterm 1 Review

Midterm 1 practice problems

Midterm Logistics

When and where
Thursday, October 10, 7:30 - 10:00 PM in 75SHS 102 (although the exam is intended to take less than 2 hours)
What can I bring?
One piece of letter-sized paper with notes on both sides (I will provide copies of the cheat sheet)
What can’t I bring?
Anything else, e.g., book, computer, other notes, etc.
I have a scheduling conflict, can I take the midterm at an alternate time?
Yes. Hopefully you already responded to the Google form, to let me know. You can pick up the exam from me in my office on Thursday (or whatever time we agreed upon).

What will the exam cover?

The exam will cover up through loops (for and while) but not include file reading. It will specifically focus on the first 8 exam topics, one question per topic. Those questions will or can involve the following:

  • Evaluating expressions (order of evaluation in parentheses, method chaining, etc.)
  • Statements
    • Function definitions (def …)
    • for loops using collections, e.g. string, list, and ranges
    • while loops (and the conversion between for loops and while loops)
    • conditional statements (if, elif, else)
    • return (and print vs. return)
  • Types
    • int
    • float
    • bool
    • str
    • list
  • Strings and Lists
    • Indexing and slicing
    • Iterating
    • Functions that take collection as arguments, e.g. len
    • Methods
    • Operators on type
  • Operators
    • +, -, *, /, //, %, **
    • =, +=, -=, *=
  • Boolean and relational operators
    • and, or, not (and their precedence)
    • <, <=, >, >=, ==, !=
  • Using modules
    • Different import approaches
      • from <module> import <material>
      • import <module> [as <prefix>]
    • random module
    • math module
    • turtle module
  • Input and output
    • input function
    • print function
  • Commenting (including block/inline comments and docstrings)
  • Good coding style

The exam will NOT include material that was in the book(s) but that we did not discuss in class, or use in our programming assignments, or practice on PrairieLearn. The exam will NOT include file reading.

Types of questions

  • Determine the output of code, e.g. value of variables, printing, turtle drawing
  • Rewrite code with better style
  • Identify bugs in code
  • Reassemble jumbled Python statements into a viable function
  • Write code to solve problem.
  • Short answer

How do you suggest I prepare?

  • Review the relevant exam topics and identify the key ideas and techniques associated with each topic. Do you understand that key idea?
  • Practice, practice, practice! Complete the previous exams, (re)-solve the practice problems and the in-class problems (available as “in-class questions” on course webpage).
  • Review the class notes. Treat the examples in the notes as practice problems, i.e., can you predict the result/solution before you look at it?

What do you suggest I put on my notes page?

Here are some (non-exhaustive) suggestions:

  • Common code snippets, e.g., building up a string, determining even/odd, maintaining a count, determining min/max, etc.
  • Common slicing patterns, e.g., every third value in a sequence, all but the first value in a sequence, etc.
  • Common kinds of errors, e.g., mismatched type, off-by-one, missing colons, missing/invalid keywords, incorrect indent, etc.

Review questions:

  1. Consider the following Python code. What are the values of s, r and y after the code executes?

    s = 5
    r = 3
    
    def bar(s):
       r = len(s)
       return r
    
    def foo(s): 
          r = 1
          for i in range(len(s)):
              bar(s[i:])
              r = r * (-bar(s[i:]))
          return r
    
    y = foo("test")

    Recall that function parameters and local variables are in a separate scope. Thus the values of s and r are unchanged, and still 5 and 3, respectively. Similary r and s within foo is distinct from r and s in bar. We could simplify the code above

    def foo(s): 
          r = 1
          for i in range(len(s)):
              r = r * (-len(s[i:])) # The same as r = r * -(len(s)-i)
          return r
    
    y = foo("test")

    Thus y will be 1 * -4 * -3 * -2 * -1 or 24.

  2. Write a function named random_test that takes three integer parameters: max, threshold and num. The function should generate num random integers between 0 and max (inclusive) and return the number of these values that were less or equal to the threshold.

    from random import randint
    
    def random_test(max, threshold, num):
        count = 0
        for i in range(num):
            r = randint(0, max)
            if r <= threshold:
                count += 1
        return count
  3. What would the following function return if we invoked it with 4 as the argument?.

    def mystery(x):
        count = 1
        total = 0
    
        while count <= x:
            total += count * count
    
        return total

    The function would not return. count is never modified and so this is an infinite loop.

  4. For each of the following tasks, indicate with would be most appropriate, a for loop, a while loop, or both a equally appropriate:

    1. Transform each letter in a string:

      A for loop is most appropriate, since we know the length of the string (the number of iterations) at the beginning of the loop.

    2. Count the number of coin flips rolls to get 10 heads:

      Since we don’t know how many flips would be required, a while loop is most appropriate.

    3. Solicit a valid password from a user:

      Since we don’t know how many attempts the user will need to provide a valid password, a while loop is most appropriate.

    4. Perform an operation on every point in a 3-D grid of known size:

      Since we know the size of the grid, a for loop (and likely nested for loops) are mos appropriate.

    5. Perform an operation on every “a” in a string:

      Either loop could be appropriate here. We could use a for loop to iterate through every character and a conditional to identify “a” characters, or a while loop along with the find method to search for “a”s, until no more are found. Neither approach is necessarily better than the other. An example of the latter could be:

      index = string.find("a"):
      while index != -1:
          # Do something for string[index]
      
          # Find next "a", by starting search after previous "a"
          index = string.find("a", index+1)      
  5. The following function is supposed to prompt the user for numbers until they enter a blank line, then it tells the user how many of the numbers were less than zero. Unfortunately, the function has a few problems and doesn’t work correctly. Identify and fix the problems.

    def neg_count():
        answer = input("Enter a number: ")
    
        below = 0
    
        while answer != "":
            if answer < 0:
                below += 1
    
        print("Below: " + below)

    Fixed version:

    # Fixed neg_count
    def neg_count():
        answer = input("Enter a number: ")
    
        below = 0
    
        while answer != "":
            # Runtime error: Need to convert string to float
            if float(answer) < 0:
                below += 1
    
            # Logical error: Need to obtain subsequent inputs in each iteration of the loop
            answer = input("Enter a number:")
    
        # Runtime error: Need to convert below to string before concatenation
        print("Below: " + str(below))

    It can be trick to just “see” the errors. Instead I suggest you approach the problem just like Python, “executing” the code and looking for various errors. Try multiple passes:

    1. Look for syntax errors,
    2. “Step” through execution looking for runtime errors (like examples above),
    3. Is the output correct for different inputs (i.e., are there logical errors)?
  6. Rewrite each of the boolean expressions below more compactly. For example, rather than writing x and x we could just write x.

    1. Rewrite:

      # x contains some bool value
      True and x

      Can be rewritten as:

      x
    2. Rewrite:

      # x contains some bool value
      False or x

      Can be rewritten as:

      x
    3. Rewrite:

      y > 0 and y < 10

      Can be rewritten as:

      0 < y < 10
    4. Rewrite:

      (y > 0 and z == 1) or (y > 0 and z == 2)

      Can be rewritten as:

      y > 0 and (z == 1 or z == 2)
    5. Rewrite:

      (y > 0 and y < 10) and (y > 0 and y < 20)

      Can be rewritten as:

      y > 0 and y < 10

      or even more concisely as 0 < y < 10, as shown above.

      The expression y > 0 and y < 10 is a subset of y > 0 and y < 20 and so if that first expression is True, then the second expression must be True. Thus the overall expression will true if and only if the first expression is True.

    6. Rewrite:

      (y > 0 and y < 10) and (y <= 0 or y >= 10)

      Can be rewritten as:

      False

      The expression y > 0 and y < 10 is the same as not (y <= 0 or y >= 10) and such both operands can’t be True at the same time, thus the overall expression must be False.

  7. The function below has two integer parameters a and b. The function works as desired, however, it is very verbose. Rewrite the function to have identical behavior (i.e., for all possible values of a and b return the same value), but to be as concise as possible.

    def could_be_better(a, b):
         if a > 4:
             if b > 15:
                 return True
             elif b < -10:
                 return False
             else
                 return True
         else:
             return False

    If we think about this as 2-D space, we observe that the function returns True when a > 4 and b >= -10 and False otherwise. We cansimplify the function to return that expression directly, e.g.,

    def better(a, b):
        return a > 4 and b >= -10
  8. We noted that Python performs lexicographic comparison of two sequences. Write a function named lessthan with two arguments, seq1 and seq2, and performs lexicographic less than of those sequences. You should only compare individual elements of the sequences.:

    >>> lessthan(["a", "b"], ["a", "b", "c"])
    True
    >>> lessthan(["a", "c"], ["a", "b", "c"])
    False
    >>> lessthan(["a", "b", "c"], ["a", "b", "c"])
    False
    def lessthan(seq1, seq2):
        # Find the length of the shorter sequence so we don't attempt to access elements
        # beyond the end of sequence
        if len(seq1) < len(seq2):
            shorter = len(seq1)
        else:
            shorter = len(seq2)
        # Iterate by index so we can access corresponding elements of the same sequence
        for i in range(shorter):
            if seq1[i] < seq2[i]:
                return True # Return as soon as we know the result
            elif seq1[i] > seq2[i]:
                return False
        # If we get here, the prefixes of the sequences are the same, return True if
        # seq1 is the shorter sequence
        return len(seq1) < len(seq2)
  9. Draw below what the following program would draw on the screen:

    from turtle import *
    
    def draw_something(x, y, length):
        pu()
        goto(x, y)
        pd()
        goto(x + length, y)
    
    for i in range(10):
        draw_something(0, i*5, i*5)
    
    done()

    The above will draw horizontal lines of increasing length at increasing vertical coordinates.

  10. Write a function named intersection that has two sequences as parameters and returns a copy of its first argument containing just those items in the first sequence present in the second. It should work for any combination of list and string arguments. As a hint, slicing evaluates to a sequence of the same type and equality operators can be applied to values of different types. For example:

    >>> intersection(["a", "b"], "aac")
    ["a"]

    Here we take advantage of slicing to create a sequence of the same type, with either zero or 1 items. Recall that len, + and slicing are defined for all sequence types.

    def intersection(seq1, seq2):
        result = seq1[:0] # Create empty sequence the same type as seq1    
        for i in range(len(seq1)):
            if seq1[i] in seq2:
                result = result + seq1[i:i+1] # Concatenate seqeunce with single item
        return result
  11. Imagine you are given a list of column positions in a building (in sorted ascending order). Write a function name span that takes the list as argument and returns the longest beam you will need to span between the columns, i.e., the maximum distance between any two adjacent columns. You can assume there are at least two columns.

    def span(columns):
        max_beam = -1 # Beam lengths can't be negative
        for i in range(1, len(columns))
            beam = columns[i] - columns[i-1]
            if beam > max_beam:
                max_beam = beam
        return max_beam