Creating a graph from the list
For Q1, you can go through the matrix row by row and column by column forming groups using lists associated with the coordintate. Place each coordinate in the list of the connecting positions above or to the left of the current one. Create a new list (group) when there are no connections, Merge the lists when the position connects two different groups.
a=[
["x", "x", 0, 0, "x","x"],
[ 0, 0, 0, 0, "x","x"],
["x", "x", 0, 0, 0, "x"],
["x", "x", 0, 0, 0, 0 ]
]
groups = dict() # group (list) at each coordinate
for r,row in enumerate(a):
for c,v in enumerate(row):
above = groups[r-1,c] if r and a[r-1][c] == v else None
left = groups[r,c-1] if c and a[r][c-1] == v else None
if above and left and above is not left:
above.extend(left) # merge left into above group
for rc in left: groups[rc] = above
g = above or left or [] # select existing group or new
g.append((r,c)) # add r,c to selected group
groups[r,c] = g # identify coordinate's group
# extract distinct groups from coordinate assignments
groups = list({id(g):g for g in groups.values()}.values())
Output:
# all groups (i.e. groups of "x"s and groups of 0s)
print(groups)
[[(0, 1), (0, 0)],
[(1, 2), (3, 2), (1, 3), (3, 5), (3, 3), (0, 2),
(2, 4), (2, 3), (2, 2), (1, 0), (3, 4), (1, 1), (0, 3)],
[(1, 4), (1, 5), (0, 5), (0, 4), (2, 5)],
[(3, 0), (2, 0), (3, 1), (2, 1)]]
# only "x" groups
xGroups = [g for g in groups if a[g[0][0]][g[0][1]] == "x"]
print(xGroups)
[[(0, 1), (0, 0)],
[(1, 4), (1, 5), (0, 5), (0, 4), (2, 5)],
[(3, 0), (2, 0), (3, 1), (2, 1)]]
For Q2, you can write a function that will find the distance between two groups by expanding on the paths that each coordinate of the first group can go through to reach any coordinate of the second group while only going through 0 positions (technically a breadth-first search):
def distance(a,g1,g2):
rows = len(a)
cols = len(a[0])
distance = 1
seen = set(g1) # track visited coordinates
paths = g1 # current reach, starts with g1 coordinates
while paths:
nextPaths = set()
for r,c in paths:
for nr,nc in [(r,c-1),(r,c+1),(r-1,c),(r+1,c)]: #neighbours
if nr not in range(rows): continue
if nc not in range(cols): continue
if (nr,nc) in seen: continue # no doubleback
if (nr,nc) in g2: return distance # g2 reached
if a[nr][nc] == 0:
nextPaths.add((nr,nc)) # advance on 0s
seen.add((nr,nc))
distance += 1 # g2 not reached, need more steps
paths = nextPaths # start from positions reached so far
return None
print(distance(a,xGroups[0],xGroups[1])) # 3
print(distance(a,xGroups[1],xGroups[2])) # 4
print(distance(a,xGroups[0],xGroups[2])) # 2
I'm posting a slightly less efficient, but perhaps better readable algotithm for the Q1.
It starts with any arbitrary "X"
and then adds its neighbours and then neighbours of the neigbours etc until there are no more neighbours, i.e. the whole group of adjacent "X"
s is found.
def neigh4(c):
i, j = c
yield (i-1, j)
yield (i+1, j)
yield (i, j-1)
yield (i, j+1)
def groups(array):
coords = {
(i,j) for i in range(len(array)) for j in range(len(array[0]))
if array[i][j] == "x"}
while coords:
group = set()
wset = {next(iter(coords))} # arbitrary member
while wset:
member = wset.pop()
wset.update(c for c in neigh4(member) if c in coords)
coords.remove(member)
group.add(member)
yield group
print(list(groups(a))) # "a" from the question
Personal note: Over 30 years ago we were given this excercise in a programming competition (in BASIC!). I was 15 years old and failed to solve it.