Wie überprüft man, ob zwei Listen in Python zirkulär identisch sind?

144

Zum Beispiel habe ich Listen:

%Vor%

Sie scheinen anders zu sein, aber wenn angenommen wird, dass der Anfang und das Ende verbunden sind, dann sind sie zirkulär 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örmigen Zustand gibt es 26.235 (55 wählen 3) Listen. Wenn jedoch die Bedingung 'circular' existiert, gibt es eine große Anzahl von zirkular identischen Listen

Zur Zeit überprüfe ich die zirkuläre Identität, indem ich folge:

%Vor%

Diese Funktion erfordert im schlimmsten Fall 55 zyklische Schaltvorgänge. Und es gibt 26.235 Listen, die miteinander verglichen werden sollen. Kurz gesagt, ich brauche 55 * 26.235 * (26.235 - 1) / 2 = 18.926.847.225 Berechnungen. Es ist ungefähr 20 Giga!

Gibt es einen guten Weg, dies mit weniger Berechnungen zu tun? Oder irgendwelche Datentypen, die zirkulär unterstützen?

    
Jeon 14.11.2014, 07:16
quelle

18 Antworten

128

Zunächst kann dies in O(n) in Bezug auf die Länge der Liste gemacht werden Sie können feststellen, dass, wenn Sie Ihre Liste 2 mal duplizieren ( [1, 2, 3] ) [1, 2, 3, 1, 2, 3] ist, dann wird Ihre neue Liste definitiv alle möglichen zyklischen Listen enthalten.

Sie müssen also nur überprüfen, ob die Liste, die Sie suchen, innerhalb eines 2-fachen der Startliste liegt. In Python können Sie dies auf folgende Weise erreichen (vorausgesetzt, dass die Längen gleich sind).

%Vor%

Einige Erklärungen zu meinem oneliner: list * 2 kombiniert eine Liste mit sich selbst, map(str, [1, 2]) konvertiert alle Zahlen in einen String und ' '.join() konvertiert das Array ['1', '2', '111'] in einen String '1 2 111' .

Wie einige Leute in den Kommentaren darauf hingewiesen haben, kann oneliner möglicherweise einige falsche Positive geben, um alle möglichen Randfälle zu erfassen:

%Vor%

P.S.1 Wenn Sie über die zeitliche Komplexität sprechen, sollten Sie beachten, dass O(n) erreicht wird, wenn die Teilzeichenfolge in O(n) time gefunden werden kann. Es ist nicht immer so und hängt von der Implementierung in Ihrer Sprache ab (, obwohl es möglicherweise in linearer Zeit KMP zum Beispiel möglich ist) ).

P.S.2 für Leute, die Angst haben, Strings zu betreiben und aufgrund dieser Tatsache denken, dass die Antwort nicht gut ist. Was ist wichtig, Komplexität und Geschwindigkeit? Dieser Algorithmus läuft möglicherweise in O(n) time und O(n) space, was ihn viel besser macht als alles in O(n^2) domain. Um dies selbst zu sehen, können Sie einen kleinen Benchmark ausführen (erstellt eine Zufallsliste, öffnet das erste Element und fügt es an das Ende an, um eine zyklische Liste zu erstellen. Sie können Ihre eigenen Manipulationen durchführen)

%Vor%

0,3 Sekunden auf meiner Maschine. Nicht wirklich lange. Versuchen Sie nun, dies mit O(n^2) solutions zu vergleichen. Während es vergleicht, können Sie von den USA nach Australien reisen (wahrscheinlich von einem Kreuzfahrtschiff)

    
Salvador Dali 14.11.2014, 07:20
quelle
38

Nicht genug in Python genug, um dies in der von Ihnen gewünschten Sprache zu beantworten, aber in C / C ++ würde ich die Nullen und Einsen in Bits umwandeln und sie auf die niedrigstwertigen Bits eines uint64_t schieben . Dies ermöglicht Ihnen, alle 55 Bits in einem Schlag zu vergleichen - 1 Uhr.

Schrecklich schnell, und das Ganze passt in On-Chip-Caches (209.880 Bytes). Hardware-Unterstützung zum gleichzeitigen Verschieben aller 55 Listenmitglieder ist nur in den Registern einer CPU verfügbar. Gleiches gilt für den Vergleich aller 55 Mitglieder gleichzeitig. Dies ermöglicht eine Eins-zu-Eins-Zuordnung des Problems zu einer Softwarelösung. (und die Verwendung der SIMD / SSE 256-Bit-Register, bei Bedarf bis zu 256 Mitglieder) Als Ergebnis ist der Code für den Leser sofort offensichtlich.

Vielleicht können Sie das in Python implementieren, ich weiß es einfach nicht gut genug, um zu wissen, ob das möglich ist oder wie die Leistung sein könnte.

Nachdem wir darauf geschlafen hatten, wurden einige Dinge offensichtlich und alles zum Besseren.

1.) Es ist so einfach, die kreisförmig verknüpfte Liste mit Bits zu drehen, für die Dalis cleverer Trick nicht nötig ist. Innerhalb eines 64-Bit-Registers wird das Standard-Bit-Shifting die Rotation sehr einfach durchführen und in einem Versuch, dies alles Python-freundlicher zu machen, indem arithmetische anstelle von Bit-Ops verwendet wird.

2.) Die Bitverschiebung kann einfach durch Division durch 2 erreicht werden.

3.) Das Ende der Liste für 0 oder 1 zu überprüfen, kann einfach mit Modulo 2 durchgeführt werden.

4.) Das Verschieben einer 0 in den Kopf der Liste vom Ende aus kann durch Division durch 2 erfolgen. Wenn nämlich die Null tatsächlich verschoben würde, würde dies das 55. Bit falsch machen, was es bereits tut absolut nichts.

5.) "Bewegen" eine 1 an den Kopf der Liste vom Schwanz kann durch Dividieren von 2 und Hinzufügen von 18.014.398.509.481.984 - das ist der Wert durch die Markierung der 55. Bit wahr und alle übrigen false erstellt werden.

6. Wenn ein Vergleich zwischen dem Anker und dem zusammengesetzten uint64_t nach einer gegebenen Drehung TRUE ist, brechen Sie auf und geben Sie TRUE zurück.

Ich würde das gesamte Array von Listen direkt in ein Array von uint64_ts konvertieren, um zu vermeiden, dass die Konvertierung wiederholt durchgeführt werden muss.

Nachdem ich ein paar Stunden damit verbracht habe, den Code zu optimieren und die Assemblersprache zu studieren, konnte ich die Laufzeit um 20% reduzieren. Ich sollte hinzufügen, dass der O / S und MSVC Compiler gestern ebenfalls mittags aktualisiert wurde. Aus welchen Gründen auch immer, die Qualität des vom C-Compiler erzeugten Codes hat sich nach dem Update (15.11.2014) dramatisch verbessert. Laufzeit ist jetzt ~ 70 Takte, 17 Nanosekunden um einen Ankerring mit allen 55 Windungen eines Testrings zu vergleichen und zu vergleichen und NxN aller Ringe gegen alle anderen erfolgt in 12,5 Sekunden .

Dieser Code ist so eng, dass alle bis auf 4 Register in 99% der Fälle nichts tun. Die Assemblersprache entspricht dem C-Code fast Zeile für Zeile. Sehr leicht zu lesen und zu verstehen. Ein tolles Montageprojekt, wenn sich jemand das selbst beibrachte.

Hardware ist Hazwell i7, MSVC 64-bit, volle Optimierungen.

%Vor%

    
RocketRoy 14.11.2014 09:14
quelle
32

Wenn Sie zwischen den Zeilen lesen, klingt es so, als ob Sie einen Vertreter jeder zirkulären Äquivalenzklasse von Strings mit 3 Einsen und 52 Nullen aufzählen möchten. Wechseln wir von einer dichten Repräsentation zu einer spärlichen (drei Zahlen in range(55) ). In dieser Darstellung ist die zirkulare Verschiebung von s by k durch das Verständnis set((i + k) % 55 for i in s) gegeben. Der lexikografische Minimum-Repräsentant in einer Klasse enthält immer die Position 0. Bei einer Menge der Form {0, i, j} mit 0 < 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 benötigen wir (i, j) <= min((j - i, 55 - i), (55 - j, 55 + i - j)) für das Original als Minimum. Hier ist ein Aufzählungscode.

%Vor%     
David Eisenstat 14.11.2014 16:00
quelle
12

Wiederholen Sie das erste Array und verwenden Sie dann den Z-Algorithmus (O ( n) Zeit) um das zweite Array innerhalb des ersten zu finden.

(Hinweis: Sie müssen das erste Array nicht physisch kopieren. Sie können während des Abgleichs einfach umbrechen.)

Das Schöne am Z-Algorithmus ist, dass es im Vergleich zu KMP, BM usw. sehr einfach ist Wenn Sie sich jedoch ambitioniert fühlen, könnten Sie String-Matching in linearer Zeit und konstant -Raum durchführen - strstr zum Beispiel tut dies. Die Implementierung wäre jedoch schmerzhafter.

    
Mehrdad 14.11.2014 11:33
quelle
6

Wenn man Salvador Dalis sehr clevere Lösung verfolgt, ist es am besten, dafür zu sorgen, dass alle Elemente die gleiche Länge haben und beide LISTS die gleiche Länge haben.

%Vor%

Keine Ahnung, ob das schneller oder langsamer ist als AshwiniChaudhary's empfohlene Regex-Lösung in Salvador Dalis Antwort, die lautet:

%Vor%     
Adam Smith 14.11.2014 07:37
quelle
3

In Anbetracht der Tatsache, dass Sie so viele Vergleiche machen müssen, lohnt es sich vielleicht, einen ersten Durchlauf durch Ihre Listen zu machen, um sie in eine Art von kanonischer Form zu konvertieren, die leicht verglichen werden kann?

Versuchen Sie, eine Reihe von kreisförmig eindeutigen Listen zu erhalten? Wenn dies der Fall ist, können Sie sie nach der Konvertierung in Tupel in ein Set werfen.

%Vor%

Entschuldigung David Eisenstat dafür, dass er seine Antwort nicht gefunden hat.

    
user3828641 14.11.2014 18:04
quelle
3

Sie können eine Liste wie folgt rollen:

%Vor%     
Stefan Gruenwald 15.11.2014 17:07
quelle
3

Konvertiere zuerst jedes deiner Listenelemente (in einer Kopie, falls nötig) in diese gedrehte Version, die lexikalisch am größten ist.

Sortieren Sie dann die resultierende Liste von Listen (wobei ein Index in der ursprünglichen Listenposition beibehalten wird) und vereinheitlichen Sie die sortierte Liste, indem Sie alle Duplikate in der ursprünglichen Liste nach Bedarf markieren.

    
user4258287 16.11.2014 15:38
quelle
2

Piggybacking auf @ SalvadorDali Beobachtung auf der Suche nach Übereinstimmungen eines in einer beliebigen Länge Slice in b + b, hier ist eine Lösung mit nur Listen-Operationen.

%Vor%

2. Ansatz: [gelöscht]

    
PaulMcG 14.11.2014 09:06
quelle
1

Keine vollständige, freistehende Antwort, aber zum Thema Optimierung durch die Reduzierung von Vergleichen dachte ich auch an normalisierte Darstellungen.

Wenn Ihr Eingabealphabet {0, 1} ist, könnten Sie die Anzahl der zulässigen Permutationen erheblich reduzieren. Drehen Sie die erste Liste in eine (pseudo-) normalisierte Form (bei der Verteilung in Ihrer Frage würde ich eine auswählen, bei der eines der 1 Bits ganz links und eines der 0 Bits ganz rechts steht). Jetzt vor jedem Vergleich nacheinander die andere Liste durch die möglichen Positionen mit dem gleichen Ausrichtungsmuster drehen.

Wenn Sie beispielsweise insgesamt vier 1 Bits haben, können bei dieser Ausrichtung maximal 4 Permutationen auftreten. Wenn Sie Cluster aus benachbarten 1 Bits haben, reduziert jedes zusätzliche Bit in einem solchen Cluster die Anzahl der Positionen.

%Vor%

Dies generalisiert zu größeren Alphabeten und verschiedenen Ausrichtungsmustern; 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 glaube ich nicht, dass das möglich ist.

    
tripleee 17.11.2014 10:07
quelle
0

Bauen Sie weiter auf RocketRoys Antwort: Wandle alle deine Listen in vorzeichenlose 64-Bit-Nummern um. Drehen Sie diese 55 Bits für jede Liste, um den kleinsten numerischen Wert zu finden.

Sie haben jetzt einen einzelnen vorzeichenlosen 64-Bit-Wert für jede Liste, den Sie direkt mit dem Wert der anderen Listen vergleichen können. Die Funktion is_circular_identical () wird nicht mehr benötigt.

(Im Wesentlichen erstellen Sie einen Identitätswert für Ihre Listen, der nicht von der Rotation der Listenelemente beeinflusst wird.) Das würde sogar funktionieren, wenn Sie eine beliebige Anzahl von Einsen in Ihren Listen haben.

    
Kris M 18.11.2014 20:39
quelle
0

Dies ist die selbe Idee von Salvador Dali, benötigt aber nicht die String-Konvertierung. Dahinter steht die gleiche KMP-Recover-Idee, um eine unmögliche Schichtkontrolle zu vermeiden. Sie rufen nur KMPModified auf (list1, list2 + list2).

%Vor%

Hoffe diese Hilfe!

    
Miguel 19.11.2014 05:12
quelle
0

Vereinfachung des Problems

  • Das Problem besteht aus der Liste der bestellten Artikel
  • Die Domäne des Werts ist binary (0,1)
  • Wir können das Problem reduzieren, indem wir fortlaufende 1 s in eine Zählung
  • mappen
  • und aufeinanderfolgende 0 s zu einer negativen Zählung

Beispiel

%Vor%
  • Dieser Prozess erfordert, dass das erste Element und das letzte Element unterschiedlich sein müssen
  • Dies reduziert die Anzahl der Vergleiche insgesamt

Überprüfung des Prozesses

  • Wenn wir annehmen, dass sie doppelt sind, können wir annehmen, wonach wir suchen
  • Grundsätzlich muss das erste Element der ersten Liste irgendwo in der anderen Liste vorhanden sein
  • Gefolgt von dem, was in der ersten Liste gefolgt wird, und auf die gleiche Weise
  • Die vorherigen Elemente sollten die letzten Elemente aus der ersten Liste sein
  • Da es kreisförmig ist, ist die Reihenfolge gleich

Der Griff

  • Die Frage ist hier, wo Sie anfangen sollen, technisch bekannt als lookup und look-ahead
  • Wir werden nur überprüfen, wo das erste Element der ersten Liste durch die zweite Liste
  • existiert
  • Die Wahrscheinlichkeit von häufigen Elementen ist niedriger, da wir die Listen in Histogramme
  • abgebildet haben

Pseudocode

%Vor% %Vor%

Funktionen

  • MAP_LIST(LIST A):LIST MAP CONSQUETIVE ELEMENTS ALS ZÄHLEN IN EINER NEUEN LISTE

  • LOOKUP_INDEX(LIST A, INTEGER E):LIST RETURN LISTE DER INDIZES, WO DAS ELEMENT E EXIST IN DER LISTE A

  • COUNT_CHAR(LIST A , INTEGER E):INTEGER COUNT WIE VIELE ZEITEN EIN ELEMENT E OCCUR IN LISTE A

  • ALPHA_NGRAM(LIST A,LIST B,INTEGER I,INTEGER N):BOOLEAN ÜBERPRÜFEN WENN B[I] GLEICHMÄSSIG ZU A[0] N-GRAM IN BEIDE RICHTUNGEN

  • ist

Endlich

Wenn die Listengröße ziemlich groß ist oder wenn das Element, von dem aus wir den Zyklus beginnen, häufig hoch ist, können wir Folgendes tun:

  • Suchen Sie nach dem am wenigsten häufigen Element in der ersten Liste, das mit

  • beginnen soll
  • erhöhen Sie den n-gram N-Parameter, um die Wahrscheinlichkeit zu verringern, den linearen Check zu durchlaufen

Khaled.K 19.11.2014 07:01
quelle
0

Eine effiziente, schnell zu rechnende "kanonische Form" für die fraglichen Listen kann abgeleitet werden als:

  • Zählen Sie die Anzahl der Nullen zwischen den Einsen (ignorieren Sie den Umbruch), um drei Zahlen zu erhalten.
  • Drehe die drei Zahlen so, dass die größte Zahl zuerst steht.
  • Die erste Zahl ( a ) muss zwischen 18 und 52 (inklusive) liegen. Re-encodiere es zwischen 0 und 34 .
  • Die zweite Zahl ( b ) muss zwischen 0 und 26 liegen, spielt aber keine große Rolle.
  • Lassen Sie die dritte Zahl fallen, da sie nur 52 - (a + b) ist und keine Informationen hinzufügt

Die kanonische Form ist die Ganzzahl b * 35 + a , die zwischen 0 und 936 (inklusive) liegt, was ziemlich kompakt ist (es gibt 477 zirkular-eindeutige Listen insgesamt).

    
Aleksandr Dubinsky 19.11.2014 15:53
quelle
0

Ich habe eine einfache Lösung geschrieben, die beide Listen vergleicht und nur den Index des verglichenen Wertes 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 leicht sein, es an jede andere Sprache anzupassen.

Dadurch können Sie auch Listen anderer Typen vergleichen.

%Vor%     
das Keks 22.11.2014 23:20
quelle
0

Wie andere bereits erwähnt haben, können Sie, sobald Sie die normalisierte Rotation einer Liste gefunden haben, diese vergleichen.

Hier ist ein Arbeitscode, der das macht, Die grundlegende Methode besteht darin, für jede Liste eine normalisierte Rotation zu finden und zu vergleichen:

  • Berechne einen normalisierten Rotationsindex für jede Liste.
  • Überstreiche beide Listen mit ihren Offsets, vergleiche jedes Element und gib es zurück, wenn sie nicht übereinstimmen.

Beachten Sie, dass diese Methode nicht von Zahlen abhängt, Sie können Listen mit Strings übergeben (alle Werte, die verglichen werden können).

Anstatt eine List-in-List-Suche zu machen, wissen wir, dass die Liste mit dem Minimalwert beginnen soll - also können wir die Minimalwerte durchlaufen und suchen, bis wir herausfinden, welche die niedrigsten aufeinanderfolgenden Werte hat für weitere Vergleiche, bis wir das Beste haben.

Es gibt viele Möglichkeiten, bei der Berechnung des Index früh zu beenden, Details zu einigen Optimierungen.

  • Überspringt die Suche nach dem besten Mindestwert, wenn nur einer vorhanden ist.
  • Überspringt die Suche nach Mindestwerten, wenn der vorherige Wert ebenfalls ein Mindestwert ist (es wird niemals eine bessere Übereinstimmung sein).
  • Überspringt die Suche, wenn alle Werte gleich sind.
  • Frühzeitig fehlschlagen, wenn Listen unterschiedliche Mindestwerte haben.
  • Verwenden Sie den regulären Vergleich, wenn Offsets übereinstimmen.
  • Passen Sie Offsets an, um zu vermeiden, dass die Indexwerte während des Vergleichs in eine der Listen eingeschlossen werden.

Beachten Sie, dass in Python eine List-in-List-Suche zwar schneller sein könnte, ich jedoch einen effizienten Algorithmus finden wollte, der auch in anderen Sprachen verwendet werden könnte. Außerdem ist es von Vorteil, das Erstellen neuer Listen zu vermeiden.

%Vor%

Siehe: dieses Snippet für weitere Tests / Beispiele.

    
ideasman42 02.01.2016 09:36
quelle
0

Sie können überprüfen, ob eine Liste A einer zyklischen Verschiebung der Liste B in der erwarteten O (N) -Zeit ziemlich leicht entspricht.

Ich würde eine Polynom-Hash-Funktion verwenden, um den Hash der Liste A und jede zyklische Verschiebung von Liste B zu berechnen. Wo eine Verschiebung von Liste B den gleichen Hash wie Liste A hat, würde ich die tatsächlichen Elemente vergleichen, um zu sehen, ob Sie sind gleich.

Der Grund dafür ist, dass Sie mit polynomialen Hash-Funktionen (die extrem häufig sind!) den Hash jeder zyklischen Verschiebung von der vorherigen in konstanter Zeit berechnen können, sodass Sie Hashwerte für alle zyklischen Verschiebungen berechnen können in O (N) Zeit.

Es funktioniert so:

Nehmen wir an, B hat N Elemente, dann ist der Hash von B mit Primzahl P:

%Vor%

Dies ist eine optimierte Methode zum Auswerten eines Polynoms in P und entspricht:

%Vor%

Beachten Sie, wie jedes B [i] mit P ^ (N-1-i) multipliziert wird. Wenn wir B um 1 nach links verschieben, wird jeder B [i] mit Ausnahme des ersten um ein zusätzliches P multipliziert. Da Multiplikation über Addition verteilt wird, können wir alle Komponenten auf einmal multiplizieren, indem wir einfach den gesamten Hash multiplizieren und dann den Faktor für das erste Element korrigieren.

Der Hash der linken Verschiebung von B ist nur

%Vor%

Die zweite Verschiebung nach links:

%Vor%

und so weiter ...

HINWEIS: Alle obigen Berechnungen werden modulo einer Maschinenwortgröße durchgeführt, und Sie müssen nur einmal P ^ N berechnen.

    
Matt Timmermans 02.01.2016 23:25
quelle
-1

Um auf die pythischste Art und Weise zu kleben, benutzen Sie Sätze!

%Vor%     
Louis 19.11.2014 06:52
quelle

Tags und Links