E=enumerate
def g(b,B):
q=[(x,y)for x,y in B if b[x][y]=='.'];Q=[q.pop(0)];S=[Q[0]]
for x,y in Q:
for X,Y in[(x+1,y),(x-1,y),(x,y-1),(x,y+1)]:
if(u:=(X,Y))in set(B)-set(S)and'.'==b[X][Y]:Q+=u,;S+=u,
return not{*q}-{*S}
F=lambda x,l,c=0,k=0:F(x[1:],l,-~c*(T:=x[0]==l),max(k,c+T))if x else k
def f(n,b):
B=[(x,y)for x,r in E(b)for y,_ in E(r)];D=[(x,y)for x,y in B if'#'==b[x][y]]
return all((n+~x,n+~y)in D for x,y in D)*g(b,B)*all(F(i,'.')>2and n>F(i,'#')for i in b)*all(F(i,'.')>2and n>F(i,'#')for i in zip(*b))
def to_board(s):
n=int(len(s)**.5);return n,[s[i:i+n]for i in range(0,len(s),n)]
TEST = """
`.........`|True|Valid grid
`#..............#`|True|Valid grid
`...#........#...`|True|Valid grid
`...#........#...`|True|Valid grid
`.........`|True|Valid grid
`#...#......#...#`|False|Valid grid
`.........................`|True|Valid grid
`##...#.............#...##`|True|Valid grid
`.................................................`|True|Valid grid
`........................#........................`|True|Valid grid
`....###.....##......##.....##......##.....###....`|True|Valid grid
`................................................................`|True|Valid grid
`##....####....##...........##......##...........##....####....##`|True|Valid grid
`...##.......#...........##.....##.....##...........#.......##...`|True|Valid grid
`#...............`|False|No 180 degree symmetry
`#..##..##..##..#`|False|2-letter entries, filled-in columns
`#........................`|False|No 180 degree symmetry
`.......#...###...#.......`|False|1-letter and 1-letter entries
`######....#....#....#....`|False|No 180 degree symmetry, filled-in column & row
`######...##...##...######`|False|Filled-in columns & rows
`...#......#......#......#......#......#......#...`|False|White squares not contiguous, filled-in column
`.................###....#....###.................`|False|1-letter entries
`...#......#...............##.....................`|False|No 180-degree symmetry
`....#.......#.......#........######........#.......#.......#....`|False|White squares not contiguous
`..#.........#.......#......##......#.......#.......#.........#..`|False|1-letter and 2-letter entries
`.#......#..............................................#......#.`|False|1-letter entries, white squares not contiguous
`...........................##......#............................`|False|No 180-degree symmetry
`####............................................................`|False|No 180-degree symmetry
`#......##......##......##......##......##......##......##......#`|False|Filled-in columns
"""
for i in filter(None, TEST.split('\n')):
a,b,_ = i.split('|')
assert f(*to_board(a[1:-1])) == eval(b)
print('tests passed!')
RT1lbnVtZXJhdGUKZGVmIGcoYixCKToKIHE9Wyh4LHkpZm9yIHgseSBpbiBCIGlmIGJbeF1beV09PScuJ107UT1bcS5wb3AoMCldO1M9W1FbMF1dCiBmb3IgeCx5IGluIFE6CiAgZm9yIFgsWSBpblsoeCsxLHkpLCh4LTEseSksKHgseS0xKSwoeCx5KzEpXToKICAgaWYodTo9KFgsWSkpaW4gc2V0KEIpLXNldChTKWFuZCcuJz09YltYXVtZXTpRKz11LDtTKz11LAogcmV0dXJuIG5vdHsqcX0teypTfQpGPWxhbWJkYSB4LGwsYz0wLGs9MDpGKHhbMTpdLGwsLX5jKihUOj14WzBdPT1sKSxtYXgoayxjK1QpKWlmIHggZWxzZSBrCmRlZiBmKG4sYik6CiBCPVsoeCx5KWZvciB4LHIgaW4gRShiKWZvciB5LF8gaW4gRShyKV07RD1bKHgseSlmb3IgeCx5IGluIEIgaWYnIyc9PWJbeF1beV1dCiByZXR1cm4gYWxsKChuK354LG4rfnkpaW4gRCBmb3IgeCx5IGluIEQpKmcoYixCKSphbGwoRihpLCcuJyk+MmFuZCBuPkYoaSwnIycpZm9yIGkgaW4gYikqYWxsKEYoaSwnLicpPjJhbmQgbj5GKGksJyMnKWZvciBpIGluIHppcCgqYikpCiAKZGVmIHRvX2JvYXJkKHMpOgoJbj1pbnQobGVuKHMpKiouNSk7cmV0dXJuIG4sW3NbaTppK25dZm9yIGkgaW4gcmFuZ2UoMCxsZW4ocyksbildCgpURVNUID0gIiIiCmAuLi4uLi4uLi5gfFRydWV8VmFsaWQgZ3JpZApgIy4uLi4uLi4uLi4uLi4uI2B8VHJ1ZXxWYWxpZCBncmlkCmAuLi4jLi4uLi4uLi4jLi4uYHxUcnVlfFZhbGlkIGdyaWQKYC4uLiMuLi4uLi4uLiMuLi5gfFRydWV8VmFsaWQgZ3JpZApgLi4uLi4uLi4uYHxUcnVlfFZhbGlkIGdyaWQKYCMuLi4jLi4uLi4uIy4uLiNgfEZhbHNlfFZhbGlkIGdyaWQKYC4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi5gfFRydWV8VmFsaWQgZ3JpZApgIyMuLi4jLi4uLi4uLi4uLi4uLiMuLi4jI2B8VHJ1ZXxWYWxpZCBncmlkCmAuLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uYHxUcnVlfFZhbGlkIGdyaWQKYC4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLiMuLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi5gfFRydWV8VmFsaWQgZ3JpZApgLi4uLiMjIy4uLi4uIyMuLi4uLi4jIy4uLi4uIyMuLi4uLi4jIy4uLi4uIyMjLi4uLmB8VHJ1ZXxWYWxpZCBncmlkCmAuLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uYHxUcnVlfFZhbGlkIGdyaWQKYCMjLi4uLiMjIyMuLi4uIyMuLi4uLi4uLi4uLiMjLi4uLi4uIyMuLi4uLi4uLi4uLiMjLi4uLiMjIyMuLi4uIyNgfFRydWV8VmFsaWQgZ3JpZApgLi4uIyMuLi4uLi4uIy4uLi4uLi4uLi4uIyMuLi4uLiMjLi4uLi4jIy4uLi4uLi4uLi4uIy4uLi4uLi4jIy4uLmB8VHJ1ZXxWYWxpZCBncmlkCmAjLi4uLi4uLi4uLi4uLi4uYHxGYWxzZXxObyAxODAgZGVncmVlIHN5bW1ldHJ5CmAjLi4jIy4uIyMuLiMjLi4jYHxGYWxzZXwyLWxldHRlciBlbnRyaWVzLCBmaWxsZWQtaW4gY29sdW1ucwpgIy4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLmB8RmFsc2V8Tm8gMTgwIGRlZ3JlZSBzeW1tZXRyeQpgLi4uLi4uLiMuLi4jIyMuLi4jLi4uLi4uLmB8RmFsc2V8MS1sZXR0ZXIgYW5kIDEtbGV0dGVyIGVudHJpZXMKYCMjIyMjIy4uLi4jLi4uLiMuLi4uIy4uLi5gfEZhbHNlfE5vIDE4MCBkZWdyZWUgc3ltbWV0cnksIGZpbGxlZC1pbiBjb2x1bW4gJiByb3cKYCMjIyMjIy4uLiMjLi4uIyMuLi4jIyMjIyNgfEZhbHNlfEZpbGxlZC1pbiBjb2x1bW5zICYgcm93cwpgLi4uIy4uLi4uLiMuLi4uLi4jLi4uLi4uIy4uLi4uLiMuLi4uLi4jLi4uLi4uIy4uLmB8RmFsc2V8V2hpdGUgc3F1YXJlcyBub3QgY29udGlndW91cywgZmlsbGVkLWluIGNvbHVtbgpgLi4uLi4uLi4uLi4uLi4uLi4jIyMuLi4uIy4uLi4jIyMuLi4uLi4uLi4uLi4uLi4uLmB8RmFsc2V8MS1sZXR0ZXIgZW50cmllcwpgLi4uIy4uLi4uLiMuLi4uLi4uLi4uLi4uLi4jIy4uLi4uLi4uLi4uLi4uLi4uLi4uLmB8RmFsc2V8Tm8gMTgwLWRlZ3JlZSBzeW1tZXRyeQpgLi4uLiMuLi4uLi4uIy4uLi4uLi4jLi4uLi4uLi4jIyMjIyMuLi4uLi4uLiMuLi4uLi4uIy4uLi4uLi4jLi4uLmB8RmFsc2V8V2hpdGUgc3F1YXJlcyBub3QgY29udGlndW91cwpgLi4jLi4uLi4uLi4uIy4uLi4uLi4jLi4uLi4uIyMuLi4uLi4jLi4uLi4uLiMuLi4uLi4uIy4uLi4uLi4uLiMuLmB8RmFsc2V8MS1sZXR0ZXIgYW5kIDItbGV0dGVyIGVudHJpZXMKYC4jLi4uLi4uIy4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4jLi4uLi4uIy5gfEZhbHNlfDEtbGV0dGVyIGVudHJpZXMsIHdoaXRlIHNxdWFyZXMgbm90IGNvbnRpZ3VvdXMKYC4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLiMjLi4uLi4uIy4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi5gfEZhbHNlfE5vIDE4MC1kZWdyZWUgc3ltbWV0cnkKYCMjIyMuLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi5gfEZhbHNlfE5vIDE4MC1kZWdyZWUgc3ltbWV0cnkKYCMuLi4uLi4jIy4uLi4uLiMjLi4uLi4uIyMuLi4uLi4jIy4uLi4uLiMjLi4uLi4uIyMuLi4uLi4jIy4uLi4uLiNgfEZhbHNlfEZpbGxlZC1pbiBjb2x1bW5zCiIiIgoKCmZvciBpIGluIGZpbHRlcihOb25lLCBURVNULnNwbGl0KCdcbicpKToKCWEsYixfID0gaS5zcGxpdCgnfCcpCglhc3NlcnQgZigqdG9fYm9hcmQoYVsxOi0xXSkpID09IGV2YWwoYikKCnByaW50KCd0ZXN0cyBwYXNzZWQhJyk=