\( \newcommand{\NOT}{\neg} \newcommand{\AND}{\wedge} \newcommand{\OR}{\vee} \newcommand{\XOR}{\oplus} \newcommand{\IMP}{\Rightarrow} \newcommand{\IFF}{\Leftrightarrow} \newcommand{\TRUE}{\text{True}\xspace} \newcommand{\FALSE}{\text{False}\xspace} \newcommand{\IN}{\,{\in}\,} \newcommand{\NOTIN}{\,{\notin}\,} \newcommand{\TO}{\rightarrow} \newcommand{\DIV}{\mid} \newcommand{\NDIV}{\nmid} \newcommand{\MOD}[1]{\pmod{#1}} \newcommand{\MODS}[1]{\ (\text{mod}\ #1)} \newcommand{\N}{\mathbb N} \newcommand{\Z}{\mathbb Z} \newcommand{\Q}{\mathbb Q} \newcommand{\R}{\mathbb R} \newcommand{\C}{\mathbb C} \newcommand{\cA}{\mathcal A} \newcommand{\cB}{\mathcal B} \newcommand{\cC}{\mathcal C} \newcommand{\cD}{\mathcal D} \newcommand{\cE}{\mathcal E} \newcommand{\cF}{\mathcal F} \newcommand{\cG}{\mathcal G} \newcommand{\cH}{\mathcal H} \newcommand{\cI}{\mathcal I} \newcommand{\cJ}{\mathcal J} \newcommand{\cL}{\mathcal L} \newcommand{\cK}{\mathcal K} \newcommand{\cN}{\mathcal N} \newcommand{\cO}{\mathcal O} \newcommand{\cP}{\mathcal P} \newcommand{\cQ}{\mathcal Q} \newcommand{\cS}{\mathcal S} \newcommand{\cT}{\mathcal T} \newcommand{\cV}{\mathcal V} \newcommand{\cW}{\mathcal W} \newcommand{\cZ}{\mathcal Z} \newcommand{\emp}{\emptyset} \newcommand{\bs}{\backslash} \newcommand{\floor}[1]{\left \lfloor #1 \right \rfloor} \newcommand{\bigfloor}[1]{\Big \lfloor #1 \Big \rfloor} \newcommand{\ceil}[1]{\left \lceil #1 \right \rceil} \newcommand{\bigceil}[1]{\Big \lceil #1 \Big \rceil} \newcommand{\abs}[1]{\left | #1 \right |} \newcommand{\bigabs}[1]{\Big | #1 \Big |} \newcommand{\xspace}{} \newcommand{\proofheader}[1]{\underline{\textbf{#1}}} \)

CSC110 Fall 2025 Assignment 1

Note: Any FAQs or clarifications relevant to the assignment will be posted here. This post will be continually updated (with newer updates at the bottom of the page), so make sure to check on it regularly – click the “Watch” button in the top-right corner to receive notifications about this thread. If you have a question which is not already addressed on this page, create a new thread to post your question on Ed.

Logistics

Advice for your first assignment

Assignments are meant to evaluate not just the basic knowledge and skills you’ve learned, but also your ability to apply your learning to new domains, and to synthesize multiple concepts and skills to solve more complex problems.

Warning: we don’t recommend trying to complete assignments in one sitting. You’ll want to make sure you take breaks so that you don’t burn yourself out, and often you might get stuck on something and want to return to it in a few days, perhaps after having asked questions on our discussion board or during office hours. Instead you should schedule time to work on the assignment (put it in your calendar!). You’ll typically have around two weeks to work on each assignment. Even if you aren’t ready to start an assignment the day it comes out, you can plan out your next two weeks to find time to work on the assignment.

Assignments are the biggest type of assessments you’ll complete in CSC110, and their scale can feel a bit intimidating at first.

To help you, we typically break assignments down into a few different parts, where each part consists of a number of questions/problems to solve.

As you schedule time to work on the assignment, you might find it useful to set a deadline for each part separately.

For example, for Assignment 1 you might say:

Getting Started

To obtain the starter files for this assignment:

  1. Click this link to download the starter files for this assignment. This will download a zip file to your computer.
  2. Extract the contents of this zip file into your csc110/assignments/ folder.
  3. You should see a new a1 folder that contains the assignment’s starter files!

You can then see these files in your PyCharm csc110 project.

Instructions for Programming Questions and PythonTA

Your programming question work should be completed in the provided starter files.

We have provided code at the bottom of some of these files for running doctest examples (we’ll discuss in lecture what this code does, and why it’s useful for you!) and running a program called PythonTA. This program checks your work for common logical, design, and style errors, and the feedback from PythonTA will be used to determine a portion of your grade for all programming parts of this assignment.

Tip: You already installed PythonTA on your own computer if you followed the software installation guide. Run PythonTA as you are working on your assignment to help identify issues and fix them as they come up to ensure that you get a perfect PythonTA grade on your final submission for this assignment. For instructions on how to run PythonTA, please check out our Assignment 1 PythonTA Guide.

Part 1: Interpreting test results

Imagine this scenario: Paul is trying to decide where he should go Black Friday shopping. He has his eyes set on a Nintendo Switch 2 ($700), a new pair of shoes ($120), an air fryer ($150), and a new couch ($600). Different malls offer different discounts depending on the item. And, because Paul’s time is limited, he is trying to figure out how much money he can save by shopping at only one mall.

He calculates his average savings by multiplying the discount with the price of the 4 things he is trying to purchase. For example, if a mall’s discounts were 10%, 25%, 50% and 30% on the items above, respectively, his total savings would be: \[ 0.1 \times 700 + 0.25 \times 120 + 0.5 \times 150 + 0.3 \times 600\], or $355.00.

Paul writes a small program to perform this calculation, with the “main” function:

def summarize_average_savings(mall_discounts: dict) -> dict:
    """Return a mapping of malls to the total savings on the items in Paul's
    shopping list for each of the malls in mall_discounts.

    Each value in mall_discounts is itself a list, containing four floats
    representing the discounts for the particular category of the 4 things
    Paul is trying to purchase.

    ...
    """

He also writes three tests using pytest for this function. You can find the code for his program and tests in the starter file a1_part1.py.

When Paul runs pytest to test his work, he sees the following output (some extraneous text has been removed):

============================= test session starts =============================
collecting ... collected 3 items
a1_part1.py::test_calculation_one_mall_no_discount PASSED                [ 33%]
a1_part1.py::test_calculation_many_malls_same_discount FAILED            [ 66%]
a1_part1.py::test_calculation_many_malls_different_discounts FAILED      [100%]
================================== FAILURES ===================================
__________________ test_calculation_many_malls_same_discount __________________
    def test_calculation_many_malls_same_discount() -> None:
        """Test summarize_average_savings with many malls that offer the same
        discounts.
        """
        same_discount = [15.0, 25.0, 50.0, 0.0]
        discounts = {
            'Eaton Centre': same_discount,
            'Fairview': same_discount,
            'Sherway': same_discount,
            'Yorkdale': same_discount,
        }

        # DO NOT MODIFY ANY OF THE BELOW LINES IN THIS TEST
        expected = 210.0
        actual = summarize_average_savings(discounts)

>       assert math.isclose(actual['Eaton Centre'], expected)
E       assert False
E        +  where False = <built-in function isclose>(2100.0, 210.0)
E        +    where <built-in function isclose> = math.isclose
a1_part1.py:111: AssertionError
_______________ test_calculation_many_malls_different_discounts _______________
    def test_calculation_many_malls_different_discounts() -> None:
        """Test calculate_average_savings with many malls that offer different
        discounts.
        """
        discounts = {
            'Eaton Centre': [30.0, 50.0, 40.0, 10.0],
            'Fairview': [20.0, 70.0, 40.0, 35.0],
            'Sherway': [15.0, 40.0, 10.0, 25.0],
            'Yorkdale': [15.0, 15.0, 25.0, 30.0],
        }

        # DO NOT MODIFY ANY OF THE BELOW LINES IN THIS TEST
        actual = summarize_average_savings(discounts)

        expected = 390.0
>       assert math.isclose(actual['Eaton Centre'], expected)
E       assert False
E        +  where False = <built-in function isclose>(3800.0, 390.0)
E        +    where <built-in function isclose> = math.isclose
a1_part1.py:132: AssertionError
=========================== short test summary info ===========================
FAILED a1_part1.py::test_calculation_many_malls_same_discount - assert False
FAILED a1_part1.py::test_calculation_many_malls_different_discounts - assert ...
========================= 2 failed, 1 passed in 1.50s =========================

In your a1.tex file, answer the following questions about this program and pytest report:

  1. Looking at the pytest report, identify which tests, if any, passed, and which tests failed. You can just state the name of the tests and whether they passed/failed, and do not need to give explanations here.

  2. For each failing test from the pytest report, there is exactly one statement of code per test causing it to fail (Note that in Paul’s code, there are some single statements that are broken up into multiple lines for readability purposes – this still counts as a single statement of code despite the multiple lines).

    The incorrect code statement may occur either within the test function itself, or Paul’s original functions.

    For each failing test:

    1. Explain in 2-3 sentences (plain English, not code) what error is causing the test to fail and why.

    2. Edit a1_part1.py by fixing the function code and/or the tests so that all tests pass. Each failing test should be fixed by changing exactly one statement of code per test. The changes must be to fix errors only; the original purpose of the functions and tests must remain the same. Rule: Do not modify any of the expected, actual or assert lines for any test. These lines are all correct.

  3. For each test that passed on the original code (before your changes in question 2), explain why that test passed even though there were errors in the Python file.

Write your responses in Part 1 of a1.tex, filling in the places marked with TODOs.

Part 2: Colour Rows 🌈

In Section 1.8 of the Course Notes, we discuss how to represent colours as tuples of three integers \((r, g, b)\), representing values for the amount of red, green, and blue used to form the colour.

Some examples are shown in the table below.

RGB Value Colour
(0, 0, 0)
(255, 0, 0)
(0, 255, 0)
(0, 0, 255)
(181, 57, 173)
(255, 255, 255)

In this part of the assignment, you’ll apply your knowledge of Python comprehensions to process collections of RGB colour tuples in interesting ways, and eventually build up a small Python program that allows you to manipulate real images!

First, we’ll define a colour row (or pixel row) to be a list of RGB triples, represented in Python as a list of lists, where each inner list has exactly three elements.

Here is an example of a colour row as a Python expression:

[[0, 255, 200], [1, 2, 3], [100, 100, 100], [181, 57, 173], [33, 0, 197]]

As the name “pixels” suggests, we’ll see later how an image is composed of multiple colour rows, but in this part we’ll keep things simple and write code that operates on just a single colour row.

  1. Warmup. Open the a1_part2.py starter file. At the top of the file we’ve provided a function warmup1 and instructions on how to complete it. Follow the instructions and then run the file in the Python Console, and call warmup1(). You should see a Pygame window appear with the colours you specified!

    Image showing warmup1 pygame window.

    Note: the Python interpreter will wait for you to close the Pygame window before returning from the function. This means you won’t be able to do anything else in the Python console until you close the Pygame window!

    Tip: as you complete the functions in this part of the assignment, you’ll want to check your work! You can use the doctest examples we provided, your own examples in the Python console, and/or call the provided a1_helpers.show_colours_pygame function (similar to how we use it in warmup1) to visualize your colour rows. Enjoy!

  2. Cropping rows. Now, complete the functions crop_row, crop_row_border_single, and crop_row_border_multiple. Each of these functions will require you to take a colour row and return just part of that row.

    Hint: recall Exercise 3 of the Lecture 2A worksheet.

While this is possible to do using list slicing (a Python feature we’ll see more of, later in this course), for this assignment you must implement these functions using comprehensions (and without list slicing), as this is what we’re evaluating on this assignment!

  1. Changing colours. Next, let’s look at how we can transform colour rows to create new rows. Implement the functions remove_red_in_row, greyscale, and sepia again using comprehensions as well as any built-in functions you may think would be useful (e.g. sum, min, max, etc.).

    Hint: if you have an RGB tuple colour, you can access its individual red, green, and blue values by the indexing expressions colour[0], colour[1], and colour[2], respectively.

  2. Distorting colours (pixelate_row). Finally, implement the function pixelate_row. This is the most challenging functions on this assignment, and you’ll need to combine the techniques you used in the previous two questions to complete them. We’ve given you some hints and examples in the function docstring, so please read them carefully.

Part 3: Working with Image Data

In the previous part, you completed a series of functions that transform a row of colours. While we hope you had fun visualizing your results using Pygame, this may have felt a little unsatisfying: coloured squares is one thing, but what about real images? In this part of Assignment 1, you’ll apply the functions you wrote in Part 1 to work on real image data!

What is image data?

Every image we see on a computer screen is made up of small squares of various colours called pixels. More precisely, every image is a rectangular grid of pixels; typical image dimensions (e.g., “256-by-256” or “1920-by-1080”) describe the number of columns and rows of pixels in the image—i.e., the image’s width and height.

Pixelated image of Spiderman. Source: https://www.buzzfeed.com/redpony59/can-you-identify-the-avenger-by-their-pixelated-im-74n84ujw9f.

This is the key insight we’ll use to extend our work from Part 4 to two-dimensional images: because images can be decomposed into rows of pixels, we can use comprehensions to apply our “colour row” functions to every row in an image!

  1. Warmup. Open the a1_part3.py starter file. At the top of the file we’ve provided a function warmup2, which will return the pixel data for a small 30-by-15 image file we’ve provided under images/spiderman.png.

    1. First, try running this file in the Python console and then calling warmup2(). You should see a very long list of colour tuples! In fact, \(30 \times 15 = 450\) tuples are displayed in total.

      >>> warmup2()
      [[125, 125, 125], [81, 87, 78], [103, 114, 117], [144, 151, 164], ...
    2. Let’s use the Python library pprint to make the output look a bit nicer. In the Python console, type in the following:

      >>> image_data = warmup2()
      >>> import pprint
      >>> pprint.pprint(image_data)
      [[[125, 125, 125],
        [81, 87, 78],
        [103, 114, 117],
        [144, 151, 164],
        ...

      Better! Notice that the triple [[[ at the start. This confirms that the image data is a list of colour rows, or list of list of lists; if you scroll down, you’ll see the “breaks” where one of the colour rows ends and another begins.

      We can check the total number of colour rows contained in image_data using the len function:

      >>> len(image_data)
      15

      This number corresponds to the height of the image.

    3. Because image_data is a list, we can use list indexing to access the individual colour rows it contains. Try typing the following into the Python console to access the topmost colour row in the image:

      >>> image_data[0]
      [[125, 125, 125], [81, 87, 78], [103, 114, 117], [144, 151, 164], ...

      And because a colour row is itself a list, we can also find its size, which is the number of pixels in a single row of the image—i.e., the image’s width.

      >>> len(image_data[0])
      30
    4. Finally, inside the body of warmup2, try uncommenting (i.e., deleting the # and space) the a1_helpers.show_colour_rows.pygame function call, and then re-running the file and calling warmup2 again. You should see a new Pygame window appear showing the individual colours of the image! If you have the patience and time, you can count to make sure there are indeed 15 rows displayed, each with 30 squares.

      Note: like with warmup1, the Python interpreter will wait for you to close the Pygame window before continuing to the return statement of the function. This means you won’t be able to see/access the colour rows in the Python console until you close the Pygame window!

  2. Practice transforming multiple colour rows. Now that you’ve explored the data types we use to represent image data, let’s actually get to transforming them! We provide you with five completed functions that directly rely on your functions from part 1: remove_red_in_image, greyscale_image, sepia_image, pixelate_rows_in_image, and crop_rows_in_image.

    Each of these functions use a list comprehension together with one of your functions from Part 1 to apply a transformation to each colour row in image data.

    We can use these functions to play around and see how all of your work can be used on some real images!

    Near the bottom of a1_part3.py, we’ve provided the skeleton of a function (transform_image) that you can use to read in an image file, perform some transformations, and then save the file.

    Experiment with this function on the provided files images/spiderman.png and images/sadias_zoo.jpeg, or copy your own images into the images/ folder and use them instead!

    This part is not for credit (we aren’t grading any changes you make to the transform_image function, except checking the code with PythonTA), so feel free to spend as much or as little time as you like on it.

    But if someone asks you what you’re working on for your first assignment in CSC110, you can use this function to show off images you’ve made! 😎

  3. Generalized cropping. Complete the function crop_image, which enables cropping out both columns and rows. In addition to completing the function body, you are required to write at least one example doctest for this function, which should follow our usual guidelines for good function examples. As we saw with the function design recipe, you should write the example first, to help you understand what the function should do.

    This function follows a similar structure to the transformation functions we already completed for you, but you’ll need to do something a bit different to select only a subset of the rows to keep.

    We have provided one constant EXAMPLE_PIXEL_GRID that you might find helpful to use in your tests. You can also uncomment the line in transform_image which calls this function once you are done, to test your work out with real images.

    Once again, you may NOT use list slicing for this function, or anywhere on this assignment!

Submission instructions

Please proofread and test your work carefully before your final submission! As we explain in Assignment Expectations, it is essential that your submitted code not contain syntax errors. Python files that contain syntax errors will receive a grade of 0 on all automated testing components (though they may receive partial or full credit on any TA grading for assignments). You have lots of time to work on this assignment and check your work (and right-click -> “Run in Python Console”), so please make sure to do this regularly and fix syntax errors right away.

  1. Login to MarkUs and go to the CSC110 course.
  2. Go to Assignment 1, and the “Submissions” tab.
  3. Submit the following files: honour_code.txt, a1_part1.py, a1_part2.py, a1_part3.py. Please note that MarkUs is picky with filenames, and so your filenames must match these exactly, including using lowercase letters.
  4. Refresh the page, and then download each file to make sure you submitted the right version.

Remember, you can submit your files multiple times before the due date. So you can aim to submit your work early, and if you find an error or a place to improve before the due date, you can still make your changes and resubmit your work.

After you’ve submitted your work, please give yourself a well-deserved pat on the back and go do something fun or look at some art or eat some chocolate or take a nap!

Strawberry taking a nap among shoes