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


121

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


56
计数DNA链中的碱基?
Indrek

12
我喜欢这个问题,因此有许多不同的方法和工具用于解决同一问题。
Journeyman Geek

10
嘿,这是边缘码高尔夫球场
Earlz

13
如果somone对Windows Powershell版本感兴趣:[System.IO.File]::ReadAllText("C:\yourfile.txt").ToCharArray() | Group-Object $_ | Sort Count -Descending
Guillaume86,

4
好的,我认为我找到了纯粹的PS方式:Get-Content "C:\eula.3082.txt" | % { $_.ToCharArray() } | Group-Object | Sort Count -Descending
Guillaume86

Answers:


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;

是一个非常快的伪一线客机。

一个简单的测试显示,在我的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)内存中运行,如果您的文件远大于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;

刚超过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

1
+1当数据量很大,而不仅仅是几个字节时,这是一个理智的解决方案。这些文件在磁盘缓存中,不是吗?
丹尼尔·贝克

2
整洁的是,它在处理中具有O(N)的复杂性,而在内存中具有O(1)的复杂性。管道通常在处理中具有O(N log N)(甚至在存储中具有O(N ^ 2))和O(N)。
马丁·乌丁

73
但是,您在扩展“命令行”的定义。
gerrit 2012年

11
史诗般地弯曲问题的要求-我批准; p。superuser.com/a/486037/10165 <-有人运行了基准测试,这最快的选择。
Journeyman Geek

2
+1我感谢我在适当的地方很好地使用了C。
杰夫·弗兰

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 -在文件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

8
血腥的Unix魔术!:D
皮托(Pitto)2012年

27
如果您的文件中只有CTAG-字符,则regexp本身就变得毫无意义,对吗?grep -o。| 排序| uniq -c同样可以很好地工作,afaik。
sylvainulg 2012年

7
+1我已经使用grep 25年了,对此一无所知-o
LarsH 2012年

9
@JourneymanGeek:这个问题是它会生成大量数据,然后将其转发以进行排序。让程序解析每个字符会更便宜。有关O(1)而不是O(N)内存复杂性的答案,请参阅Dave的答案。
马丁·尤丁

2
@Pitto coreutils的本机Windows版本已广泛使用-只需询问Google或类似机构
即可-OrangeDog

46

试试这个,灵感来自@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和-字符的数量,则可以通过管道wc -l而不是传递grep输出sort | uniq -c。只需对这种方法稍加修改,就可以计算出很多不同的事物。


我真的需要深入研究coreutils和regex的困境。p
Journeyman Geek

2
@JourneymanGeek:学习正则表达式很值得,因为它对很多事情都有用。只需了解它的局限性,并且不要通过尝试执行正则表达式功能之外的事情来滥用其功能,例如尝试解析XHTML
crazy2be 2012年

20
grep -o'[ATCGN-]'在这里可能更具可读性。
sylvainulg 2012年

14

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

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

...产生如下所示的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}

有趣的是,从代码的清晰性来看,Python在大多数情况下可以轻松击败bash。


11

与Guru的awk方法类似:

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

10

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

要处理特定的文件名:

 od -a FILENAME_HERE | cut -b 9- | tr " " \\n | egrep -v "^$" | sort | uniq -c

或作为过滤器:

 od -a | cut -b 9- | tr " " \\n | egrep -v "^$" | sort | uniq -c

它的工作方式如下:

  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

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

1
使用[[:alpha:]]而不是.in sed仅匹配字符而不是换行符。
克劳迪乌斯2012年

1
[[:alpha:]]如果您还尝试匹配-问题中提到的内容,将会失败
2012年

正确。最好在sed中添加第二个表达式,以首先过滤掉所有其他内容,然后在所需的字符上进行显式匹配:sed -e 's/[^ATCGN-]//g' -e 's/\([ATCGN-]\)/\1\n/g' foo | sort | uniq -c。但是,我不知道该如何摆脱换行符:\
Claudius

7

您可以合并grepwc执行以下操作:

grep -o 'character' file.txt | wc -w

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

示例:对于包含字符串的文件TGC-GTCCNATGCGNNTCACANN-,输出为:

A  3
T  4
C  6
G  4
N  5
-  2

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


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


3
他们需要根据需要的每个字符重复此操作...我要补充。我可以发誓,这里有一个更优雅的解决方案,但它需要更多戳戳; p
Journeyman Geek

@JourneymanGeek好点。我想到的一种方法是将字符放入数组中并遍历整个数组。我已经更新了我的帖子。
Indrek '10

IMO太复杂了。只需使用grep -ea -et等。如果将其放入数组中并循环遍历,是否不必每个字符都经过grep循环一次?
Journeyman Geek

@JourneymanGeek你可能是对的。uniq -c也似乎是获得格式正确的输出的更好方法。我不是* nix专家,以上正是我从我有限的知识和一些手册页中所收集的内容:)
Indrek 2012年

我也是如此;上个学期的一项任务是对大约5000个通讯录条目进行排序,而uniq使这变得更加容易。
Journeyman Geek

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

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

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


1
您可以s/cache[letters[x]]/cache[letters[x]]+cache[toupper(letters[x])]挖掘它使其不区分大小写,而不影响其速度。
戴夫

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

然后,累计总和位于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

这是的很好用法tr
adavid 2012年

4

我既不知道uniq也不知道grep -o,但是由于我对@JourneymanGeek和@ crazy2be的评论获得了如此的支持,也许我应该将其变成一个自己的答案:

如果您知道文件中只有“好”字符(要计数的字符),则可以

grep . -o YourFile | sort | uniq -c

如果仅必须计算一些字符而其他字符不计算(即分隔符)

grep '[ACTGN-]' YourFile | sort | uniq -c

第一个使用正则表达式通配符.,它匹配任何单个字符。第二个使用“接受的字符集”,没有特定的顺序,除了-必须排在最后(A-C被解释为“ A和之间的任何字符” C)。在这种情况下,必须使用引号,以便您的外壳程序不会尝试将其扩展为检查单个字符文件(如果有的话)(如果没有则产生“不匹配”错误)。

请注意,“ sort”也有一个-unique标志,因此它仅报告一次,而没有伴随标志来计数重复项,因此uniq确实是必需的。


-如果您使用反斜杠将其转义,则不必走到最后:'[A\-CTGN]'应该可以正常工作。
Indrek

2

一个愚蠢的人:

tr -cd ATCGN- | iconv -f ascii -t ucs2 | tr '\0' '\n' | sort | uniq -c
  • tr删除(-d)除(-c)ATCGN-以外的所有字符
  • iconv 转换为ucs2(UTF16限制为2个字节),以便在每个字节之后添加一个0字节,
  • 另一个tr将这些NUL字符转换为NL。现在每个角色都在自己的行上
  • sort | uniq -c计算每条uniq

这是非标准(GNU)-ogrep选项的替代方法。


您能否在此简要说明命令和逻辑?
安德鲁·兰伯特

2
time $( { tr -cd ACGTD- < dna.txt | dd | tr -d A | dd | tr -d C | dd | tr -d G |
dd | tr -d T | dd | tr -d D | dd | tr -d - | dd >/dev/null; } 2>tmp ) &&
grep byte < tmp | sort -r -g | awk '{ if ((s-$0)>=0) { print s-$0} s=$0 }'

输出格式不是最好的...

real    0m0.176s
user    0m0.200s
sys     0m0.160s
2069046
2070218
2061086
2057418
2070062
2052266

操作原理:

  • $({command | command} 2> tmp)将流的stderr重定向到一个临时文件。
  • dd将stdin输出到stdout,并输出传递到stderr的字节数
  • tr -d一次过滤一个字符
  • grep和sort将dd的输出过滤为降序
  • awk计算差异
  • sort仅在后处理阶段用于处理dd实例的退出顺序的不确定性

速度似乎是60MBps +


改进:摆脱tmp?使用“粘贴”来打印涉及的信件?
Aki Suihkonen 2012年

1

样本文件:

$ cat file
aix
unix
linux

命令:

$ sed 's/./&\n/g' file | awk '!/^$/{a[$0]++}END{for (i in a)print i,a[i];}'
u 2
i 3
x 3
l 1
n 2
a 1

-1表示不够清晰,并且张贴单线而不解释。AFAIK,这可能是叉子炸弹
PPC 2012年

1

结合其他一些

chars='abcdefghijklmnopqrstuvwxyz-'
grep -o -i "[$chars]" foo|sort | uniq -c

添加| sort -nr以按频率顺序查看结果。


1

简短答案:

如果情况允许,请将低字符集的文件大小与无字符的文件大小进行比较,以获取偏移量并仅计算字节数。

啊,但是纠结的细节:

这些都是Ascii字符。每一个字节。当然,文件中还包含额外的元数据,这些元数据可用于操作系统和创建该文件的应用所使用的各种内容。在大多数情况下,无论元数据如何,我都希望它们占用相同的空间,但是当您首先测试该方法,然后在不担心它的情况下,验证您的偏移量恒定时,我会尝试保持相同的环境。另一个难题是,换行符通常包含两个ascii空格字符,并且任何制表符或空格都将是一个。如果您可以确定这些内容将存在并且无法事先知道有多少个内容,那么我现在就停止阅读。

看起来似乎有很多限制,但是如果您可以轻松地建立它们,那么如果您有很多需要研究的问题,这将使我成为最容易/性能最好的方法(如果这是脱氧核糖核酸,那似乎很有可能)。检查一吨文件的长度并减去一个常数比在每个文件上运行grep(或类似文件)要快得多。

如果:

  • 这些是纯文本文件中的简单不间断字符串
  • 它们具有相同的文件类型,这些文件类型是由相同的香草非格式文本编辑器(如Scite)创建的(只要您检查空格/返回,就可以粘贴)或某些人编写的基本程序

还有两件事可能不重要,但我首先要进行测试

  • 文件名长度相等
  • 文件位于同一目录中

尝试通过执行以下操作查找偏移:

将一个空文件与一个带有几个易于计数的字符的文件进行比较,将一个空文件与带有几个其他字符的文件进行比较。如果从其他两个文件中减去空文件后得到的字节数与字符数相匹配,就可以了。检查文件长度,然后减去该空值。如果要尝试找出多行文件,大多数编辑器都会为换行符附加两个特殊的一字节字符,因为一个字符会被Microsoft忽略,但是在这种情况下,您至少必须使用grep来换行您也可以使用grep完成所有操作。


1

Haskell方式:

import Data.Ord
import Data.List
import Control.Arrow

main :: IO ()
main = interact $
  show . sortBy (comparing fst) . map (length &&& head) . group . sort

它是这样的:

112123123412345
=> sort
111112222333445
=> group
11111 2222 333 44 5
=> map (length &&& head)
(5 '1') (4 '2') (3 '3') (2 '4') (1,'5')
=> sortBy (comparing fst)
(1 '5') (2 '4') (3 '3') (4 '2') (5 '1')
=> one can add some pretty-printing here
...

编译和使用:

$ ghc -O2 q.hs
[1 of 1] Compiling Main             ( q.hs, q.o )
Linking q ...
$ echo 112123123412345 | ./q
[(1,'\n'),(1,'5'),(2,'4'),(3,'3'),(4,'2'),(5,'1')]%       
$ cat path/to/file | ./q
...

可能不适用于大文件。


1

快速的Perl破解:

perl -nle 'while(/[ATCGN]/g){$a{$&}+=1};END{for(keys(%a)){print "$_:$a{$_}"}}'
  • -n:遍历输入行,但不为它们打印任何内容
  • -l:自动删除或添加换行符
  • while:遍历当前行中所有出现的请求符号
  • END:最后打印结果
  • %a:哈希值的存储位置

完全不出现的字符将不包括在结果中。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.