Die Aufgabe besteht darin, die Anzahl der unterschiedlichen Teilzeichenfolgen der Länge k für k = 1,2,3,4, ..... zu zählen.
Ausgabe
Sie sollten eine Zeile pro Zeile ausgeben, die k
Sie mit einer Nummer pro Ausgabezeile vervollständigen können. Ihre Ausgabe sollte in der Reihenfolge steigen, k
bis Ihnen die Zeit ausgeht.
Ergebnis
Ihre Punktzahl ist die höchste k, die Sie in weniger als 1 Minute auf meinem Computer erreichen können.
Sie sollten http://hgdownload.cse.ucsc.edu/goldenPath/hg38/chromosomes/chr2.fa.gz als Eingabe verwenden und Zeilenumbrüche ignorieren. Ihr Code sollte jedoch zwischen Groß- und Kleinschreibung unterscheiden.
Sie können den Eingang dekomprimieren, bevor Sie das Timing starten.
Der folgende (ineffiziente) Code zählt die Anzahl der verschiedenen 4-mers.
awk -v substr_length=4 '(len=length($0))>=substr_length{for (i=1; (i-substr_length)<len; i++) substrs[substr($0,i,substr_length)]++}; END{for (i in substrs) print substrs[i], i}' file.txt|wc
Speichergrenzen
Damit sich Ihr Code auf meinem Computer gut verhält und die Aufgabe schwieriger wird, beschränke ich den Arbeitsspeicher, den Sie verwenden können, auf 2 GB
ulimit -v 2000000
bevor Sie Ihren Code ausführen. Ich bin mir bewusst, dass dies keine solide Methode ist, um die RAM-Nutzung einzuschränken. Verwenden Sie daher bitte keine einfallsreichen Methoden, um diese Grenze zu umgehen, indem Sie beispielsweise neue Prozesse erzeugen. Natürlich können Sie Multithread-Code schreiben, aber wenn jemand dies tut, muss ich lernen, wie man den dafür verwendeten RAM begrenzt.
Tie Breaker
Im Falle eines Unentschieden für ein Maximum werde k
ich die Zeit bestimmen, wie lange es dauert, bis die Ergebnisse erreicht sind k+1
und der schnellste gewinnt. Für den Fall, dass sie innerhalb einer Sekunde bis zur gleichen Zeit laufen k+1
, gewinnt die erste Einreichung.
Sprachen und Bibliotheken
Sie können jede Sprache verwenden, die einen frei verfügbaren Compiler / Interpreter / etc. für Linux und alle Bibliotheken, die auch für Linux frei verfügbar sind.
Meine Maschine Die Timings werden auf meiner Maschine ausgeführt. Dies ist eine Standard-Ubuntu-Installation auf einem AMD FX-8350 Eight-Core-Prozessor auf einem Asus M5A78L-M / USB3-Motherboard (Sockel AM3 +, 8 GB DDR3). Dies bedeutet auch, dass ich Ihren Code ausführen kann. Verwenden Sie daher nur leicht verfügbare freie Software und fügen Sie bitte vollständige Anweisungen zum Kompilieren und Ausführen Ihres Codes bei.
Testausgabe
Der Code von FUZxxl gibt Folgendes aus (aber nicht alle innerhalb von 1 Minute), was ich für richtig halte.
14
92
520
2923
15714
71330
265861
890895
2482912
5509765
12324706
29759234
Ligatabelle
- k> = 4000 FUZxxl (C)
- k = 16 von Keith Randall (C ++)
- k = 10 nach FUZxxl (C)
Wie stark können Sie Ihren Code auf die Eingabe spezialisieren?
- Es wäre klar, dass dies die Konkurrenz ruinieren würde, wenn Sie nur die Antworten vorberechnen und von Ihrem Code ausgeben lassen würden. Tu das nicht.
- Im Idealfall muss alles, was Ihr Code benötigt, um mehr über die Daten zu erfahren, die zur schnelleren Ausführung verwendet werden, zur Laufzeit gelernt werden.
- Sie können jedoch davon ausgehen, dass die Eingabe wie die Daten in den * .fa-Dateien unter http://hgdownload.cse.ucsc.edu/goldenPath/hg38/chromosomes/ aussieht .
J
einer naive Lösung wäre einfach sein `[: ~.]` Aber vermutet , dass es wird nicht geschnitten.Antworten:
C (≥ 4000)
Dieser Code verwendet weder weniger als 2 GB RAM (er verbraucht etwas mehr) noch erzeugt er in der ersten Minute eine Ausgabe. Aber wenn Sie für etwa sechs Minuten warten, es druckt alle die k - mer zählt auf einmal.
Eine Option ist enthalten, um das höchste k zu begrenzen, für das wir die k- mere zählen. Wenn k auf einen vernünftigen Bereich begrenzt ist, endet der Code in weniger als einer Minute und verwendet weniger als 2 GiB RAM. OP hat diese Lösung bewertet und endet in weniger als einer Minute für einen Grenzwert, der nicht wesentlich höher als 4000 ist.
Wie funktioniert es?
Der Algorithmus besteht aus vier Schritten.
Sortieren Sie ein Array von Indizes nach dem Eingabepuffer. Die Suffixe der Zeichenfolge
mississippi
lauten beispielsweise:Diese in lexikografischer Reihenfolge sortierten Zeichenfolgen sind:
Es ist leicht zu erkennen, dass alle gleichen Teilzeichenfolgen der Länge k für alle k in benachbarten Einträgen des nach Suffix sortierten Arrays gefunden werden.
Es wird ein ganzzahliges Array ausgefüllt, in dem wir an jedem Index k die Anzahl der verschiedenen k- mere speichern. Dies geschieht auf leicht gewundene Weise, um den Prozess zu beschleunigen. Betrachten Sie zwei benachbarte Einträge als nach Suffix sortiertes Array.
p bezeichnet die Länge des längsten gemeinsamen Präfixes der beiden Einträge, l bezeichnet die Länge des zweiten Eintrags. Für ein solches Paar finden wir einen neuen unterschiedlichen Teilstring der Länge k für p < k ≤ l . Da p ≪ l häufig gilt, ist es unpraktisch, eine große Anzahl von Array-Einträgen für jedes Paar zu erhöhen. Stattdessen speichern wir das Array als Differenzarray, wobei jeder Eintrag k die Differenz zur Anzahl der k- mere zur Anzahl der ( k - 1) -mers bezeichnet. Dies führt zu einer Aktualisierung des Formulars
in eine viel schnellere Aktualisierung des Formulars
Wenn wir sorgfältig beobachten, dass l immer unterschiedlich ist und tatsächlich jede 0 < l < n genau einmal erscheint, können wir die Subtraktionen weglassen und stattdessen 1 von jeder Differenz subtrahieren, wenn wir von Differenzen in Beträge umrechnen.
Der Quellcode
Diese Quelle verwendet libdivsufsort zum Sortieren von Suffix-Arrays. Der Code generiert beim Kompilieren mit diesem Aufruf eine Ausgabe gemäß der Spezifikation.
Alternativ kann der Code eine Binärausgabe erzeugen, wenn er mit dem folgenden Aufruf kompiliert wird.
Um das höchste k zu begrenzen, für das k- mere gezählt werden sollen, geben Sie -DICAP = k an, wobei k die Grenze ist:
Kompilieren Sie mit,
-O3
wenn Ihr Compiler diese Option bereitstellt.Das binäre Dateiformat kann mit folgendem Programm in das für Menschen lesbare Ausgabeformat konvertiert werden:
Beispielausgabe
Eine Beispielausgabe im Binärformat für die Datei
chr22.fa
finden Sie hier . Bittebzip2 -d
zuerst mit dekomprimieren . Die Ausgabe erfolgt nur im Binärformat, da sie viel besser komprimiert wird (3,5 kB gegenüber 260 MB) als die Ausgabe im lesbaren Format. Beachten Sie jedoch, dass die Referenzausgabe im unkomprimierten Zustand eine Größe von 924 MB hat. Möglicherweise möchten Sie eine Pipe wie die folgende verwenden:quelle
cat
. Verwenden Sie die Shell-Umleitung wie in./dsskmer <~/Downloads/chr2.fs
. Der Code muss wissen, wie lang die Eingabedatei ist, und das ist mit einer Pipe nicht möglich.C ++, k = 16, 37 Sekunden
Berechnet alle 16-mers in der Eingabe. Jedes 16-mer wird 4 Bit zu einem Symbol in ein 64-Bit-Wort gepackt (wobei ein Bitmuster für EOF reserviert ist). Die 64-Bit-Wörter werden dann sortiert. Die Antwort für jedes k kann abgelesen werden, indem man sich ansieht, wie oft sich die 4 * k oberen Bits der sortierten Wörter ändern.
Für den Testeingang verwende ich ca. 1,8 GB direkt unter dem Kabel.
Ich bin sicher, dass die Lesegeschwindigkeit verbessert werden könnte.
Ausgabe:
Programm:
Kompilieren mit
g++ -O3 kmer.cc -o kmer
und ausführen mit./kmer chr2.fa
.quelle
C ++ - eine Verbesserung gegenüber der FUZxxl-Lösung
Ich verdiene absolut keine Anerkennung für die Berechnungsmethode selbst, und wenn sich in der Zwischenzeit kein besserer Ansatz zeigt, sollte das Kopfgeld zu Recht an FUZxxl gehen.
Ich habe einfach Kasai et al. Algorithmus zur Berechnung von LCPs in O (n).
Der Rest ist eine bloße Anpassung des FUZxxl-Codes, wobei hier und da präzisere C ++ - Funktionen verwendet werden.
Ich habe den ursprünglichen Berechnungscode hinterlassen, um Vergleiche zu ermöglichen.
Da die langsamsten Prozesse die SA-Konstruktion und die LCP-Anzahl sind, habe ich die meisten anderen Optimierungen entfernt, um zu vermeiden, dass der Code für vernachlässigbare Gewinne überladen wird.
Ich habe die SA-Tabelle um das Präfix Null erweitert. Das erleichtert die LCP-Berechnung.
Ich habe keine Option zur Längenbeschränkung bereitgestellt. Der langsamste Prozess ist jetzt die SA-Berechnung, die nicht verkleinert werden kann (oder zumindest sehe ich nicht, wie es sein könnte).
Ich habe auch die Option für die binäre Ausgabe entfernt und die Anzeige auf die ersten 10 Werte beschränkt.
Ich gehe davon aus, dass dieser Code nur ein Proof of Concept ist, sodass keine überfüllten Displays oder gesättigten Festplatten erforderlich sind.
Erstellen der ausführbaren Datei
Ich musste das gesamte Projekt (einschließlich der Lite-Version von
divsufsort
) für x64 kompilieren , um das Win32 2Gb-Zuweisungslimit zu überwinden.divsufsort
Code gibt eine Reihe von Warnungen aus, daint
s anstelle vonsize_t
s häufig verwendet wird. Dies ist jedoch kein Problem für Eingaben unter 2 GB (für die ohnehin 26 GB RAM erforderlich wären: D).Linux Build
Kompilieren
main.cpp
unddivsufsort.c
Verwenden der Befehle:Ich
divsufsort
gehe davon aus, dass die reguläre Bibliothek unter nativem Linux einwandfrei funktionieren sollte, solange Sie etwas mehr als 3 GB zuweisen können.Aufführungen
Der Kasai-Algorithmus erfordert die inverse SA-Tabelle, die 4 weitere Bytes pro Zeichen für insgesamt 13 verbraucht (anstelle von 9 mit der FUZxxl-Methode).
Der Speicherverbrauch für den Referenzeingang liegt somit über 3 GB.
Andererseits wird die Rechenzeit dramatisch verbessert, und der gesamte Algorithmus befindet sich jetzt in O (n):
Weitere Verbesserungen
SA-Bau ist jetzt der langsamste Prozess.
Einige
divsufsort
Teile des Algorithmus sollen mit den mir unbekannten Funktionen eines Compilers parallelisiert werden. Falls erforderlich, sollte sich der Code jedoch leicht an klassischeres Multithreading anpassen lassen ( z. B. à la C ++ 11).Die Bibliothek verfügt außerdem über eine Vielzahl von Parametern, darunter verschiedene Bucket-Größen und die Anzahl der unterschiedlichen Symbole in der Eingabezeichenfolge. Ich habe sie nur flüchtig betrachtet, aber ich vermute, dass das Komprimieren des Alphabets einen Versuch wert sein könnte, wenn Ihre Zeichenfolgen endlose ACTG-Permutationen sind ( und Sie verzweifelt nach Auftritten suchen ).
Es gibt auch einige parallelisierbare Methoden zur Berechnung von LCP aus SA, aber da der Code auf einem Prozessor, der etwas leistungsfähiger als mein mickriger [email protected] ist und der gesamte Algorithmus in O (n) ist, unter einer Minute laufen sollte , bezweifle ich dies wäre die Mühe wert.
quelle
C (kann auf meinem Computer bis zu 10 in einer Minute lösen)
Dies ist eine sehr einfache Lösung. Es konstruiert einen Baum der gefundenen k- mere und zählt sie. Um Speicherplatz zu sparen, werden Zeichen zuerst in Ganzzahlen von 0 bis n - 1 konvertiert, wobei n die Anzahl der verschiedenen Zeichen in der Eingabe ist. Daher müssen wir keinen Platz für Zeichen bereitstellen, die niemals angezeigt werden. Außerdem wird den Blättern weniger Speicher zugewiesen als anderen Knoten, um weiteren Speicher zu sparen. Diese Lösung benötigt zur Laufzeit auf meinem Computer etwa 200 MiB RAM. Ich verbessere es immer noch, vielleicht ist es in der Iteration sogar noch schneller!
Speichern Sie zum Kompilieren den folgenden Code in einer Datei mit dem Namen
kmers.c
und führen Sie ihn dann auf einem POSIX-ähnlichen Betriebssystem aus:Sie könnten ersetzen wollen
-O3
für ,-O
wenn Ihr Compiler unterstützt das. So führen Sie zunächst auspackenchr2.fa.gz
inchr2.fa
und dann aus:Dies erzeugt die Ausgabe Schritt für Schritt. Möglicherweise möchten Sie sowohl Zeit als auch Raum einschränken. Verwenden Sie so etwas wie
Ressourcen nach Bedarf zu reduzieren.
Verbesserungen
quelle
timeout 60s
funktioniert für mich in Ordnung, sodass Sie nach 1 Minute keine Möglichkeit zum Erstellen des Codes benötigen.ulimit -t 60
.