Homework 3 - The Grid class
Due by 11:59pm on October 15, 2025
Objectives
- Develop a fully functional class
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
Specification
Grid Class
In Lab 11 you implemented some parts of the Grid
class. In this Homework, you
will complete that class in preparation for Project 2.
We will build different tools to construct and work with grids.
-
Grid(width, height)
- constructs new grid. Initially all locations areNone
-
Grid.width
,Grid.height
- width/height properties -
Grid.get(x, y)
- returns contents at(x, y)
(error if(x, y)
out of bounds) -
Grid.set(x, y, val)
- sets value into the grid at(x, y)
(error if(x, y)
out of bounds) -
build()
- this method takes a nested list and turns it into aGrid
object. -
copy()
- returns a copy of the currentGrid
object -
check_list_malformed()
- This method verifies that the nested list passed to thebuild()
method is of the correct structure (i.e. rectangular) to properly build a grid. -
__str__()
and__repr__()
-
__eq__()
Method Signatures
Here are the remaining method signatures for the methods we still need to create/finish in the Grid
class:
@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.
"""
@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]]
"""
def copy(self):
"""
Return a new grid, a duplicate of the original.
>>> grid = Grid.build([[1, 2], [4, 5]])
>>> grid_copy = grid.copy()
>>> grid_copy is grid
False
"""
def __eq__(self, other):
"""
>>> grid1 = Grid.build([[1, 1, 1], [2, 3, 5]])
>>> grid2 = Grid.build([[1, 1, 1], [2, 3, 5]])
>>> grid_lst = [[1, 1, 1], [2, 3, 5]]
>>> grid1 == grid2
True
>>> grid1 == grid_lst
True
"""
def __repr__(self):
"""
>>> repr(Grid.build([[5, 5], [3, 2]]))
'Grid.build([[5, 5], [3, 2]])'
"""
This homework03.zip file has the test code for this homework and a blank Grid.py file. You can 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.
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 lists is 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.
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 the height and width of the grid from the length 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 complete 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 Grid’s __repr__()
method should return the string Grid.build(...)
, where
...
is the result of repr() called on the object’s internal data array (the
nested list that stores its data).
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.