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 ?
Réponses
Trop de publicités?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
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
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.
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.
- Réponses précédentes
- Plus de réponses