Finden Sie effizient binäre Zeichenfolgen mit geringem Hamming-Abstand in großen Mengen

80

Problem:

Bei einer großen (~ 100 Millionen) Liste von vorzeichenlosen 32-Bit-Ganzzahlen, einem vorzeichenlosen 32-Bit-Ganzzahl-Eingabewert und einer maximalen Hamming-Entfernung werden alle Listenelemente zurückgegeben, die innerhalb der angegebenen Hamming-Entfernung des Eingabewerts liegen.

Die tatsächliche Datenstruktur zum Speichern der Liste ist offen, die Leistungsanforderungen schreiben eine In-Memory-Lösung vor, die Kosten für den Aufbau der Datenstruktur sind zweitrangig, die geringen Kosten für die Abfrage der Datenstruktur sind kritisch.

Beispiel:

For a maximum Hamming Distance of 1 (values typically will be quite small)

And input: 
00001000100000000000000001111101

The values:
01001000100000000000000001111101 
00001000100000000010000001111101 

should match because there is only 1 position in which the bits are different.

11001000100000000010000001111101

should not match because 3 bit positions are different.

Meine bisherigen Gedanken:

Verwenden Sie für den entarteten Fall einer Hamming-Distanz von 0 einfach eine sortierte Liste und führen Sie eine binäre Suche nach dem spezifischen Eingabewert durch.

Wenn der Hamming-Abstand immer nur 1 wäre, könnte ich jedes Bit in der ursprünglichen Eingabe umdrehen und die obigen 32 Mal wiederholen.

Wie kann ich effizient (ohne die gesamte Liste zu scannen) Listenmitglieder mit einem Hamming-Abstand> 1 ermitteln?

Eric J.
quelle
Wie wäre es mit einer Mutation der Kriterien um die erwartete Hamming-Distanz? Eine wiederkehrende Funktion kann dies tun. Der nächste Schritt wird darin bestehen, die Vereinigung der beiden Listen zu erreichen.
XecP277
Hier ist ein kürzlich veröffentlichter Artikel zu diesem Problem: Verarbeitung von Hamming-Entfernungsabfragen in großem Maßstab .
Hammar
@Eric Du hast gesagt "Für eine maximale Hamming-Distanz von 1 (Werte sind normalerweise ziemlich klein)" . Können Sie sagen, was "ziemlich klein" bedeutete?
Stefan Pochmann
@Eric Waren die ~ 100 Millionen Zahlen alle eindeutig oder gab es Duplikate?
Stefan Pochmann
@StefanPochmann: Es gibt keine Duplikate. Die größte Entfernung von Interesse wäre 4-5.
Eric J.

Antworten:

111

Frage: Was wissen wir über den Hamming-Abstand d (x, y)?

Antworten:

  1. Es ist nicht negativ: d (x, y) ≥ 0
  2. Es ist nur Null für identische Eingaben: d (x, y) = 0 ⇔ x = y
  3. Es ist symmetrisch: d (x, y) = d (y, x)
  4. Es gehorcht der Dreiecksungleichung , d (x, z) ≤ d (x, y) + d (y, z)

Frage: Warum interessiert es uns?

Antwort: Weil dies bedeutet, dass der Hamming-Abstand eine Metrik für einen metrischen Raum ist . Es gibt Algorithmen zum Indizieren von Metrikräumen.

Sie können auch Algorithmen für „räumliche Indizierung“ im Allgemeinen sehen, bewaffnet mit dem Wissen , dass Ihr Raum nicht euklidischen ist , aber es ist ein metrischer Raum. Viele Bücher zu diesem Thema behandeln die Indizierung von Zeichenfolgen mithilfe einer Metrik wie der Hamming-Entfernung.

Fußnote: Wenn Sie den Hamming-Abstand von Strings mit fester Breite vergleichen, können Sie möglicherweise eine signifikante Leistungsverbesserung erzielen, indem Sie Assembly- oder Prozessor-Intrinsics verwenden. Mit GCC ( manuell ) tun Sie beispielsweise Folgendes :

static inline int distance(unsigned x, unsigned y)
{
    return __builtin_popcount(x^y);
}

Wenn Sie dann GCC darüber informieren, dass Sie für einen Computer mit SSE4a kompilieren, sollte sich dies meiner Meinung nach auf nur ein paar Opcodes reduzieren.

Bearbeiten: Laut einer Reihe von Quellen ist dies manchmal / oft langsamer als der übliche Mask / Shift / Add-Code. Das Benchmarking zeigt, dass auf meinem System eine C-Version die GCCs __builtin_popcountum etwa 160% übertrifft .

Nachtrag: Ich war selbst neugierig auf das Problem und habe drei Implementierungen profiliert: lineare Suche, BK-Baum und VP-Baum. Beachten Sie, dass VP- und BK-Bäume sehr ähnlich sind. Die untergeordneten Elemente eines Knotens in einem BK-Baum sind "Schalen" von Bäumen, die Punkte enthalten, die jeweils einen festen Abstand vom Baumzentrum haben. Ein Knoten in einem VP-Baum hat zwei untergeordnete Elemente, von denen eines alle Punkte innerhalb einer Kugel enthält, die auf der Mitte des Knotens zentriert ist, und das andere untergeordnete Element alle Punkte außerhalb. Sie können sich also einen VP-Knoten als einen BK-Knoten mit zwei sehr dicken "Schalen" anstelle vieler feinerer vorstellen.

Die Ergebnisse wurden auf meinem 3,2-GHz-PC erfasst, und die Algorithmen versuchen nicht, mehrere Kerne zu verwenden (was einfach sein sollte). Ich habe eine Datenbankgröße von 100M Pseudozufallszahlen gewählt. Die Ergebnisse sind der Durchschnitt von 1000 Abfragen für die Entfernung 1..5 und 100 Abfragen für die Entfernung 6..10 und die lineare Suche.

  • Datenbank: 100 Millionen pseudozufällige Ganzzahlen
  • Anzahl der Tests: 1000 für Abstand 1..5, 100 für Abstand 6..10 und linear
  • Ergebnisse: Durchschnittliche Anzahl der Abfragetreffer (sehr ungefähr)
  • Geschwindigkeit: Anzahl der Abfragen pro Sekunde
  • Abdeckung: Durchschnittlicher Prozentsatz der pro Abfrage untersuchten Datenbank
                - BK-Baum - - VP-Baum - - Linear -
Dist Ergebnisse Geschwindigkeit Cov Geschwindigkeit Cov Geschwindigkeit Cov
1 0,90 3800 0,048% 4200 0,048%
2 11 300 0,68% 330 0,65%
3 130 56 3,8% 63 3,4%
4 970 18 12% 22 10%
5 5700 8,5 26% 10 22%
6 2,6e4 5,2 42% 6,0 37%
7 1.1e5 3.7 60% 4.1 54%
8 3,5e5 3,0 74% 3,2 70%
9 1,0e6 2,6 85% 2,7 82%
10 2,5e6 2,3 91% 2,4 90%
beliebige 2,2 100%

In Ihrem Kommentar haben Sie erwähnt:

Ich denke, BK-Bäume könnten verbessert werden, indem ein Haufen BK-Bäume mit verschiedenen Wurzelknoten erzeugt und verteilt wird.

Ich denke, dies ist genau der Grund, warum der VP-Baum (etwas) besser abschneidet als der BK-Baum. Da es eher "tiefer" als "flacher" ist, vergleicht es mit mehr Punkten, anstatt feinkörnigere Vergleiche mit weniger Punkten zu verwenden. Ich vermute, dass die Unterschiede in höherdimensionalen Räumen extremer sind.

Ein letzter Tipp: Blattknoten im Baum sollten für einen linearen Scan nur flache Anordnungen von Ganzzahlen sein. Bei kleinen Sätzen (möglicherweise 1000 Punkte oder weniger) ist dies schneller und speichereffizienter.

Dietrich Epp
quelle
9
Hurra! Meine 10k Wiederholung ist hier ;-)
Dietrich Epp
Ich habe den metrischen Raum betrachtet, ihn aber verworfen, als mir klar wurde, wie nahe alles beieinander liegt. Es ist klar, dass BK-Baum nur Brute Force ist und daher keine Optimierung darstellt. M-Tree und VP-Tree werden auch keine Optimierung sein, da alles nahe beieinander liegt. (Eine Hamming-Distanz von 4 entspricht einer Distanz von zwei, während eine Hamming-Distanz von 2 einer Distanz von Wurzel zwei entspricht.)
Neil G
1
Der Hamming-Abstand für Ganzzahlen fester Größe ist identisch mit der L1-Norm, wenn Sie Ganzzahlen als Bitfolgen betrachten. Andernfalls ist die "Standard" -L1-Norm zwischen zwei Zeichenfolgen die Summe der positiven Abstände zwischen den Elementen.
Mokosha
2
@DietrichEpp Dies ist eine der erstaunlichsten Antworten, die ich je auf SO gefunden habe. Ich wollte gerade fragen, wie lange es dauert, den Index zu erstellen, aber dann habe ich gesehen, dass Sie den Code gepostet haben. Antwort: Auf einem 3,5-GHz-i7-3770K wird ein 1-Millionen-Artikel-BK-Baum in 0,034 Sekunden und ein 100-Millionen-Artikel-BK-Baum in 13 Sekunden erstellt. Der Bau von VP-Bäumen dauert etwa viermal länger und lässt meine Fans lautstark drehen.
Mark E. Haase
2
@StefanPochmann: Sie scheinen die Schaltfläche "Weitere Antwort hinzufügen" mit der Schaltfläche "Kommentar hinzufügen" verwechselt zu haben. Schauen Sie unten auf der Seite nach, dort finden Sie die Schaltfläche "Weitere Antwort hinzufügen".
Dietrich Epp
13

Ich habe eine Lösung geschrieben, bei der ich die Eingangsnummern in einem Bitsatz von 2 bis 32 Bit darstelle, damit ich in O (1) überprüfen kann, ob eine bestimmte Zahl in der Eingabe enthalten ist. Dann generiere ich für eine abgefragte Zahl und eine maximale Entfernung rekursiv alle Zahlen innerhalb dieser Entfernung und vergleiche sie mit dem Bitset.

Für den maximalen Abstand 5 sind dies beispielsweise 242825 Zahlen ( Summe d = 0 bis 5 {32 wähle d} ). Zum Vergleich: Dietrich Epps VP-Tree-Lösung durchläuft beispielsweise 22% der 100 Millionen Zahlen, dh 22 Millionen Zahlen.

Ich habe Dietrichs Code / Lösungen als Grundlage verwendet, um meine Lösung hinzuzufügen und mit seiner zu vergleichen. Hier sind die Geschwindigkeiten in Abfragen pro Sekunde für maximale Entfernungen von bis zu 10:

Dist     BK Tree     VP Tree         Bitset   Linear

   1   10,133.83   15,773.69   1,905,202.76   4.73
   2      677.78    1,006.95     218,624.08   4.70
   3      113.14      173.15      27,022.32   4.76
   4       34.06       54.13       4,239.28   4.75
   5       15.21       23.81         932.18   4.79
   6        8.96       13.23         236.09   4.78
   7        6.52        8.37          69.18   4.77
   8        5.11        6.15          23.76   4.68
   9        4.39        4.83           9.01   4.47
  10        3.69        3.94           2.82   4.13

Prepare     4.1s       21.0s          1.52s  0.13s
times (for building the data structure before the queries)

Für kleine Entfernungen ist die Bitset-Lösung bei weitem die schnellste der vier. Der Autor der Frage, Eric, kommentierte unten, dass die größte Entfernung von Interesse wahrscheinlich 4-5 sein würde. Natürlich wird meine Bitset-Lösung für größere Entfernungen langsamer, sogar langsamer als die lineare Suche (für die Entfernung 32 würde sie 2 32 Zahlen durchlaufen ). Aber für Distanz 9 führt es immer noch leicht.

Ich habe auch Dietrichs Tests modifiziert. Jedes der obigen Ergebnisse dient dazu, den Algorithmus mindestens drei Abfragen und so viele Abfragen wie möglich in etwa 15 Sekunden lösen zu lassen (ich mache Runden mit 1, 2, 4, 8, 16 usw. Abfragen, bis mindestens 10 Sekunden vergangen sind insgesamt bestanden). Das ist ziemlich stabil, ich bekomme sogar ähnliche Zahlen für nur 1 Sekunde.

Meine CPU ist ein i7-6700. Mein Code (basierend auf Dietrichs) ist hier (ignoriere die Dokumentation dort zumindest vorerst, weiß nicht, was ich dagegen tun soll, aber er tree.centhält den gesamten Code und meine test.batShows, wie ich kompiliert und ausgeführt habe (ich habe die Flags von Dietrichs verwendet Makefile)). . Verknüpfung zu meiner Lösung .

Eine Einschränkung: Meine Abfrageergebnisse enthalten nur einmal Zahlen. Wenn die Eingabeliste also doppelte Zahlen enthält, kann dies erwünscht sein oder nicht. In dem Fall des fraglichen Autors Eric gab es keine Duplikate (siehe Kommentar unten). In jedem Fall kann diese Lösung für Personen geeignet sein, die entweder keine Duplikate in der Eingabe haben oder keine Duplikate in den Abfrageergebnissen möchten oder benötigen (ich denke, es ist wahrscheinlich, dass die reinen Abfrageergebnisse nur ein Mittel zum Zweck sind und dann Ein anderer Code verwandelt die Zahlen in etwas anderes, z. B. eine Karte, die eine Zahl einer Liste von Dateien zuordnet, deren Hash diese Zahl ist.

Stefan Pochmann
quelle
Die größte Entfernung von Interesse wäre wahrscheinlich 4-5, daher ist diese Lösung sehr interessant. Es gibt keine Duplikate in der tatsächlichen Domäne, die die Frage inspiriert haben.
Eric J.
3

Ein gängiger Ansatz (zumindest für mich üblich) besteht darin, Ihre Bitfolge in mehrere Blöcke zu unterteilen und diese Blöcke nach einer genauen Übereinstimmung als Vorfilterschritt abzufragen. Wenn Sie mit Dateien arbeiten, erstellen Sie so viele Dateien, wie Sie Blöcke haben (z. B. 4 hier), wobei jeder Block vor Ihnen permutiert wird, und sortieren dann die Dateien. Sie können eine binäre Suche verwenden und Ihre Suche sogar über und unter einem passenden Teil für den Bonus erweitern.

Sie können dann eine bitweise Hamming-Distanzberechnung für die zurückgegebenen Ergebnisse durchführen, die nur eine kleinere Teilmenge Ihres gesamten Datensatzes sein sollte. Dies kann mithilfe von Datendateien oder SQL-Tabellen erfolgen.

Um es noch einmal zusammenzufassen: Angenommen, Sie haben eine Reihe von 32-Bit-Zeichenfolgen in einer Datenbank oder in Dateien und möchten jeden Hash finden, der sich innerhalb eines 3-Bit-Hamming-Abstands oder weniger Ihrer "Abfrage" -Bitzeichenfolge befindet:

  1. Erstellen Sie eine Tabelle mit vier Spalten: Jede enthält ein 8-Bit-Slice (als Zeichenfolge oder Int) der 32-Bit-Hashes, Islice 1 bis 4. Wenn Sie Dateien verwenden, erstellen Sie vier Dateien, von denen jede eine Permutation der Slices ist eine "Insel" vor jeder "Reihe"

  2. Schneiden Sie Ihre Abfragebitzeichenfolge auf die gleiche Weise in qslice 1 bis 4.

  3. Fragen Sie diese Tabelle so ab, dass eine von qslice1=islice1 or qslice2=islice2 or qslice3=islice3 or qslice4=islice4. Dies gibt Ihnen jede Zeichenfolge, die innerhalb von 7 Bits ( 8 - 1) von der Abfragezeichenfolge liegt. Wenn Sie eine Datei verwenden, führen Sie in jeder der vier permutierten Dateien eine binäre Suche durch, um dieselben Ergebnisse zu erzielen.

  4. Berechnen Sie für jede zurückgegebene Bitfolge paarweise die genaue Hamming-Distanz mit Ihrer Abfrage-Bitfolge (Rekonstruktion der indexseitigen Bitfolgen aus den vier Slices entweder aus der DB oder aus einer permutierten Datei).

Die Anzahl der Operationen in Schritt 4 sollte viel geringer sein als eine vollständige paarweise Hamming-Berechnung Ihrer gesamten Tabelle und ist in der Praxis sehr effizient. Darüber hinaus ist es einfach, die Dateien in kleinere Dateien zu zerlegen, wenn eine höhere Geschwindigkeit durch Parallelität erforderlich ist.

In Ihrem Fall suchen Sie natürlich nach einer Art Selbstverknüpfung, dh nach allen Werten, die sich in einiger Entfernung voneinander befinden. Der gleiche Ansatz funktioniert IMHO immer noch, obwohl Sie von einem Startpunkt aus für Permutationen (unter Verwendung von Dateien oder Listen), die den Startblock gemeinsam nutzen, nach oben und unten expandieren und die Hamming-Distanz für den resultierenden Cluster berechnen müssen.

Wenn der Speicher anstelle von Dateien im Speicher ausgeführt wird, liegt Ihr 100-MB-32-Bit-String-Datensatz im Bereich von 4 GB. Daher benötigen die vier permutierten Listen möglicherweise mehr als 16 GB RAM. Ich erhalte jedoch hervorragende Ergebnisse mit Dateien mit Speicherzuordnung und muss weniger RAM für Datensätze ähnlicher Größe benötigen.

Es sind Open Source-Implementierungen verfügbar. Das Beste im Raum ist IMHO das für Mozh von Moz , C ++, aber für 64-Bit-Strings und nicht für 32-Bit-Zeichenfolgen.

Diese beschränkte Happing Abstand Ansatz wurde zuerst AFAIK durch beschrieben Moses Charikar in seiner „simhash“ Samen Papier und dem entsprechenden Google - Patent :

  1. CA. NÄCHSTE NACHBARSUCHE IM HAMMING-RAUM

[...]

Bei gegebenen Bitvektoren, die jeweils aus d Bits bestehen, wählen wir N = O (n 1 / (1+)) zufällige Permutationen der Bits. Für jede zufällige Permutation σ behalten wir eine sortierte Reihenfolge O σ der Bitvektoren in lexikographischer Reihenfolge der durch σ permutierten Bits bei. Wenn ein Abfragebitvektor q gegeben ist, finden wir den ungefähren nächsten Nachbarn, indem wir Folgendes tun:

Für jede Permutation σ führen wir eine binäre Suche nach O σ durch, um die beiden Bitvektoren zu lokalisieren, die q am nächsten liegen (in der lexikografischen Reihenfolge, die durch durch σ permutierte Bits erhalten wird). Wir suchen nun in jeder der sortierten Reihenfolgen O σ nach Elementen oberhalb und unterhalb der von der binären Suche zurückgegebenen Position in der Reihenfolge der Länge des längsten Präfixes, das mit q übereinstimmt.

Monika Henziger hat dies in ihrem Artikel "Suche nach nahezu doppelten Webseiten: eine umfassende Bewertung von Algorithmen" erweitert :

3.3 Die Ergebnisse für Algorithmus C.

Wir haben die Bitfolge jeder Seite in 12 nicht überlappende 4-Byte-Teile aufgeteilt, 20B-Teile erstellt und die C-Ähnlichkeit aller Seiten berechnet, die mindestens ein Teil gemeinsam hatten. Dieser Ansatz findet garantiert alle Seitenpaare mit einem Unterschied von bis zu 11, dh C-Ähnlichkeit 373, kann jedoch bei größeren Unterschieden einige übersehen.

Dies wird auch in der Arbeit von Gurmeet Singh Manku, Arvind Jain und Anish Das Sarma in der Veröffentlichung von Fast -Duplikaten für das Web- Crawlen erläutert :

  1. DAS HAMMING-ENTFERNUNGSPROBLEM

Definition: Identifizieren Sie anhand einer Sammlung von f-Bit-Fingerabdrücken und eines Abfragefingerabdrucks F, ob sich ein vorhandener Fingerabdruck in höchstens k Bits von F unterscheidet. (In der Batch-Modus-Version des obigen Problems haben wir eine Reihe von Abfrage-Fingerabdrücken anstelle eines einzelnen Abfrage-Fingerabdrucks.)

[...]

Intuition: Betrachten Sie eine sortierte Tabelle mit 2 df-Bit wirklich zufälligen Fingerabdrücken. Konzentrieren Sie sich nur auf die wichtigsten d-Bits in der Tabelle. Eine Auflistung dieser D-Bit-Zahlen entspricht „fast einem Zähler“ in dem Sinne, dass (a) einige 2-D-Bit-Kombinationen existieren und (b) sehr wenige D-Bit-Kombinationen dupliziert werden. Andererseits sind die niedrigstwertigen f - d Bits "fast zufällig".

Wählen Sie nun d so, dass | d - d | ist eine kleine ganze Zahl. Da die Tabelle sortiert ist, reicht eine einzige Sonde aus, um alle Fingerabdrücke zu identifizieren, die mit F in d höchstwertigen Bitpositionen übereinstimmen. Da | d - d | klein ist, wird auch erwartet, dass die Anzahl solcher Übereinstimmungen gering ist. Für jeden übereinstimmenden Fingerabdruck können wir leicht herausfinden, ob er sich in höchstens k Bitpositionen von F unterscheidet oder nicht (diese Unterschiede wären natürlich auf die f - d niedrigstwertigen Bitpositionen beschränkt).

Das oben beschriebene Verfahren hilft uns, einen vorhandenen Fingerabdruck zu lokalisieren, der sich von F in k Bitpositionen unterscheidet, die alle auf die niedrigstwertigen f - d Bits von F beschränkt sind. Dies erledigt eine angemessene Anzahl von Fällen. Um alle Fälle abzudecken, reicht es aus, eine kleine Anzahl zusätzlicher sortierter Tabellen zu erstellen, wie im nächsten Abschnitt formell beschrieben.

Hinweis: Ich habe eine ähnliche Antwort auf eine verwandte Nur-DB-Frage gepostet

Philippe Ombredanne
quelle
2

Sie können jede mögliche Variation Ihrer ursprünglichen Liste innerhalb des angegebenen Hamming-Abstands vorberechnen und in einem Bloom-Filter speichern. Dies gibt Ihnen ein schnelles "NEIN", aber nicht unbedingt eine klare Antwort auf "JA".

Speichern Sie für JA eine Liste aller Originalwerte, die jeder Position im Bloom-Filter zugeordnet sind, und gehen Sie sie einzeln durch. Optimieren Sie die Größe Ihres Bloom-Filters, um Kompromisse zwischen Geschwindigkeit und Speicher einzugehen.

Ich bin mir nicht sicher, ob alles genau funktioniert, aber es scheint ein guter Ansatz zu sein, wenn Sie Laufzeit-RAM zum Brennen haben und bereit sind, sehr viel Zeit für die Vorberechnung aufzuwenden.

Leopd
quelle
Wird nein nicht sehr unwahrscheinlich sein? 2 Prozent der Einträge sind vorhanden.
Neil G
1

Wie wäre es, wenn Sie die Liste sortieren und dann in dieser sortierten Liste eine binäre Suche nach den verschiedenen möglichen Werten in Ihrer Hamming-Entfernung durchführen?

borribel
quelle
2
Für eine Hamming-Distanz von 1 ist dies sinnvoll, da 32 Permutationen des ursprünglichen Eingangs vorhanden sind (jedes Bit im ursprünglichen Eingang einmal umdrehen). Für eine Hamming-Distanz von 2 gibt es viel mehr permutierte Eingabewerte, nach denen gesucht werden müsste.
Eric J.
2
1024 + 32 + 1-Suchvorgänge sind keine besonders große Anzahl von binären Suchvorgängen. Selbst 32 ^ 3 Suchanfragen sind nicht so viele.
τεκ
@EricJ - Es gibt jedoch 100 Millionen Daten. Es ist immer noch vernünftig - angesichts der Tatsache, dass auf dem Poster angegeben ist, dass die Kosten für den Aufbau der Datenstruktur zweitrangig sind - für eine angemessene Hamming-Distanz.
Borrible
Siehe Bit-String-Suche nach nächsten Nachbarn , bei der verschiedene Sortierungen verwendet werden, und dann binäre Suche.
Denis
1

Ein möglicher Ansatz zur Lösung dieses Problems ist die Verwendung einer Disjoint-Set-Datenstruktur . Die Idee ist, Listenmitglieder mit Hamming-Abstand <= k in derselben Menge zusammenzuführen. Hier ist der Umriss des Algorithmus:

  • Für jedes Mitglied der Liste alle möglichen berechnen Wert mit Hamming - Distanz <= k. Für k = 1 gibt es 32 Werte (für 32-Bit-Werte). Für k = 2 sind 32 + 32 * 31/2 Werte.

    • Testen Sie für jeden berechneten Wert , ob er in der ursprünglichen Eingabe enthalten ist. Sie können ein Array mit der Größe 2 ^ 32 oder eine Hash-Map verwenden, um diese Prüfung durchzuführen.

    • Wenn sich der Wert in der ursprünglichen Eingabe befindet, führen Sie eine "Vereinigungs" -Operation mit dem Listenmitglied durch .

    • Behalten Sie die Anzahl der ausgeführten Gewerkschaftsoperationen in einer Variablen bei.

Sie starten den Algorithmus mit N disjunkten Mengen (wobei N die Anzahl der Elemente in der Eingabe ist). Jedes Mal, wenn Sie eine Vereinigungsoperation ausführen, verringern Sie die Anzahl der disjunkten Sätze um 1. Wenn der Algorithmus beendet wird, werden in der Datenstruktur des disjunkten Satzes alle Werte mit dem Hamming-Abstand <= k in disjunkten Sätzen gruppiert. Diese disjunkte Datenstruktur kann in nahezu linearer Zeit berechnet werden .

Marcio Fonseca
quelle
Ich verstehe nicht. Wenn Ihr Eingabesatz {11000000, 0110000, 00110000, 00011000, 00001100, 00000110, 00000011} und k = 2 ist, wird Ihr Algorithmus wahrscheinlich jedes Element mit seinem nächsten Nachbarn vereinheitlichen (sie haben den Hamming-Abstand 2) und somit alle vereinheitlichen . 11000000 und 00000011 haben jedoch keinen Hamming-Abstand 2; Ihr Hamming-Abstand beträgt 4. Das grundlegende Problem bei der Verwendung von disjunkt gesetzten Wäldern (Union-Find) besteht darin, dass die Nähe keine Äquivalenzbeziehung ist.
Jonas Kölker
Guter Punkt! Sie müssen jedoch berücksichtigen, dass jedes Element nacheinander verarbeitet wird. Sobald eine Übereinstimmung gefunden wurde, wird das übereinstimmende Element aus der Liste entfernt. In Ihrem Beispiel wäre nach der Vereinigungsoperation zwischen 11000000 und 01100000 letztere nicht für die Vereinigung mit 00110000 verfügbar. Sie würden am Ende 5 Sätze erhalten und die Eingabe nur mit einem repräsentativen Element jedes Satzes vergleichen.
Marcio Fonseca
Ich verstehe Ihren Vorschlag nicht. Vielleicht könnten Sie es codieren (für einen kleinen Wert von n)? Hier ist eine Sache, auf die Sie testen sollten: Wenn Sie vier Listenmitglieder x, y, z, w mit jeweils einem Hamming-Abstand von 3 zum nächsten haben und Ihr Abfrage-Hamming-Abstand 5 beträgt, gehören x und y zur gleichen Äquivalenzklasse (d. H. Gewerkschaftsfundbaum)? Werden y und z? Werden z und w? Wie verwenden Sie die Äquivalenzklassen, um zu entscheiden, was ausgegeben werden soll? Soweit ich das beurteilen kann, verwenden Sie den Union-Find für alles, was Sie verwenden, um Ihre Ausgabe zu duplizieren. Ich denke, ein Hash-Set kann nur gut funktionieren. Aber ich bin nicht sicher, ob ich verstanden habe?
Jonas Kölker
1

Hier ist eine einfache Idee: Führen Sie eine byteweise Radix-Sortierung der 100-m-Eingabe-Ganzzahlen durch, wobei das höchstwertige Byte zuerst angezeigt wird, und verfolgen Sie die Bucket-Grenzen auf den ersten drei Ebenen in einer externen Struktur.

Beginnen Sie zum Abfragen mit einem Entfernungsbudget von dund Ihrem Eingabewort w. bBerechnen Sie für jeden Bucket in der obersten Ebene mit Bytewert den Hamming-Abstand d_0zwischen bund das High-Byte von w. Durchsuchen Sie diesen Bucket rekursiv mit einem Budget von d - d_0: Das heißt, für jeden Bytewert b'sei d_1der Hamming-Abstand zwischen b'und das zweite Byte von w. Suchen Sie rekursiv in der dritten Ebene mit einem Budget von d - d_0 - d_1usw.

Beachten Sie, dass die Eimer einen Baum bilden. Wenn Ihr Budget negativ wird, hören Sie auf, diesen Teilbaum zu durchsuchen. Wenn Sie rekursiv in ein Blatt absteigen, ohne Ihr Entfernungsbudget zu sprengen, sollte dieser Blattwert Teil der Ausgabe sein.

Hier ist eine Möglichkeit, die externe Bucket-Grenzstruktur darzustellen: Haben Sie ein Array mit der Länge 16_777_216 ( = (2**8)**3 = 2**24), wobei das Element am Index ider Startindex des Buckets ist, der Werte im Bereich [256 * i, 256 * i + 255] enthält. Um den Index eins jenseits des Endes dieses Buckets zu finden, schauen Sie nach Index i + 1 (oder verwenden Sie das Ende des Arrays für i + 1 = 2 ** 24).

Das Speicherbudget beträgt 100 m * 4 Bytes pro Wort = 400 MB für die Eingänge und 2 ** 24 * 4 Bytes pro Adresse = 64 MiB für die Indexierungsstruktur oder insgesamt nur knapp einen halben Gig. Die Indexierungsstruktur ist ein Overhead von 6,25% für die Rohdaten. Sobald Sie die Indexierungsstruktur erstellt haben, müssen Sie natürlich nur das niedrigste Byte jedes Eingabeworts speichern, da die anderen drei im Index implizit in der Indexierungsstruktur enthalten sind, und zwar für insgesamt ~ (64 + 50) MB.

Wenn Ihre Eingabe nicht gleichmäßig verteilt ist, können Sie die Bits Ihrer Eingabewörter mit einer (einzelnen, universell geteilten) Permutation permutieren, die die gesamte Entropie zum oberen Rand des Baums bringt. Auf diese Weise werden durch die erste Bereinigungsstufe größere Teile des Suchraums entfernt.

Ich habe einige Experimente ausprobiert, und dies funktioniert ungefähr so ​​gut wie die lineare Suche, manchmal sogar noch schlimmer. Soviel zu dieser ausgefallenen Idee. Na ja, zumindest ist es speichereffizient.

Jonas Kölker
quelle
Vielen Dank, dass Sie diese Alternative geteilt haben. "Speicher ist billig" in meiner Umgebung, aber eine speichereffiziente Lösung kann jemand anderem zugute kommen.
Eric J.