120 votes

Quel est le moyen le plus rapide de compter le nombre de chaque caractère dans un fichier ?

Je veux compter les A, les T, les C, les G, les N et les caractères "-" dans un fichier, ou chaque lettre si nécessaire. Existe-t-il une commande Unix rapide pour faire cela ?

136voto

Tymanthius Points 137

Si vous voulez de la vraie vitesse :

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;

Est un pseudo-liner incroyablement rapide.

Un simple test montre que sur mon Core i7 CPU 870 @ 2.93GHz il compte à peine plus de 600MB/s :

$ 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

Contrairement aux solutions impliquant un tri, celle-ci fonctionne en mémoire constante (4K), ce qui est très utile, si votre fichier est beaucoup plus grand que votre ram.

Et, bien sûr, avec un peu d'huile de coude, on peut gagner 0,7 seconde :

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;

On obtient un peu plus de 1,1 Go/s à l'arrivée :

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

À titre de comparaison, j'ai testé certaines des autres solutions présentées sur cette page qui semblaient promettre une certaine vitesse.

El sed / awk La solution a fait un effort courageux, mais est morte après 30 secondes. Avec une regex aussi simple, je m'attends à ce que ce soit un bogue dans sed (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

La méthode perl semblait également prometteuse, mais j'ai abandonné après 7 minutes d'exécution.

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

real    7m44.161s
user    4m53.941s
sys     2m35.593s

119voto

Chochos Points 3364

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

Il fera l'affaire comme une doublure. Une petite explication est cependant nécessaire.

grep -o foo.text -e A -e T -e C -e G -e N -e - recherche dans le fichier foo.text les lettres a et g et le caractère - pour chaque caractère que vous voulez rechercher. Il l'imprime également un caractère par ligne.

sort le classe dans l'ordre. Cela prépare le terrain pour l'outil suivant

uniq -c compte les occurrences consécutives en double d'une ligne quelconque. Dans ce cas, puisque nous avons une liste triée de caractères, nous obtenons un compte précis des occurrences des caractères que nous avons extraits à la première étape.

Si foo.txt contenait la chaîne de caractères GATTACA- voici ce que j'obtiendrais avec cet ensemble de commandes

[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

46voto

Chip D Points 11

Essayez celle-ci, inspirée par la réponse de @Journeyman.

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

La clé est de connaître l'option -o pour grep . Cela divise la correspondance, de sorte que chaque ligne de sortie correspond à une seule instance du motif, plutôt qu'à la ligne entière pour chaque ligne qui correspond. Compte tenu de ces connaissances, tout ce dont nous avons besoin est un motif à utiliser, et un moyen de compter les lignes. En utilisant une regex, nous pouvons créer un motif disjonctif qui correspondra à tous les caractères que vous avez mentionnés :

A|T|C|G|N|-

Cela signifie "correspond à A ou T ou C ou G ou N ou -". Le manuel décrit diverses syntaxes d'expressions régulières que vous pouvez utiliser .

Maintenant, nous avons une sortie qui ressemble à quelque chose comme ceci :

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

Notre dernière étape consiste à fusionner et à compter toutes les lignes similaires, ce qui peut être accompli simplement avec une fonction sort | uniq -c comme dans la réponse de @Journeyman. Le tri nous donne un résultat comme celui-ci :

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

Qui, lorsqu'il est acheminé par uniq -c ressemble finalement à ce que nous voulons :

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

Addendum : Si vous voulez totaliser le nombre de caractères A, C, G, N, T, et - dans un fichier, vous pouvez faire passer la sortie de grep par wc -l 代わりに sort | uniq -c . Il y a beaucoup de choses différentes que vous pouvez compter avec seulement de légères modifications de cette approche.

14voto

Erebus Points 348

Une doublure qui compte toutes les lettres en utilisant Python :

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

...produisant une sortie conviviale YAML comme celle-ci :

{'\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}

Il est intéressant de voir comment, la plupart du temps, Python peut facilement battre même bash en termes de clarté du code.

11voto

James Mertz Points 390

Similaire à celui de Guru awk méthode :

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

SistemesEz.com

SystemesEZ est une communauté de sysadmins où vous pouvez résoudre vos problèmes et vos doutes. Vous pouvez consulter les questions des autres sysadmins, poser vos propres questions ou résoudre celles des autres.

Powered by:

X