Homework 3 - The Grid class
Due by 11:59pm on May 21, 2025
Example Input and Output
grid = Grid.build([[5, 4, 3, 3, 4], [4, 1, 5, 2, 2]]) #new method
repr(grid) #updated repr
>>> 'Grid.build([[5, 4, 3, 3, 4], [4, 1, 5, 2, 2]])'
copy_grid = grid.copy() #new method
grid2 = [[1, 1, 1], [2, 3, 5]]
grid == grid2 #updated
>>> False
Grid.check_list_malformed([[2,1],[55,55,55,55]]) #static method
>>> ValueError
Objectives
- Develop a fully functional class
Introduction
In Lab 11 you implemented some parts of the Grid
class. In this Homework you
will complete that class in preparation for Project 2.
So far you’ve created the following methods in your Grid
class:
__init__()
__str__()
__repr__()
__eq__()
get()
set()
in_bounds()
In this homework you’ll implement the rest of the Grid
interface consisting of
the following methods:
build()
- this method takes a nested list and turns it into aGrid
object.copy()
- returns a copy of the currentGrid
objectcheck_list_malformed()
- This method verifies that the nest list passed to thebuild()
method is of the correct structure (i.e. rectangular) to properly build a grid.__repr__()
- You’ll give this the correct functionality__eq__()
- You’ll expand the functionality of this one.
The homework03.zip file has the test code for this homework and a blank Grid.py file. You can just replace that blank file with the one you’ve already been working on.
Part 1 - Remaining Functionality
Task 1 - check_list_malformed()
(10 pts)
Since the build()
method needs the check_list_malformed()
method, let’s
implement check_list_malformed()
first. This method should have the built-in
Python @staticmethod
decorator added to it. This will allow us to call it
without actually having a Grid
object, which makes sense because
check_list_malformed()
has nothing to do with actually managing a grid. Thus
the function definition for this method should look like:
@staticmethod
def check_list_malformed(lst):
"""
Given a list that represents a 2D nested Grid, check that it has the
right shape. Raise a ValueError if it is malformed.
>>> Grid.check_list_malformed([[1, 2], [4, 5]])
>>> Grid.check_list_malformed(1)
Traceback (most recent call last):
...
ValueError: Input must be a non-empty list of lists.
>>> Grid.check_list_malformed([[1, 2], [4, 5, 6]])
Traceback (most recent call last):
...
ValueError: All items in list must be lists of the same length.
>>> Grid.check_list_malformed([[1, 2], 3])
Traceback (most recent call last):
...
ValueError: Input must be a list of lists.
"""
This is a data sanity check method that verifies that the user’s input data is good (in the correct format) before we try to use it. This method will check to see that the passed in data is in a valid format and throws an exception if it is not. One of the points of Object Oriented programming and data encapsulation is to make sure that data is consistently valid and in a good state.
When we eventually write the build()
method, it will take as input a nested
list where each element in the outer list is a row of the grid and each element
in each of the nested list are the values for the columns. So a list that looks
like this:
lst = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
corresponds to a grid that looks like this:
block-beta columns 6 1:2 2:2 3:2 4:2 5:2 6:2 7:2 8:2 9:2
The purpose of check_list_malformed()
is to verify that the data is in the
correct “nested list” format. The data passed in to build()
has to meet the
following criteria:
- The object passed in should be a list object
- The top-level list should not be empty
- Each element of the list object should also be a list object
- Each element of the top-level list should have the same length
Create the check_list_malformed()
method in your Grid
class and write code
that checks each of the conditions listed above. If the object passed in doesn’t
pass a test, then the code should throw (raise
) a ValueError()
exception
with an appropriate message. Be wary of nesting your code too much – if the
logic starts to get complicated with lots of levels of indentation, ask yourself
there’s a simpler way to check these conditions.
If all the checks pass, the method should just end and does not need to return anything.
NoteIf you want to review static methods, review the section about static methods in Lab 10 here.
Task 2 - build()
(10 pts)
Now that check_list_malformed()
is written, we are ready to write the
build()
method. This method should also have the @staticmethod
decorator
added to it, since we are using it to create a Grid
object and thus don’t have
one yet:
@staticmethod
def build(lst):
"""
Given a list that represents a 2D nested Grid construct a Grid object.
Grid.build([[1, 2, 3], [4, 5 6]])
>>> Grid.build([[1, 2, 3], [4, 5, 6]]).array
[[1, 2, 3], [4, 5, 6]]
"""
This method should do the following:
- Call
check_list_malformed()
on the passed-in list. Do not do this in atry
block. Ifcheck_list_malformed()
method throws an exception, we want it to kill this method as well. Thus, we don’t want to catch the exception here. - Determine height and width of the grid from the len of the list object passed in (the height) and the length of one of the sub lists (the width).
- Create a new
Grid
object with the height and width. - Set the new
Grid
’s array attribute to a deep copy of the list that was passed in. If you remember from your implementation of the__init__()
method, that is the format we are using to represent the grid. - Return the newly created
Grid
object.
In step 4, we do a deep copy so that our grid is fully encapsulated by our class object. If we just assign the existing list to the .array attribute, think about what happens if someone changes the original list passed in? Will that change the data in our object? Why? Remember that part of writing classes is so that the class has completely control over its data and the data can only be changed through the class methods.
To do a deep copy, you can copy the values by hand (a set of loops) or you can
use Python’s deepcopy()
function in the copy
library. Just add:
from copy import deepcopy
to your file to access the function.
In the main code block of your file, write some code to test that build()
is
working and verify that you are getting a separate copy of the data and not
pointing at the same list object that was passed in.
Task 3 - Copy the Grid
(6 pts)
The final bit is to write the copy()
method that will return a copy of the
Grid
object it is invoked on. It takes no parameters and returns a Grid
object that is a copy of the current one.
The implementation of this method is straightforward and can be done in one line of code as you can use methods you’ve already implemented to create the copy. Remember, the copy should be completely independent of the original.
Once you’ve implemented the copy()
method, write some code in your main block
to test it and verify that it is working properly.
Part 2 - Functionality Updates
Task 1 - Update __repr__()
(6 pts)
In Lab 11, we wrote a simple __repr__()
function that just printed the same
thing as the __str__()
method so that the function would be present. Now we’ll
get it fully functional so that it does what __repr__()
is supposed to do:
return a string that can be evaluated to recreate the original object. To do
that, we needed the build()
method that you hadn’t written in Lab 11.
In order to recreate the class we need three things: the height, the width, and
the data. If we look at our build()
method, we can see that the nested list we
pass in provides all three of those essential “ingredients” to construct a grid
object.
The __repr__()
method should return a string containing Grid.build()
, where
build()
takes as an argument the representation provided by the repr()
function of the internal data array (nested list containing the data) of the
current object.
For example, if you create a Grid
object using the build()
method like so:
>>> grid = Grid.build([[1, 2], [3, 4]])
calling the repr()
function on it will return a string formatted like so:
>>> repr(grid)
'Grid.build([[1, 2], [3, 4]])'
Update your __repr__()
method and test it to make sure it works.
Task 2 - Extend the __eq__()
method (6 pts)
Finally, let’s extend the usefulness of our __eq__()
method. Right now, it
only accepts Grid
objects as the items it can compare with “self” (or, the
current grid) to see if their array attributes are the same. You are going to
add to this function to allow comparison between a current grid object and a
nested list.
In the if
statement that checks to see if other
is an instance of Grid
,
add an elif
clause to check if other
is an instance of list
. If it is,
return self.array == other
.
Turn in your work
A Note on GradingThe alert student may have noticed that the parts of this Homework only add up to 38 points. There are still 50 points possible for the assignment, the other 12 points will come from tests on the parts of the class you wrote in Lab 11.
Submit your Grid.py file on Canvas via Gradescope where it will be checked via the auto grader. We will be testing all of the methods of your class to verify that they are working properly. Make sure you name them exactly as we specified.