1

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?

3
  • I think the Pythonic thing to do would be to use numpy. Or is this primarily a programming exercise? Commented Oct 5, 2013 at 1:02
  • @tom10 I guess it's more of a philosophical question than a practical one, but since you mention it, I can definitely see how using numpy is the philosophically correct choice ("don't reinvent the wheel", etc.) as well as the practical one. I guess I figured numpy would be overkill, since I am restricting myself to relatively small square matrices with boolean entries. Is that something I just need to learn to get over as a budding python programmer? Commented Oct 5, 2013 at 1:26
  • 1
    I posted an answer. For numpy though, it's a great tool to have, but not if you'll never use it. I suggest just see what it can do and whether you think you'll use it. Commented Oct 5, 2013 at 1:47

1 Answer 1

4

I think a more Pythonic way to write:

g.truthify_coordinate(row, col)

is to be able to write:

g[row][col] = True   # or g[row, col] = True

The second is much easier to understand when reading it, and that is one of Python's more important guidelines: code is read much more often than it is written. (Though I'm far from an expert as what qualifies as Pythonic.)

That said, it seems that you're redeveloping numpy, and reimplementing a good tool is mistake in most situations. There may be reasons not to use numpy, but it should be explored first. Also, its design will give you ideas for you own approach, should you chose not to use numpy.

Sign up to request clarification or add additional context in comments.

1 Comment

The last paragraph was a great piece of advice, and prompted me to do two things: Firstly, I changed the syntax and method names to mirror numpy's, and secondly, I started a project branch that subclasses numpy's ndarray, so I can switch over if it looks like I'm re-implementing too much. Thanks for your thoughts, they were really helpful!

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.