Section 3. Parameters, Return Values, Lists & Images


Brahm Capoor, Juliette Woodrow, Parth Sarin, Kara Eng, Tori Qiu and Peter Maldonado

This week in section, your goal is to practice and gain familiarity with the principles of parameters and return values, experiment with lists, and work on some interesting image problems using the SimpleImage library. We'll also explore some interesting applications of doctests in the debugging process.

Here is the starter code:

Pycharm Project

Parameters

Checking Ranges

Implement the following function which takes in 3 integers as parameters

def in_range(n, low, high)
	"""
	Returns True if n is between low and high, inclusive. 
	high is guaranteed to be greater than low.
	"""

Next, complete the program by writing a main function which reads in three integers from the user and calls in_range to determine whether or not the second number is in between the first and third. Here are a few sample runs of the program (user input is italicized):

Enter first number: *42*
Enter second number: *8*
Enter third number: *50*
8 is not in between 42 and 50

Enter first number: *8*
Enter second number: *42*
Enter third number: *50*
42 is in between 8 and 50

Enter first number: *50*
Enter second number: *42*
Enter third number: *8*
42 is in between 50 and 8
def in_range(n, low, high):
    """
    Returns True if n is between low and high, inclusive.
    high is guaranteed to be greater than low.
    """
    if n >= low and n <= high:
        return True
    # we don't need an else statement here because
    # returning True would have ended the function
    # early
    return False


def in_range(n, low, high):
    """
    A clever alternative solution that leverages the fact that 
    we're computing a boolean expression anyway
    """
    return n >= low and n <= high


def main():
    num_1 = int(input("Enter first number: "))
    num_2 = int(input("Enter second number: "))
    num_3 = int(input("Enter third number: "))

    if in_range(num_2, num_1, num_3) or in_range(num_2, num_3, num_1):
        print(str(num_2) + " is in between " +
              str(num_1) + " and " + str(num_3))
    else:
        print(str(num_2) + " is not in between " +
              str(num_1) + " and " + str(num_3))


if __name__ == "__main__":
    main()

FizzBuzz

In the game Fizz Buzz, players take turns counting up from one. If a player’s turn lands on a number that’s divisible by 3, she should say “fizz” instead of the number, and if it lands on a number that’s divisible by 5, she should say “buzz” instead of the number. If the number is both a multiple of 3 and of 5, she should say "fizzbuzz" instead of the number. A spectator sport, it is not.

What it is, however, is an interesting problem in control flow and parameter usage. Write a function called fizzbuzz which accepts as a parameter an integer called n. The function should count up until and including n, fizzing and buzzing the correct numbers along the way. Once it's done, the function should return how many numbers were fizzed or buzzed along the way.

Next, complete your program by writing a main function that reads in an integer from the user and plays fizzbuzz until it counts to the number then prints out the count of the numbers that were fizzed, buzzed, or fizzbuzzed. Here's a sample run of the program (user input is italicized):

Number to count to: *17*
1
2
Fizz
4
Buzz
Fizz
7
8
Fizz
Buzz
11
Fizz
13
14
Fizzbuzz
16
17
7 numbers were fizzed, buzzed, or fizzbuzzed
def fizzbuzz(n):
    count = 0
    for i in range(1, n + 1):
        if i % 15 == 0:
            count += 1
            print("Fizzbuzz")
        elif i % 3 == 0:
            count += 1
            print("Fizz")
        elif i % 5 == 0:
            count += 1
            print("Buzz")
        else:
            print(i)
    return count


def main():
    num = int(input("Number to count to: "))
    count = fizzbuzz(num)
    print(str(count) + " numbers were fizzed or buzzed")


if __name__ == "__main__":
    main()

Lists

Interesting List

Implement the following function:

def make_interesting_list(num_list): 
  """
  Accepts a list of integers and returns a new list based on the given list. 
  The new list should have each of the values in num_list as follows: 
  if the integer in num_list is less than zero, the new list should not have 
  this value; if the integer in num_list is greater than or equal to zero and 
  odd, the new list should have this integer value with 10 added to it; if 
  the integer in num_list is greater than or equal to zero and even, the new 
  list should have this integer value unchanged; 
  >>> num_list = [-2, 33, 14, 6, -13, 9, 2]
  >>> make_interesting_list(num_list)
  [43, 14, 6, 19, 2]
  """
def make_interesting_list(num_list): 
  result = [] 
  for value in num_list: 
    if value >= 0: 
      if (value % 2) == 1: # Check if value is odd 
        result.append(value * 10) 
      else: 
        result.append(value) 
  return result

Collapse List

Implement the following function:

def collapse(lst):
    """
    Accepts a list of integers as a parameter and returns a new 
    list containing the result of replacing each pair of integers 
    with the sum of that pair.

    If the list stores an odd number of elements, the final element 
    is not collapsed

    >>> nums = [7, 2, 8, 9, 4, 13, 7, 1, 9, 10]
    >>> collapse(nums)
    [9, 17, 17, 8, 19]
    >>> nums = [1, 2, 3, 4, 5]
    >>> collapse(nums)
    [3, 7, 5]
    """
def collapse(nums):
  result = []
  for i in range(len(nums)//2):
    # since we're going pairs at a time, we only need
    # to go as many as times as half the number of elements
    result.append(nums[i * 2] + nums[i * 2 + 1])

  if len(nums) % 2 == 1:
    result.append(nums[-1])
  return result

Rotate

Implement the following function:

def rotate_list_right(lst, n):
    """
    returns a 'rotate' version of the list that rotates numbers 
    to the right n times. Each element in numbers is shifted
    forward n places, and the last n elements are moved to 
    the start of the list.

    Your function should not change the list that is passed as a 
    parameter.

    >>> rotate_list_right([1, 2, 3, 4, 5], 2)
    [4, 5, 1, 2, 3]
    """
def rotate_list_right(numbers, num):
    """
    returns a 'rotate' version of the list that rotates numbers 
    to the right n times. Each element in numbers is shifted
    forward n places, and the last n elements are moved to 
    the start of the list.

    Your function should not change the list that is passed as a 
    parameter.

    >>> rotate_list_right([1, 2, 3, 4, 5], 2)
    [4, 5, 1, 2, 3]
    """
    output_list = []
    
    for i in range(len(numbers) - num, len(numbers)):
      output_list.append(numbers[i])
    
    for i in range(0, len(numbers) - num):
      output_list.append(numbers[i])
    
    return output_list

Images

Implement the following functions to familiarize yourself with the various ways you can work with images. Each function takes in a string filename, which can then be converted into a SimpleImage. Each function should end by displaying the image, which can be done by calling the .show() function on an image.

Double Left

Implement the following function:

def double_left(filename):
  """
  Takes the left half of image, and copies it on top of the right half.
  """

Here is an example of the output of the function called on an image of Karel: Double Left

def double_left(filename):
    """
    Takes the left half of image, and copies it on top of the right half.
    """
    image = SimpleImage(filename)
    mid_x = image.width // 2
    for y in range(image.height):
        for x in range(mid_x):
            pixel = image.get_pixel(x, y)
            pixel_right = image.get_pixel(mid_x + x, y)
            pixel_right.red = pixel.red
            pixel_right.green = pixel.green
            pixel_right.blue = pixel.blue
    image.show()

Squeeze Width

Implement the following function:

def squeeze_width(filename, n):
  """
  A funhouse mirror effect governed by the int parameter n. 
  Create a new image with the same height as the original, but
  with a width that is n times smaller than the original's. 
  Copy the original image such that it is squeezed horizontally.

  For example, if n = 4, the pixels in the output image with an 
  x coordinate of 0 would be copies of the input pixels with
  an x coordinate of 0 * 4 = 0, while pixels in the output 
  image with an x coordinate of 1 would be copies of the input
  pixels with an x coordinate of 1 * 4 = 4, and so on.
  """

Here is an example run of what should happen:

def squeeze_width(filename, n):
    image = SimpleImage(filename)
    out = SimpleImage.blank(width=image.width // n, height=image.height)
    for y in range(out.height):
        for x in range(out.width):
            pixel_out = out.get_pixel(x, y)
            pixel = image.get_pixel(x * n, y)

            pixel_out.red = pixel.red
            pixel_out.green = pixel.green
            pixel_out.blue = pixel.blue
    out.show()

Crop Image

Implement the following function:

def crop_image(filename, n):
    """
    crops the image to fit inside of a frame by shaving 
    n pixels from each side of the image. 

    You may assume that n is less than half the width of 
    the image. 
    """

Here is an example of the output of the function called on an image of Karel:

def crop_image(filename, n):
    """
    Takes the left half of image, and copies it on top of the right half.
    """
    image = SimpleImage(filename)
    out = SimpleImage.blank(image.width - 2 * n, image.height - 2 * n)

    for y in range(out.height):
        for x in range(out.width):
            input_pixel = image.get_pixel(x + n, y + n)
            out.set_pixel(x, y, input_pixel)

    out.show()

Debugging with Doctests

In this problem, we'll work through using doctests to help us debug a program. Suppose we're trying to write a function which given two integers, calculates the Greatest Common Divisor of both numbers. The Greatest Common Divisor of two numbers is the largest number that is a factor in both of them. In greatest_common_divisor.py, we've provided the following buggy implementation of a function called greatest_common_divisor:

def greatest_common_divisor(a, b):
    """
    Return the greatest common divisor of a and b.

    >>> greatest_common_divisor(4, 16)
    4
    >>> greatest_common_divisor(16, 4)
    4
    >>> greatest_common_divisor(9, 24)
    3
    """

    lower = a

    gcd = 1
    for i in range(1, lower):
      if a % i == 0 or b % i == 0:
        gcd = i

At first glance, this looks like a reasonable way of solving the problem, but it turns out there are several issues. By running the doctests for the function, try identifying what the bugs are and correcting them. You might wish to add more doctests to more exhaustively test your function.

Here is a a fixed version of the program :)

def greatest_common_divisor(a, b):
    """
    Return the greatest common divisor of a and b.

    >>> greatest_common_divisor(4, 16)
    4
    >>> greatest_common_divisor(16, 4)
    4
    >>> greatest_common_divisor(9, 24)
    3
    """
    if a < b:
        lower = a
    else:
        lower = b

    gcd = 1
    for i in range(1, lower + 1):
        if a % i == 0 and b % i == 0:
            gcd = i
    
    return gcd