ファイル内のA、T、C、G、N、および「-」文字、または必要に応じてすべての文字を数えたいのですが、これを行う簡単なUnixコマンドはありますか?
実際の速度が必要な場合:
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
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
@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出力をパイプすることができます。このアプローチにわずかな変更を加えるだけで、数えることができるさまざまなことがたくさんあります。
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でさえ簡単に打ち負かすことができる方法を見るのは興味深いことです。
達人のawk
メソッドに似ています:
Perl -e 'while (<>) {$c{$&}++ while /./g} print "$c{$_} $_\n" for keys %c'
数年間UNIXを使用した後、さまざまなフィルタリングおよびカウントタスクを実行するための多数の小さな操作のリンクに非常に習熟します。 awk
やsed
のようなもの、cut
やtr
のようなものもあります。ここに私がそれをする方法があります:
特定のファイル名を処理するには:
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
それはこのように動作します:
od -a
は、ファイルをASCII文字に分割します。cut -b 9-
は、プレフィックスod
を削除します。tr " " \\n
は、文字間のスペースを改行に変換するため、1行に1文字ずつ入ります。egrep -v "^$"
これが作成する余分な空白行をすべて取り除きます。sort
は、各キャラクターのインスタンスを一緒に収集します。uniq -c
は、各行の繰り返し数をカウントします。「Hello、world!」改行が続き、これを得た:
1 ,
1 !
1 d
1 e
1 H
3 l
1 nl
2 o
1 r
1 sp
1 w
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
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が受け入れた回答は この質問 でした。
これを行うには、grep
とwc
を組み合わせることができます。
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回実行する必要があることです。ファイルのサイズによっては、これによりパフォーマンスが著しく低下する可能性があります。一方、このようにすると、他のコードとは別の行にあるため、検索されている文字をすばやく確認し、追加/削除するのが少し簡単になります。
まともな実装はソートを回避すると思います。しかし、すべてを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
私はuniq
についてもgrep -o
についても知りませんでしたが、@ JourneymanGeekと@ crazy2beに関する私のコメントにはそのようなサポートがあったので、おそらくそれを独自のアンサーに変えるべきでしょう。
ファイルに「良い」文字(カウントしたい文字)しかないことがわかっている場合は、
grep . -o YourFile | sort | uniq -c
一部の文字のみをカウントし、他の文字はカウントしない場合(つまり、区切り文字)
grep '[ACTGN-]' YourFile | sort | uniq -c
1つ目は、任意の1文字に一致する正規表現ワイルドカード.
を使用します。 2番目は-
が最後に来る必要があることを除いて、特定の順序なしで「受け入れられた文字のセット」を使用します(A-C
は、A
とC
)。その場合は引用符が必要です。これにより、シェルが拡張して単一文字のファイルをチェックしないようにします(存在しない場合は「一致なし」エラーが生成されます)。
「sort」には-u
niqueフラグもあるので、一度だけ報告するが、重複をカウントするコンパニオンフラグはないため、uniq
は必須です。
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
動作理論:
速度は60MBps +のようです
ばかげたもの:
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オプションの代替手段です。
他のいくつかを組み合わせる
chars='abcdefghijklmnopqrstuvwxyz-'
grep -o -i "[$chars]" foo|sort | uniq -c
追加 | sort -nr
頻度順に結果を表示します。
短い答え:
状況が許せば、低い文字セットのファイルサイズを文字なしのファイルサイズと比較して、オフセットを取得し、バイトを数えるだけです。
ああ、しかしもつれた詳細:
それらはすべてアスキー文字です。 1バイトあたり。もちろんファイルには、OSとそれを作成したアプリで使用されるさまざまなもののために追加のメタデータが付加されています。ほとんどの場合、これらはメタデータに関係なく同じ量のスペースを占めると予想しますが、最初にアプローチをテストするときに同じ状況を維持し、心配する前に一定のオフセットがあることを確認します。 その他の問題は、改行には通常2つのASCII空白文字が含まれ、タブまたはスペースはそれぞれ1つになるということです。これらが存在し、事前にいくつあるかを知る方法がないと確信できる場合は、今は読みません。
多くの制約のように思えるかもしれませんが、それらを簡単に確立できれば、これが大量に見られる場合(これがDNAである可能性が高いと思われます)、これが最も簡単で最高のパフォーマンスを発揮するアプローチです。大量のファイルの長さをチェックし、定数を減算すると、すべてのファイルでgrep(または同様の)を実行するよりも高速になります。
次の場合:
重要ではないかもしれないが、最初にテストする2つのこと
以下を実行してオフセットを見つけてください:
空のファイルを、人間が数えやすい数文字を含むものと、さらに数文字を含むものと比較します。他の2つのファイルの両方から空のファイルを差し引くと、文字数と一致するバイト数が得られれば完了です。ファイルの長さを確認し、その空の量を差し引きます。複数行のファイルを理解しようとする場合、ほとんどのエディターは改行用に2つの特別な1バイト文字を添付します。1つはMicrosoftによって無視される傾向があるためですが、その場合は少なくとも空白文字をgrepする必要があります。すべてgrepを使用することもできます。
サンプルファイル:
$ 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
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
...
巨大なファイルには向かないかもしれません。
簡単なPerlハック:
Perl -nle 'while(/[ATCGN]/g){$a{$&}+=1};END{for(keys(%a)){print "$_:$a{$_}"}}'
-n
:入力行を繰り返し処理しますが、何も出力しません-l
:改行を自動的に削除または追加しますwhile
:現在の行で要求されたシンボルのすべての出現を反復しますEND
:最後に、結果を出力します%a
:値が格納されるハッシュまったく発生しない文字は結果に含まれません。