Как я могу подсчитать количество различных символов в файле?

Вопрос или проблема

Мне нужна программа, которая выводит количество различных символов в файле. Пример:

> stats testfile
' ': 207
'e': 186
'n': 102

Существует ли инструмент, который делает это?

Следующий вариант должен сработать:

$ sed 's/\(.\)/\1\n/g' text.txt | sort | uniq -c

Сначала вставляем новую строку после каждого символа, размещая каждый символ на своей строке. Затем сортируем его. Затем используем команду uniq для удаления дубликатов, добавляя в начало каждой строки количество вхождений этого символа.

Чтобы отсортировать список по частоте, передайте его в sort -nr.

Решение Стивена является хорошим и простым. Оно не так эффективно для очень больших файлов (файлов, которые не помещаются комфортно примерно в половину вашей оперативной памяти) из-за этапа сортировки. Вот версия на awk. Она также немного сложнее, потому что пытается правильно обрабатывать несколько специальных символов (новые строки, ', \, :).

awk '
  {for (i=1; i<=length; i++) ++c[substr($0,i,1)]; ++c[RS]}
  function chr (x) {return x=="\n" ? "\\n" : x==":" ? "\\072" :
                           x=="\\" || x=="'\''" ? "\\" x : x}
  END {for (x in c) printf "'\''%s'\'': %d\n", chr(x), c[x]}
' | sort -t : -k 2 -r | sed 's/\\072/:/'

Вот решение на Perl по той же принципу. Perl обладает преимуществом возможности сортировать внутренне. Также это решение не будет насчитывать лишнюю новую строку, если файл не оканчивается символом новой строки.

perl -ne '
  ++$c{$_} foreach split //;
  END { printf "'\''%s'\'': %d\n", /[\\'\'']/ ? "\\$_" : /./ ? $_ : "\\n", $c{$_}
        foreach (sort {$c{$b} <=> $c{$a}} keys %c) }'

Медленная, но относительно экономная по памяти версия, с использованием ruby. Около дюжины МБ ОЗУ независимо от размера входных данных.

# count.rb
ARGF.
  each_char.
  each_with_object({}) {|e,a| a[e] ||= 0; a[e] += 1}.
  each {|i| puts i.join("\t")}

ruby count.rb < input.txt
t       20721
d       20628
S       20844
k       20930
h       20783
... и так далее

Более очевидное решение, которое я использую для подсчета вхождений символов в файле:

cat filename | grep -o . | sort | uniq -c | sort -bnr

отправляет вывод в grep, который затем печатает каждый символ на одной строке | sort, затем выводит каждый символ столько раз, сколько он встречается в файле | uniq подсчитывает количество вхождений | sort -n снова сортирует этот ввод по числу

С файлом, содержащим текст “Peanut butter and jelly caused the elderly lady to think about her past.”

Вывод:

     13  
      9 e
      7 d
      5 s
      5 a
      4 o
      4 h

... и больше

Первая строка будет количество пробелов в файле, вы можете убрать их, если хотите использовать tr -d " "

Просто и относительно эффективно:

fold -c1 testfile.txt | sort | uniq -c

Просто скажите fold перенести (т.е. вставить новую строку) после каждого 1 символа.



Как тестировалось:

  • файл размером 128 МБ, содержащий только ASCII символы
    • Создан с помощью find . -type f -name '*.[hc]' -exec cat {} >> /tmp/big.txt \; в нескольких кодовых базах.
  • рабочая станция (реальное железо, не виртуальная машина)
  • переменная окружения LC_ALL=C

Время выполнения в порядке убывания:

  • Решение Стивена sed|sort|uniq (https://unix.stackexchange.com/a/5011/427210): 102.5 сек
  • Мое решение fold|sort|uniq: 59.3 сек
  • Мое решение fold|sort|uniq, с параметром --buffer-size=12G для sort: 38.9 сек
  • Мое решение fold|sort|uniq, с параметрами --buffer-size=12G и --stable для sort: 37.9 сек
  • Решение Гайлса на perl (https://unix.stackexchange.com/a/5013/427210): 34.0 сек
    • Победитель! Как говорится, самая быстрая сортировка — это не нужно сортировать. :-)





Добавлено, 01янв2025:

Стоит попробовать другой подход, если возможны только несколько символов и мы знаем, какие это символы. Допустим, возможные символы: {q , w , e , r , t , y}; тогда

declare -r tempfile=`mktemp`
declare -ri sizeFull=`stat -c %s hugeInput.txt`
declare -i sizePartial
declare char
for char in q w e r t y
do
    tr -d $char <hugeInput.txt >$tempfile
    sizePartial=`stat -c %s $tempfile`
    echo "${char}: $((sizeFull-sizePartial))"
done
rm $tempfile   # Очистка.

Вообще без сортировки!

Ответ или решение

Чтобы подсчитать количество различных символов в файле, вы можете использовать несколько подходов. Ваша задача — создать программу, которая выведет количество каждого уникального символа в файле. Ниже приводятся некоторые методы, которые помогут вам это сделать.

Метод с использованием утилит командной строки

  1. Команда sed, sort, и uniq:
sed 's/\(.\)/\1\n/g' текстовый_файл | sort | uniq -c | sort -nr

Этот метод включает следующие шаги:

  • С помощью sed создается новая строка после каждого символа, что позволяет вывести каждый символ на отдельной строке.
  • Затем sort упорядочивает символы.
  • uniq -c подсчитывает число вхождений каждого уникального символа.
  • Вторичный sort -nr упорядочивает вывод по убыванию частоты.
  1. Команда-пайплайн с grep, sort, и uniq:
cat filename | grep -o . | sort | uniq -c | sort -bnr

Этот метод:

  • Использует grep -o для вывода каждого символа на новой строке.
  • Сортирует символы, затем uniq подсчитывает вхождения, а sort -bnr окончательно упорядочивает результаты.
  1. Альтернативный подход с fold:
fold -w1 filename | sort | uniq -c | sort -nr

Этот подход использует fold для разбиения строки после каждого символа и последующей сортировки и подсчета.

Скрипты на языках программирования

  1. AWK:
awk '
  {for (i = 1; i <= length; i++) ++count[substr($0, i, 1)]; ++count[RS]}
  function chr (x) {return x == "\n" ? "\\n" : x == ":" ? "\\072" :
                           x == "\\" || x == "\047" ? "\\" x : x}
  END {for (x in count) printf "'\''%s'\'': %d\n", chr(x), count[x]}
' filename | sort -t ':' -k2 -r | sed 's/\\072/:/'

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

  1. Perl:
perl -ne '
  ++$c{$_} foreach split //;
  END { printf "'\''%s'\'': %d\n", /[\\'\'']/ ? "\\$_" : /./ ? $_ : "\\n", $c{$_}
        foreach (sort {$c{$b} <=> $c{$a}} keys %c) }'

Perl известен своей эффективностью при манипуляциях с текстом и обладает встроенными функциями для сортировки.

  1. Ruby:
# count.rb
ARGF.
  each_char.
  each_with_object(Hash.new(0)) { |char, count| count[char] += 1 }.
  each { |char, count| puts "#{char}\t#{count}" }

Этот Ruby скрипт использует разумное количество оперативной памяти, что делает его подходящим для обработки больших файлов.

Оптимизация производительности

  • Для увеличения производительности используйте большие буферы в сортировках или выполняйте сортировку памяти.
  • Если допустимо, можно предварительно определить возможные символы и использовать компрессированные подсчеты, как в следующем примере:
declare -r tempfile=$(mktemp)
declare -ri sizeFull=$(stat -c %s input.txt)

for char in {a..z}; do
  tr -d "$char" <input.txt >"$tempfile"
  sizePartial=$(stat -c %s "$tempfile")
  echo "${char}: $((sizeFull - sizePartial))"
done

rm "$tempfile"

Заключение

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

Оцените материал
Добавить комментарий

Капча загружается...