title: Lab 8: References; 2-D lists [TOC] ### Task 0: Trace code involving references 1. Consider the following Python program: :::python def foo(values, x): for i in range(len(values)): values[i] = i x += 1 a = [4, 5, 6] b = a[:] c = a x = 5 foo(c, x) print('a =', a) print('b =', b) print('c =', c) print('x =', x) Let's trace through it together in [Python Tutor][tutor]. Before the function call `foo(c, x)` is made: * How many distinct lists are there? * Which variables refer to the same list? During the execution of the function: * What is the value of the parameter `values`? * What values does the variable `i` take on? * When the function call is about to complete, what return value does Python Tutor specify? Why does this make sense? After the function call returns: * What output is produced? * Why have the values of some of the variables changed, while others have not? 2. What is the output of the following Python program? ***Draw a memory diagram to help illustrate your answer.*** :::python def foo(values): for i in range(len(values)): values[i] *= 2 def bar(values): for x in values: x *= 2 a = [1, 2, 3] b = a c = b[:] foo(a) bar(c) print('a =', a) print('b =', b) print('c =', c) ***At the end of the lab, you should turn in your memory diagram to the TF. To get credit for attendance at today's lab, you must have made a good effort on this memory diagram.*** ### Task 1: Understand and write functions for 2-D lists In this task we'll work with 2-D lists of single-digit integers between 0 and 9. 0. Begin by downloading the following file: [`lab8task1.py`][lab8task1]. Open that file in IDLE, and put your work for this problem in that file. 1. In `lab8task1.py`, we have given you a function called `create_grid(height, width)` that creates and returns a 2-D list of `height` rows and `width` columns in which all of the cells--i.e., all of the elements of the sublists--have a value of `0`. Try this function by running the file and entering the following from the Shell: >>> grid = create_grid(3, 5) >>> grid [[0, 0, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0]] 2. As shown above, when Python prints a 2-D list, it "flattens" the list into one long list (wrapping it onto subsequent lines as needed). To allow you to print your grids in 2-D--with one row per line--we've also provided a function called `print_grid(grid)`: >>> print_grid(grid) 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 3. We have also given you a function called `mod_grid(grid, n)`, which is supposed to take a 2-D list of integers (`grid`) and an integer `n`, and to replace each value in the list with the modulus of that value with `n`. The current version of `mod_grid` doesn't work correctly. To see why, let's copy the following program into [Python Tutor][tutor] and trace through it: :::python def mod_grid(grid, n): height = len(grid) width = len(grid[0]) for r in range(height): for c in range(width): val = grid[r][c] val = val % n mygrid = [[5, 4, 3, 2], [3, 6, 9, 1], [4, 7, 2, 9]] mod_grid(mygrid, 2) print(mygrid) * As the nested loop executes, what happens to the values of `r` and `c`? * Why isn't the loop doing what it's supposed to do? How could we fix it? 4. Go ahead and fix the version of `mod_grid` that's found in `lab8task1.py`. Run the file in IDLE and test it: >>> mygrid = [[5, 4, 3, 2], [3, 6, 9, 1], [4, 7, 2, 9]] >>> print_grid(mygrid) 5 4 3 2 3 6 9 1 4 7 2 9 >>> mod_grid(mygrid, 2) >>> print_grid(mygrid) 1 0 1 0 1 0 1 1 0 1 0 1 5. Note that `mod_grid()` doesn't return a value. It doesn't need to! That's because its parameter `grid` gets a copy of the *reference* to the original 2-D list, and thus any changes that it makes to the internals of that list will still be visible after the function returns. In Python, when a function doesn't return a value, it returns the special value `None`. The Python Shell doesn't ordinarily show us this value, but we can see it as follows: >>> result = mod_grid(mygrid, 2) >>> print(result) None 6. As shown above, `mod_grid()` modifies the internals of the original 2-D list that is passed in for the parameter `grid`. Create a new version of the function called `mod_grid_new(grid, n)` that leaves the original 2-D list unchanged, and instead creates and returns a new 2-D list containing the results of taking the modulus of each list value with `n`. * Before the loop, create a new 2-D list with the same dimensions as the original one. (*Hint:* Call the `create_grid` function to do the work for you!) * Modify the loop so that it stores the result of each modulus calculation in the new 2-D list, rather than in the original one. However, make sure that you still take the modulus of the values from the original list. * One other change is needed. What is it? Rerun the file and test your new function: >>> mygrid = [[5, 4, 3, 2], [3, 6, 9, 1], [4, 7, 2, 9]] >>> result = mod_grid_new(mygrid, 2) >>> print_grid(result) 1 0 1 0 1 0 1 1 0 1 0 1 >>> print_grid(mygrid) 5 4 3 2 3 6 9 1 4 7 2 9 Two things worth noting: * Because `mod_grid_new()` creates a new 2-D list (rather than modifying the internals of the original one), it must return the new list. If we want to be able to use that new list, we need to assign the return value to a variable, as we do above. * The internals of `mygrid` should ***not*** be changed by `mod_grid_new()`. ### Task 2: Work with images represented as 2-D lists [Problem Set 7][ps7] included a problem in which you manipulated PNG images using nested loops. In those problems, you used *image objects* and their associated methods to manipulate the pixels of an image. In this lab and in [Problem Set 8][ps8], we're going to take a different approach. Rather than using an image object, we'll represent an image as a 2-D list of pixels -- i.e., as a 2-D list of `[R,G,B]` triples. (Note: Because each pixel is itself a list, we will actually be dealing with 3-D lists! However, it's easier to think of images as 2-D lists, because our processing of them will use the same techniques that we've used for other 2-D lists.) 0. Begin by downloading the following zip file: [lab8image.zip][lab8image] Unzip this archive, and you should find a folder named `lab8image`, and within it several files, including `lab8task2.py`. Open that file in IDLE, and put your work for this problem in that file. **You should *not* move any of the files out of the `lab8image` folder.** Keeping everything together will ensure that your functions are able to make use of the image-processing module that we've provided. 1. At the top of `lab8task2.py`, we've included an `import` statement for the `hmcpng` module, which will allow you to convert a PNG image file to a 2-D lists of pixels, and vice versa. To see how this module works, run `lab8task2.py` in IDLE and enter the following command from the Shell: :::python >>> pixels = load_pixels('spam.png') After executing this statement, the variable `pixels` is a reference to a 2-D list in which: * each row corresponds to one row of pixels in the image `spam.png` * each column corresponds to one column of pixels in the image `spam.png` * each element `pixels[r][c]` of the 2-D list is an [R,G,B] triple for the pixel at row `r`, column `c` in the image `spam.png` For example: :::python >>> len(pixels) # the number of rows (i.e., the height of the image) 344 >>> pixels[0][0] # the pixel in the upper-left corner of the image is red [255, 0, 0] >>> pixels[10][20] # the pixel at row 10, column 20 is white [255, 255, 255] 2. To see a simple example of modifying an image, enter the following: :::python >>> for r in range(len(pixels)): pixels[r][r] = [0, 0, 255] # make the pixels on the diagonal blue ([0, 0, 255]) >>> save_pixels(pixels, 'blue_diag_spam.png') blue_diag_spam.png saved. After executing these commands, your `lab8image` folder should now include a file named `blue_diag_spam.png` (although the `.png` extension may or may not be visible). Double-clicking on that file should show you the following image:

![blue_diag_spam.png][blue_diag] 3. In `lab8task2.py`, we've given you a function `create_uniform_image(height, width, pixel)` that creates and returns a 2-D list of pixels with `height` rows and `width` columns in which all of the pixels have the RGB values given by `pixel`. Because all of the pixels will have the same color values, the resulting grid of pixels will produce an image with a uniform color -- i.e., a solid block of that color. Try testing this function by entering the following from the Shell: :::python >>> pixels = create_uniform_image(100, 200, [255, 0, 0]) # all red >>> save_pixels(pixels, 'red.png') red.png saved. Because `[255,0,0]` represents the color red, you should obtain the following solid red image:

![red.png][red] 4. If you look closely at our `create_uniform_image` function, you'll notice that we do *not* make a deep copy of the 1-D list represented by `pixel`. (To do so, we would have needed to compute a full slice of `pixel`, or to take some comparable step.) Failing to perform a deep copy was a deliberate choice on our part, because it allows us to illustrate the impact of Python's use of references in a dramatic way. Enter the following commands to create and save a new image that is based on the pixels used to create `red.png`: :::python >>> pixels[0][0] # the pixel at [0][0] (row 0, column 0) [255, 0, 0] >>> pixels[0][0][1] # the green component of the pixel at [0][0] 0 >>> pixels[0][0][1] = 255 >>> save_pixels(pixels, 'mystery.png') mystery.png saved. **Before viewing this image, try to predict what it will look like...** Now go ahead and view it! Pretty amazing, isn't it? There are 20000 cells in the 2-D list `pixels`, and this new image is the result of a single assignment to the green component of `pixels[0][0]`! 5. In [Problem Set 7][ps7], we gave you a function that used image objects to invert a PNG image. In `lab8task2.py`, we've given you a function called `invert()` that does the same thing, but using a 2-D list of pixels. Review that function now to see how it works. A few things worth noting: * Rather than taking the name of an image file as a parameter, this version of `invert()` takes a 2-D list of pixels. This means that you need to load the image ahead of time using `load_pixels()`, and then pass the resulting 2-D list of pixels to `invert()`. For example: >>> pixels = load_pixels('spam.png') >>> inverted = invert(pixels) * Rather than save the inverted image itself, this version of `invert` creates and returns a new 2-D list of pixels for the inverted image. This means that you need to store the return value in a variable (as shown above), and then save the image separately using `save_pixels()`: >>> save_pixels(inverted, 'inverted_spam.png') 6. Blue light is known to interfere with the body's sleep cycle, so apps have been developed to reduce the amount of blue light that your phone or laptop emits in the evening. Write a function `remove_blue(pixels)` that takes a 2-D list `pixels` containing pixels for an image, and that creates and returns a new 2-D list of pixels for an image in which the blue component of every pixel is set to 0. For example: >>> pixels = load_pixels('spam.png') >>> no_blue = remove_blue(pixels) >>> save_pixels(no_blue, 'no_blue_spam.png') If your function is working correctly, you should end up with the following result:

![no_blue_spam.png][no_blue_spam] ### Task 3: Submit your work ***You should turn in the paper with your memory diagram from Task 0 to the teaching fellow.*** You should use Apollo to submit: * your `lab8task1.py` file * your `lab8task2.py` file ***Don't worry if you didn't finish all of the tasks. You should just submit whatever work you were able to complete during lab.*** Here are the steps: 1. Login to Apollo, using the link in the left-hand navigation bar. You will need to use your Kerberos user name and password. 2. Find the appropriate lab section on the main page and click "Upload files." 3. For each file that you want to submit, find the matching upload section for the file. Make sure that you use the right section for each file. You may upload any number of files at a time. 4. Click the "Upload" button at the bottom of the page. 5. Review the upload results. If Apollo reports any issues, return to the upload page by clicking the link at the top of the results page, and try the upload again, per Apollo's advice. 6. Once all of your files have been successfully uploaded, return to the upload page by clicking the link at the top of the results page. The upload page will show you when you uploaded each file, and it will give you a way to view or download the uploaded file. ***Click on the link for each file so that you can ensure that you submitted the correct file.*** [tutor]: http://pythontutor.com/visualize.html#py=3 [ps7]: ../problem_sets/ps7.html [ps8]: ../problem_sets/ps8.html [blue_diag]: ../files/ps8/blue_diag_spam.png [red]: ../files/ps8/red.png [lab8task1]: ../files/labs/lab8/lab8task1.py [lab8image]: ../files/labs/lab8/lab8image.zip [no_blue_spam]: ../files/labs/lab8/no_blue_spam.png