简体   繁体   中英

Find a nodes 8 neighbors on a 2D grid

I need to find the cardinal and diagonal neighbors of any node in a two dimensional array. Take the below array. I'm searching from the 1 which is [1, 1] on the array. It should return the surrounding 0's. I can find the neighbors just find as shown in some of the below code but they're too slow.

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

I can brute force it like this to return the coordinates of all 8 surrounding nodes.

def ReturnNeighbors(x, y):
    numpy.array([(x-1, y), (x, y-1), (x+1, y), (x, y+1),
               (x-1, y-1), (x+1, y+1), (x-1, y+1), (x+1, y-1)])

def ReturnNeighbors_4(x, y):
    for i in xrange(x - 1, x + 2):
        for j in xrange(y - 1, y + 2):
            if (i, j) != (x, y):
                yield (i, j)

Or by calculating the distance to all nodes and returning those with a distance < 2 but these and the other solutions I've figured out are slow. The whole reason I'm learning to numpy is for the speed. I think I need to use scipy convolve2d or a cKDtree but they're too complicated for me.

The way I handled this before switching to numpy is my array was full of actual node objects as the values. Each object stored its x and y coordinates in the grid and a set of its neighbors. Like this. I can't use this method anymore as filling a gigantic array full of nodes takes ages. Even in a small 100x100 map that's 10000 node objects! I plan to later not only have much larger maps but multiple maps active at any given time. I've actually ran out of memory trying to create bigger maps due to the nodes imprint. It works for little dungeons but not worlds with multiple maps being simulated.

ExampleNode(object):
    def __init__(self, x, y):
        self.x = x
        self.y = y
        self.neighbors = set()

        # The neighbors are found and added to the above set later.

node = Example(0, 0)

for neighbor in node.neighbors:
    do thing with each neighbor

I need a nodes neighbors for a variety of reasons. Smoothing a map using cellular automata, splattering blood nearby in combat, pathfinding with breadth first search and more. This is for a roguelike I'm creating. The brute force method works fine for small 60x60 dungeons but now I'm expanding the scope and generating a world map. It's only 200x200 and irritatingly slow.

Let's assume that the input array is named A that holds all the integers and we are working on just the elements that are 1s in it and would try to get their neighbours. Here's one approach to do so -

# Get offsets for row and column
R_offset,C_offset = np.meshgrid(np.arange(-1,2),np.arange(-1,2))

# Get row and column indices for places where elements are 1s
R_match,C_match = np.nonzero(A==1)

# Store number of matches as it would be frequently used
N = R_match.size

# Get offsetted row, col indices for all matches
R_idx = (R_match[:,None,None] + R_offset).reshape(N,-1)
C_idx = (C_match[:,None,None] + C_offset).reshape(N,-1)

# Based on boundary conditions set invalid ones to zeros
valid_mask = (R_idx>=0) & (C_idx>=0) & (R_idx<A.shape[0]) & (C_idx<A.shape[1])
valid_mask[:,4] = 0 # Set the pivot(self/center) ones to invalid

# Using valid mask, "cut off" elements from each group of 9 elems
cut_idx = valid_mask.sum(1).cumsum()

# Finally form groups
grps_R = np.split(R_idx[valid_mask],cut_idx)[:-1]
grps_C = np.split(C_idx[valid_mask],cut_idx)[:-1]

Sample run and explanation on how to interpret and use the outputs -

In [256]: A
Out[256]: 
array([[1, 0, 0, 0, 0],
       [0, 0, 0, 0, 0],
       [0, 0, 1, 0, 0],
       [0, 0, 0, 0, 1]])

In [257]: grps_R
Out[257]: [array([1, 0, 1]), array([1, 2, 3, 1, 3, 1, 2, 3]), array([2, 3, 2])]

In [258]: grps_C
Out[258]: [array([0, 1, 1]), array([1, 1, 1, 2, 2, 3, 3, 3]), array([3, 3, 4])]

Thus, we have three groups based on the three 1s in A .

The first one is at the top left corner -

In [259]: np.column_stack((R_match[0],C_match[0]))  # First point
Out[259]: array([[0, 0]])

In [260]: np.column_stack((grps_R[0],grps_C[0]))    # Its three neighbors
Out[260]: 
array([[1, 0],
       [0, 1],
       [1, 1]])

The second one is at (2,2) -

In [263]: np.column_stack((R_match[1],C_match[1]))  # Second point 
Out[263]: array([[2, 2]])

In [264]: np.column_stack((grps_R[1],grps_C[1]))    # Its eight neighbors
Out[264]: 
array([[1, 1],
       [2, 1],
       [3, 1],
       [1, 2],
       [3, 2],
       [1, 3],
       [2, 3],
       [3, 3]])

Finally the third one is at (4,5) -

In [265]: np.column_stack((R_match[2],C_match[2]))  # Third point 
Out[265]: array([[3, 4]])

In [266]: np.column_stack((grps_R[2],grps_C[2]))    # Its three neighbors
Out[266]: 
array([[2, 3],
       [3, 3],
       [2, 4]])

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM