120

Я хочу посчитать буквы A, T, C, G и N в файле или каждую букву, если необходимо, есть ли быстрая команда Unix, чтобы сделать это?

18 ответов18

135

Если вам нужна реальная скорость:

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 870 @ 2,93 ГГц он составляет чуть более 600 МБ / с:

$ 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

В отличие от решений, связанных с сортировкой, этот работает в постоянной (4 КБ) памяти, что очень полезно, если ваш файл намного больше, чем ОЗУ.

И, конечно же, немного смазав локоть, мы можем сбрить 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,1 ГБ / с заканчиваются в:

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
118

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 - greps файл 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
45

Попробуйте этот, вдохновленный ответом @ Journeyman.

grep -o -E 'A|T|C|G|N|-' foo.txt | sort | uniq -c

Ключ знает о опции -o для grep. Это разделяет совпадение, так что каждая выходная строка соответствует одному экземпляру шаблона, а не всей строке для любой совпадающей строки. Учитывая эти знания, нам нужен только шаблон и способ подсчета строк. Используя регулярное выражение, мы можем создать дизъюнктивный шаблон, который будет соответствовать любому из указанных вами символов:

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 и - в файле, вы можете передать вывод grep через wc -l вместо sort | uniq -c . Есть много разных вещей, которые вы можете посчитать, лишь слегка изменив этот подход.

13

Один лайнер, считающий все буквы с использованием 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

Похож на метод Гуру в awk :

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

После использования 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

Это работает так:

  1. od -a разделяет файл на символы ASCII.
  2. cut -b 9- удаляет префикс od put.
  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 , аналогичный решению Дэвида Шварца.

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

Используя строки последовательности из 22hgp10a.txt, разница во времени между grep и awk в моей системе делает использование awk способом ...

[Редактировать]: После просмотра скомпилированного решения Дейва, забудьте также 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 секунд.

Сед объяснен в принятом ответе на этот вопрос.
Бенчмаркинг как в принятом ответе на этот вопрос.
На этот вопрос был принят ghostdog74 ответ.

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 должен запускаться один раз для каждого символа. В зависимости от размера ваших файлов это может привести к заметному снижению производительности. С другой стороны, когда это делается таким образом, становится немного проще быстро увидеть, какие символы ищут, и добавить / удалить их, так как они находятся на отдельной строке от остальной части кода.

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 МБ памяти.
Конечно, мое любимое решение:

time cat >f.c && gcc -O6 f.c && ./a.out
# then type your favourite c-program
real    0m42.130s
4

Я не знал ни об uniq ни о grep -o , но, поскольку мои комментарии к @JourneymanGeek и @ crazy2be имели такую поддержку, возможно, мне стоит превратить его в своего собственного ответчика:

Если вы знаете, что в вашем файле есть только "хорошие" символы (те, которые вы хотите посчитать), вы можете перейти к

grep . -o YourFile | sort | uniq -c

Если только некоторые символы должны быть подсчитаны, а другие нет (т.е. разделители)

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

Первый использует подстановочный знак регулярного выражения . , которые соответствуют любому отдельному символу. Второй использует "набор принятых символов", без определенного порядка, за исключением того, что - должен стоять последним (A-C интерпретируется как «любой символ между A и C). В этом случае требуются кавычки, чтобы ваша оболочка не пыталась расширить ее, чтобы проверить односимвольные файлы, если они есть (и выдает ошибку "нет совпадения", если ее нет).

Обратите внимание, что "sort" также имеет флаг -u nique, так что он сообщает об этом только один раз, но нет сопутствующего флага для подсчета дубликатов, поэтому uniq действительно обязателен.

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 +

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) -o grep.

1

Объединяя несколько других

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

Добавить | sort -nr чтобы увидеть результаты в порядке частоты.

1

Короткий ответ:

Если обстоятельства позволяют, сравните размеры файлов с низкими наборами символов с одним без символов, чтобы получить смещение и просто подсчитать байты.

Ах, но запутанные детали:

Это все персонажи Ascii. Один байт на. В файлах, конечно же, есть дополнительные метаданные, предваряемые для различных вещей, используемых ОС и приложением, которое ее создало. В большинстве случаев я ожидал бы, что они будут занимать одинаковое количество места независимо от метаданных, но я постараюсь сохранить идентичные обстоятельства, когда вы сначала проверяете подход, а затем проверяете, что у вас есть постоянное смещение, прежде чем беспокоиться об этом. Другой недостаток заключается в том, что разрывы строк обычно включают два символа пробела ascii и любые табуляции или пробелы будут по одному для каждого. Если вы можете быть уверены, что они будут присутствовать, и нет никакого способа узнать, сколько их заранее, я бы прекратил читать сейчас.

Это может показаться большим количеством ограничений, но если вы можете легко их установить, это выглядит как самый простой / наиболее эффективный подход, если у вас есть тонна из них, чтобы рассмотреть (что, вероятно, если это ДНК). Проверка тонны файлов на длину и вычитание константы будет быстрее, чем выполнение grep (или аналогичного) для каждого.

Если:

  • Это простые неразрывные строки в чистых текстовых файлах
  • Они находятся в идентичных типах файлов, созданных тем же ванильным неформатирующим текстовым редактором, как Scite (вставка в порядке, если вы проверяете наличие пробелов / возвратов) или какой-то базовой программой, написанной кем-то

И две вещи, которые могут не иметь значения, но я бы попробовал сначала

  • Имена файлов имеют одинаковую длину
  • Файлы находятся в одном каталоге

Попробуйте найти смещение, выполнив следующие действия:

Сравните пустой файл с файлом, содержащим несколько легко подсчитываемых человеком символов, с файлом, содержащим несколько символов. Если вычитание пустого файла из двух других файлов даст вам количество байтов, соответствующее количеству символов, все готово. Проверьте длину файла и вычтите это пустое количество. Если вы хотите попытаться выяснить многострочные файлы, большинство редакторов прикрепляют два специальных однобайтовых символа для разрывов строк, поскольку один из них, как правило, игнорируется Microsoft, но вам придется по крайней мере использовать grep для пробелов в этом случае Вы могли бы также сделать все это с помощью grep.

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

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: хэш, где хранятся значения

Символы, которые вообще не встречаются, не будут включены в результат.

Всё ещё ищете ответ? Посмотрите другие вопросы с метками .