Zum Beispiel habe ich Listen:
a[0] = [1, 1, 1, 0, 0]
a[1] = [1, 1, 0, 0, 1]
a[2] = [0, 1, 1, 1, 0]
# and so on
Sie scheinen unterschiedlich zu sein, aber wenn angenommen wird, dass Anfang und Ende miteinander verbunden sind, sind sie kreisförmig identisch.
Das Problem ist, dass jede Liste, die ich habe, eine Länge von 55 hat und nur drei Einsen und 52 Nullen enthält. Ohne kreisförmige Bedingung gibt es 26.235 (55 wählen 3) Listen. Wenn jedoch die Bedingung "Rundschreiben" besteht, gibt es eine große Anzahl von zirkular identischen Listen
Derzeit überprüfe ich die zirkuläre Identität wie folgt:
def is_dup(a, b):
for i in range(len(a)):
if a == list(numpy.roll(b, i)): # shift b circularly by i
return True
return False
Diese Funktion erfordert im schlimmsten Fall 55 zyklische Schaltvorgänge. Und es gibt 26.235 Listen, die miteinander verglichen werden müssen. Kurz gesagt, ich benötige 55 * 26.235 * (26.235 - 1) / 2 = 18.926.847.225 Berechnungen. Es sind ungefähr 20 Giga!
Gibt es eine gute Möglichkeit, dies mit weniger Berechnungen zu tun? Oder irgendwelche Datentypen, die Rundschreiben unterstützen ?
Antworten:
Zunächst einmal kann dies in
O(n)
Bezug auf die Länge der Liste erfolgen. Sie können feststellen, dass Ihre neue Liste definitiv alle möglichen zyklischen Listen enthält , wenn Sie Ihre Liste zweimal duplizieren ([1, 2, 3]
)[1, 2, 3, 1, 2, 3]
.Sie müssen also nur überprüfen, ob sich die Liste, die Sie suchen, innerhalb Ihrer Startliste befindet. In Python können Sie dies auf folgende Weise erreichen (vorausgesetzt, die Längen sind gleich).
Einige Erklärungen zu meinem Oneliner:
list * 2
Kombiniert eine Liste mit sich selbst,map(str, [1, 2])
konvertiert alle Zahlen in Zeichenfolgen und' '.join()
konvertiert das Array['1', '2', '111']
in eine Zeichenfolge'1 2 111'
.Wie von einigen Personen in den Kommentaren erwähnt, kann Oneliner möglicherweise einige falsch positive Ergebnisse liefern, um alle möglichen Randfälle abzudecken:
PS1 Wenn es um Zeitkomplexität geht, ist zu beachten, dass
O(n)
dies erreicht wird, wenn Teilzeichenfolgen rechtzeitig gefunden werdenO(n)
können. Dies ist nicht immer der Fall und hängt von der Implementierung in Ihrer Sprache ab ( obwohl dies möglicherweise beispielsweise in linearer Zeit KMP erfolgen kann).PS2 für Leute, die Angst vor einer Saitenoperation haben und aufgrund dieser Tatsache denken, dass die Antwort nicht gut ist. Was wichtig ist, ist Komplexität und Geschwindigkeit. Dieser Algorithmus läuft möglicherweise
O(n)
zeitlich undO(n)
räumlich, was ihn viel besser macht als alles in derO(n^2)
Domäne. Um dies selbst zu sehen, können Sie einen kleinen Benchmark ausführen (beim Erstellen einer zufälligen Liste wird das erste Element eingefügt und an das Ende angehängt, wodurch eine zyklische Liste erstellt wird. Sie können Ihre eigenen Manipulationen vornehmen).0,3 Sekunden auf meiner Maschine. Nicht wirklich lange. Versuchen Sie nun, dies mit
O(n^2)
Lösungen zu vergleichen . Während des Vergleichs können Sie von den USA nach Australien reisen (höchstwahrscheinlich mit einem Kreuzfahrtschiff).quelle
In Python nicht gut genug informiert, um dies in der von Ihnen gewünschten Sprache zu beantworten, aber in C / C ++ würde ich angesichts der Parameter Ihrer Frage die Nullen und Einsen in Bits konvertieren und sie auf die niedrigstwertigen Bits eines uint64_t übertragen. Auf diese Weise können Sie alle 55 Bits auf einen Schlag vergleichen - 1 Uhr.
Unglaublich schnell, und das Ganze passt in On-Chip-Caches (209.880 Bytes). Die Hardwareunterstützung zum gleichzeitigen Verschieben aller 55 Listenmitglieder nach rechts ist nur in den Registern einer CPU verfügbar. Gleiches gilt für den gleichzeitigen Vergleich aller 55 Mitglieder. Dies ermöglicht eine 1-zu-1-Zuordnung des Problems zu einer Softwarelösung. (und unter Verwendung der SIMD / SSE 256-Bit-Register bis zu 256 Mitglieder, falls erforderlich) Als Ergebnis ist der Code für den Leser sofort offensichtlich.
Möglicherweise können Sie dies in Python implementieren. Ich weiß es einfach nicht gut genug, um zu wissen, ob dies möglich ist oder wie hoch die Leistung sein könnte.
Nach dem Schlafen wurden einige Dinge offensichtlich und alles zum Besseren.
1.) Es ist so einfach, die zirkulär verknüpfte Liste mit Bits zu drehen, dass Dalis sehr cleverer Trick nicht notwendig ist. Innerhalb eines 64-Bit-Registers wird die Standard-Bitverschiebung die Rotation sehr einfach durchführen und in dem Versuch, dies alles Python-freundlicher zu machen, indem Arithmetik anstelle von Bit-Ops verwendet wird.
2.) Die Bitverschiebung kann leicht durch Teilen durch 2 erreicht werden.
3.) Das Überprüfen des Listenendes auf 0 oder 1 kann mit Modulo 2 problemlos durchgeführt werden.
4.) Das "Verschieben" einer 0 vom Ende zum Ende der Liste kann durch Teilen durch 2 erfolgen. Wenn die Null tatsächlich verschoben würde, würde dies das 55. Bit falsch machen, was es bereits ist, indem absolut nichts getan wird.
5.) Das Verschieben einer 1 vom Ende zum Ende der Liste kann durch Teilen durch 2 und Addieren von 18.014.398.509.481.984 erfolgen. Dies ist der Wert, der durch Markieren des 55. Bits als wahr und aller anderen als falsch erzeugt wird.
6.) Wenn ein Vergleich des Ankers und des zusammengesetzten uint64_t nach einer bestimmten Drehung WAHR ist, brechen Sie und geben Sie WAHR zurück.
Ich würde das gesamte Array von Listen direkt vorab in ein Array von uint64_ts konvertieren, um zu vermeiden, dass die Konvertierung wiederholt durchgeführt werden muss.
Nachdem ich einige Stunden damit verbracht hatte, den Code zu optimieren und die Assemblersprache zu studieren, konnte ich 20% der Laufzeit sparen. Ich sollte hinzufügen, dass der O / S- und MSVC-Compiler gestern auch mittags aktualisiert wurde. Aus welchen Gründen auch immer, die Qualität des vom C-Compiler erstellten Codes hat sich nach dem Update (15.11.2014) dramatisch verbessert. Die Laufzeit beträgt jetzt ~ 70 Uhren, 17 Nanosekunden , um einen Ankerring mit allen 55 Windungen eines Testrings zusammenzustellen und zu vergleichen, und NxN aller Ringe gegen alle anderen ist in 12,5 Sekunden erledigt .
Dieser Code ist so eng, dass bis auf 4 Register 99% der Zeit nichts tun. Die Assemblersprache entspricht fast Zeile für Zeile dem C-Code. Sehr leicht zu lesen und zu verstehen. Ein großartiges Montageprojekt, wenn sich jemand das selbst beibringt.
Hardware ist Hazwell i7, MSVC 64-Bit, vollständige Optimierungen.
quelle
Wenn Sie zwischen den Zeilen lesen, klingt es so, als würden Sie versuchen, einen Vertreter jeder kreisförmigen Äquivalenzklasse von Zeichenfolgen mit 3 Einsen und 52 Nullen aufzulisten. Wechseln wir von einer dichten Darstellung zu einer spärlichen (Satz von drei Zahlen in
range(55)
). In dieser Darstellung ist die zirkuläre Verschiebung vons
byk
durch das Verständnis gegebenset((i + k) % 55 for i in s)
. Der lexikografische Mindestrepräsentant in einer Klasse enthält immer die Position 0. Bei einem Satz des Formulars{0, i, j}
mit0 < i < j
sind die anderen Kandidaten für das Minimum in der Klasse{0, j - i, 55 - i}
und{0, 55 - j, 55 + i - j}
. Daher muss(i, j) <= min((j - i, 55 - i), (55 - j, 55 + i - j))
das Original minimal sein. Hier ist ein Aufzählungscode.quelle
Wiederholen Sie das erste Array und verwenden Sie dann den Z-Algorithmus (O (n) Zeit), um das zweite Array im ersten zu finden.
(Hinweis: Sie müssen das erste Array nicht physisch kopieren. Sie können es einfach während des Abgleichs umbrechen.)
Das Schöne am Z-Algorithmus ist, dass er im Vergleich zu KMP, BM usw. sehr einfach
ist. Wenn Sie sich jedoch ehrgeizig fühlen, können Sie den String-Abgleich in linearer Zeit und konstantem Raum durchführen -
strstr
zum Beispiel. Die Implementierung wäre jedoch schmerzhafter.quelle
Nach der sehr intelligenten Lösung von Salvador Dali können Sie am besten sicherstellen, dass alle Elemente gleich lang sind und beide LISTEN gleich lang sind.
Keine Ahnung, ob dies schneller oder langsamer ist als die von AshwiniChaudhary empfohlene Regex-Lösung in Salvador Dalis Antwort, die lautet:
quelle
str.format
n
Zeiten aufzurufen , um die resultierende Zeichenfolge zu formatieren. Ich nehme an .... :)Angesichts der Tatsache, dass Sie so viele Vergleiche durchführen müssen, lohnt es sich möglicherweise, Ihre Listen zunächst durchzugehen, um sie in eine kanonische Form umzuwandeln, die leicht verglichen werden kann.
Versuchen Sie, eine Reihe von zirkular eindeutigen Listen zu erhalten? Wenn ja, können Sie sie nach der Konvertierung in Tupel in ein Set werfen.
Entschuldigung an David Eisenstat, dass er seine ähnliche Antwort nicht entdeckt hat.
quelle
Sie können eine Liste wie folgt rollen:
quelle
Konvertieren Sie zuerst jedes Ihrer Listenelemente (ggf. in einer Kopie) in die gedrehte Version, die lexikalisch am größten ist.
Sortieren Sie dann die resultierende Liste der Listen (wobei ein Index an der ursprünglichen Listenposition beibehalten wird) und vereinheitlichen Sie die sortierte Liste, indem Sie alle Duplikate in der ursprünglichen Liste nach Bedarf markieren.
quelle
Huckepack auf @ SalvadorDalis Beobachtung bei der Suche nach Übereinstimmungen von a in einem beliebigen a-langen Slice in b + b, hier ist eine Lösung, die nur Listenoperationen verwendet.
2. Ansatz: [gelöscht]
quelle
rollmatch([1, 0, 1, 1], [0, 1, 1, 1])
.Keine vollständige, freistehende Antwort, aber beim Thema Optimierung durch Reduzierung von Vergleichen dachte auch ich an normalisierte Darstellungen.
Wenn Ihr Eingabealphabet {0, 1} ist, können Sie die Anzahl der zulässigen Permutationen erheblich reduzieren. Drehen Sie die erste Liste in eine (pseudo-) normalisierte Form (angesichts der Verteilung in Ihrer Frage würde ich eine auswählen, bei der sich eines der 1 Bits ganz links und eines der 0 Bits ganz rechts befindet). Drehen Sie nun vor jedem Vergleich nacheinander die andere Liste durch die möglichen Positionen mit demselben Ausrichtungsmuster.
Wenn Sie beispielsweise insgesamt vier 1-Bits haben, kann es bei dieser Ausrichtung höchstens 4 Permutationen geben, und wenn Sie Cluster benachbarter 1-Bits haben, reduziert jedes zusätzliche Bit in einem solchen Cluster die Anzahl der Positionen.
Dies verallgemeinert sich auf größere Alphabete und unterschiedliche Ausrichtungsmuster; Die größte Herausforderung besteht darin, eine gute Normalisierung mit nur wenigen möglichen Darstellungen zu finden. Im Idealfall wäre es eine richtige Normalisierung mit einer einzigen eindeutigen Darstellung, aber angesichts des Problems denke ich nicht, dass dies möglich ist.
quelle
Aufbauend auf der Antwort von RocketRoy: Konvertieren Sie alle Ihre Listen im Voraus in vorzeichenlose 64-Bit-Zahlen. Drehen Sie für jede Liste diese 55 Bits, um den kleinsten numerischen Wert zu finden.
Sie haben jetzt für jede Liste einen einzelnen vorzeichenlosen 64-Bit-Wert, den Sie direkt mit dem Wert der anderen Listen vergleichen können. Die Funktion is_circular_identical () ist nicht mehr erforderlich.
(Im Wesentlichen erstellen Sie einen Identitätswert für Ihre Listen, der nicht von der Rotation der Listenelemente betroffen ist.) Dies würde sogar funktionieren, wenn Sie eine beliebige Anzahl von Einsen in Ihren Listen haben.
quelle
Dies ist die gleiche Idee von Salvador Dali, benötigt aber keine String-Konvertierung. Dahinter steckt die gleiche KMP-Wiederherstellungsidee, um eine unmögliche Schichtinspektion zu vermeiden. Sie rufen nur KMPModified auf (Liste1, Liste2 + Liste2).
Ich hoffe das hilft!
quelle
Das Problem vereinfachen
(0,1)
1
s einer Zählung zuordnen0
s in eine negative ZählungBeispiel
Prozess überprüfen
Der Griff
lookup
undlook-ahead
Pseudo-Code
Funktionen
MAP_LIST(LIST A):LIST
KARTENFOLGENDE ELEMENTE ALS ZÄHLER IN EINER NEUEN LISTELOOKUP_INDEX(LIST A, INTEGER E):LIST
RÜCKGABELISTE DER INDIZES,E
IN DENEN DAS ELEMENT IN DER LISTE EXISTIERTA
COUNT_CHAR(LIST A , INTEGER E):INTEGER
Zählen Sie, wieE
viele Male ein Element in einer Liste auftrittA
ALPHA_NGRAM(LIST A,LIST B,INTEGER I,INTEGER N):BOOLEAN
PRÜFEN, WENNB[I]
SIEA[0]
N-GRAM
IN BEIDEN ANWEISUNGEN ENTSPRECHENSchließlich
Wenn die Listengröße ziemlich groß sein wird oder wenn das Element, von dem aus wir den Zyklus überprüfen, häufig hoch ist, können wir Folgendes tun:
Suchen Sie zunächst nach dem am wenigsten häufigen Element in der ersten Liste
Erhöhen Sie den n-Gramm-N-Parameter, um die Wahrscheinlichkeit zu verringern, dass die lineare Prüfung durchlaufen wird
quelle
Eine effiziente, schnell zu berechnende "kanonische Form" für die betreffenden Listen kann abgeleitet werden als:
a
) muss zwischen18
und52
(einschließlich) liegen. Codieren Sie es neu zwischen0
und34
.b
) muss zwischen0
und stehen26
, aber es spielt keine Rolle.52 - (a + b)
und keine Informationen hinzufügtDie kanonische Form ist die Ganzzahl
b * 35 + a
, die zwischen0
und936
(einschließlich) liegt und ziemlich kompakt ist (insgesamt gibt es477
kreisförmig eindeutige Listen).quelle
Ich habe eine einfache Lösung geschrieben, die beide Listen vergleicht und nur den Index des verglichenen Werts für jede Iteration erhöht (und umschließt).
Ich kenne Python nicht gut, also habe ich es in Java geschrieben, aber es ist wirklich einfach, daher sollte es einfach sein, es an jede andere Sprache anzupassen.
Auf diese Weise können Sie auch Listen anderer Typen vergleichen.
quelle
Wie bereits erwähnt, können Sie die normalisierte Rotation einer Liste vergleichen, sobald Sie sie gefunden haben.
Hier ist ein Arbeitscode, der dies tut. Die grundlegende Methode besteht darin, eine normalisierte Rotation für jede Liste zu finden und zu vergleichen:
Beachten Sie, dass diese Methode nicht von Zahlen abhängt. Sie können Listen mit Zeichenfolgen übergeben (alle Werte, die verglichen werden können).
Anstatt eine Liste-in-Liste-Suche durchzuführen, möchten wir, dass die Liste mit dem Mindestwert beginnt. Daher können wir die Mindestwerte durchlaufen und suchen, bis wir den niedrigsten Wert in Folge gefunden haben, und diesen für weitere Vergleiche speichern bis wir das Beste haben.
Es gibt viele Möglichkeiten, bei der Berechnung des Index vorzeitig zu beenden, Details zu einigen Optimierungen.
Beachten Sie, dass in Python eine Liste-in-Liste-Suche möglicherweise schneller ist. Ich war jedoch daran interessiert, einen effizienten Algorithmus zu finden, der auch in anderen Sprachen verwendet werden kann. Es ist auch von Vorteil, das Erstellen neuer Listen zu vermeiden.
Siehe: diese Schnipsel für einige weitere Tests / Beispiele.
quelle
Sie können ganz einfach überprüfen, ob eine Liste A einer zyklischen Verschiebung von Liste B in der erwarteten O (N) -Zeit entspricht.
Ich würde eine Polynom-Hash-Funktion verwenden, um den Hash von Liste A und jede zyklische Verschiebung von Liste B zu berechnen. Wenn eine Verschiebung von Liste B denselben Hash wie Liste A hat, würde ich die tatsächlichen Elemente vergleichen, um festzustellen, ob sie gleich sind .
Der Grund dafür ist, dass Sie mit Polynom-Hash-Funktionen (die sehr häufig sind!) Den Hash jeder zyklischen Verschiebung aus der vorherigen in konstanter Zeit berechnen können, sodass Sie Hashes für alle zyklischen Verschiebungen in O ( N) Zeit.
Es funktioniert so:
Nehmen wir an, B hat N Elemente, dann ist der Hash von B unter Verwendung von Primzahl P:
Dies ist eine optimierte Methode zur Bewertung eines Polynoms in P und entspricht:
Beachten Sie, wie jedes B [i] mit P ^ (N-1-i) multipliziert wird. Wenn wir B um 1 nach links verschieben, wird jedes B [i] mit einem zusätzlichen P multipliziert, mit Ausnahme des ersten. Da sich die Multiplikation über die Addition verteilt, können wir alle Komponenten gleichzeitig multiplizieren, indem wir den gesamten Hash multiplizieren und dann den Faktor für das erste Element festlegen.
Der Hash der Linksverschiebung von B ist gerecht
Die zweite Verschiebung nach links:
und so weiter...
HINWEIS: Alle oben genannten Berechnungen werden modulo mit einer bestimmten Maschinenwortgröße durchgeführt, und Sie müssen P ^ N nur einmal berechnen.
quelle
Verwenden Sie Sets, um die pythonischste Methode zu finden!
quelle