web-dev-qa-db-ja.com

ファイル内の各文字の数を数える最も速い方法は何ですか?

ファイル内のA、T、C、G、N、および「-」文字、または必要に応じてすべての文字を数えたいのですが、これを行う簡単なUnixコマンドはありますか?

120
Kirt

実際の速度が必要な場合:

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
136
Dave

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を検索し、文字-で検索する各文字を検索します。また、1行1文字で印刷します。

sortはそれを順番にソートします。これで次のツールの準備が整います

uniq -cは、任意の行の重複する連続オカレンスをカウントします。この場合、ソートされた文字のリストがあるので、最初のステップで文字を取り出したときのカウントがきちんと得られます。

Foo.txtに文字列GATTACA-thisが含まれている場合、このコマンドセットから取得したものは

[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
119
Journeyman Geek

@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

最後のステップは、@ Journeymanの回答のように、sort | uniq -cを使用して簡単に実行できる類似の行をすべてマージしてカウントすることです。ソートにより、次のような出力が得られます。

$ 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の代わりにsort | uniq -cを介してgrep出力をパイプすることができます。このアプローチにわずかな変更を加えるだけで、数えることができるさまざまなことがたくさんあります。

46
crazy2be

Pythonを使用してすべての文字を数える1つのライナー:

$ 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でさえ簡単に打ち負かすことができる方法を見るのは興味深いことです。

14

達人のawkメソッドに似ています:

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

数年間UNIXを使用した後、さまざまなフィルタリングおよびカウントタスクを実行するための多数の小さな操作のリンクに非常に習熟します。 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は、文字間のスペースを改行に変換するため、1行に1文字ずつ入ります。
  4. egrep -v "^$"これが作成する余分な空白行をすべて取り除きます。
  5. sortは、各キャラクターのインスタンスを一緒に収集します。
  6. uniq -cは、各行の繰り返し数をカウントします。

「Hello、world!」改行が続き、これを得た:

  1 ,
  1 !
  1 d
  1 e
  1 H
  3 l
  1 nl
  2 o
  1 r
  1 sp
  1 w
10
David Schwartz

sedの部分は @ Guruの回答 に基づいています。DavidSchwartzのソリューションと同様に、uniqを使用した別のアプローチがあります。

$ 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
9
Claudius

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が受け入れた回答は この質問 でした。

7
Thell

これを行うには、grepwcを組み合わせることができます。

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

grepは指定されたファイルで指定されたテキストを検索し、-oオプションはデフォルトではなく実際の一致(つまり、探していた文字)のみを出力するように指示します。検索テキストが見つかった各行を印刷します。

wcは、各ファイルのバイト数、ワード数、行数、またはこの場合はgrepコマンドの出力を出力します。 -wオプションは、単語をカウントするように指示します。各単語は検索文字の出現です。もちろん、grepは検索文字の出現ごとに別の行に出力するため、-lオプション(行を数える)も機能します。

一度に複数の文字に対してこれを行うには、文字を配列に入れてループします。

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 grep および man wc を参照してください。


このアプローチの欠点は、ユーザーJourneyman Geekが以下のコメントで指摘しているように、grepは各文字に対して1回実行する必要があることです。ファイルのサイズによっては、これによりパフォーマンスが著しく低下する可能性があります。一方、このようにすると、他のコードとは別の行にあるため、検索されている文字をすばやく確認し、追加/削除するのが少し簡単になります。

7
Indrek

まともな実装はソートを回避すると思います。しかし、すべてを4回読み取ることも悪い考えであるため、各文字に1つずつ、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
6
Aki Suihkonen

私はuniqについてもgrep -oについても知りませんでしたが、@ JourneymanGeekと@ crazy2beに関する私のコメントにはそのようなサポートがあったので、おそらくそれを独自のアンサーに変えるべきでしょう。

ファイルに「良い」文字(カウントしたい文字)しかないことがわかっている場合は、

grep . -o YourFile | sort | uniq -c

一部の文字のみをカウントし、他の文字はカウントしない場合(つまり、区切り文字)

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

1つ目は、任意の1文字に一致する正規表現ワイルドカード.を使用します。 2番目は-が最後に来る必要があることを除いて、特定の順序なしで「受け入れられた文字のセット」を使用します(A-Cは、AC)。その場合は引用符が必要です。これにより、シェルが拡張して単一文字のファイルをチェックしないようにします(存在しない場合は「一致なし」エラーが生成されます)。

「sort」には-uniqueフラグもあるので、一度だけ報告するが、重複をカウントするコンパニオンフラグはないため、uniqは必須です。

4
sylvainulg
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は一度に1文字をフィルターで除外します
  • grepおよびsortは、ddの出力を降順でフィルタリングします
  • awkは差を計算します
  • sortはpost-processingステージでのみ使用され、ddのインスタンスの終了順序の不確実性を処理します

速度は60MBps +のようです

2
Aki Suihkonen

ばかげたもの:

tr -cd ATCGN- | iconv -f ascii -t ucs2 | tr '\0' '\n' | sort | uniq -c
  • trは、(-d)ATCGN-c)以外のすべての文字を削除します
  • iconvは、ucs2(UTF16は2バイトに制限されています)に変換して、各バイトの後に0バイトを追加します。
  • 別のtrは、これらのNUL文字をNLに変換します。今、すべてのキャラクターが独自のラインにいます
  • sort | uniq -c-それぞれを数える---(uniq

これは、非標準(GNU)-o grepオプションの代替手段です。

2
sch

他のいくつかを組み合わせる

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

追加 | sort -nr頻度順に結果を表示します。

1
Keith Wolters

短い答え:

状況が許せば、低い文字セットのファイルサイズを文字なしのファイルサイズと比較して、オフセットを取得し、バイトを数えるだけです。

ああ、しかしもつれた詳細:

それらはすべてアスキー文字です。 1バイトあたり。もちろんファイルには、OSとそれを作成したアプリで使用されるさまざまなもののために追加のメタデータが付加されています。ほとんどの場合、これらはメタデータに関係なく同じ量のスペースを占めると予想しますが、最初にアプローチをテストするときに同じ状況を維持し、心配する前に一定のオフセットがあることを確認します。 その他の問題は、改行には通常2つのASCII空白文字が含まれ、タブまたはスペースはそれぞれ1つになるということです。これらが存在し、事前にいくつあるかを知る方法がないと確信できる場合は、今は読みません。

多くの制約のように思えるかもしれませんが、それらを簡単に確立できれば、これが大量に見られる場合(これがDNAである可能性が高いと思われます)、これが最も簡単で最高のパフォーマンスを発揮するアプローチです。大量のファイルの長さをチェックし、定数を減算すると、すべてのファイルでgrep(または同様の)を実行するよりも高速になります。

次の場合:

  • これらは、純粋なテキストファイル内の途切れのない単純な文字列です。
  • それらはSciteのような同じバニラ非フォーマットテキストエディター(スペース/リターンをチェックする限り貼り付けても構いません)または誰かが書いたいくつかの基本的なプログラムによって作成された同じファイルタイプです

重要ではないかもしれないが、最初にテストする2つのこと

  • ファイル名は同じ長さです
  • ファイルは同じディレクトリにあります

以下を実行してオフセットを見つけてください:

空のファイルを、人間が数えやすい数文字を含むものと、さらに数文字を含むものと比較します。他の2つのファイルの両方から空のファイルを差し引くと、文字数と一致するバイト数が得られれば完了です。ファイルの長さを確認し、その空の量を差し引きます。複数行のファイルを理解しようとする場合、ほとんどのエディターは改行用に2つの特別な1バイト文字を添付します。1つはMicrosoftによって無視される傾向があるためですが、その場合は少なくとも空白文字をgrepする必要があります。すべてgrepを使用することもできます。

1
Erik Reppen

サンプルファイル:

$ 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
Guru

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
ht.

簡単なPerlハック:

Perl -nle 'while(/[ATCGN]/g){$a{$&}+=1};END{for(keys(%a)){print "$_:$a{$_}"}}'
  • -n:入力行を繰り返し処理しますが、何も出力しません
  • -l:改行を自動的に削除または追加します
  • while:現在の行で要求されたシンボルのすべての出現を反復します
  • END:最後に、結果を出力します
  • %a:値が格納されるハッシュ

まったく発生しない文字は結果に含まれません。

1
MvG