Ich suche nach einer Möglichkeit zu testen, ob sich eine bestimmte Zeichenfolge für die gesamte Zeichenfolge wiederholt oder nicht.
Beispiele:
[
'0045662100456621004566210045662100456621', # '00456621'
'0072992700729927007299270072992700729927', # '00729927'
'001443001443001443001443001443001443001443', # '001443'
'037037037037037037037037037037037037037037037', # '037'
'047619047619047619047619047619047619047619', # '047619'
'002457002457002457002457002457002457002457', # '002457'
'001221001221001221001221001221001221001221', # '001221'
'001230012300123001230012300123001230012300123', # '00123'
'0013947001394700139470013947001394700139470013947', # '0013947'
'001001001001001001001001001001001001001001001001001', # '001'
'001406469760900140646976090014064697609', # '0014064697609'
]
sind Saiten, die sich wiederholen, und
[
'004608294930875576036866359447',
'00469483568075117370892018779342723',
'004739336492890995260663507109',
'001508295625942684766214177978883861236802413273',
'007518796992481203',
'0071942446043165467625899280575539568345323741',
'0434782608695652173913',
'0344827586206896551724137931',
'002481389578163771712158808933',
'002932551319648093841642228739',
'0035587188612099644128113879',
'003484320557491289198606271777',
'00115074798619102416570771',
]
sind Beispiele für diejenigen, die dies nicht tun.
Die sich wiederholenden Abschnitte der Zeichenfolgen, die mir gegeben werden, können ziemlich lang sein, und die Zeichenfolgen selbst können 500 oder mehr Zeichen umfassen. Daher scheint es furchtbar langsam, jedes Zeichen zu durchlaufen, um ein Muster zu erstellen, und dann das Muster mit dem Rest der Zeichenfolge zu vergleichen. Multiplizieren Sie das mit möglicherweise Hunderten von Zeichenfolgen, und ich sehe keine intuitive Lösung.
Ich habe mich ein wenig mit Regexen befasst und sie scheinen gut zu sein, wenn Sie wissen, wonach Sie suchen oder zumindest die Länge des Musters, das Sie suchen. Leider weiß ich auch nicht.
Wie kann ich feststellen, ob sich eine Zeichenfolge wiederholt und wenn ja, welche Subsequenz am kürzesten wiederholt wird?
Antworten:
Hier ist eine übersichtliche Lösung, die reguläre Ausdrücke und langsame In-Python-Schleifen vermeidet:
In der von @davidism gestarteten Community-Wiki-Antwort finden Sie Benchmark-Ergebnisse. Zusammenfassend,
(Die Worte dieser Antwort, nicht meine.)
Dies basiert auf der Beobachtung, dass eine Zeichenfolge genau dann periodisch ist, wenn sie einer nichttrivialen Drehung von sich selbst entspricht. Ein großes Lob an @AleksiTorhamo für die Erkenntnis, dass wir dann die Hauptperiode aus dem Index des ersten Auftretens von
s
in wiederherstellen können(s+s)[1:-1]
, und für die Information über die Optionenstart
undend
Argumente von Pythonstring.find
.quelle
.find()
oder.index()
anstelle vonin
z.(s+s).find(s, 1, -1)
.(s+s).find(s, 1, -1)
es (sehr geringfügig) schneller sein wird als(s+s)[1:-1].find(s)
zumindest bei größeren Saiten, da das Schneiden bedeutet, dass Sie eine weitere Kopie (fast) der gesamten Saite erstellen müssen."abcd"
lassen Sie das Zeichen rechts ab und kleben Sie es wieder links an, um es zu erhalten"dabc"
. Diese Prozedur wird als Drehen einer Zeichenfolge um 1 Zeichen nach rechts bezeichnet . Wiederholen Sie dien
Schritte, um eine Zeichenfolge umn
Zeichen nach rechts zu drehen . Beachten Sie nun, dass bei einer Zeichenfolgek
, die durch ein beliebiges Vielfaches nach rechts gedreht wird,k
die Zeichenfolge unverändert bleibt. Eine nichttriviale Drehung einer Zeichenfolge ist eine, deren Zeichennummer kein Vielfaches der Länge der Zeichenfolge ist.Hier ist eine Lösung mit regulären Ausdrücken.
Durchlaufen der Beispiele in der Frage:
... erzeugt diese Ausgabe:
Der reguläre Ausdruck
(.+?)\1+$
ist in drei Teile gegliedert:(.+?)
ist eine übereinstimmende Gruppe, die mindestens ein (aber so wenig wie möglich) Zeichen enthält (weil+?
es nicht gierig ist ).\1+
prüft im ersten Teil auf mindestens eine Wiederholung der übereinstimmenden Gruppe.$
Überprüft das Ende der Zeichenfolge, um sicherzustellen, dass nach den wiederholten Teilzeichenfolgen kein zusätzlicher, sich nicht wiederholender Inhalt vorhanden ist (undre.match()
stellt sicher, dass vor den wiederholten Teilzeichenfolgen kein sich nicht wiederholender Text vorhanden ist ).In Python 3.4 und höher können Sie das ablegen
$
undre.fullmatch()
stattdessen verwenden oder (in jedem Python mindestens bis 2.3) in die andere Richtung gehen undre.search()
mit der Regex verwenden^(.+?)\1+$
, die mehr auf den persönlichen Geschmack als auf irgendetwas anderes zurückzuführen ist.quelle
Sie können die Beobachtung machen, dass die Länge einer Zeichenfolge durch die Länge ihrer wiederholten Sequenz teilbar sein muss, damit sie als Wiederholung betrachtet werden kann. In Anbetracht dessen ist hier eine Lösung, die Teiler der Länge von
1
bisn / 2
einschließlich generiert , die ursprüngliche Zeichenfolge in Teilzeichenfolgen mit der Länge der Teiler unterteilt und die Gleichheit der Ergebnismenge testet:BEARBEITEN: In Python 3 hat der
/
Operator standardmäßig die Float-Division geändert. Um dieint
Unterteilung von Python 2 zu erhalten, können Sie die verwenden//
stattdessen Operator verwenden. Vielen Dank an @ TigerhawkT3, dass Sie mich darauf aufmerksam gemacht haben.Der
//
Operator führt sowohl in Python 2 als auch in Python 3 eine Ganzzahldivision durch. Daher habe ich die Antwort aktualisiert, um beide Versionen zu unterstützen. Der Teil, in dem wir testen, ob alle Teilzeichenfolgen gleich sind, ist jetzt eine Kurzschlussoperation mitall
und einem Generatorausdruck.UPDATE: Als Reaktion auf eine Änderung der ursprünglichen Frage wurde der Code jetzt aktualisiert, um den kleinsten sich wiederholenden Teilstring zurückzugeben, falls vorhanden und
None
nicht. @godlygeek hat vorgeschlagen, zu verwendendivmod
, die Anzahl der Iterationen auf demdivisors
Generator zu reduzieren , und der Code wurde entsprechend aktualisiert. Es gibt nun alle positiven Teilern
in aufsteigender Reihenfolge ohne sichn
selbst zurück.Weiteres Update für hohe Leistung: Nach mehreren Tests bin ich zu dem Schluss gekommen, dass das einfache Testen der Zeichenfolgengleichheit die beste Leistung aller Slicing- oder Iteratorlösungen in Python bietet. Daher habe ich ein Blatt aus dem Buch von @ TigerhawkT3 genommen und meine Lösung aktualisiert. Es ist jetzt über 6x so schnell wie zuvor, deutlich schneller als Tigerhawks Lösung, aber langsamer als Davids.
quelle
(n/2)
inklusive.n / 2
indivisors()
seinn // 2
?Hier sind einige Benchmarks für die verschiedenen Antworten auf diese Frage. Es gab einige überraschende Ergebnisse, einschließlich einer stark unterschiedlichen Leistung in Abhängigkeit von der getesteten Saite.
Einige Funktionen wurden geändert, um mit Python 3 zu funktionieren (hauptsächlich durch Ersetzen
/
durch//
, um die Ganzzahldivision sicherzustellen). Wenn Sie einen Fehler feststellen, Ihre Funktion hinzufügen oder eine weitere Testzeichenfolge hinzufügen möchten, pingen Sie @ZeroPiraeus im Python-Chatroom .Zusammenfassend: Es gibt ungefähr einen 50-fachen Unterschied zwischen den Lösungen mit der besten und der schlechtesten Leistung für den großen Satz von Beispieldaten, die hier von OP bereitgestellt werden (über diesen Kommentar). Die Lösung von David Zhang ist der klare Gewinner und übertrifft alle anderen um das Fünffache für das große Beispielset.
Einige der Antworten sind sehr in extrem großen Fällen ohne Übereinstimmung langsam. Ansonsten scheinen die Funktionen je nach Test gleichwertig zu sein oder klare Gewinner zu sein.
Hier sind die Ergebnisse, einschließlich der mit Matplotlib und Seaborn erstellten Diagramme, um die verschiedenen Verteilungen zu zeigen:
Korpus 1 (mitgelieferte Beispiele - kleiner Satz)
Korpus 2 (mitgelieferte Beispiele - großes Set)
Korpus 3 (Randfälle)
Die Tests und Rohergebnisse finden Sie hier .
quelle
Nicht-Regex-Lösung:
Schnellere Nicht-Regex-Lösung dank @ThatWeirdo (siehe Kommentare):
Die obige Lösung ist sehr selten um einige Prozent langsamer als das Original, aber normalerweise ein gutes Stück schneller - manchmal sogar viel schneller. Es ist immer noch nicht schneller als das des Davidismus für längere Saiten, und die Regex-Lösung von Zero ist für kurze Saiten überlegen. Es kommt mit Zeichenfolgen von etwa 1000-1500 Zeichen am schnellsten heraus (laut Davidismus-Test auf Github - siehe seine Antwort). Unabhängig davon ist es in allen von mir getesteten Fällen zuverlässig die zweitschnellste (oder bessere). Danke, ThatWeirdo.
Prüfung:
Ergebnisse:
quelle
repeat('aa')
kehrt zurückNone
len(string[0:i])
ist immer gleichi
(zumindest in diesem Fall). Das Ersetzen dieser und auch das Speichernlen(string)
undstring[0:i]
in Variablen kann die Dinge beschleunigen. Auch IMO ist dies eine großartige Lösung, fantastisch;)Halbieren Sie zunächst die Zeichenfolge, solange es sich um ein "2-teiliges" Duplikat handelt. Dies reduziert den Suchraum bei einer geraden Anzahl von Wiederholungen. Wenn Sie vorwärts arbeiten, um die kleinste sich wiederholende Zeichenfolge zu finden, überprüfen Sie, ob die Aufteilung der vollständigen Zeichenfolge durch eine immer größere Teilzeichenfolge nur zu leeren Werten führt. Es müssen nur Teilstrings
length // 2
getestet werden, da alles darüber keine Wiederholungen hätte.Dies gibt die kürzeste Übereinstimmung zurück oder Keine, wenn keine Übereinstimmung vorliegt.
quelle
Das Problem kann
O(n)
im schlimmsten Fall auch mit der Präfixfunktion gelöst werden.Beachten Sie, dass es im Allgemeinen langsamer sein kann (UPD: und viel langsamer) als andere Lösungen, die von der Anzahl der Teiler von abhängen
n
, aber normalerweise früher fehlschlagen. Ich denke, einer der schlechten Fälle für sie wird seinaaa....aab
, wo es solche gibtn - 1 = 2 * 3 * 5 * 7 ... *p_n - 1
a
‚sZunächst müssen Sie die Präfixfunktion berechnen
dann gibt es entweder keine Antwort oder die kürzeste Zeit ist
und Sie müssen nur überprüfen, ob
k != n and n % k == 0
(wennk != n and n % k == 0
dann die Antwort ists[:k]
, gibt es keine AntwortSie können den Beweis hier überprüfen (auf Russisch, aber der Online-Übersetzer wird wahrscheinlich den Trick machen)
quelle
prefix_function()
Python ist nicht gültig: Sie haben fehlende Doppelpunkte in Ihrenwhile
undif
-Anweisungen und&&
stattdessenand
. Nachdem diese behoben wurden, schlägt diesUnboundLocalError: local variable 'i' referenced before assignment
aufgrund der Leitung fehlfor i in range(i, n):
.prefix_function()
, um ähnliche Ergebnisse wie die anderen Antworten zurückzugeben - entweder die kürzeste Teilzeichenfolge oderNone
-, werde ich sie in einen überarbeiteten Benchmark aufnehmen, den ich zusammenstelle.Diese Version versucht nur die Kandidatensequenzlängen, die Faktoren der Zeichenfolgenlänge sind. und verwendet den
*
Operator, um eine Zeichenfolge in voller Länge aus der Kandidatensequenz zu erstellen:Vielen Dank an TigerhawkT3, der bemerkt hat, dass dies
length // 2
ohne+ 1
denabab
Fall nicht passen würde .quelle
range
Limit von habenlength//2
, genau wie ich - Sie müssen das ändern,length//2+1
wenn Sie Zeichenfolgen fangen möchten, die genau verdoppelt sind (z'aabaab'
. B. ).Hier ist eine einfache Lösung ohne reguläre Ausdrücke.
Für Teilketten von
s
aus nullte Index ausgehend von Längen 1 durchlen(s)
, ob die Teilkette ,substr
die sich wiederholendes Muster ist. Diese Prüfung kann durchgeführt werden, indemsubstr
mit sich selbstratio
Zeiten verkettet werden , so dass die Länge der so gebildeten Zeichenkette gleich der Länge von ists
. Daherratio=len(s)/len(substr)
.Gibt zurück, wenn zuerst ein solcher Teilstring gefunden wird. Dies würde den kleinstmöglichen Teilstring liefern, falls einer existiert.
quelle
Ich habe mit mehr als acht Lösungen für dieses Problem begonnen. Einige basierten auf Regex (Match, Findall, Split), andere auf dem Schneiden und Testen von Strings, andere auf String-Methoden (Find, Count, Split). Jedes hatte Vorteile in Bezug auf Codeklarheit, Codegröße, Geschwindigkeit und Speicherverbrauch. Ich wollte meine Antwort hier veröffentlichen, als ich bemerkte, dass die Ausführungsgeschwindigkeit als wichtig eingestuft wurde. Deshalb habe ich weitere Tests und Verbesserungen durchgeführt, um dies zu erreichen:
Diese Antwort scheint einigen anderen Antworten hier ähnlich zu sein, hat jedoch einige Geschwindigkeitsoptimierungen, die andere nicht verwendet haben:
xrange
ist in dieser Anwendung etwas schneller,s[:n]
direkte Verwendung vermeiden wir, in jeder Schleife eine Variable zu erstellen.Es würde mich interessieren, wie sich dies in den Standardtests mit gängiger Hardware verhält. Ich glaube, es wird in den meisten Tests weit hinter David Zhangs exzellentem Algorithmus zurückbleiben, sollte aber ansonsten ziemlich schnell sein.
Ich fand dieses Problem sehr kontraintuitiv. Die Lösungen, von denen ich dachte, sie wären schnell, waren langsam. Die Lösungen, die langsam aussahen, waren schnell! Es scheint, dass Pythons String-Erstellung mit dem Multiplikationsoperator und String-Vergleichen stark optimiert sind.
quelle
statistics
Modul), so musste ich Ihre ändern/
s zu//
s und ersetzenxrange()
mitrange()
(die verhält sich wie 2.x istxrange()
in 3.x).//
in 3.x ist die Ganzzahldivision (genau wie das 2.x-Verhalten von/
), während 3.x die Gleitdivision/
ist (was sicher viel langsamer wäre, selbst wenn es Ihre Lösung nicht durch einen Verwendungsversuch beschädigen würde ein Float als Index). Wie bereits erwähnt, ist 3.xrange()
dasselbe wie 2.xxrange()
;range()
In 3.x gibt es kein Äquivalent zu 2.x. Ich denke also nicht, dass dies die Ursache für eine Diskrepanz zwischen dem Benchmark und den von Ihnen vorgenommenen Timings ist. Es ist wahrscheinlich nur so, dass 3.x insgesamt langsamer ist als 2.x (oder vielleicht ist Ihre Maschine schneller als meine).Diese Funktion läuft sehr schnell (getestet und ist mehr als dreimal schneller als die schnellste Lösung hier bei Zeichenfolgen mit mehr als 100.000 Zeichen und der Unterschied wird größer, je länger das sich wiederholende Muster ist). Es wird versucht, die Anzahl der Vergleiche zu minimieren, die erforderlich sind, um die Antwort zu erhalten:
Beachten Sie, dass beispielsweise für eine Zeichenfolge der Länge 8 nur Fragmente der Größe 4 überprüft werden und keine weiteren Tests durchgeführt werden müssen, da das Muster der Länge 1 oder 2 zu einer Wiederholung des Musters der Länge 4 führen würde:
quelle
In David Zhangs Antwort, wenn wir eine Art kreisförmigen Puffer haben, wird dies nicht funktionieren:
principal_period('6210045662100456621004566210045662100456621')
Aufgrund des Starts621
, wo ich es gerne ausgespuckt hätte:00456621
.Um seine Lösung zu erweitern, können wir Folgendes verwenden:
quelle
Hier ist der Code in Python, der die Wiederholung der Unterzeichenfolge in der vom Benutzer angegebenen Hauptzeichenfolge überprüft .
Eingabe :
Ausgabe :
Eingabe :
Ausgabe :
quelle