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


121

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


56
DNA鎖の塩基を数えますか?
インドレック

12
この質問が大好きなので、同じ問題を解決するために使用されるさまざまなアプローチやツールがあります。
ジャーニーマンオタク

10
へえ、これはボーダーラインのコードゴルフです
-Earlz

13
Somoneのは、Windows PowerShellのバージョンに関心がある場合:[System.IO.File]::ReadAllText("C:\yourfile.txt").ToCharArray() | Group-Object $_ | Sort Count -Descending
Guillaume86

4
[OK]を私は、私は純粋なPSの方法を見つけたと思う:Get-Content "C:\eula.3082.txt" | % { $_.ToCharArray() } | Group-Object | Sort Count -Descending
Guillaume86

回答:


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分間実行した後でgaveめました

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)があります。
マーティンUeding

73
ただし、「コマンドライン」の定義をかなり拡張しています。
-gerrit

11
質問の要件の壮大な曲げ-私は承認する; p。superuser.com/a/486037/10165 <-誰かがベンチマークを実行しました、これ最速のオプションです。
ジャーニーマンオタク

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

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

8
ブラッディユニックスマジック!:D
ピッツ

27
ファイルにCTAG-文字しかない場合、正規表現自体は無意味になりますよね?grep -o。| 並べ替える| uniq -cも同様に機能します、afaik。
シルベヌル

7
+1私は25年間grepを使用していますが、について知りませんでした-o
LarsH

9
@JourneymanGeek:これに関する問題は、ソートのために転送される大量のデータを生成することです。プログラムに各文字を解析させる方が安価です。O(N)メモリの複雑さの代わりにO(1)については、Daveの答えを参照してください。
マーティンUeding

2
@PittoネイティブのWindowsは、coreutilsののビルド広く利用されている-ちょうどGoogleに問い合わせるか、somesuch
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
ジャーニーマンオタク

2
@JourneymanGeek:正規表現を学習することは、非常に多くのことに役立つので、トラブルに見合うだけの価値があります。それが制限であることを理解し、XHTMLを解析しようとするなど、正規表現機能の範囲外のことをしようとすることでパワーを乱用しないでください。
crazy2be

20
ここでは、grep -o '[ATCGN-]'をもう少し読みやすくすることができます。
シルベヌル

14

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を簡単に破ることができることを見るのは興味深いことです。


11

達人の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 文字間のスペースを改行に変換して、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

9

sed基づいている部分@達人の答えは、ここで使用して別のアプローチだ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

1
改行ではなく文字のみに一致させるに[[:alpha:]]は、.in ではなく使用しsedます。
クラウディウス

1
[[:alpha:]]-質問で言及されたのようなものも一致させようとすると失敗します
-Izkata

正しい。sedに2番目の式を追加して、最初に他のすべてを除外してから、目的の文字に明示的に一致させる方がよい場合がありますsed -e 's/[^ATCGN-]//g' -e 's/\([ATCGN-]\)/\1\n/g' foo | sort | uniq -c。ただし、改行を削除する方法がわかりません:\
Claudius

7

あなたは組み合わせることができますgrepし、wcこれを行うには:

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


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


3
必要な文字ごとに繰り返す必要があります...追加します。もっとエレガントなソリューションがあると断言できますが、もっと突っ込む必要があります; p
ジャーニーマンオタク

@JourneymanGeek良い点。思い浮かぶアプローチの1つは、文字を配列に入れてループ処理することです。投稿を更新しました。
インドレック

複雑すぎるIMO。grep -ea -etなどを使用します。配列に入れてループ処理する場合、文字ごとにgrepサイクルを実行する必要はありませんか?
ジャーニーマンオタク

@JourneymanGeekあなたはおそらく正しいです。uniq -cまた、適切にフォーマットされた出力を取得するより良い方法のようです。私は* nixの第一人者ではありません。上記は、限られた知識といくつかのマニュアルページからまとめたものです:)
Indrek

私もそうしました; p、そして最後の任期の1つは、約5000個のアドレス帳エントリをソートすることでしたが、uniqでLOTが簡単になりました。
ジャーニーマンオタク

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
アダビッド

4

私は知っていたしませんでしたuniqでもについてgrep -o、しかし@JourneymanGeekと@ crazy2beの私のコメントは、このようなサポートを持っていたことから、多分私は、独自のanwserにそれを回す必要があります。

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

grep . -o YourFile | sort | uniq -c

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

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

最初のものは、.任意の単一文字に一致する正規表現wildcardを使用します。2番目のものは、「受け入れられる文字のセット」を使用します。特定の順序は-ありませんが、最後に来る必要があります(A-CAとの間の任意の文字」と解釈されますC)。その場合、引用符が必要であるため、シェルはそれを展開して1文字のファイルがあればそれをチェックしようとしません(そして、一致しない場合は「一致しない」エラーを生成します)。

"sort"には-uniqueフラグもあるため、一度だけレポートするようになっていますが、重複をカウントするコンパニオンフラグuniqはないため、実際には必須です。


-バックスラッシュでエスケープする場合、最後に来る必要はありません:'[A\-CTGN]'正常に動作するはずです。
インドレック

2

愚かなもの:

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

これは、非標準(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)は、ストリームの標準エラーを一時ファイルにリダイレクトします。
  • ddはstdinをstdoutに出力し、stderrに渡されたバイト数を出力します
  • tr -dは一度に1文字を除外します
  • grepおよびsortは、ddの出力を降順にフィルターします
  • awkは差を計算します
  • ソートは、ddのインスタンスの終了順序の不確実性を処理するために、後処理段階でのみ使用されます

速度は60MBps以上のようです


改善:tmpを削除しますか?「貼り付け」を使用して、関係する文字を印刷しますか?
アキ水コネン

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。私の知る限り、これはフォーク爆弾かもしれない
PPC

1

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

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

| sort -nr頻度の順に結果を見るために追加します。


1

簡潔な答え:

状況に応じて、低文字セットのファイルサイズを文字なしのファイルサイズと比較して、オフセットを取得し、バイトをカウントします。

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

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

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

次の場合:

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

そして問題ではないかもしれないが、最初にテストする2つのこと

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

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

空のファイルを、人間が数えやすい数文字のファイルと、さらに数文字のファイルと比較します。他の2つのファイルの両方から空のファイルを減算すると、文字カウントに一致するバイトカウントが得られたら、完了です。ファイルの長さを確認し、その空の量を減算します。複数行のファイルを把握したい場合、ほとんどのエディターは改行のために2つの特別な1バイト文字を添付します。1つは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:値が保存されるハッシュ

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

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.