La classe Re2d, Python 2
Mise à jour: ajout du problème "9. Alignement".
Mon approche consiste à utiliser le module Python re pour effectuer la recherche et la correspondance. La classe Re2d prépare le texte pour le traitement, exécute les fonctions et formate les résultats pour la sortie.
Notez qu'il ne s'agit pas d'un tout nouveau langage: il s'agit du langage d'expression régulière standard projeté en 2 dimensions avec des indicateurs supplémentaires pour les modes 2D supplémentaires.
La classe a les usages suivants:
re2dobject = Re2d(<horizontal pattern>, [<vertical pattern>], [<flags>])
Les deux modèles sont des modèles RE de texte linéaire standard. Si aucun motif vertical n'est fourni, la classe utilisera également le motif horizontal pour la correspondance verticale. Les drapeaux sont les drapeaux RE standard avec certaines extensions 2D.
Essai
1. Finding chessboards
Chessboard pattern at (2, 1, 4, 3)
print '\n1. Finding chessboards'
reob1 = Re2d('#(_#)+_?|_(#_)+#?')
found = reob1.search('~______~\n~##_#_#~\n~#_#_##~\n~##_#_#~\n~______~')
print 'Chessboard pattern at', found
assert not reob1.search('#_##\n_#_#\n__#_\n#_#_\n#_#_')
La méthode de recherche a trouvé un motif d’échiquier et renvoie une position de 4 tuple. Le tuple a la x,y
position du premier caractère de la correspondance et
width, height
la zone correspondante. Un seul motif est donné, il sera donc utilisé pour la correspondance horizontale et verticale.
2. Verifying chessboards
Is chess? True
print '\n2. Verifying chessboards'
reob2 = Re2d('^#(_#)*_?|_(#_)*#?$')
print 'Is chess?', reob2.match('_#_#_#_#\n#_#_#_#_\n_#_#_#_#')
assert not reob2.match('_#_#_#__\n__#_#_#_\n_#_#_#__')
L'échiquier a été vérifié avec la méthode de correspondance qui renvoie un booléen. Notez que les caractères ^
et les caractères de $
début et de fin doivent obligatoirement correspondre au
texte entier .
3. Rectangle of digits
Found: [(0, 1, 5, 3), (1, 1, 4, 3), (2, 1, 3, 3), (3, 1, 2, 3), (0, 2, 5, 2), (1, 2, 4, 2), (2, 2, 3, 2), (3, 2, 2, 2), (6, 3, 2, 2)]
Not found: None
print '\n3. Rectangle of digits'
reob3 = Re2d(r'\d\d+', flags=MULTIFIND)
print 'Found:', reob3.search('hbrewvgr\n18774gwe\n84502vgv\n19844f22\ncrfegc77')
print 'Not found:', reob3.search('uv88wn000\nvgr88vg0w\nv888wrvg7\nvvg88wv77')
Nous utilisons maintenant le MULTIFIND
drapeau pour renvoyer toutes les correspondances possibles pour le bloc de 2 chiffres ou plus. La méthode trouve 9 correspondances possibles. Notez qu'ils peuvent se chevaucher.
4. Word search (orthogonal only)
Words: [(0, 0, 4, 1), (0, 3, 4, 1), (3, 3, -4, -1), (3, 2, -4, -1), (3, 0, -4, -1)] [(0, 0, 1, 4), (3, 0, 1, 4), (3, 3, -1, -4), (0, 3, -1, -4)]
Words: ['SNUG', 'WOLF', 'FLOW', 'LORE', 'GUNS'] ['S\nT\nE\nW', 'G\nO\nL\nF', 'F\nL\nO\nG', 'W\nE\nT\nS']
No words: [] []
print '\n4. Word search (orthogonal only)'
words = 'GOLF|GUNS|WOLF|FLOW|LORE|WETS|STEW|FLOG|SNUG'
flags = HORFLIP | VERFLIP | MULTIFIND
reob4a, reob4b = Re2d(words, '.', flags), Re2d('.', words, flags)
matching = 'SNUG\nTEQO\nEROL\nWOLF'
nomatch = 'ABCD\nEFGH\nIJKL\nMNOP'
print 'Words:', reob4a.search(matching), reob4b.search(matching)
print 'Words:', reob4a.findall(matching), reob4b.findall(matching)
print 'No words:', reob4a.findall(nomatch), reob4b.findall(nomatch)
Ce test montre l'utilisation du retournement vertical et horizontal. Cela permet de faire correspondre les mots inversés. Les mots en diagonale ne sont pas supportés. Le
MULTIFIND
drapeau permet plusieurs correspondances qui se chevauchent dans les 4 directions. La méthode findall utilise la recherche pour trouver les cases correspondantes, puis extrait les blocs de texte correspondants. Notez que la recherche utilise la largeur et / ou la hauteur négatives pour les correspondances dans le sens inverse. Les mots dans la direction verticale ont des caractères de nouvelle ligne - ceci est cohérent avec le concept de blocs de caractères 2D.
7. Calvins portals
Portals found: [(3, 1, 5, 6)]
Portal not found None
print '\n7. Calvins portals'
reob7 = Re2d(r'X\.{2,22}X|.X{2,22}.', r'X\.{3,22}X|.X{3,22}.', MULTIFIND)
yes = '....X......\n.XXXXXX.XX.\n...X...X...\n.X.X...XXX.\n...X...X.X.\n.XXX...X.X.\nX..XXXXX.X.'
no = 'XX..XXXX\nXX..X..X\nXX..X..X\n..X.X..X\n.X..X.XX'
print 'Portals found:', reob7.search(yes)
print 'Portal not found', reob7.search(no)
Cette recherche nécessitait des modèles distincts pour chaque dimension, la taille minimale étant différente pour chacune.
9. Alignment
Found: ['#.,##', '##'] ['#\n.\n,\n.\n#', '#\n,\n.\n#']
Found: [(3, 4, 5, 1), (6, 4, 2, 1)] [(7, 0, 1, 5), (3, 1, 1, 4)]
Not found: None None
print '\n9. Alignment'
reob9a = Re2d(r'#.*#', r'.', MULTIFIND)
reob9b = Re2d(r'.', r'#.*#', MULTIFIND)
matching = '.,.,.,.#.,\n,.,#,.,.,.\n.,.,.,.,.,\n,.,.,.,.,.\n.,.#.,##.,\n,.,.,.,.,.'
nomatch = '.,.#.,.,\n,.,.,.#.\n.,#,.,.,\n,.,.,.,#\n.#.,.,.,\n,.,.#.,.\n#,.,.,.,\n,.,.,#,.'
print 'Found:', reob9a.findall(matching), reob9b.findall(matching)
print 'Found:', reob9a.search(matching), reob9b.search(matching)
print 'Not found:', reob9a.search(nomatch), reob9b.search(nomatch)
Cet ensemble de 2 recherches trouve 2 correspondances verticales et 2 correspondances horizontales, mais ne peut pas trouver la #.,#
chaîne incorporée .
10. Collinear Points (orthogonal only)
Found: [(0, 1, 7, 1)] [(3, 1, 1, 4)]
Not found: None None
print '\n10. Collinear Points (orthogonal only)'
matching = '........\n#..#..#.\n...#....\n#.......\n...#....'
nomatch = '.#..#\n#..#.\n#....\n..#.#'
reob10h = Re2d(r'#.*#.*#', '.')
reob10v = Re2d('.', r'#.*#.*#')
flags = MULTIFIND
print 'Found:', reob10h.search(matching, flags), reob10v.search(matching, flags)
print 'Not found:', reob10h.search(nomatch, flags), reob10v.search(nomatch, flags)
Ici, nous utilisons 2 recherches pour trouver des correspondances dans les deux sens. Il est capable de trouver plusieurs correspondances orthogonales mais cette approche ne prend pas en charge les correspondances en diagonale.
12. Avoid qQ
Found: (2, 2, 4, 4)
Not found: None
print '\n12. Avoid qQ'
reob12 = Re2d('[^qQ]{4,4}')
print 'Found:', reob12.search('bhtklkwt\nqlwQklqw\nvtvlwktv\nkQtwkvkl\nvtwlkvQk\nvnvevwvx')
print 'Not found:', reob12.search('zxvcmn\nxcvncn\nmnQxcv\nxcvmnx\nazvmne')
Cette recherche trouve la première correspondance.
13. Diamond Mining
.X.
X.X
.X.
.X.
X.X
.X.
..X..
./.\.
X...X
.\./.
\.X..
..X..
./.\.
X...X
.\./.
..X..
.XX.\
//.\.
X...X
.\./.
..X..
...X...
../.\..
./.X.\.
X.X.X.X
.\.X.//
..\./X.
.X.X..\
Diamonds: [(2, 2, 3, 3), (0, 6, 3, 3)] [(8, 0, 5, 5), (10, 2, 5, 5), (5, 3, 5, 5)] [(0, 0, 7, 7)]
Not found: None None None
print '\n13. Diamond Mining'
reob13a = Re2d(r'.X.|X.X', flags=MULTIFIND)
reob13b = Re2d(r'..X..|./.\\.|X...X|.\\./.', flags=MULTIFIND)
reob13c = Re2d(r'...X...|../.\\..|./...\\.|X.....X|.\\.../.|..\\./..', flags=MULTIFIND)
match = '''
...X......X....
../.\..../.\...
./.X.\..X...X..
X.X.X.XX.\./.\.
.\.X.//.\.X...X
..\./X...X.\./.
.X.X..\./...X..
X.X....X.......
.X.............
'''.strip().replace(' ', '')
nomatch = '''
.X......./....
.\....X.......
...X.\.\...X..
..X.\...\.X.\.
...X.X...X.\.X
../X\...\...X.
.X...\.\..X...
..\./.X....X..
...X..../.....
'''.strip().replace(' ', '')
for diamond in reob13a.findall(match)+reob13b.findall(match)+reob13c.findall(match):
print diamond+'\n'
print 'Diamonds:', reob13a.search(match), reob13b.search(match), reob13c.search(match)
print 'Not found:', reob13a.search(nomatch), reob13b.search(nomatch), reob13c.search(nomatch)
Le problème des diamants est plus difficile. Trois objets de recherche sont nécessaires pour les trois tailles. Il peut trouver les six diamants dans le jeu d’essais, mais il n’est pas adapté aux diamants de taille variable. Ce n'est qu'une solution partielle au problème des diamants.
Code Python 2
import sys
import re
DEBUG = re.DEBUG
IGNORECASE = re.IGNORECASE
LOCALE = re.LOCALE
UNICODE = re.UNICODE
VERBOSE = re.VERBOSE
MULTIFIND = 1<<11
ROTATED = 1<<12 # not implemented
HORFLIP = 1<<13
VERFLIP = 1<<14
WRAPAROUND = 1<<15 # not implemented
class Re2d(object):
def __init__(self, horpattern, verpattern=None, flags=0):
self.horpattern = horpattern
self.verpattern = verpattern if verpattern != None else horpattern
self.flags = flags
def checkblock(self, block, flags):
'Return a position if block matches H and V patterns'
length = []
for y in range(len(block)):
match = re.match(self.horpattern, block[y], flags)
if match:
length.append(len(match.group(0)))
else:
break
if not length:
return None
width = min(length)
height = len(length)
length = []
for x in range(width):
column = ''.join(row[x] for row in block[:height])
match = re.match(self.verpattern, column, flags)
if match:
matchlen = len(match.group(0))
length.append(matchlen)
else:
break
if not length:
return None
height = min(length)
width = len(length)
# if smaller, verify with RECURSIVE checkblock call:
if height != len(block) or width != len(block[0]):
newblock = [row[:width] for row in block[:height]]
newsize = self.checkblock(newblock, flags)
return newsize
return width, height
def mkviews(self, text, flags):
'Return views of text block from flip/rotate flags, inc inverse f()'
# TODO add ROTATED to generate more views
width = len(text[0])
height = len(text)
views = [(text, lambda x,y,w,h: (x,y,w,h))]
if flags & HORFLIP and flags & VERFLIP:
flip2text = [row[::-1] for row in text[::-1]]
flip2func = lambda x,y,w,h: (width-1-x, height-1-y, -w, -h)
views.append( (flip2text, flip2func) )
elif flags & HORFLIP:
hortext = [row[::-1] for row in text]
horfunc = lambda x,y,w,h: (width-1-x, y, -w, h)
views.append( (hortext, horfunc) )
elif flags & VERFLIP:
vertext = text[::-1]
verfunc = lambda x,y,w,h: (x, height-1-y, w, -h)
views.append( (vertext, verfunc) )
return views
def searchview(self, textview, flags=0):
'Return matching textview positions or None'
result = []
for y in range(len(textview)):
testtext = textview[y:]
for x in range(len(testtext[0])):
size = self.checkblock([row[x:] for row in testtext], flags)
if size:
found = (x, y, size[0], size[1])
if flags & MULTIFIND:
result.append(found)
else:
return found
return result if result else None
def search(self, text, flags=0):
'Return matching text positions or None'
flags = self.flags | flags
text = text.split('\n') if type(text) == str else text
result = []
for textview, invview in self.mkviews(text, flags):
found = self.searchview(textview, flags)
if found:
if flags & MULTIFIND:
result.extend(invview(*f) for f in found)
else:
return invview(*found)
return result if result else None
def findall(self, text, flags=0):
'Return matching text blocks or None'
flags = self.flags | flags
strmode = (type(text) == str)
text = text.split('\n') if type(text) == str else text
result = []
positions = self.search(text, flags)
if not positions:
return [] if flags & MULTIFIND else None
if not flags & MULTIFIND:
positions = [positions]
for x0,y0,w,h in positions:
if y0+h >= 0:
lines = text[y0 : y0+h : cmp(h,0)]
else:
lines = text[y0 : : cmp(h,0)]
if x0+w >= 0:
block = [row[x0 : x0+w : cmp(w,0)] for row in lines]
else:
block = [row[x0 : : cmp(w,0)] for row in lines]
result.append(block)
if strmode:
result = ['\n'.join(rows) for rows in result]
if flags & MULTIFIND:
return result
else:
return result[0]
def match(self, text, flags=0):
'Return True if whole text matches the patterns'
flags = self.flags | flags
text = text.split('\n') if type(text) == str else text
for textview, invview in self.mkviews(text, flags):
size = self.checkblock(textview, flags)
if size:
return True
return False