sdasdadas
sdasdadas

Reputation: 25096

Python / Pygame - Nearest Coordinates on Grid

This is being implemented with Python and Pygame but it's a fairly general programming question (meaning implementation independent).

I have a function which takes as input an x and y integer and should generate a 3x3 grid of neighbouring points (the x and y included).

Note: the 0,0 origin begins at the top left. x increases as you move right, y increases as you move down.

Eg.

def nearest_grid(x, y):
    return [[(x-1,y-1),(x,y-1),(x+1,y-1)],[(x-1,y)(x,y),(x+1,y)],[(x-1,y+1),(x,y+1),(x+1,y+1)]]

So, given a grid and a point (marked p) it returns the following as a list of 3 lists:

x  x  x
x  p  x
x  x  x

Is this the most effective / legible way to do this in Python?

EDIT: Suppose I wanted to pass a radius value (where the above radius value would be 1). So, if I passed a radius value of 2 then the above method would quickly become tiresome. Is there a more general way?

Upvotes: 2

Views: 751

Answers (2)

senderle
senderle

Reputation: 150957

I rather like this numpy-based solution:

>>> import numpy
>>> def nearest_grid(x, y, radius=1):
...     X, Y = numpy.mgrid[-radius:radius + 1, -radius:radius + 1]
...     return numpy.dstack((X + x, Y + y))
... 
>>> nearest_grid(1, 2)
array([[[0, 1],
        [0, 2],
        [0, 3]],

       [[1, 1],
        [1, 2],
        [1, 3]],

       [[2, 1],
        [2, 2],
        [2, 3]]])

Here's a highly generalized version that accepts any number of coordinates. This doesn't split the return list into a grid; it just returns a flat list of neighbors for simplicity.

>>> def nearest_grid(*dims, **kwargs):
...     radius = kwargs.get('radius', 1)
...     width = radius * 2 + 1
...     dims = (d - radius for d in dims)
...     return list(itertools.product(*(xrange(d, d + width) for d in dims)))
... 
>>> nearest_grid(1, 2, 3, radius=1)
[(0, 1, 2), (0, 1, 3), (0, 1, 4), (0, 2, 2), (0, 2, 3), (0, 2, 4), 
 (0, 3, 2), (0, 3, 3), (0, 3, 4), (1, 1, 2), (1, 1, 3), (1, 1, 4), 
 (1, 2, 2), (1, 2, 3), (1, 2, 4), (1, 3, 2), (1, 3, 3), (1, 3, 4), 
 (2, 1, 2), (2, 1, 3), (2, 1, 4), (2, 2, 2), (2, 2, 3), (2, 2, 4), 
 (2, 3, 2), (2, 3, 3), (2, 3, 4)]

Note that these both return indices in the opposite order you requested. Superficially, this simply means that you need only reverse the order of arguments -- i.e. pass (y, x) or (z, y, x) instead of (x, y) or (x, y, z). I could have done this for you, but observe the problem with this approach.

>>> def nearest_grid(x, y, radius=1):
...     X, Y = numpy.mgrid[-radius:radius + 1, -radius:radius + 1]
...     return numpy.dstack((Y + y, X + x))
... 
>>> grid
array([[[0, 0],
        [1, 0],
        [2, 0]],

       [[0, 1],
        [1, 1],
        [2, 1]],

       [[0, 2],
        [1, 2],
        [2, 2]]])

Now we have a grid in which the values are stored in [x, y] order. What happens when we use them as indices to grid?

>>> grid = nearest_grid(1, 1)
>>> x, y = 0, 2
>>> grid[x][y]
array([2, 0])

We don't get the cell we expected! That's because with a grid laid out like so:

grid = [[(x, y), (x, y), (x, y)],
        [(x, y), (x, y), (x, y)],
        [(x, y), (x, y), (x, y)]]

grid[0] gives us the first row, i.e. the y = 0 row. So now we have to reverse the order:

>>> grid[y][x]
array([0, 2])

Better to store values in row-major ((y, x)) order.

Upvotes: 2

Hugh Bothwell
Hugh Bothwell

Reputation: 56634

def nearby_grid_points(x, y, r=1):
    res = []
    for dy in xrange(-r, r+1):
        res.append([(x+dx, y+dy) for dx in xrange(-r, r+1)])
    return res

Upvotes: 5

Related Questions