Python Regex alle überlappenden Übereinstimmungen finden?

95

Ich versuche, alle 10-stelligen Zahlenreihen innerhalb einer größeren Zahlenreihe mit re in Python 2.6 zu finden.

Ich bin leicht in der Lage, keine überlappenden Übereinstimmungen zu erfassen, aber ich möchte jede Übereinstimmung in der Zahlenserie. Z.B.

in "123456789123456789"

Ich sollte die folgende Liste bekommen:

[1234567891,2345678912,3456789123,4567891234,5678912345,6789123456,7891234567,8912345678,9123456789]

Ich habe Verweise auf einen "Lookahead" gefunden, aber die Beispiele, die ich gesehen habe, zeigen nur Zahlenpaare und keine größeren Gruppierungen, und ich konnte sie nicht über die beiden Ziffern hinaus konvertieren.

Hosen
quelle
6
Die vorgestellten Lösungen funktionieren nicht, wenn die überlappenden Übereinstimmungen an derselben Stelle beginnen. Wenn Sie beispielsweise "a | ab | abc" mit "abcd" abgleichen, wird nur ein Ergebnis zurückgegeben. Gibt es eine Lösung dafür, bei der match () nicht mehrmals aufgerufen wird und die Endgrenze manuell verfolgt wird?
Vítor De Araújo
@ VítorDeAraújo: Überlappende Regexe wie (a|ab|abc)können im Allgemeinen als nicht überlappende Regexe mit verschachtelten Capture-Gruppen umgeschrieben werden, z. B. (a(b(c)?)?)?wenn wir beim Auspacken eines Matches alle außer der äußersten (dh ganz links) Capture-Gruppe ignorieren. zugegebenermaßen ist dies leicht schmerzhaft und weniger lesbar. Dies wird auch ein leistungsfähigerer Regex sein.
smci

Antworten:

175

Verwenden Sie eine Erfassungsgruppe in einem Lookahead. Der Lookahead erfasst den Text, an dem Sie interessiert sind, aber die tatsächliche Übereinstimmung ist technisch gesehen die Teilzeichenfolge mit der Breite Null vor dem Lookahead, sodass sich die Übereinstimmungen technisch nicht überlappen:

import re 
s = "123456789123456789"
matches = re.finditer(r'(?=(\d{10}))',s)
results = [int(match.group(1)) for match in matches]
# results: 
# [1234567891,
#  2345678912,
#  3456789123,
#  4567891234,
#  5678912345,
#  6789123456,
#  7891234567,
#  8912345678,
#  9123456789]
mechanisches Fleisch
quelle
2
Meine Antwort ist mindestens zweimal schneller als diese. Aber diese Lösung ist schwierig, ich stimme ihr zu.
Eyquem
16
Erläuterung = Anstatt nach dem Muster (10 Stellen) zu suchen, wird nach allem gesucht, was dem Muster folgt. So findet es Position 0 der Zeichenfolge, Position 1 der Zeichenfolge und so weiter. Dann greift es nach Gruppe (1) - dem passenden Muster und erstellt eine Liste davon. Sehr cool.
Tal Weiss
Ich hatte keine Ahnung, dass Sie übereinstimmende Gruppen in Lookaheads verwenden könnten, die normalerweise nicht in einer Übereinstimmung enthalten sein sollen (und die übereinstimmenden Untergruppen erscheinen tatsächlich nicht als vollständige Übereinstimmung). Da diese Technik in Python 3.4 immer noch zu funktionieren scheint, wird sie vermutlich eher als Feature als als Fehler angesehen.
JAB
10
Ich bin StackOverflow beigetreten, habe Fragen beantwortet und meinen Ruf verbessert, damit ich diese Antwort positiv bewerten kann. Ich bin momentan mit Python 2.4 festgefahren, daher kann ich die erweiterten Regex-Funktionen von Python 3 nicht verwenden, und dies ist genau die Art von bizarren Tricks, nach denen ich gesucht habe.
TheSoundDefense
2
Könnten Sie dem Code weitere Erklärungen hinzufügen? Laut Stack Overflow ist dies nicht der beste Weg, nur Code in einer Antwort zu haben. Es wird definitiv den Menschen helfen.
Akshay Hazari
76

Sie können auch versuchen, das Drittanbieter- regexModul (nicht re) zu verwenden, das überlappende Übereinstimmungen unterstützt.

>>> import regex as re
>>> s = "123456789123456789"
>>> matches = re.findall(r'\d{10}', s, overlapped=True)
>>> for match in matches: print match
...
1234567891
2345678912
3456789123
4567891234
5678912345
6789123456
7891234567
8912345678
9123456789
David C.
quelle
17

Ich mag Regexe, aber sie werden hier nicht benötigt.

Einfach

s =  "123456789123456789"

n = 10
li = [ s[i:i+n] for i in xrange(len(s)-n+1) ]
print '\n'.join(li)

Ergebnis

1234567891
2345678912
3456789123
4567891234
5678912345
6789123456
7891234567
8912345678
9123456789
eyquem
quelle
10
Regexes werden hier nur nicht benötigt, weil Sie das Spezialwissen "innerhalb einer größeren Reihe von Zahlen" anwenden, sodass Sie bereits wissen, dass jede Position 0 <= i < len(s)-n+1garantiert der Beginn einer 10-stelligen Übereinstimmung ist. Ich denke auch, dass Ihr Code beschleunigt werden könnte, wäre interessant, Code-Golf für Geschwindigkeit.
smci