The Setting
I'm writing a class in python that manipulates a 2-D square array of booleans.
class Grid(object):
def __init__(self, length):
self.length = length
self.grid = [[False]*length for i in range(length)]
def coordinates(self, index):
return (index // self.length, index % self.length)
Sometimes in my application it makes sense to access an item by its coordinates, but sometimes it makes more sense to access an item by its index. I also frequently need to falsify or truthify a batch of items at once. Without making the class very complicated, I can do this like so:
g = Grid(8)
# access by coordinates
g.grid[4][3] = True
# access by index
coords = g.coordinates(55)
g[coords[0]][coords[1]] = True
# truthify a batch of coordinates
to_truthify = [(1, 3), (2, 3), (2, 7)]
for row, col in to_truthify:
g.grid[row][col] = True
# falsify a batch of indices
to_falsify = [19, 22, 60]
for i in to_falsify:
coords = g.coordinates(i)
g.grid[coords[0]][coords[1]] = False
The First Steps
Naturally, I want to add some mutator methods to my Grid object to make it so that I don't have to directly access the objects innards and write a bunch of loops:
def set_coordinate(self, row, col, value):
self.grid[row][col] = bool(value)
def set_index(self, i, value):
coords = self.coordinates(i)
self.set_coordinates(coords[0], coords[1], value)
def set_coordinates(self, coordinates, value):
for row, col in coordinates:
self.set_coordinate(row, col, value)
def set_indices(self, indices, value):
for i in indices:
self.set_index(i, value)
The accessor methods are straightforward as well. I may also want to add some semantically meaningful aliases:
def truthify_coordinate(self, row, col):
self.set_coordinate(row, col, True)
def falsify_coordinate(self, row, col):
self.set_coordinate(row, col, False)
def truthify_coordinates(self, coordinates):
self.set_coordinates(coordinates, True)
... etc ...
The Idea
I want to make a method called, for example, set_item, where the location can be either an iterable of length two representing coordinates or a scalar index.
def set_item(self, location, value):
try:
location = self.coordinates(location)
except TypeError:
pass
self.set_coordinates(location[0], location[1], value)
The Pros and Cons
The upside of this (obviously) is that I don't need to specify whether the location is a pair of coordinates or an index, so when I am setting a batch of locations at once, they need not all be the same time. For example, the following:
indices = [3, 5, 14, 60]
coordinates = [(1, 7), (4, 5)]
g.truthify_indices(indices)
g.truthify_coordinates(coordinates)
becomes
locations = [3, 5, (1, 7), 14, (4, 5), 60]
g.truthify(locations)
which is much cleaner and easier to read and understand, in my opinion.
One of the downsides is that something like g.truthify((2, 3)) is harder to decipher right away (is it setting a coordinate or two indices?). There may be more that I haven't thought of.
The Question
Is implementing this idea is the pythonic thing to do, or should I stick with distinguishing between indices and coordinates explicitly?