Encontrando um padrão em uma grade Python [duplicado]
Eu gerei aleatoriamente uma grade contendo 0 e 1:
1 1 0 0 0 1 0 1
1 1 1 0 1 1 1 1
1 0 0 0 1 0 1 1
0 0 1 0 1 0 1 1
1 1 1 1 0 0 1 1
0 0 1 1 1 1 1 0
0 1 0 0 1 0 1 1
Como posso iterar pela grade para encontrar o maior cluster de 1s , que é igual ou maior do que 4 itens (em linha e coluna)?
Presumo que preciso manter uma contagem de cada cluster encontrado durante a iteração e uns de seus mais de 4 itens, registrar e contar em uma lista e, em seguida, encontrar o maior número.
O problema é que não consigo descobrir como fazer isso em linhas e colunas e registrar a contagem. Eu fiz uma iteração na grade, mas não tenho certeza de como ir além de duas linhas.
Por exemplo, no exemplo acima, o maior cluster é 8. Existem alguns outros clusters na grade, mas eles têm 4 elementos:
AA 0 0 0 1 0 1
A A 1 0 1 1 1 1
1 0 0 0 1 0 1 1
0 0 1 0 1 0 1 1
1 1 1 BB 0 0 1 1
0 0 BB 1 1 1 0
0 0 1 1
O código que tentei:
rectcount = []
for row in range(len(grid)):
for num in range(len(grid[row])):
# count = 0
try:
# if grid[row][num] == 1:
# if grid[row][num] == grid[row][num + 1] == grid[row + 1][num] == grid[row + 1][num + 1]:
# count += 1
if grid[row][num] == grid[row][num + 1]:
if grid[row + 1][num] == grid[row][num + 1]:
count += 1
# if grid[row][num] == grid[row][num + 1] and grid[row][num] == grid[row + 1][num]:
# count += 1
else:
count = 0
if grid[row][num] == grid[row + 1][num]:
count += 1
except:
pass
Respostas
Eu implementei três algoritmos.
O primeiro algoritmo é Simple, usando a abordagem mais fácil de loops aninhados, ele tem O(N^5) complexidade de tempo (onde Nestá um lado da grade de entrada, 10para o nosso caso), para nossas entradas de tamanho, o 10x10tempo O(10^5)está bem. Algo id no código é algo = 0. Se você apenas deseja ver esse algoritmo, pule para a linha ------ Simple Algorithmdentro do código.
O segundo algoritmo é Advanced, usando a abordagem de Programação Dinâmica , sua complexidade O(N^3)é muito mais rápida do que o primeiro algoritmo. Algo id no código é algo = 1. Pule para a linha ------- Advanced Algorithmdentro do código.
Terceiro algoritmo Simple-ListCompque implementei só por diversão, é quase igual Simple, mesma O(N^5)complexidade, mas usando as compreensões de lista do Python em vez de loops regulares, é por isso que é mais curto, também um pouco mais lento porque não usa algumas otimizações. Algo id no código é algo = 2. Vá para a linha ------- Simple-ListComp Algorithmdentro do código para ver o algo.
O resto do código, além dos algoritmos, implementa a verificação da exatidão dos resultados (verificação dupla entre algoritmos), impressão dos resultados, produção de entradas de texto. O código é dividido em função de tarefa de solução e função de solve()teste test(). solve()function tem muitos argumentos para permitir a configuração do comportamento da função.
Todas as linhas de código principais são documentadas por comentários, leia-os para aprender como usar o código. Basicamente, se a svariável contém texto de várias linhas com elementos de grade, o mesmo que na sua pergunta, basta executar solve(s, text = True)e ele resolverá a tarefa e imprimirá os resultados. Você também pode escolher o algoritmo entre duas versões (0 (Simples) e 1 (Avançada) e 2 (Comp-Lista Simples)) fornecendo os próximos argumentos para resolver a função algo = 0, check = False(aqui 0 para algo 0). Observe o test()corpo da função para ver o exemplo mais simples de uso.
Algoritmos de saída para o console por padrão todos os clusters, do maior para o menor, maior é representada por .símbolo, o resto por B, C, D, ..., Zsímbolos. Você pode definir o argumento show_non_max = Falsena função solve se quiser que apenas o primeiro (maior) cluster seja mostrado.
Vou explicar o algoritmo simples:
- Basicamente, o que o algoritmo faz - ele pesquisa todos os
1sretângulos angulares possíveis e armazena informações sobre o máximo deles em umamamatriz 2D.Top-leftponto de tal retângulo é(i, j),top-right-(i, k),bottom-left-(l, j + angle_offset),bottom-right-(l, k + angle_offset), todos os 4 cantos, é por isso que temos tantos loops. - Nos dois loops externos
i(linha),j(coluna), iteramos sobre a grade inteira, esta(i, j)posição será otop-leftponto do1sretângulo, precisamos iterar a grade inteira porque todos os1sretângulos possíveis podem tertop-leftem qualquer(row, col)ponto da grade inteira. No início dojloop, verificamos se a grade na(i, j)posição deve sempre conter,1porque dentro dos loops procuramos todo o retângulo com1sapenas. kloop itera por todas astop-rightposições possíveis(i, k)do1sretângulo. Devemos quebrar o loop se for(i, k)igual a0porque não há ponto para estenderkmais para a direita porque esse retângulo sempre conterá0.- Em laços prévios que fixa
top-leftetop-rightcantos do rectângulo. Agora precisamos pesquisar dois cantos inferiores. Para isso, precisamos estender o retângulo para baixo em diferentes ângulos até chegarmos primeiro0. offo loop tenta estender o retângulo para baixo em todos os ângulos possíveis (0(reto vertical),+1(45graus deslocados para a direita de cima para baixo),-1(-45graus)),offbasicamente é esse número quegrid[y][x]está "acima" (corresponde a porY)grid[y + 1][x + off].ltenta estender o retângulo para baixo (naYdireção) em ângulos diferentesoff. Ele é estendido até o primeiro0porque não pode ser estendido além (porque cada retângulo já conterá0).- Dentro do
lloop existe umaif grid[l][max(0, j + off * (l - i)) : min(k + 1 + off * (l - i), c)] != ones[:k - j + 1]:condição, basicamente, issoifserve para verificar se a última linha do retângulo contém tudo,1senão isso,ifquebra do loop. Essa condição compara duaslistfatias de não igualdade. A última linha do retângulo se estende de um ponto(l, j + angle_offset)(expressãomax(0, j + off * (l - i)), limitado ao máximo0 <= X) ao ponto(l, k + angle_offset)(expressãomin(k + 1 + off * (l - i), c), limitado ao mínimoX < c). - Dentro do
lloop existem outras linhas,ry, rx = l, k + off * (l - i)calcula obottom-rightponto do retângulo(ry, rx)que é(l, k + angle_offset), esta(ry, rx)posição é usada para armazenar o máximo encontrado dentro domaarray, este array armazena todos os retângulos máximos encontrados,ma[ry][rx]contém informações sobre o retângulo que tembottom-rightum ponto(ry, rx). rv = (l + 1 - i, k + 1 - j, off)line calcula o novo candidato possível para ama[ry][rx]entrada da matriz, possível porquema[ry][rx]é atualizado apenas se o novo candidato tiver uma área maior de1s. Aqui, orv[0]valor dentro darvtupla contémheightde tal retângulo,rv[1]contémwidthde tal retângulo (widthigual ao comprimento da linha inferior do retângulo),rv[2]contém o ângulo de tal retângulo.- A condição
if rv[0] * rv[1] > ma[ry][rx][0] * ma[ry][rx][1]:e seu corpo apenas verificam se arvárea é maior do que o máximo atual dentro do arrayma[ry][rx]e, se for maior, esta entrada do array é atualizada (ma[ry][rx] = rv). Vou lembrar quema[ry][rx]contém informações(width, height, angle)sobre o retângulo de área máxima encontrado atualmente que tembottom-rightponto em(ry, rx)e que tem esteswidth,heighteangle. - Feito! Após a execução do algoritmo, o array
macontém informações sobre todos os retângulos angulares de área máxima (clusters) de, de1smodo que todos os clusters possam ser restaurados e impressos posteriormente no console. O maior de todos esses1s-clusters é igual a algunsrv0 = ma[ry0][rx0], apenas itere uma vez por todos os elementos demae encontre esse ponto(ry0, rx0)para quema[ry0][rx0][0] * ma[ry0][rx0][1](área) seja máxima. Então, o maior cluster terábottom-rightponto(ry0, rx0),bottom-leftponto(ry0, rx0 - rv0[1] + 1),top-rightponto(ry0 - rv0[0] + 1, rx0 - rv0[2] * (rv0[0] - 1)),top-leftponto(ry0 - rv0[0] + 1, rx0 - rv0[1] + 1 - rv0[2] * (rv0[0] - 1))(aquirv0[2] * (rv0[0] - 1)está apenas o deslocamento do ângulo, ou seja, quanto deslocado é a primeira linha ao longo emXcomparação com a última linha do retângulo).
Experimente online!
# ----------------- Main function solving task -----------------
def solve(
grid, *,
algo = 1, # Choose algorithm, 0 - Simple, 1 - Advanced, 2 - Simple-ListComp
check = True, # If True run all algorithms and check that they produce same results, otherwise run just chosen algorithm without checking
text = False, # If true then grid is a multi-line text (string) having grid elements separated by spaces
print_ = True, # Print results to console
show_non_max = True, # When printing if to show all clusters, not just largest, as B, C, D, E... (chars from "cchars")
cchars = ['.'] + [chr(ii) for ii in range(ord('B'), ord('Z') + 1)], # Clusters-chars, these chars are used to show clusters from largest to smallest
one = None, # Value of "one" inside grid array, e.g. if you have grid with chars then one may be equal to "1" string. Defaults to 1 (for non-text) or "1" (for text).
offs = [0, +1, -1], # All offsets (angles) that need to be checked, "off" is such that grid[i + 1][j + off] corresponds to next row of grid[i][j]
debug = False, # If True, extra debug info is printed
):
# Preparing
assert algo in [0, 1, 2], algo
if text:
grid = [l.strip().split() for l in grid.splitlines() if l.strip()]
if one is None:
one = 1 if not text else '1'
r, c = len(grid), len(grid[0])
sgrid = '\n'.join([''.join([str(grid[ii][jj]) for jj in range(c)]) for ii in range(r)])
mas, ones = [], [one] * max(c, r)
# ----------------- Simple Algorithm, O(N^5) Complexity -----------------
if algo == 0 or check:
ma = [[(0, 0, 0) for jj in range(c)] for ii in range(r)] # Array containing maximal answers, Lower-Right corners
for i in range(r):
for j in range(c):
if grid[i][j] != one:
continue
for k in range(j + 1, c): # Ensure at least 2 ones along X
if grid[i][k] != one:
break
for off in offs:
for l in range(i + 1, r): # Ensure at least 2 ones along Y
if grid[l][max(0, j + off * (l - i)) : min(k + 1 + off * (l - i), c)] != ones[:k - j + 1]:
l -= 1
break
ry, rx = l, k + off * (l - i)
rv = (l + 1 - i, k + 1 - j, off)
if rv[0] * rv[1] > ma[ry][rx][0] * ma[ry][rx][1]:
ma[ry][rx] = rv
mas.append(ma)
ma = None
# ----------------- Advanced Algorithm using Dynamic Programming, O(N^3) Complexity -----------------
if algo == 1 or check:
ma = [[(0, 0, 0) for jj in range(c)] for ii in range(r)] # Array containing maximal answers, Lower-Right corners
for off in offs:
d = [[(0, 0, 0) for jj in range(c)] for ii in range(c)]
for i in range(r):
f, d_ = 0, [[(0, 0, 0) for jj in range(c)] for ii in range(c)]
for j in range(c):
if grid[i][j] != one:
f = j + 1
continue
if f >= j:
# Check that we have at least 2 ones along X
continue
df = [(0, 0, 0) for ii in range(c)]
for k in range(j, -1, -1):
t0 = d[j - off][max(0, k - off)] if 0 <= j - off < c and k - off < c else (0, 0, 0)
if k >= f:
t1 = (t0[0] + 1, t0[1], off) if t0 != (0, 0, 0) else (0, 0, 0)
t2 = (1, j - k + 1, off)
t0 = t1 if t1[0] * t1[1] >= t2[0] * t2[1] else t2
# Ensure that we have at least 2 ones along Y
t3 = t1 if t1[0] > 1 else (0, 0, 0)
if k < j and t3[0] * t3[1] < df[k + 1][0] * df[k + 1][1]:
t3 = df[k + 1]
df[k] = t3
else:
t0 = d_[j][k + 1]
if k < j and t0[0] * t0[1] < d_[j][k + 1][0] * d_[j][k + 1][1]:
t0 = d_[j][k + 1]
d_[j][k] = t0
if ma[i][j][0] * ma[i][j][1] < df[f][0] * df[f][1]:
ma[i][j] = df[f]
d = d_
mas.append(ma)
ma = None
# ----------------- Simple-ListComp Algorithm using List Comprehension, O(N^5) Complexity -----------------
if algo == 2 or check:
ma = [
[
max([(0, 0, 0)] + [
(h, w, off)
for h in range(2, i + 2)
for w in range(2, j + 2)
for off in offs
if all(
cr[
max(0, j + 1 - w - off * (h - 1 - icr)) :
max(0, j + 1 - off * (h - 1 - icr))
] == ones[:w]
for icr, cr in enumerate(grid[max(0, i + 1 - h) : i + 1])
)
], key = lambda e: e[0] * e[1])
for j in range(c)
]
for i in range(r)
]
mas.append(ma)
ma = None
# ----------------- Checking Correctness and Printing Results -----------------
if check:
# Check that we have same answers for all algorithms
masx = [[[cma[ii][jj][0] * cma[ii][jj][1] for jj in range(c)] for ii in range(r)] for cma in mas]
assert all([masx[0] == e for e in masx[1:]]), 'Maximums of algorithms differ!\n\n' + sgrid + '\n\n' + (
'\n\n'.join(['\n'.join([' '.join([str(e1).rjust(2) for e1 in e0]) for e0 in cma]) for cma in masx])
)
ma = mas[0 if not check else algo]
if print_:
cchars = ['.'] + [chr(ii) for ii in range(ord('B'), ord('Z') + 1)] # These chars are used to show clusters from largest to smallest
res = [[grid[ii][jj] for jj in range(c)] for ii in range(r)]
mac = [[ma[ii][jj] for jj in range(c)] for ii in range(r)]
processed = set()
sid = 0
for it in range(r * c):
sma = sorted(
[(mac[ii][jj] or (0, 0, 0)) + (ii, jj) for ii in range(r) for jj in range(c) if (ii, jj) not in processed],
key = lambda e: e[0] * e[1], reverse = True
)
if len(sma) == 0 or sma[0][0] * sma[0][1] <= 0:
break
maxv = sma[0]
if it == 0:
maxvf = maxv
processed.add((maxv[3], maxv[4]))
show = True
for trial in [True, False]:
for i in range(maxv[3] - maxv[0] + 1, maxv[3] + 1):
for j in range(maxv[4] - maxv[1] + 1 - (maxv[3] - i) * maxv[2], maxv[4] + 1 - (maxv[3] - i) * maxv[2]):
if trial:
if mac[i][j] is None:
show = False
break
elif show:
res[i][j] = cchars[sid]
mac[i][j] = None
if show:
sid += 1
if not show_non_max and it == 0:
break
res = '\n'.join([''.join([str(res[ii][jj]) for jj in range(c)]) for ii in range(r)])
print(
'Max:\nArea: ', maxvf[0] * maxvf[1], '\nSize Row,Col: ', (maxvf[0], maxvf[1]),
'\nLowerRight Row,Col: ', (maxvf[3], maxvf[4]), '\nAngle: ', ("-1", " 0", "+1")[maxvf[2] + 1], '\n', sep = ''
)
print(res)
if debug:
# Print all computed maximums, for debug purposes
for cma in [ma, mac]:
print('\n' + '\n'.join([' '.join([f'({e0[0]}, {e0[1]}, {("-1", " 0", "+1")[e0[2] + 1]})' for e0_ in e for e0 in (e0_ or ('-', '-', 0),)]) for e in cma]))
print(end = '-' * 28 + '\n')
return ma
# ----------------- Testing -----------------
def test():
# Iterating over text inputs or other ways of producing inputs
for s in [
"""
1 1 0 0 0 1 0 1
1 1 1 0 1 1 1 1
1 0 0 0 1 0 1 1
0 0 1 0 1 0 1 1
1 1 1 1 0 0 1 1
0 0 1 1 1 1 1 0
0 1 0 0 1 0 1 1
""",
"""
1 0 1 1 0 1 0 0
0 1 1 0 1 0 0 1
1 1 0 0 0 0 0 1
0 1 1 1 0 1 0 1
0 1 1 1 1 0 1 1
1 1 0 0 0 1 0 0
0 1 1 1 0 1 0 1
""",
"""
0 1 1 0 1 0 1 1
0 0 1 1 0 0 0 1
0 0 0 1 1 0 1 0
1 1 0 0 1 1 1 0
0 1 1 0 0 1 1 0
0 0 1 0 1 0 1 1
1 0 0 1 0 0 0 0
0 1 1 0 1 1 0 0
"""
]:
solve(s, text = True)
if __name__ == '__main__':
test()
Resultado:
Max:
Area: 8
Size Row,Col: (4, 2)
LowerRight Row,Col: (4, 7)
Angle: 0
CC000101
CC1011..
100010..
001010..
1BBB00..
00BBBDD0
010010DD
----------------------------
Max:
Area: 6
Size Row,Col: (3, 2)
LowerRight Row,Col: (2, 1)
Angle: -1
10..0100
0..01001
..000001
0BBB0101
0BBB1011
CC000100
0CC10101
----------------------------
Max:
Area: 12
Size Row,Col: (6, 2)
LowerRight Row,Col: (5, 7)
Angle: +1
0..01011
00..0001
000..010
BB00..10
0BB00..0
001010..
10010000
01101100
----------------------------