Was ist der schnellste Weg, um die Anzahl der Zeichen in einer Datei zu zählen?

121

Ich möchte die Zeichen von A, T, C, G, N und "-" in einer Datei oder bei Bedarf jeden Buchstaben zählen. Gibt es dazu einen schnellen Unix-Befehl?

Kirstin
quelle
56
Basenzählung in DNA-Strängen?
Indrek
12
Ich liebe diese Frage, so viele verschiedene Ansätze und Werkzeuge, um das gleiche Problem zu lösen.
Geselle Geek
10
He, das ist Borderline-Code-Golf
Earlz
13
wenn sich jemand für die windows powershell version interessiert:[System.IO.File]::ReadAllText("C:\yourfile.txt").ToCharArray() | Group-Object $_ | Sort Count -Descending
Guillaume86
4
Ok, ich glaube, ich habe den reinen PS-Weg gefunden:Get-Content "C:\eula.3082.txt" | % { $_.ToCharArray() } | Group-Object | Sort Count -Descending
Guillaume86

Antworten:

136

Wenn Sie eine echte Geschwindigkeit wollen:

echo 'int cache[256],x,y;char buf[4096],letters[]="tacgn-"; int main(){while((x=read(0,buf,sizeof buf))>0)for(y=0;y<x;y++)cache[(unsigned char)buf[y]]++;for(x=0;x<sizeof letters-1;x++)printf("%c: %d\n",letters[x],cache[letters[x]]);}' | gcc -w -xc -; ./a.out < file; rm a.out;

Ist ein unglaublich schneller Pseudo-One-Liner.

Ein einfacher Test zeigt, dass es auf meiner Core i7-CPU 870 bei 2,93 GHz etwas mehr als 600 MB / s gibt:

$ du -h bigdna 
1.1G    bigdna

time ./a.out < bigdna 
t: 178977308
a: 178958411
c: 178958823
g: 178947772
n: 178959673
-: 178939837

real    0m1.718s
user    0m1.539s
sys     0m0.171s

Im Gegensatz zu Sortierlösungen wird diese im konstanten Speicher (4 KB) ausgeführt, was sehr nützlich ist, wenn Ihre Datei viel größer als Ihr RAM ist.

Und natürlich können wir mit ein wenig Ellbogenfett 0,7 Sekunden abschneiden:

echo 'int cache[256],x,buf[4096],*bp,*ep;char letters[]="tacgn-"; int main(){while((ep=buf+(read(0,buf,sizeof buf)/sizeof(int)))>buf)for(bp=buf;bp<ep;bp++){cache[(*bp)&0xff]++;cache[(*bp>>8)&0xff]++;cache[(*bp>>16)&0xff]++;cache[(*bp>>24)&0xff]++;}for(x=0;x<sizeof letters-1;x++)printf("%c: %d\n",letters[x],cache[letters[x]]);}' | gcc -O2 -xc -; ./a.out < file; rm a.out;

Netze mit etwas mehr als 1,1 GB / s bei:

real    0m0.943s
user    0m0.798s
sys     0m0.134s

Zum Vergleich habe ich einige der anderen Lösungen auf dieser Seite getestet, die ein gewisses Geschwindigkeitsversprechen zu haben schienen.

Die sed/ awksolution unternahm eine tapfere Anstrengung, starb jedoch nach 30 Sekunden. Bei einem so einfachen regulären Ausdruck erwarte ich, dass dies ein Fehler in sed ist (GNU sed Version 4.2.1):

$ time sed 's/./&\n/g' bigdna | awk '!/^$/{a[$0]++}END{for (i in a)print i,a[i];}' 
sed: couldn't re-allocate memory

real    0m31.326s
user    0m21.696s
sys     0m2.111s

Die Perl-Methode schien ebenfalls vielversprechend, aber ich gab auf, nachdem ich sie 7 Minuten lang ausgeführt hatte

time perl -e 'while (<>) {$c{$&}++ while /./g} print "$c{$_} $_\n" for keys %c' < bigdna 
^C

real    7m44.161s
user    4m53.941s
sys     2m35.593s
Dave
quelle
1
+1 Für eine vernünftige Lösung, wenn es sich um viele Daten und nicht nur um eine Handvoll Bytes handelt. Die Dateien befinden sich jedoch im Festplatten-Cache, nicht wahr?
Daniel Beck
2
Das Schöne ist, dass es eine Komplexität von O (N) in der Verarbeitung und O (1) im Speicher hat. Die Pipes haben normalerweise O (N log N) in der Verarbeitung (oder sogar O (N ^ 2)) und O (N) im Speicher.
Martin Ueding
73
Sie dehnen die Definition von "Kommandozeile" jedoch ziemlich aus.
Gerrit
11
Epische Biegung der Anforderungen der Frage -Ich stimme zu; p. superuser.com/a/486037/10165 <- Jemand hat Benchmarks durchgeführt, und dies ist die schnellste Option.
Geselle Geek
2
+1 Ich weiß es zu schätzen, dass ich C an den richtigen Stellen gut gebrauchen kann.
Jeff Ferland
119

grep -o foo.text -e A -e T -e C -e G -e N -e -|sort|uniq -c

Wird den Trick als Einzeiler machen. Eine kleine Erklärung ist jedoch erforderlich.

grep -o foo.text -e A -e T -e C -e G -e N -e -Durchsucht die Datei nach den Buchstaben a und g sowie das Zeichen -für jedes Zeichen, nach dem Sie suchen möchten. Es wird auch ein Zeichen pro Zeile gedruckt.

sortsortiert es in der Reihenfolge. Dies bereitet die Bühne für das nächste Werkzeug

uniq -czählt die doppelten aufeinanderfolgenden Vorkommen einer Zeile. In diesem Fall erhalten wir, da wir eine sortierte Liste von Zeichen haben, eine genaue Zählung der Zeichen, die wir im ersten Schritt ausgegraut haben

Wenn foo.txt die Zeichenfolge enthält, ist GATTACA-dies das, was ich aus diesem Satz von Befehlen erhalten würde

[geek@atremis ~]$ grep -o foo.text -e A -e T -e C -e G -e N -e -|sort|uniq -c
      1 -
      3 A
      1 C
      1 G
      2 T
Journeyman Geek
quelle
8
Verdammte Unix-Magie! : D
Pitto
27
Wenn sich nur CTAG-Zeichen in Ihren Dateien befinden, wird der reguläre Ausdruck selbst sinnlos, oder? grep -o. | sortieren | uniq -c würde genauso gut funktionieren, afaik.
Sylvainulg
7
+1 Ich benutze grep seit 25 Jahren und wusste nichts darüber -o.
LarsH
9
@JourneymanGeek: Das Problem dabei ist, dass es viele Daten generiert, die dann zum Sortieren weitergeleitet werden. Es wäre billiger, ein Programm jedes Zeichen analysieren zu lassen. Siehe Daves Antwort für eine O (1) statt O (N) Speicherkomplexitätsantwort.
Martin Ueding
2
@Pitto Native Windows-Versionen von Coreutils sind weit verbreitet - fragen Sie einfach Google oder etwas Ähnliches
OrangeDog
46

Probieren Sie dieses aus, inspiriert von der Antwort von @ Journeyman.

grep -o -E 'A|T|C|G|N|-' foo.txt | sort | uniq -c

Der Schlüssel ist das Wissen über die Option -o für grep . Dadurch wird die Übereinstimmung aufgeteilt, sodass jede Ausgabezeile einer einzelnen Instanz des Musters entspricht und nicht der gesamten Zeile für jede übereinstimmende Zeile. Angesichts dieses Wissens brauchen wir nur ein Muster und eine Methode, um die Linien zu zählen. Mit einem regulären Ausdruck können wir ein disjunktives Muster erstellen, das mit jedem der von Ihnen genannten Zeichen übereinstimmt:

A|T|C|G|N|-

Dies bedeutet "Übereinstimmung mit A oder T oder C oder G oder N oder -". Das Handbuch beschreibt verschiedene Syntaxregeln für reguläre Ausdrücke, die Sie verwenden können .

Jetzt haben wir eine Ausgabe, die ungefähr so ​​aussieht:

$ grep -o -E 'A|T|C|G|N|-' foo.txt 
A
T
C
G
N
-
-
A
A
N
N
N

Unser letzter Schritt ist das Zusammenführen und Zählen aller ähnlichen Zeilen, was einfach mit einem sort | uniq -cwie in der Antwort von @ Journeyman ausgeführt werden kann. Die Sortierung ergibt folgende Ausgabe:

$ grep -o -E 'A|T|C|G|N|-' foo.txt | sort
-
-
A
A
A
C
G
N
N
N
N
T

Was, wenn es durchgeleitet wird uniq -c, dem entspricht, was wir wollen:

$ grep -o -E 'A|T|C|G|N|-' foo.txt | sort | uniq -c
      2 -
      3 A
      1 C
      1 G
      4 N
      1 T

Nachtrag: Wenn Sie die Anzahl der Zeichen A, C, G, N, T und - in einer Datei addieren möchten, können Sie die grep-Ausgabe durchleiten, wc -lanstatt sort | uniq -c. Es gibt viele verschiedene Dinge, die Sie mit nur geringfügigen Änderungen an diesem Ansatz zählen können.

crazy2be
quelle
Ich muss mich wirklich mit den Kaninchenlöchern befassen, die Coreutils und Regex sind. Das ist etwas eleganter als meins, p
Journeyman Geek
2
@JourneymanGeek: Regex zu lernen ist die Mühe wert, da es für so viele Dinge nützlich ist. Verstehen Sie einfach die Einschränkungen und missbrauchen Sie die Macht nicht, indem Sie versuchen, Dinge zu tun, die außerhalb des Bereichs der Regex-Fähigkeiten liegen, wie zum Beispiel den Versuch, XHTML zu analysieren .
crazy2be
20
grep -o '[ATCGN-]' könnte hier etwas lesbarer sein.
Sylvainulg
14

Ein Zeilenumbruch, der alle Buchstaben mit Python zählt:

$ python -c "import collections, pprint; pprint.pprint(dict(collections.Counter(open('FILENAME_HERE', 'r').read())))"

... eine YAML-freundliche Ausgabe wie diese erzeugen:

{'\n': 202,
 ' ': 2153,
 '!': 4,
 '"': 62,
 '#': 12,
 '%': 9,
 "'": 10,
 '(': 84,
 ')': 84,
 '*': 1,
 ',': 39,
 '-': 5,
 '.': 121,
 '/': 12,
 '0': 5,
 '1': 7,
 '2': 1,
 '3': 1,
 ':': 65,
 ';': 3,
 '<': 1,
 '=': 41,
 '>': 12,
 '@': 6,
 'A': 3,
 'B': 2,
 'C': 1,
 'D': 3,
 'E': 25}

Es ist interessant zu sehen, wie oft Python in Bezug auf die Klarheit des Codes sogar Bash schlagen kann.

Giampaolo Rodolà
quelle
11

Ähnlich wie bei Guru awk:

perl -e 'while (<>) {$c{$&}++ while /./g} print "$c{$_} $_\n" for keys %c'
Grawity
quelle
10

Nachdem Sie UNIX einige Jahre lang verwendet haben, können Sie eine Reihe kleiner Vorgänge sehr gut miteinander verknüpfen, um verschiedene Filter- und Zählaufgaben auszuführen. Jeder hat seinen eigenen Stil - manche mögen awkund sed, manche mögen cutund tr. So würde ich es machen:

So verarbeiten Sie einen bestimmten Dateinamen:

 od -a FILENAME_HERE | cut -b 9- | tr " " \\n | egrep -v "^$" | sort | uniq -c

oder als Filter:

 od -a | cut -b 9- | tr " " \\n | egrep -v "^$" | sort | uniq -c

Das funktioniert so:

  1. od -a trennt die Datei in ASCII-Zeichen.
  2. cut -b 9-Entfernt die Präfix- odPuts.
  3. tr " " \\n Konvertiert die Leerzeichen zwischen Zeichen in Zeilenumbrüche, sodass pro Zeile ein Zeichen vorhanden ist.
  4. egrep -v "^$" entfernt alle zusätzlichen Leerzeilen, die dadurch entstehen.
  5. sort sammelt Instanzen jedes Charakters zusammen.
  6. uniq -c zählt die Anzahl der Wiederholungen jeder Zeile.

Ich fütterte es "Hallo, Welt!" gefolgt von einer neuen Zeile und bekam diese:

  1 ,
  1 !
  1 d
  1 e
  1 H
  3 l
  1 nl
  2 o
  1 r
  1 sp
  1 w
David Schwartz
quelle
9

Da der sedTeil auf der Antwort von @ Gurus basiert , wird hier ein anderer Ansatz verwendet uniq, der der Lösung von David Schwartz ähnelt.

$ cat foo
aix
linux
bsd
foo
$ sed 's/\(.\)/\1\n/g' foo | sort | uniq -c
4 
1 a
1 b
1 d
1 f
2 i
1 l
1 n
2 o
1 s
1 u
2 x
Claudius
quelle
1
Verwenden Sie [[:alpha:]]anstelle von " .in" sednur Zeichen und keine Zeilenumbrüche.
Claudius
1
[[:alpha:]]wird scheitern, wenn Sie auch versuchen, -
Dinge
Richtig. Es könnte schöner sein , einen zweiten Ausdruck zu sed hinzufügen , um erste herauszufiltern alles andere und dann auf die gewünschten Zeichen explizit übereinstimmen: sed -e 's/[^ATCGN-]//g' -e 's/\([ATCGN-]\)/\1\n/g' foo | sort | uniq -c. Ich weiß jedoch nicht, wie ich die Zeilenumbrüche dort beseitigen soll: \
Claudius,
7

Sie können kombinieren grepund dies wctun:

grep -o 'character' file.txt | wc -w

grepDurchsucht die angegebenen Dateien nach dem angegebenen Text, und die -oOption weist sie an, nur die tatsächlichen Übereinstimmungen (dh die Zeichen, nach denen Sie gesucht haben) zu drucken, und nicht die Standardeinstellung, bei der jede Zeile gedruckt wird, in der sich der Suchtext befand gefunden auf.

wcGibt die Byte-, Wort- und Zeilenzahlen für jede Datei oder in diesem Fall die Ausgabe des grepBefehls aus. Die -wOption fordert Sie auf, Wörter zu zählen, wobei jedes Wort ein Vorkommen Ihres Suchzeichens ist. Natürlich würde die -lOption (die Zeilen zählt) auch funktionieren, da grepjedes Vorkommen Ihres Suchzeichens in einer separaten Zeile gedruckt wird.

Um dies für eine Anzahl von Zeichen gleichzeitig zu tun, platzieren Sie die Zeichen in einem Array und führen Sie eine Schleife darüber aus:

chars=(A T C G N -)
for c in "${chars[@]}"; do echo -n $c ' ' && grep -o $c file.txt | wc -w; done

Beispiel: Für eine Datei, die die Zeichenfolge enthält, lautet TGC-GTCCNATGCGNNTCACANN-die Ausgabe:

A  3
T  4
C  6
G  4
N  5
-  2

Weitere Informationen finden Sie unter man grepund man wc.


Der Nachteil dieses Ansatzes ist, wie der Benutzer Journeyman Geek unten in einem Kommentar festhält, dass greper für jeden Charakter einmal ausgeführt werden muss. Je nachdem, wie groß Ihre Dateien sind, kann dies zu einer spürbaren Leistungsbeeinträchtigung führen. Andererseits ist es auf diese Weise etwas einfacher, schnell zu erkennen, nach welchen Zeichen gesucht wird, und sie hinzuzufügen / zu entfernen, da sie sich in einer vom Rest des Codes getrennten Zeile befinden.

Indrek
quelle
3
sie müssten es je nach Wunsch wiederholen ... würde ich hinzufügen. Ich könnte schwören, dass es eine elegantere Lösung gibt, aber es muss mehr gestoßen werden: p
Journeyman Geek
@JourneymanGeek Guter Punkt. Ein Ansatz, der mir in den Sinn kommt, besteht darin, die Zeichen in ein Array zu setzen und in einer Schleife zu durchlaufen. Ich habe meinen Beitrag aktualisiert.
Indrek
zu komplex IMO. Verwenden Sie einfach grep -ea -et und so weiter. Müssten Sie den Grep-Zyklus nicht einmal pro Zeichen durchlaufen, wenn Sie ihn in ein Array einfügen und in einer Schleife durchlaufen würden?
Geselle Geek
@JourneymanGeek Du hast wahrscheinlich recht. uniq -cscheint auch eine bessere Möglichkeit zu sein, eine schön formatierte Ausgabe zu erhalten. Ich bin kein * nix Guru, das Obige ist genau das, was ich aus meinem begrenzten Wissen und einigen Manpages zusammengestellt habe :)
Indrek
Ich habe es auch getan, und eine meiner Aufgaben im letzten Semester umfasste das Durchsuchen von etwa 5000 Adressbucheinträgen, und Uniq hat es viel einfacher gemacht.
Geselle Geek
7

Verwenden Sie die Sequenzzeilen aus 22hgp10a.txt, um den Zeitunterschied zwischen grep und awk auf meinem System zu ermitteln.

[Bearbeiten]: Nachdem Sie Daves kompilierte Lösung gesehen haben, vergessen Sie auch awk, da seine in ~ 0,1 Sekunden in dieser Datei abgeschlossen ist, um die Groß- und Kleinschreibung zu berücksichtigen.

# A nice large sample file.
wget http://gutenberg.readingroo.ms/etext02/22hgp10a.txt

# Omit the regular text up to the start `>chr22` indicator.
sed -ie '1,/^>chr22/d' 22hgp10a.txt

sudo test # Just get sudo setup to not ask for password...

# ghostdog74 answered a question <linked below> about character frequency which
# gave me all case sensitive [ACGNTacgnt] counts in ~10 seconds.
sudo chrt -f 99 /usr/bin/time -f "%E elapsed, %c context switches" \
awk -vFS="" '{for(i=1;i<=NF;i++)w[$i]++}END{for(i in w) print i,w[i]}' 22hgp10a.txt

# The grep version given by Journeyman Geek took a whopping 3:41.47 minutes
# and yielded the case sensitive [ACGNT] counts.
sudo chrt -f 99 /usr/bin/time -f "%E elapsed, %c context switches" \
grep -o foo.text -e A -e T -e C -e G -e N -e -|sort|uniq -c

Die Groß- und Kleinschreibung von Ghostdog ist in ca. 14 Sekunden erledigt.

Das Sed wird in der akzeptierten Antwort auf diese Frage erklärt .
Das Benchmarking entspricht der akzeptierten Antwort auf diese Frage .
Die akzeptierte Antwort von ghostdog74 war auf diese Frage .

Thell
quelle
1
Sie können s/cache[letters[x]]/cache[letters[x]]+cache[toupper(letters[x])]die Groß- und Kleinschreibung aufheben, ohne die Geschwindigkeit zu beeinträchtigen.
Dave
6

Ich denke, dass jede anständige Implementierung eine Sortierung vermeidet. Aber weil es auch eine schlechte Idee ist, alles viermal zu lesen, könnte man irgendwie einen Stream erzeugen, der vier Filter durchläuft, einen für jedes Zeichen, der herausgefiltert wird und bei dem die Streamlängen auch irgendwie berechnet werden.

time cat /dev/random | tr -d -C 'AGCTN\-' | head -c16M >dna.txt
real    0m5.797s
user    0m6.816s
sys     0m1.371s

$ time tr -d -C 'AGCTN\-' <dna.txt | tee >(wc -c >tmp0.txt) | tr -d 'A' | 
tee >(wc -c >tmp1.txt) | tr -d 'G' | tee >(wc -c >tmp2.txt) | tr -d 'C' | 
tee >(wc -c >tmp3.txt) | tr -d 'T' | tee >(wc -c >tmp4.txt) | tr -d 'N' | 
tee >(wc -c >tmp5.txt) | tr -d '\-' | wc -c >tmp6.txt && cat tmp[0-6].txt

real    0m0.742s
user    0m0.883s
sys     0m0.866s

16777216
13983005
11184107
8387205
5591177
2795114
0

Die kumulierten Summen sind dann in tmp [0-6] .txt

Bei diesem Ansatz gibt es lediglich 13 Pipes, die in weniger als 1 MB Speicher konvertiert werden.
Natürlich ist meine Lieblingslösung:

time cat >f.c && gcc -O6 f.c && ./a.out
# then type your favourite c-program
real    0m42.130s
Aki Suihkonen
quelle
Dies ist eine sehr schöne Verwendung von tr.
Adavid
4

Ich wusste weder über uniqnoch über grep -o, aber da meine Kommentare zu @JourneymanGeek und @ crazy2be solche Unterstützung hatten, sollte ich es vielleicht zu einer eigenen Antwort machen:

Wenn Sie wissen, dass Ihre Datei nur "gute" Zeichen enthält (die Sie zählen möchten), können Sie wählen

grep . -o YourFile | sort | uniq -c

Wenn nur einige Zeichen gezählt werden müssen und andere nicht (dh Trennzeichen)

grep '[ACTGN-]' YourFile | sort | uniq -c

Der erste verwendet den Platzhalter für reguläre Ausdrücke ., der mit einem einzelnen Zeichen übereinstimmt. Der zweite verwendet eine 'Menge von akzeptierten Zeichen' ohne bestimmte Reihenfolge, mit der Ausnahme, dass -die letzte sein muss ( A-Cwird als 'beliebiges Zeichen zwischen Aund ' interpretiert C). In diesem Fall sind Anführungszeichen erforderlich, damit Ihre Shell nicht versucht, diese zu erweitern, um etwaige Einzelzeichendateien zu überprüfen (und einen "no match" -Fehler zu erzeugen, wenn keine vorhanden sind).

Beachten Sie, dass "sort" auch ein -uNique-Flag hat, sodass Dinge nur einmal gemeldet werden, aber kein Companion-Flag, um Duplikate zu zählen. Dies uniqist in der Tat obligatorisch.

Sylvainulg
quelle
-muss nicht zuletzt kommen, wenn Sie es mit einem Backslash entkommen: '[A\-CTGN]'sollte gut funktionieren.
Indrek
2

Ein dummer:

tr -cd ATCGN- | iconv -f ascii -t ucs2 | tr '\0' '\n' | sort | uniq -c
  • trlöscht ( -d) alle Zeichen außer ( -c) ATCGN-
  • iconv nach ucs2 konvertieren (UTF16 auf 2 Byte begrenzt), um nach jedem Byte ein 0-Byte hinzuzufügen,
  • eine andere tr, um diese NUL-Zeichen in NL zu übersetzen. Jetzt ist jedes Zeichen in einer eigenen Zeile
  • sort | uniq -cum jede einzelne Zeile zu zählen

Dies ist eine Alternative zur nicht standardmäßigen -ogrep-Option (GNU) .

sch
quelle
Können Sie hier eine kurze Erklärung der Befehle und der Logik geben?
Andrew Lambert
2
time $( { tr -cd ACGTD- < dna.txt | dd | tr -d A | dd | tr -d C | dd | tr -d G |
dd | tr -d T | dd | tr -d D | dd | tr -d - | dd >/dev/null; } 2>tmp ) &&
grep byte < tmp | sort -r -g | awk '{ if ((s-$0)>=0) { print s-$0} s=$0 }'

Das Ausgabeformat ist nicht das beste ...

real    0m0.176s
user    0m0.200s
sys     0m0.160s
2069046
2070218
2061086
2057418
2070062
2052266

Theorie der Arbeitsweise:

  • $ ({command | command} 2> tmp) leitet den stderr des Streams in eine temporäre Datei um.
  • dd gibt stdin nach stdout und die Anzahl der an stderr übergebenen Bytes aus
  • tr -d filtert jeweils ein Zeichen heraus
  • grep und sort filtert die Ausgabe von dd in absteigender Reihenfolge
  • awk berechnet die Differenz
  • sort wird nur in der Nachbearbeitungsphase verwendet , um die Unsicherheit der Ausgangsreihenfolge von Instanzen von dd zu behandeln

Die Geschwindigkeit scheint 60MBps + zu sein

Aki Suihkonen
quelle
Verbesserungen: tmp loswerden? Verwenden Sie 'Einfügen', um den betreffenden Brief zu drucken?
Aki Suihkonen
1

Beispieldatei:

$ cat file
aix
unix
linux

Befehl:

$ sed 's/./&\n/g' file | awk '!/^$/{a[$0]++}END{for (i in a)print i,a[i];}'
u 2
i 3
x 3
l 1
n 2
a 1
Guru
quelle
-1 für mangelnde Klarheit und für das Posten eines Einzeilers ohne Erklärung. AFAIK, das könnte eine Gabelbombe sein
PPC
1

Ein paar andere kombinieren

chars='abcdefghijklmnopqrstuvwxyz-'
grep -o -i "[$chars]" foo|sort | uniq -c

Hinzufügen | sort -nr, um die Ergebnisse in der Reihenfolge der Häufigkeit anzuzeigen.

Keith Wolters
quelle
1

Kurze Antwort:

Wenn es die Umstände zulassen, vergleichen Sie die Dateigrößen niedriger Zeichensätze mit denen ohne Zeichen, um einen Offset zu erhalten und nur die Bytes zu zählen.

Ah, aber die verworrenen Details:

Das sind alles Ascii-Charaktere. Ein Byte pro. Dateien haben natürlich zusätzliche Metadaten für eine Vielzahl von Dingen, die vom Betriebssystem und der App, die es erstellt hat, verwendet werden. In den meisten Fällen würde ich davon ausgehen, dass diese unabhängig von den Metadaten den gleichen Speicherplatz belegen, aber ich würde versuchen, identische Umstände beizubehalten, wenn Sie den Ansatz zuerst testen und dann überprüfen, ob Sie einen konstanten Versatz haben, bevor Sie sich keine Gedanken darüber machen. Das andere Problem ist, dass Zeilenumbrüche normalerweise zwei ASCII-Leerzeichen enthalten und Tabulatoren oder Leerzeichen jeweils eins sind. Wenn Sie sicher sein können, dass diese vorhanden sein werden und es keine Möglichkeit gibt, vorher zu wissen, wie viele es sind, würde ich jetzt aufhören zu lesen.

Es mag wie eine Menge Einschränkungen erscheinen, aber wenn Sie sie leicht feststellen können, erscheint mir dies als der Ansatz mit der einfachsten / besten Leistung, wenn Sie eine Menge davon betrachten müssen (was wahrscheinlich ist, wenn es sich um DNA handelt). Eine Tonne von Dateien auf Länge zu prüfen und eine Konstante zu subtrahieren, wäre schneller, als bei jedem grep (oder ähnlichem) auszuführen.

Wenn:

  • Dies sind einfache ununterbrochene Zeichenfolgen in reinen Textdateien
  • Sie befinden sich in identischen Dateitypen, die mit demselben nicht formatierenden Vanilla-Texteditor wie Scite (Einfügen ist in Ordnung, solange Sie nach Leerzeichen / Rückgaben suchen) oder einem Basisprogramm erstellt wurden, das jemand geschrieben hat

Und zwei Dinge, die vielleicht nicht wichtig sind, mit denen ich aber zuerst testen würde

  • Die Dateinamen sind gleich lang
  • Die Dateien befinden sich im selben Verzeichnis

Versuchen Sie, den Versatz wie folgt zu ermitteln:

Vergleichen Sie eine leere Datei mit einer mit ein paar leicht zu zählenden Zeichen mit einer mit ein paar weiteren Zeichen. Wenn Sie die leere Datei von den beiden anderen Dateien subtrahieren, erhalten Sie Bytezahlen, die der Anzahl der Zeichen entsprechen, sind Sie fertig. Überprüfen Sie die Dateilängen und subtrahieren Sie diese leere Menge. Wenn Sie versuchen möchten, mehrzeilige Dateien zu ermitteln, fügen die meisten Editoren zwei Ein-Byte-Sonderzeichen für Zeilenumbrüche hinzu, da eines von Microsoft eher ignoriert wird. In diesem Fall müssen Sie jedoch mindestens nach Leerzeichen suchen du könntest es genauso gut alles mit grep machen.

Erik Reppen
quelle
1

Haskell Weg:

import Data.Ord
import Data.List
import Control.Arrow

main :: IO ()
main = interact $
  show . sortBy (comparing fst) . map (length &&& head) . group . sort

es funktioniert so:

112123123412345
=> sort
111112222333445
=> group
11111 2222 333 44 5
=> map (length &&& head)
(5 '1') (4 '2') (3 '3') (2 '4') (1,'5')
=> sortBy (comparing fst)
(1 '5') (2 '4') (3 '3') (4 '2') (5 '1')
=> one can add some pretty-printing here
...

Kompilieren und Verwenden von:

$ ghc -O2 q.hs
[1 of 1] Compiling Main             ( q.hs, q.o )
Linking q ...
$ echo 112123123412345 | ./q
[(1,'\n'),(1,'5'),(2,'4'),(3,'3'),(4,'2'),(5,'1')]%       
$ cat path/to/file | ./q
...

Nicht gut für große Dateien.

ht.
quelle
1

Schneller Perl-Hack:

perl -nle 'while(/[ATCGN]/g){$a{$&}+=1};END{for(keys(%a)){print "$_:$a{$_}"}}'
  • -n: Iteriere über Eingabezeilen, aber drucke nichts für sie
  • -l: Zeilenumbrüche automatisch entfernen oder hinzufügen
  • while: Alle Vorkommen Ihrer angeforderten Symbole in der aktuellen Zeile durchlaufen
  • END: Am Ende werden die Ergebnisse gedruckt
  • %a: Hash, wo die Werte gespeichert sind

Zeichen, die überhaupt nicht vorkommen, werden nicht in das Ergebnis einbezogen.

MvG
quelle