计算文件中每个字符的数量的最快方法是什么?

计算文件中每个字符的数量的最快方法是什么?

我想计算文件中 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'

相关内容