Section 8. Final Reivew


Written by Juliette Woodrow

This week in section we are going to do a few problems to help you prepare for the final exam. This handout is not comprehensive of what will be on the final. Please see the practice final and watch lecture to hear more exam info.

Images

Fuse Images

Implement the function called fuse_images(image1, image2, dist_btn_rectangles) as described below.

Fuse images takes as parameters two different images, image1 and image2 of the same size and fuses them together such that a rectangular cutout of image2 is placed over image1. It will be placed over image one kind of like a frame. We call it a rectangular cut out rather than a rectangle because we only want the outside of a rectangle. The size of the rectangular cut out is determined by the third parameter dist_btn_rectangles.

Consider the images below:

Here a rectangular cut out with size, dist_btn_rectangles on all sides, of megamind.jpg is placed on top of obama.jpg.

You are guaranteed that dist_btn_rectangles will always be small enough such that the modifications described above can be completed without error. In the below drawing, x represents the dist_btn_rectangles parameter.

Notice that you are not making a new image. You are modifying image1. To break down solving this, we recommend writing nested loops to copy over each side of the rectangular cut out. We highly recommend drawing out a picture to get the math right for the for loop ranges.

Solution 1

def fuse_images(image1, image2, dist_btn_rectangles): 
    # Copy rectangle on the left side 
    for y in range(dist_btn_rectangles, image1.height - dist_btn_rectangles): 
        for x in range(dist_btn_rectangles, dist_btn_rectangles * 2): 
            new_pixel = image2.get_pixel(x, y) 
            image1.set_pixel(x, y, new_pixel) 
 
    # Copy rectangle on the right side
    for y in range(dist_btn_rectangles, image1.height - dist_btn_rectangles): 
        for x in range(image1.width - (2 * dist_btn_rectangles), image1.width - dist_btn_rectangles):
            new_pixel = image2.get_pixel(x, y) 
            image1.set_pixel(x, y, new_pixel) x = dist_btn_rectangles

    # Copy rectangle at the top side
    for y in range(dist_btn_rectangles, dist_btn_rectangles * 2): 
        for x in range(dist_btn_rectangles * 2, image1.width - 2 * dist_btn_rectangles): 
            new_pixel = image2.get_pixel(x, y) 
            image1.set_pixel(x, y, new_pixel) 
 
    # Copy rectangle on the bottom side
    for y in range(image1.height - (2 * dist_btn_rectangles), image1.height - dist_btn_rectangles): 
        for x in range(dist_btn_rectangles * 2, image1.width - 2 * dist_btn_rectangles): 
            new_pixel = image2.get_pixel(x, y) 
            image1.set_pixel(x, y, new_pixel

    image1.show()

Solution 2

def fuse_images(image1, image2, dist_btn_rectangles):
   # Copy rectangle on the left and right side
    for y in range(dist_btn_rectangles, image1.height - dist_btn_rectangles):
        for x in range(dist_btn_rectangles, dist_btn_rectangles * 2):
            new_pixel = image2.get_pixel(x, y)
            image1.set_pixel(x, y, new_pixel)

        for x in range(image1.width - (2 * dist_btn_rectangles), image1.width - dist_btn_rectangles):
            new_pixel = image2.get_pixel(x, y)
            image1.set_pixel(x, y, new_pixel)

    # Copy rectangle on the right and left side
    for x in range(dist_btn_rectangles * 2, image1.width - 2 * dist_btn_rectangles):
        for y in range(dist_btn_rectangles, dist_btn_rectangles * 2):
            new_pixel = image2.get_pixel(x, y)
            image1.set_pixel(x, y, new_pixel)

        for y in range(image1.height - (2 * dist_btn_rectangles), image1.height - dist_btn_rectangles):
            new_pixel = image2.get_pixel(x, y)
            image1.set_pixel(x, y, new_pixel)

    image1.show()

Solution 3

def fuse_images(image1, image2, dist_btn):
	
	# copies over image 1 so that when we modify we don't lose the inside 
    image3 = SimpleImage.blank(image1.width, image1.height)
    for y in range(image1.height):
        for x in range(image2.width):
            pix = image1.get_pixel(x, y)
            image3.set_pixel(x, y, pix)

    # copies over the rectangle of image2 onto image1
    for y in range(dist_btn, image1.height - dist_btn):
        for x in range(dist_btn, image1.width - dist_btn):
            pix = image2.get_pixel(x, y)
            image1.set_pixel(x, y, pix)

    # copies back the middle of the rectangle cut out from image3 onto image1
    for y in range(2*dist_btn, image1.height - 2*dist_btn):
        for x in range(2 * dist_btn, image1.width - 2 * dist_btn):
            pix = image3.get_pixel(x, y)
            image1.set_pixel(x, y, pix)

Lists of Lists

Groceries

You’ve decided to optimize your weekly grocery trip by representing shopping lists for each store you visit as a grocery grid - a list of grocery lists! To support each business equally, you decide to buy one of each type of item, for example a fruit, a vegetable, and a carbohydrate, at each store. So your lists might look like this:

Trader Joe’s: Banana, Kale, Baguette
H-Mart: Mango, Broccoli, Buns
Munger Market: Apple, Salad mix, Cookie

Once you’ve stored them in a grid, you’ve got the following:

grid = [['Trader Joe's', 'Banana', 'Kale', 'Baguette'],
        ['H-Mart', 'Mango', 'Broccoli', 'Buns'],
        ['Munger Market', 'Apple', 'Salad mix', 'Cookie']]

Now, to check your inventory at home, you want to be able to use your grid and return a list of all the items of a single type you’ve bought for the week. We represent types by the index in each grocery list where that type appears. In this problem, fruit would be index 1, vegetables index 2, and carbs index 3. We ignore index 0, which holds the store names. Note that our grid could have any number of lists with any number of item types; this is just an example.

Write a function get_groceries(index, grid) that takes in the item type index and a grid as shown above and returns the list of all items you bought of this type. For example, if you wanted all the vegetables you got this week, you’d call get_groceries(2, grid) which would return: ['Kale', 'Broccoli', 'Salad mix'].

def get_groceries(index, grid):
    lst = []
    num_lists = len(grid)
    for i in range(num_lists):
        grocery_list = grid[i]
        item = grocery_list[index]
        lst.append(item)
    return lst

Dictionaries

Animal Counts

Let’s say we had everyone in class vote on their favorite animals by entering an animal name into an online poll. The results were then put into a file so that each line of the file is an animal name and the number of votes it received. Here some some of the results:

Dog: 34
Bird: 20
cat: 14
Elephant: 7
dog: 5
CAT: 2

What we want to do is to read in the data from this file into a dictionary where the key is the animal name (string) and the value is the number of votes this animal got (integer).

Unfortunately, the polling mechanism didn’t take into account that some people would capitalize the names of animals differently. Notice that above, “Dog” and “dog” each get their own score, but really we’d like to sum all the votes for each animal name in a case-insensitive way, so that we see that 39 people have “dog” as their favorite animal.

Your task is to do just this; write a helper function get_animal_counts(filename) that takes in a filename and returns a dictionary with the duplicate counts (for keys with the same characters but different capitalization) combined. The new keys should be all lowercase. Calling our helper function with the file above would return:

{'dog': 39, 'bird': 20, 'cat': 16, 'elephant': 7}
def get_animal_counts(filename):
    counts_dict = {}
    with open(filename) as f:
        for line in f:
            line = line.strip()
            line = line.split(':') # split into list by colon
            animal_name = line[0].lower() # case-insensitive
            count = int(line[1])
            if animal_name not in counts_dict: # initialize
                counts_dict[animal_name] = 0
            counts_dict[animal_name] += count # update
    return counts_dict

Finding Grandchildren

Implement a function find_grandchildren(parents_to_children), which given a dictionary parents whose keys are strings representing parent names and whose values are lists of those parent's children's names, produces and return s a dictionary mapping from people's names to those lists of those people's grandchildren's names.

For example, given this dictionary:

parents_to_children = {
  'Khaled': ['Chibundu', 'Jesmyn'],
  'Daniel': ['Khaled', 'Eve'],
  'Jesmyn': ['Frank'],
  'Eve': ['Grace']
}

Daniel's grandchildren are Chibundu, Jesmyn and Grace, since those are the children of his children. Additionally, Khaled is Frank's grandparent, since Frank's parent is Khaled's child. Therefore, calling find_grandchildren(parents_to_children) returns this dictionary:

{
  'Khaled': ['Frank'],
  'Daniel': ['Chibundu', 'Jesmyn', 'Grace']
}

Note that the people who aren't grandparents don't show up as keys in the new dictionary.

SAMPLE_INPUT = {
    'Khaled': ['Chibundu', 'Jesmyn'],
    'Daniel': ['Khaled', 'Eve'],
    'Jesmyn': ['Frank'],
    'Eve': ['Grace']
}
​
​
def add_grandchildren(grandparents_dictionary, grandparent, new_grandchildren):
    if grandparent not in grandparents_dictionary:
        # if we haven't seen this grandparent before, add them to the dictionary
        grandparents_dictionary[grandparent] = []
​
    # add the new grandchildren to the grandparent's list of 
    # grandchildren
    current_grandchildren = grandparents_dictionary[grandparent]
    current_grandchildren += new_grandchildren
​
​
def find_grandchildren(parents_dictionary):
    """
    >>> find_grandchildren(SAMPLE_INPUT)
    {'Khaled': ['Frank'], 'Daniel': ['Chibundu', 'Jesmyn', 'Grace']}
    """
    grandparents_dictionary = {}
    for parent in parents_dictionary:
        children = parents_dictionary[parent]
        for child in children:
            if child in parents_dictionary: # check if the child is a parent themselves
                grandchildren = parents_dictionary[child]
                add_grandchildren(grandparents_dictionary,
                                  parent, grandchildren)
    return grandparents_dictionary

Classes and OOP

Meme Generator

Our goal is to implement the MemeGenerator class in memegenerator.py for use in the following program:

# import the MemeGenerator class from memegenerator.py
from memegenerator import MemeGenerator

def main():
    # initalize the meme generator with an image
    meme_gen = MemeGenerator('one-does-not.png')

    # add text to the top and bottom of the meme
    meme_gen.add_text('ONE DOES NOT SIMPLY', 60, 5)
    meme_gen.add_text('MAKE A MEME GENERATOR IN PYTHON', 10, 220)

    # generate the meme!
    meme_gen.render()

if __name__ == '__main__':
    main()

(Feel free to change this program to generate different memes!) When we run memegen_test.py, we generate this variation of a classic meme:

Boromir raising his hand. Top text says 'One does not simply' and bottom text says 'Make a meme generator in Python' Implement the following methods in the MemeGenerator class:

  • __init__(self, filename): the constructor which initializes the meme's image, and accepts as a parameter the filename of the image.
  • set_image(self, filename): updates the meme's image.
  • add_text(self, text, x, y): records the string text to be placed at position (x, y) on the meme.

Note: SimpleImage supports a create_text method that you can use by calling img.create_text(text, x, y, font_filename). The text is the text that you want to draw onto the image, x and y are the coordinates of the top-left corner of the text, and font_filename is the filename that contains the font you want to draw the text in (usually this ends with .ttf). Optionally, you can also add a size parameter and a color parameter if you want to change the size of the text or the color of the text. You might do that by calling img.create_text(text, x, y, font_filename, size=10, color='white') to create 10pt white text.

from simpleimage import SimpleImage

FONT_FILE = 'impact.ttf'
SIZE = 50
COLOR = 'white'

class MemeGenerator:

    def __init__(self, filename):
        self.texts = []
        self.image_filename = filename

    def set_image(self, filename):
        self.image_filename = filename

    def add_text(self, text, x, y):
        self.texts.append((text, x, y))

    def render(self):
        img = SimpleImage(self.image_filename)
        for text, x, y in self.texts:
            img.create_text(text, x, y, FONT_FILE, color=COLOR, size=SIZE)
        img.show()