Частота слов в тексте

Рейтинг: 8Ответов: 6Опубликовано: 31.03.2011

Ниже приведенный скрипт на Python подсчитывает частоту слов в тексте (непрерывных последовательностей букв за исключением знаков препинания) и выводит таблицу результатов.

Работает правильно. Вопрос вот в чем: можно ли сделать то же самое проще (например, меньше строк кода) на Python, Bash, PHP, Perl или это лучший способ?

import sys
import string

file = open(sys.argv[1], "r")
text = file.read()
file.close()

table = string.maketrans("", "")
words = text.lower().split(None)

frequencies = {}
for word in words:
    trimmed = word.translate(table, string.punctuation)
    frequencies[trimmed] = frequencies.get(trimmed, 0) + 1

keys = sorted(frequencies.keys())
for word in keys:
    print "%-32s %d" % (word, frequencies[word])

Ответы

▲ 5Принят

В вашем примере встречая подобную строку: "aa,bb,cc" она считается как "aabbcc 1", а должно быть: aa 1 bb 1 cc 1

Итак, мой вариант на Perl:

#!/usr/bin/perl
use strict;

my %result;
while (<>) {
    $result{ lc $_ }++ for /(\w+)/g;
}

printf "%-32s %d\n", $_, $result{$_} for sort keys %result;

можно его конечно скомпоновать и в одну строчку. но будет нечитаемо.

▲ 3

python, регекспы

import re
import sys
import operator

file = open(sys.argv[1], "r")
text = file.read().decode("utf8")
file.close()

words = re.findall(r"(\w+)", text, re.UNICODE)

stats = {}
for word in words:
    stats[word] = stats.get(word, 0) + 1

stats_list = sorted(stats.iteritems(), key = operator.itemgetter(1))
for word, count in stats_list:
    print "%-32s %d" % (word, count)
▲ 3

Подсчёт частоты слов с поддержкой Unicode (.casefold(), \w+):

  • текст, в кодировке из локали, задаётся из файлов, указанных в командной строке, или со стандартного ввода (если не указаны)
  • слова выводятся в порядке убывания популярности
  • кодировка для вывода может отличаться от кодировки на входе
  • выводит построчно, с указанным в вопросе форматированием (ширина под слово — 32 символа)
#!/usr/bin/env python3
import fileinput
import re
from collections import Counter

words = (word for line in fileinput.input()
         for word in re.findall(r'\w+', line.casefold()))
for word, count in Counter(words).most_common():
    print("%-32s %d" % (word, count))

Вот версия, близкая к поведению Питон 2 кода из вопроса:

  • читает байты из файла, переводит их в нижний регистр, разбивает на слова по стандартному пробелу (не поддерживает Unicode)
  • удаляет ascii пунктуацию из каждого слова (может пустая строка остаться)
  • считает получившиеся слова, сортирует их по величине байт
  • выводит построчно, с указанным в вопросе форматированием (ширина под слово — 32 байта)
#!/usr/bin/env python3
import os
import string
import sys
from collections import Counter
from pathlib import Path

words = Path(sys.argv[1]).read_bytes().lower().split()
chars_to_trim = string.punctuation.encode()
trimmed = (word.translate(None, chars_to_trim) for word in words)
for word, count in sorted(Counter(trimmed).items()):
    sys.stdout.buffer.write(b"%-32s %d%s" % (word, count, os.linesep.encode()))

Два примера разный вывод генерируют как правило.

▲ 2

Мой вариант:

#!/usr/bin/perl
use strict;

my %frec;

sub calc{
    $frec{ $1 }++ while( $_[0] =~ /\b(\S+)\b/g );
}

my $fileName = shift or die( "Uasge: $0 filenameWithText" );
open FF, $fileName;
calc( $_ ) for( <FF> );
foreach( sort{ $frec{$b} <=> $frec{$a} } keys %frec ){
    printf( "%-32s %d\n", $_, $frec{ $_ } );
}
close FF;
▲ 2

Скрипт на bash/awk, для коллекции:

#/bin/bash

if [ -z "$1" ]
then
  echo "Usage: `basename $0` filename"
  exit 1
fi

for x in $(sed -rn 's/\W+/ /gp' $1);
do
  echo $x
done | awk '{print tolower($0)}' | sort | awk '
{
  if (!word) {
    word = $1
    num = 0
  } else if (word == $1) {
    num++
  } else {
    print word, num+1
    word = $1
    num = 0
  }
}'
▲ 2

Можно воспользоваться модулем nltk, который предназначен для работы с естественными текстами:

In [54]: from nltk import word_tokenize, FreqDist

In [55]: data = open(r'c:/temp/TWAIN.LOG').read()

In [56]: fdist = FreqDist(word.lower() for word in word_tokenize(data) if word.isalpha())

10 наиболее часто встречающихся слов:

In [57]: fdist.most_common(10)
Out[57]:
[('message', 10),
 ('ctwtrace', 4),
 ('ctwunk', 3),
 ('dsm', 3),
 ('dsmentrydiagexit', 3),
 ('rc', 3),
 ('cc', 3),
 ('thunker', 2),
 ('scannerredirection', 2),
 ('to', 2)]

весь словарь целиком:

In [58]: dict(fdist)
Out[58]:
{'message': 10,
 'ctwunk': 3,
 'reset': 1,
 'log': 1,
 'starting': 1,
 'thunker': 2,
 'why': 1,
 'ca': 1,
 'we': 1,
 'find': 1,
 'the': 1,
 'window': 1,
 'dsm': 3,
 'dsmentrydiagexit': 3,
 'rc': 3,
 'cc': 3,
 'ctwtrace': 4,
 'scannerredirection': 2,
 'to': 2,
 'null': 2,
 'control': 2,
 'identity': 2,
 'getfirst': 1,
 'getnext': 1}