2010-02-17 10 views
10

Hier ist eine sehr einfache Art und Weise eine suffix array aus einer Zeichenfolge in Python zu bauen:strcmp für Python oder wie Substrings effizient sortieren (ohne Kopie), wenn ein Suffix Array Aufbau

def sort_offsets(a, b): 
    return cmp(content[a:], content[b:]) 

content = "foobar baz foo" 
suffix_array.sort(cmp=sort_offsets) 
print suffix_array 
[6, 10, 4, 8, 3, 7, 11, 0, 13, 2, 12, 1, 5, 9] 

jedoch „Inhalt [a: ] "erstellt eine Kopie des Inhalts, die sehr ineffizient wird, wenn der Inhalt groß wird. Also frage ich mich, ob es eine Möglichkeit gibt, die beiden Teilstrings zu vergleichen, ohne sie kopieren zu müssen. Ich habe versucht, den eingebauten Puffer zu verwenden, aber es hat nicht funktioniert.

+0

Was ist Ihr ‚Inhalt‘ typischerweise aussehen? Englischer Text? Zufällige Reihenfolge? Irgendwas dazwischen? Wie groß sind die Chancen für lange (sagen wir über 100 Zeichen) Teilstrings in "content"? –

+0

Ich schrieb diesen [Python-Code, der all Teil der langen Schnur sortieren] (http://stackoverflow.com/a/13693834/448474) (1000000 Zeichen) und die längsten wiederholt Teilzeichenfolge in 5 Sekunden finden. – hynekcer

Antwort

5

Ich weiß nicht, ob es eine schnelle Art und Weise ist Strings zu vergleichen, aber Sie können Ihren Code viel schneller (und einfacher) machen, indem key anstelle von cmp:

suffix_array.sort(key=lambda a: content[a:]) 

Diese den Teil schaffen nur einmal für jeden Wert von a.

Edit: Ein möglicher Nachteil ist, dass es O (n^2) Speicher für die Teilketten erfordern.

+0

Und das Argument 'sort'' cmp' ist in 3.x verschwunden. –

+0

Dies dauert 15 Sekunden für eine Zeichenfolge der Länge 75000 auf meiner Maschine, so wird dies nicht skalieren - aber nette Idee. – ephes

6

Die buffer Funktion kopiert nicht die gesamte Zeichenfolge, sondern erzeugt ein Objekt, das nur die Quellzeichenfolge verweist. Mit dem Vorschlag von interjay wäre das:

suffix_array.sort(key=lambda a: buffer(content, a)) 
+0

Ich habe auch mit Puffer herum gespielt, aber nicht das richtige Suffix-Array bekommen. Diese Zeile sieht sehr vielversprechend aus, funktioniert aber auch nicht (mein kurzes Beispiel funktioniert, bricht aber bei größeren). Sobald ich weiß, warum es bricht, werde ich wieder kommentieren. – ephes

+0

Ha! Es ist weil str! = Unicode.Meine größere Strings sind Unicode und deshalb sollte ich geschrieben haben: sizeof_pointer = len (str (Puffer (u'a '))) suffix_array.sort (key = Lambda a: Puffer (Inhalt, a * sizeof_pointer)) Um diese Hässlichkeit zu vermeiden, verwende ich normalisierte utf8-codierte Strings anstelle von Unicode. Seltsam. – ephes

3

+1 für ein sehr interessantes Problem! Ich kann keine offensichtliche Art und Weise sehe dies direkt zu tun, aber ich konnte eine signifikante Beschleunigung (für 100000 Zeichenkette eine Größenordnung) erhalten, indem die folgende Vergleichsfunktion anstelle von Ihnen mit:

def compare_offsets2(a, b): 
    return (cmp(content[a:a+10], content[b:b+10]) or 
      cmp(content[a:], content[b:])) 

Mit anderen Worten, beginnen Sie mit dem Vergleich der ersten 10 Zeichen jedes Suffixes; nur wenn das Ergebnis dieses Vergleichs 0 ist, was anzeigt, dass Sie eine Übereinstimmung für die ersten 10 Zeichen haben, fahren Sie fort, die gesamten suffices zu vergleichen. Experiment mit dem besten Preis zu finden:

Offensichtlich 10 könnte alles sein.

Diese Vergleichsfunktion ist auch ein schönes Beispiel für etwas, das nicht leicht mit einer Schlüsselfunktion ersetzt wird.

+1

Ich bekomme noch bessere Ergebnisse, indem ich zuerst eine schlüsselbasierte Sortierung mit key = lambda a: content [a: a + 10] mache und dann mit der CMP-basierten Sortierung weiter oben nachschaue. Pythons Sortieralgorithmus eignet sich besonders für Listen, die bereits in fast sortierter Reihenfolge vorliegen. –

+0

Gute Idee! Der wirkliche Anwendungsfall besteht darin, alle Dokumente zu finden, die mit einem alphanumerischen Teilstring übereinstimmen. Der "Inhalt" ist eine Verkettung aller Dokumente und da ich weiß, welchen Offset man für jedes Dokument im Inhalt hat, benutze ich die binäre Suche (in der Offset-Liste), um die maximale Anzahl von Zeichen zu finden, die ich kopieren muss. Aber es ist immer noch ziemlich langsam. Wahrscheinlich muss ich es in c ... – ephes

+0

Eine andere Möglichkeit, bevor Sie C verwenden: Es könnte möglich sein, das Ctypes-Modul zu verwenden, um Cs strcmp zu erhalten, und das von Python zu verwenden. Eingebettete Nullzeichen in Python-Strings würden Schwierigkeiten verursachen, aber das ist in der Praxis kein Problem. Aber ich stimme zu, dass dies scheint genau die Art von Aufgabe, wo das Umschreiben der langsamen Teil in C angemessen ist. –

0

könnten Sie die blist extension type verwenden, die ich geschrieben habe. Ein blist funktioniert wie die eingebaute in list, sondern (unter anderem) verwendet copy-on-write, so dass eine Scheibe unter O braucht Zeit und Speicher (n log).

from blist import blist 

content = "foobar baz foo" 
content = blist(content) 
suffix_array = range(len(content)) 
suffix_array.sort(key = lambda a: content[a:]) 
print suffix_array 
[6, 10, 4, 8, 3, 7, 11, 0, 13, 2, 12, 1, 5, 9] 

Ich konnte eine suffix_array aus einer zufällig erzeugten 100.000-Zeichenkette in weniger als 5 Sekunden erzeugen, und das schließt die Zeichenkette zu erzeugen.

+0

Getestetes Blist mit einem Teststring von 12 Millionen Zeichen. Ich gab nach einer Stunde CPU-Zeit auf. Die Speicherauslastung betrug zu diesem Zeitpunkt 21 GByte und wuchs. Die Pufferlösung benötigt 6,7 GByte und endet nach 8 Minuten. Da meine realen Daten ungefähr 500 Millionen Zeichen haben, werden beide Lösungen nicht funktionieren. Atm ich benutze http://sites.google.com/site/yuta256/sais, um das sortierte suffix_array zu bekommen und lese es wieder in python mit array.array.fromfile ... – ephes

+0

Ich schlage vor, dass Sie Ihre ursprüngliche Frage aktualisieren, um Ihre zu erwähnen echte Daten enthalten 500 Millionen Zeichen. Wie viel Speicher braucht man, um das suffix_array vor dem Sortieren zu speichern? Für so große Daten würde ich definitiv C verwenden, um den Overhead des Speichers zu reduzieren. –