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

Kir*_*irt 120 unix shell characters command-line

我想计算文件中的 A 的 T 的 C 的 G 的 N 和“-”字符,或者每个字母(如果需要),是否有快速的 Unix 命令来执行此操作?

Dav*_*ave 136

如果你想要一些真正的速度:

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;
Run Code Online (Sandbox Code Playgroud)

是一个令人难以置信的快速伪单线。

一个简单的测试表明,在我的 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
Run Code Online (Sandbox Code Playgroud)

与涉及排序的解决方案不同,这个解决方案在恒定 (4K) 内存中运行,如果您的文件远大于您的 ram,这将非常有用。

而且,当然,用一点肘部油脂,我们可以减少 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;
Run Code Online (Sandbox Code Playgroud)

Nets 刚刚超过 1.1GB/s 完成:

real    0m0.943s
user    0m0.798s
sys     0m0.134s
Run Code Online (Sandbox Code Playgroud)

为了进行比较,我在此页面上测试了一些其他解决方案,这些解决方案似乎具有某种速度承诺。

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
Run Code Online (Sandbox Code Playgroud)

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
Run Code Online (Sandbox Code Playgroud)

  • 不过,您对“命令行”的定义进行了相当多的扩展。 (73认同)
  • 问题要求的史诗般的弯曲 - 我同意; p。http://superuser.com/a/486037/10165 &lt;- 有人运行了基准测试,这*是*最快的选择。 (11认同)
  • 巧妙的是它的处理复杂度为 O(N),内存中的复杂度为 O(1)。管道通常有 O(N log N) 在处理(甚至 O(N^2))和 O(N) 在内存中。 (2认同)
  • +1 我很感激我在正确的地方很好地使用了 C。 (2认同)

Jou*_*eek 119

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 -grep 文件 foo.text 中的字母 a 和 g 以及-您要搜索的每个字符的字符。它还每行打印一个字符。

sort按顺序排序。这为下一个工具奠定了基础

uniq -c计算任何行的重复连续出现。在这种情况下,由于我们有一个排序的字符列表,我们可以清楚地计算出我们在第一步中找到的字符的时间

如果 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
Run Code Online (Sandbox Code Playgroud)

  • 如果您的文件中只有 CTAG- 字符,则正则表达式本身就变得毫无意义,对吗?grep -o 。| 排序 | uniq -c 会同样有效,afaik。 (27认同)
  • @JourneymanGeek:问题在于它会生成大量数据,然后将其转发以进行排序。让程序解析每个字符会更便宜。请参阅 Dave 对 O(1) 而非 O(N) 内存复杂度答案的回答。 (9认同)
  • 该死的unix魔法!:D (8认同)
  • +1 我使用 grep 已经 25 年了,但不知道 `-o`。 (7认同)
  • @Pitto 本机 Windows 构建的 coreutils 广泛可用 - 问问谷歌或类似的 (2认同)

cra*_*2be 46

试试这个,灵感来自@Journeyman 的回答。

grep -o -E 'A|T|C|G|N|-' foo.txt | sort | uniq -c
Run Code Online (Sandbox Code Playgroud)

关键是了解grep 的 -o 选项。这将匹配拆分,以便每个输出行对应于模式的单个实例,而不是匹配的任何行的整行。有了这些知识,我们所需要的只是一个要使用的模式,以及一种计算行数的方法。使用正则表达式,我们可以创建一个分离模式来匹配您提到的任何字符:

A|T|C|G|N|-
Run Code Online (Sandbox Code Playgroud)

这意味着“匹配 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
Run Code Online (Sandbox Code Playgroud)

我们的最后一步是合并和计算所有相似的行,这可以简单地用 a 完成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
Run Code Online (Sandbox Code Playgroud)

其中,当通过管道传输时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
Run Code Online (Sandbox Code Playgroud)

附录:如果要计算文件中 A、C、G、N、T 和 - 字符的总数,可以通过管道来wc -l代替.grep 输出sort | uniq -c。只需对这种方法稍作修改,您就可以计算出许多不同的东西。

  • grep -o '[ATCGN-]' 在这里可能更具可读性。 (20认同)
  • @JourneymanGeek:学习正则表达式非常值得,因为它对很多事情都有用。只需了解它的局限性,不要滥用权力,尝试做正则表达式功能范围之外的事情,例如 [尝试解析 XHTML](http://stackoverflow.com/questions/1732348/regex-match-open- tags-except-xhtml-self-contained-tags)。 (2认同)

Gia*_*olà 14

使用 Python 计算所有字母的一个班轮:

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

...产生像这样的 YAML 友好输出:

{'\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}
Run Code Online (Sandbox Code Playgroud)

有趣的是,在大多数情况下,Python 在代码的清晰度方面甚至可以轻松击败 bash。


use*_*686 11

类似于 Guru 的awk方法:

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


Dav*_*rtz 10

使用 UNIX 几年后,您将非常熟练地将许多小操作链接在一起以完成各种过滤和计数任务。每个人都有自己的风格——有人喜欢awksed,有人喜欢cuttr。这是我要做的方式:

要处理特定的文件名:

 od -a FILENAME_HERE | cut -b 9- | tr " " \\n | egrep -v "^$" | sort | uniq -c
Run Code Online (Sandbox Code Playgroud)

或作为过滤器:

 od -a | cut -b 9- | tr " " \\n | egrep -v "^$" | sort | uniq -c
Run Code Online (Sandbox Code Playgroud)

它是这样工作的:

  1. od -a 将文件分隔为 ASCII 字符。
  2. cut -b 9-消除前缀od放置。
  3. tr " " \\n 将字符之间的空格转换为换行符,因此每行一个字符。
  4. egrep -v "^$" 摆脱了由此产生的所有额外空行。
  5. sort 将每个角色的实例收集在一起。
  6. uniq -c 计算每行的重复次数。

我喂它“你好,世界!” 后跟一个换行符,得到了这个:

  1 ,
  1 !
  1 d
  1 e
  1 H
  3 l
  1 nl
  2 o
  1 r
  1 sp
  1 w
Run Code Online (Sandbox Code Playgroud)


小智 9

sed部分基于@Guru 的回答,这是另一种使用方法uniq,类似于 David Schwartz 的解决方案。

$ 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
Run Code Online (Sandbox Code Playgroud)


Ind*_*rek 7

你可以结合grepwc来做到这一点:

grep -o 'character' file.txt | wc -w
Run Code Online (Sandbox Code Playgroud)

grep在给定的文件中搜索指定的文本,该-o选项告诉它只打印实际匹配项(即您要查找的字符),而不是默认打印搜索文本所在的每一行上发现。

wc打印每个文件的字节、字和行数,或者在这种情况下,是grep命令的输出。该-w选项告诉它计算单词,每个单词都是搜索字符的一次出现。当然,该-l选项(计算行数)也可以使用,因为grep在单独的行上打印每个出现的搜索字符。

要一次对多个字符执行此操作,请将字符放入数组中并对其进行循环:

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

示例:对于包含 string 的文件TGC-GTCCNATGCGNNTCACANN-,输出将是:

A  3
T  4
C  6
G  4
N  5
-  2
Run Code Online (Sandbox Code Playgroud)

有关更多信息,请参阅man grepman wc


正如用户 Journeyman Geek 在下面的评论中指出的那样,这种方法的缺点是grep必须为每个角色运行一次。根据文件的大小,这可能会导致明显的性能下降。另一方面,当这样做时,可以更容易地快速查看正在搜索哪些字符,并添加/删除它们,因为它们与代码的其余部分位于不同的行上。

  • 他们需要根据他们想要的字符重复它......我会补充。我可以发誓有一个更优雅的解决方案,但它需要更多的戳;p (3认同)

The*_*ell 7

使用 22hgp10a.txt 中的序列行,我的系统上 grep 和 awk 之间的时间差异使使用 awk 成为可行的方法...

[编辑]:在看过 Dave 编译的解决方案后也忘记了 awk,因为他在这个文件上大约 0.1 秒内完成了完整的区分大小写计数。

# 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
Run Code Online (Sandbox Code Playgroud)

不区分大小写的 ghostdog 版本在大约 14 秒内完成。

sed 在此问题的已接受答案中进行了解释。
基准测试与此问题的公认答案相同。
ghostdog74 接受的答案是这个问题


Aki*_*nen 6

我认为任何体面的实现都避免了排序。但是因为读取所有内容 4 次也是一个坏主意,我认为可以以某种方式生成一个流,该流通过 4 个过滤器,每个字符一个,过滤掉并且流长度也以某种方式计算。

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
Run Code Online (Sandbox Code Playgroud)

然后累积总和在 tmp[0-6].txt .. 所以工作仍在进行中

在这种方法中只有 13 个管道,转换为小于 1 Mb 的内存。
当然,我最喜欢的解决方案是:

time cat >f.c && gcc -O6 f.c && ./a.out
# then type your favourite c-program
real    0m42.130s
Run Code Online (Sandbox Code Playgroud)