Pythonic and efficient way of finding adjacent cells in grid

浪尽此生 提交于 2019-12-29 04:45:30

问题


I am building a tile based app in Python using pyglet/openGL wherein I'll need to find the all of the adjacent cells for a given cell. I am working in one quadrant of a Cartesian grid. Each cell has an x and y value indicating it's position in the grid( x_coord and y_coord ). These are not pixel values, rather grid positions. I am looking for an efficient way to get the adjacent cells. At max there are eight possible adjacent cells, but because of the bounds of the grid there could be as few as 3. Pseudo-code for a simple yet probably inefficient approach looks something like this:

def get_adjacent_cells( self, cell ):
     result = []
     x_coord = cell.x_coord
     y_coord = cell.y_coord
     for c in grid.cells:
          if c.x_coord == x_coord and c.y_coord == y_coord: # right
               result.append( c )
          if c.x_coord == x_coord - 1 and c.y_coord == y_coord + 1: # lower right
               result.append( c )
          if c.x_coord == x_coord - 1 and c.y_coord == y_coord: # below
               result.append( c )
          if c.x_coord == x_coord - 1 and c.y_coord == y_coord - 1: lower left
               result.append( c )
          if c.x_coord == x_coord and c.y_coord == y_coord - 1: right
               result.append( c )
          // -- similar conditional for remaining cells

This would probably work just fine, though it is likely that this code will need to run every frame and in a larger grid it may affect performance. Any ideas for a more streamlined and less cpu intensive approach? Or, should I just roll with this approach?

Thanks in advance.


回答1:


It wasn't clear to me if there was other information in the cells than just the x and y coordinates. In any case, I think that a change of data structures is needed to make this faster.

I assumed that there is extra information in the cells and made grid.cells as a dictionary with the keys being tuples of the coordinates. A similar thing could be done withgrid.cells as a set if there is only the coordinate information in the cells.

def get_adjacent_cells( self, x_coord, y_coord ):
    result = {}
    for x,y in [(x_coord+i,y_coord+j) for i in (-1,0,1) for j in (-1,0,1) if i != 0 or j != 0]:
        if (x,y) in grid.cells:
            result[(x,y)] = grid.cells[(x,y)]

Depending on what you want to do with the data, you might not want to make result a dict, but hopefully you get the idea. This should be much faster than your code because your code is making 8 checks on every cell in grid.cells.




回答2:


Your code is going to be as slow as large is your grid, because you're iterating over the cells just to get 8 of them (of which you already know their coordinates).

If you can do random access by their indices, I suggest something like the following:

adjacency = [(i,j) for i in (-1,0,1) for j in (-1,0,1) if not (i == j == 0)] #the adjacency matrix

def get_adjacent_cells( self, cell ):
     x_coord = cell.x_coord
     y_coord = cell.y_coord
     for dx, dy in adjacency:
          if 0 <= (x_coord + dx) < max_x and 0 <= y_coord + dy < max_y: #boundaries check
#yielding is usually faster than constructing a list and returning it if you're just using it once
              yield grid[x_coord + dx, y_coord + dy]

max_x and max_y are supposed to be the size of the grid, and the grid.__getitem__ is supposed to accept a tuple with the coordinates and return the cell in that position.




回答3:


Well, this won't help performance any, but you can avoid code duplication by saying

if abs(c.x_coord - x_coord) == 1 or abs(c.y_coord - y_coord) == 1:
    result.append(c)

To affect performance, your grid cells should know who their neighbors are, either through an attribute like c.neighbors, or through an implicit structure, like a list of lists, so you can access by coordinate.

grid = [[a,b,c],
        [d,e,f],
        [g,h,i]]

Then you can check for neighborliness using the list indices.




回答4:


This is probably the most efficient way to look for neighbours if grid.cells is implemented as a set (though there's a mistake in the first if-statement - you need to test for equality to x_coord + 1 rather than to x_coord).

However, implementing grid.cells as a list of lists would allow you to refer to individual cells by row and column number. It would also allow you to measure the total number of rows and columns. get_adjacent_cells could then work by first checking which edges border the current cell, and then looking up the neighbours in all other directions and appending them to the result list.




回答5:


In a grid, adjacency means you need only one step of either coordinate to reach the other if I'm not mistakenly or high.

 if abs(c.x_coord -_coord +c.y_coord-y_coord) == 1
     print "they are adjacent!"



回答6:


This works with numpy arrays

def get_adjacent_cells(arr, selected_idxs):
    """
    >>> arr = np.ones((3,))
    >>> get_adjacent_cells(arr, {(1,)})
    {(0,), (1,), (2,)}
    >>> arr = np.ones((3,2))
    >>> get_adjacent_cells(arr, {(1,1)})
    {(0, 1), (1, 0), (1, 1), (2, 1)}
    >>> arr = np.ones((3,2,3))
    >>> {(0, 1, 0), (1, 0, 0), (1, 1, 0), (1, 1, 1), (2, 1, 0)}
    >>> arr = np.ones((3,2,3))
    >>> get_adjacent_cells(arr, {(1,1,0), (0,1,0)})
    {(0, 0, 0), (0, 1, 0), (0, 1, 1), (1, 0, 0), (1, 1, 0), (1, 1, 1), (2, 1, 0)}
    """
    w = np.asarray(list(selected_idxs))
    new_idxs = []
    for col in range(w.shape[1]):
        w_ = w.copy()
        w_[:,col] += 1
        new_idxs.extend(list(w_))
        w_ = w.copy()
        w_[:,col] -= 1
        new_idxs.extend(list(w_))

    new_idxs = np.array(new_idxs)

    # remove out of bounds coordinates
    for col, dim_size in enumerate(arr.shape):
        new_idxs = new_idxs[(new_idxs[:, col] >= 0) & (new_idxs[:, col] < dim_size)]

    return selected_idxs.union(map(tuple, new_idxs))



来源:https://stackoverflow.com/questions/2373306/pythonic-and-efficient-way-of-finding-adjacent-cells-in-grid

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!