Вопрос или проблема
Мне нужна программа, которая выводит количество различных символов в файле. Пример:
> 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 # Очистка.
Вообще без сортировки!
Ответ или решение
Чтобы подсчитать количество различных символов в файле, вы можете использовать несколько подходов. Ваша задача — создать программу, которая выведет количество каждого уникального символа в файле. Ниже приводятся некоторые методы, которые помогут вам это сделать.
Метод с использованием утилит командной строки
- Команда
sed
,sort
, иuniq
:
sed 's/\(.\)/\1\n/g' текстовый_файл | sort | uniq -c | sort -nr
Этот метод включает следующие шаги:
- С помощью
sed
создается новая строка после каждого символа, что позволяет вывести каждый символ на отдельной строке. - Затем
sort
упорядочивает символы. uniq -c
подсчитывает число вхождений каждого уникального символа.- Вторичный
sort -nr
упорядочивает вывод по убыванию частоты.
- Команда-пайплайн с
grep
,sort
, иuniq
:
cat filename | grep -o . | sort | uniq -c | sort -bnr
Этот метод:
- Использует
grep -o
для вывода каждого символа на новой строке. - Сортирует символы, затем
uniq
подсчитывает вхождения, аsort -bnr
окончательно упорядочивает результаты.
- Альтернативный подход с
fold
:
fold -w1 filename | sort | uniq -c | sort -nr
Этот подход использует fold
для разбиения строки после каждого символа и последующей сортировки и подсчета.
Скрипты на языках программирования
- 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 считается мощным инструментом для текстовой обработки, предлагая гибкость в обработке специального формата символов.
- Perl:
perl -ne '
++$c{$_} foreach split //;
END { printf "'\''%s'\'': %d\n", /[\\'\'']/ ? "\\$_" : /./ ? $_ : "\\n", $c{$_}
foreach (sort {$c{$b} <=> $c{$a}} keys %c) }'
Perl известен своей эффективностью при манипуляциях с текстом и обладает встроенными функциями для сортировки.
- 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"
Заключение
Выбор способа подсчета символов в файле зависит от ваших требований: будет ли это одноразовая задача или регулярная операция, а также от размера и сложности обрабатываемого текста. Указанные выше решения покрывают широкий спектр случаев использования от примитивных командных сценариев до более оптимизированных программных подходов, которые адекватны для больших наборов данных.