我想计算文件中 A、T、C、G、N 和“-”字符的数量,或者如果需要的话计算每个字母的数量,有没有快速的 Unix 命令可以做到这一点?
答案1
如果你想要真正的速度:
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;
是一种速度极快的伪单行程序。
一个简单的测试表明,在我的 Core i7 CPU 870 @ 2.93GHz 上,它的速度刚刚超过 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
与涉及排序的解决方案不同,这个解决方案在常量(4K)内存中运行,如果您的文件远大于您的内存,这非常有用。
当然,只要稍微努力一下,我们就可以缩短 0.7 秒:
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;
网速刚刚超过 1.1GB/s,完成于:
real 0m0.943s
user 0m0.798s
sys 0m0.134s
为了进行比较,我测试了此页面上一些似乎具有某种速度保证的其他解决方案。
sed
/解决方案awk
做出了巨大的努力,但 30 秒后就失效了。对于如此简单的正则表达式,我预计这是 sed(GNU sed 版本 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
perl 方法似乎也很有希望,但我运行了 7 分钟后就放弃了
time perl -e 'while (<>) {$c{$&}++ while /./g} print "$c{$_} $_\n" for keys %c' < bigdna
^C
real 7m44.161s
user 4m53.941s
sys 2m35.593s
答案2
grep -o foo.text -e A -e T -e C -e G -e N -e -|sort|uniq -c
一行代码就能搞定一切。不过需要一点解释。
grep -o foo.text -e A -e T -e C -e G -e N -e -
greps 文件 foo.text 中的字母 a 和 g 以及要搜索的每个字符的字符-
。它还会将其打印为一行一个字符。
sort
按顺序排序。这为下一个工具奠定了基础
uniq -c
计算任意行中重复出现的次数。在本例中,由于我们有一个排序的字符列表,因此我们可以清楚地计算出第一步中 grep 出来的字符
如果 foo.txt 包含字符串,GATTACA-
那么我将从这组命令中得到以下结果
[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
答案3
尝试一下这个,受到@Journeyman 的回答的启发。
grep -o -E 'A|T|C|G|N|-' foo.txt | sort | uniq -c
关键是要知道grep 的 -o 选项。这会将匹配拆分,这样输出的每一行都对应模式的一个实例,而不是匹配的任何行的整行。有了这些知识,我们所需要的只是一个要使用的模式,以及一种计算行数的方法。使用正则表达式,我们可以创建一个分离模式,它将匹配您提到的任何字符:
A|T|C|G|N|-
这意味着“匹配 A 或 T 或 C 或 G 或 N 或 -”。手册描述了您可以使用各种正则表达式语法。
现在我们的输出看起来像这样:
$ grep -o -E 'A|T|C|G|N|-' foo.txt
A
T
C
G
N
-
-
A
A
N
N
N
我们的最后一步是合并并计算所有相似的行,这可以简单地用 来完成sort | uniq -c
,如 @Journeyman 的回答中所示。排序为我们提供了如下输出:
$ grep -o -E 'A|T|C|G|N|-' foo.txt | sort
-
-
A
A
A
C
G
N
N
N
N
T
当通过管道传输时uniq -c
,它最终类似于我们想要的:
$ grep -o -E 'A|T|C|G|N|-' foo.txt | sort | uniq -c
2 -
3 A
1 C
1 G
4 N
1 T
附录:如果要计算文件中 A、C、G、N、T 和 - 字符的总数量,可以通过管道传输 grep 输出,wc -l
而不是sort | uniq -c
。只需对这种方法进行轻微修改,就可以计算出很多不同的东西。
答案4
与Guru的方法类似awk
:
perl -e 'while (<>) {$c{$&}++ while /./g} print "$c{$_} $_\n" for keys %c'