Поиск шаблона в сетке Python [дубликат]
У меня случайно сгенерированная сетка, содержащая 0 и 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
Как я могу выполнить итерацию по сетке, чтобы найти самый большой кластер единиц , который равен или превышает 4 элемента (по строке и столбцу)?
Я предполагаю, что мне нужно вести подсчет каждого найденного кластера во время итерации, а также более четырех элементов, записывать и подсчитывать в списке, а затем находить наибольшее число.
Проблема в том, что я не могу понять, как это сделать в строках и столбцах и записать счетчик. Я прошел через сетку, но не знаю, как продвинуться дальше двух строк.
Например, в приведенном выше примере самый большой кластер - 8. В сетке есть еще несколько кластеров, но они имеют 4 элемента:
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 BB 0 0 1 1
0 0 BB 1 1 1 0
0 1 0 0 1 0 1 1
Код, который я пробовал:
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
Ответы
Я реализовал три алгоритма.
Первый алгоритм Simple, использующий простейший подход вложенных циклов, имеет O(N^5) временную сложность (где в нашем случае N- одна сторона входной сетки 10), для наших входов размером 10x10time O(10^5)вполне нормально. Algo идентификатор в коде algo = 0. Если вы просто хотите увидеть этот алгоритм, перейдите к строке ------ Simple Algorithmвнутри кода.
Второй алгоритм Advanced, используя подход динамического программирования , его сложность O(N^3)намного быстрее, чем первый алгоритм. Algo идентификатор в коде algo = 1. Перейти к строке ------- Advanced Algorithmвнутри кода.
Третий алгоритм, который Simple-ListCompя реализовал просто для развлечения, он почти такой же, как Simpleи та же O(N^5)сложность, но с использованием понимания списков Python вместо обычных циклов, поэтому он короче, а также немного медленнее, потому что не использует некоторые оптимизации. Algo идентификатор в коде algo = 2. Перейдите к строке ------- Simple-ListComp Algorithmвнутри кода, чтобы увидеть алгоритм.
Остальной код, помимо алгоритмов, реализует проверку правильности результатов (двойная проверка между алгоритмами), печать результатов, создание текстовых вводов. Код разделен на функцию решения задачи и функцию solve()тестирования test(). solve()Функция имеет много аргументов, позволяющих настроить поведение функции.
Все основные строки кода задокументированы комментариями, прочтите их, чтобы узнать, как использовать код. В основном, если sпеременная содержит многострочный текст с элементами сетки, как и в вашем вопросе, вы просто запускаете, solve(s, text = True)и он решает задачу и печатает результаты. Также вы можете выбрать алгоритм из двух версий (0 (простой) и 1 (расширенный) и 2 (Simple-ListComp)), указав следующие аргументы для функции решения algo = 0, check = False(здесь 0 для алгоритма 0). Посмотрите на test()тело функции, чтобы увидеть простейший пример использования.
Алгоритмы вывода на консоль по умолчанию все кластеры, от самых больших до самых маленьких, по величине обозначается .символом, остальное по B, C, D, ..., Zсимволы. Вы можете установить аргумент show_non_max = Falseв функции решения, если хотите, чтобы отображался только первый (самый большой) кластер.
Я объясню простой алгоритм:
- В основном то, что делает алгоритм - он перебирает все возможные угловые
1sпрямоугольники и сохраняет информацию о максимальном из них вma2D-массив.Top-leftточка такого прямоугольника(i, j),top-right-(i, k),bottom-left-(l, j + angle_offset),bottom-right-(l, k + angle_offset), все 4 угла, поэтому у нас так много петель. - Во внешних двух циклах
i(строка),j(столбец) мы перебираем всю сетку, эта(i, j)позиция будетtop-leftточкой1sпрямоугольника, нам нужно перебирать всю сетку, потому что все возможные1sпрямоугольники могут находитьсяtop-leftв любой(row, col)точке всей сетки. В началеjцикла мы проверяем, что сетка в(i, j)позиции всегда должна содержать,1потому что внутри циклов мы ищем весь прямоугольник1sтолько с. kцикл перебирает все возможныеtop-rightпозиции(i, k)в1sпрямоугольнике. Мы должны выйти из цикла, если(i, k)равно,0потому что нет смысла расширятьсяkвправо, потому что такой прямоугольник всегда будет содержать0.- В предыдущих петлях мы закрепили
top-leftиtop-rightуглы прямоугольника. Теперь нам нужно найти два нижних угла. Для этого нам нужно растянуть прямоугольник вниз под разными углами, пока не достигнем первого0. offцикл пытается расширить прямоугольник вниз на все возможные углы (0(прямая вертикаль),+1(45градусы, сдвинутые вправо сверху вниз),-1(-45градусы)), вoffосновном это такое число, котороеgrid[y][x]находится «вверху» (соответствует byY)grid[y + 1][x + off].lпытается расширить прямоугольник вниз (поYнаправлению) под разными угламиoff. Он расширяется до первого,0потому что он не может быть расширен дальше (потому что каждый такой прямоугольник уже будет содержать0).- Внутри
lцикла естьif grid[l][max(0, j + off * (l - i)) : min(k + 1 + off * (l - i), c)] != ones[:k - j + 1]:условие, в основном оноifпредназначено для проверки того, что последняя строка прямоугольника содержит все,1если это неifвыходит из цикла. Это условие сравнивает дваlistсреза на предмет неравенства. Последняя строка прямоугольника простирается от точки(l, j + angle_offset)(выражениеmax(0, j + off * (l - i)), максимальное ограничение должно быть0 <= X) до точки(l, k + angle_offset)(выражениеmin(k + 1 + off * (l - i), c), минимальное ограничение должно бытьX < c). - Внутри
lцикла есть другие строки,ry, rx = l, k + off * (l - i)вычисляетbottom-rightточку прямоугольника, то(ry, rx)есть(l, k + angle_offset)эта(ry, rx)позиция используется для хранения найденного максимума внутриmaмассива, этот массив хранит все максимальные найденные прямоугольники,ma[ry][rx]содержит информацию о прямоугольнике, который имеетbottom-rightточку(ry, rx). rv = (l + 1 - i, k + 1 - j, off)line вычисляет нового возможного кандидата дляma[ry][rx]входа в массив, возможно, потому чтоma[ry][rx]обновляется только в том случае, если новый кандидат имеет большую площадь1s. Здесьrv[0]значение внутриrvкортежа содержитheightтакой прямоугольник,rv[1]содержитwidthтакой прямоугольник (widthравное длине нижней строки прямоугольника),rv[2]содержит угол такого прямоугольника.- Condition
if rv[0] * rv[1] > ma[ry][rx][0] * ma[ry][rx][1]:и его тело просто проверяют, больше лиrvплощадь, чем текущий максимум внутри массива,ma[ry][rx]и если она больше, то эта запись массива обновляется (ma[ry][rx] = rv). Напомню, что онma[ry][rx]содержит информацию(width, height, angle)о текущем найденном прямоугольнике максимальной площади, у которого естьbottom-rightточка(ry, rx)и есть ониwidth,heightиangle. - Выполнено! После запуска алгоритма массив
maсодержит информацию обо всех прямоугольниках (кластерах) с максимальным углом наклона,1sчтобы все кластеры можно было восстановить и распечатать позже на консоли. Наибольший из всех таких1s-кластеров равен некоторымrv0 = ma[ry0][rx0], просто переберите все элементыmaи найдите такую точку,(ry0, rx0)чтобыma[ry0][rx0][0] * ma[ry0][rx0][1](площадь) была максимальной. Тогда самый большой кластер будет иметьbottom-rightточку(ry0, rx0),bottom-leftточку(ry0, rx0 - rv0[1] + 1),top-rightточку(ry0 - rv0[0] + 1, rx0 - rv0[2] * (rv0[0] - 1)),top-leftточку(ry0 - rv0[0] + 1, rx0 - rv0[1] + 1 - rv0[2] * (rv0[0] - 1))(здесьrv0[2] * (rv0[0] - 1)просто угловое смещение, т.е. насколько сдвинута первая строка поXсравнению с последней строкой прямоугольника).
Попробуйте онлайн!
# ----------------- 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()
Вывод:
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
----------------------------