简体   繁体   中英

Grouping neighbors in a 2D list in python

I have a 2D list which is a game board containing either an empty space which is a dot (.), an "x" or an "o". I also have a list called groups = [] Each element in this list will be a another list (group), and that list will contain n amount of lists, where each list will look like this: [x, y] (x and y coordinates).

A group is made only of o's or x'es. A group might only be one stone (x or o). A stone belongs to a group if it has one or more connections. A connection is where the stone (x for example) has a same "color" (x - black, o - white) stone next to it (x - 1, x + 1) or above/below it (y - 1, y + 1). Here's a 4x4 board example.

[['x', 'x', '.', 'x'],
 ['x', '.', '.', '.'],
 ['.', '.', '.', '.'],
 ['x', 'x', '.', '.']]

here it would look like this:

groups = [ [ [0, 0], [1, 0], [0, 1] ], [ [0, 3], [1, 3] ], [ [3, 0] ] ]

I've came up with a code, which works but isn't efficient enough. Also, if my explanation of the problem isn't good enough, write a comment and I'll try to explain it further. Here's the code I've came up with:

    if len(cells) > 0:
        while len(niezuzyte) != 0:   
            grupa = [niezuzyte[0]]
            zuzyte.append(niezuzyte[0])
            niezuzyte = [x for x in cells if x not in zuzyte]
            for ziomek in grupa:
                for cel in cells:
                    if abs(cel[0] - ziomek[0]) <= 1 and abs(cel[1] - ziomek[1]) <= 1:
                        if abs(cel[0] - ziomek[0]) + abs(cel[1] - ziomek[1]) < 2 and cel not in grupa:
                                zuzyte.append(cel)
                                grupa.append(cel)
                do_wyjeby.append(grupa)
                niezuzyte = [x for x in cells if x not in zuzyte]

the list names are polish but I hope it doesn't matter too much, cells is a group with indexes of all either x'es or o's on the board. The code also gives me groups of [y, x] instead of [x, y] but that shouldn't matter too much. Any tips/help would be greatly appreaciated, thanks!

You are looking for labelling of connected components, which is provided by, among others, scikit-image . Start by turning your list into an array:

import numpy as np
board = np.array([
    ['x', 'x', '.', 'x'],
    ['x', '.', '.', '.'],
    ['.', '.', '.', '.'],
    ['x', 'x', '.', '.']
])

You can get the connected components for a given "color" by masking that color and getting the labels for it:

from skimage.measure import label
labels, count = label(board == 'x', return_num=True, connectivity=1)

You can extract the coordinates into the format you want using np.argwhere :

groups = [np.argwhere(labels == x) for x in range(1, count + 1)]

To get coordinates of o s, do the same thing, but for board == 'o' . Keep in mind that the data format here is not particularly useful for indexing, so unless you have some specific analysis in mind, you might consider other formats.

If you want the x and o data intermingled, skimage.measure.label will correctly label any different regions in an integer image. For example:

board = np.array([
    ['x', 'x', '.', 'x'],
    ['x', '.', '.', '.'],
    ['.', 'o', 'o', 'o'],
    ['x', 'x', '.', '.']
])
iboard = np.zeros_like(board, dtype='uint8')
iboard[board == 'x'] = 1
iboard[board == 'o'] = 2
labels, count = label(iboard, return_num=True, connectivity=1)

Now labels will look like this:

array([[1, 1, 0, 2],
       [1, 0, 0, 0],
       [0, 3, 3, 3],
       [4, 4, 0, 0]])

You can then apply the comprehension with np.argwhere to all the labels at once.

It looks as though you need to implement a clustering algorithm.

First split the board into two copies, one where there is only "o" and empty space (".") and one where there is only "x" and empty space(".").

You can then implement a version of DBSCAN, using the 'cityblock' (L1) metric instead of 'euclidean' (L2).

https://www.kdnuggets.com/2020/04/dbscan-clustering-algorithm-machine-learning.html

In my own projects, I was able to directly use sklearn.cluster.DBSCAN , however, I wouldn't recommend it here. sklearn.cluster.DBSCAN seems to use floating-point values for distance even with the 'cityblock' metric, so using it on a low-resolution image (such as your game board) is not likely to work properly. Reimplementing it on your own shouldn't be so bad.

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