Элегантные способы подсчета частоты слов в файле - PullRequest
11 голосов
/ 03 февраля 2011

Каковы элегантные и эффективные способы подсчета частоты каждого "английского" слова в файле?

Ответы [ 8 ]

15 голосов
/ 03 февраля 2011

Прежде всего, я определяю letter_only std::locale, чтобы игнорировать знаки препинания, исходящие из потока, и читать только действительные "английские" буквы из входного потока.Таким образом, поток будет обрабатывать слова "ways", "ways." и "ways!" как одно и то же слово "ways", поскольку поток будет игнорировать знаки пунктуации, такие как "." и "!".

struct letter_only: std::ctype<char> 
{
    letter_only(): std::ctype<char>(get_table()) {}

    static std::ctype_base::mask const* get_table()
    {
        static std::vector<std::ctype_base::mask> 
            rc(std::ctype<char>::table_size,std::ctype_base::space);

        std::fill(&rc['A'], &rc['z'+1], std::ctype_base::alpha);
        return &rc[0];
    }
};

Раствор 1

int main()
{
     std::map<std::string, int> wordCount;
     ifstream input;
     input.imbue(std::locale(std::locale(), new letter_only())); //enable reading only letters!
     input.open("filename.txt");
     std::string word;
     while(input >> word)
     {
         ++wordCount[word];
     }
     for (std::map<std::string, int>::iterator it = wordCount.begin(); it != wordCount.end(); ++it)
     {
           cout << it->first <<" : "<< it->second << endl;
     }
}

Раствор 2

struct Counter
{
    std::map<std::string, int> wordCount;
    void operator()(const std::string & item) { ++wordCount[item]; }
    operator std::map<std::string, int>() { return wordCount; }
};

int main()
{
     ifstream input;
     input.imbue(std::locale(std::locale(), new letter_only())); //enable reading only letters!
     input.open("filename.txt");
     istream_iterator<string> start(input);
     istream_iterator<string> end;
     std::map<std::string, int> wordCount = std::for_each(start, end, Counter());
     for (std::map<std::string, int>::iterator it = wordCount.begin(); it != wordCount.end(); ++it)
     {
          cout << it->first <<" : "<< it->second << endl;
     }
 }
2 голосов
/ 03 февраля 2011

Мое решение следующее.Во-первых, все символы преобразуются в пробелы.Затем для извлечения слов используется в основном то же решение, что и здесь ранее:

const std::string Symbols = ",;.:-()\t!¡¿?\"[]{}&<>+-*/=#'";
typedef std::map<std::string, unsigned int> WCCollection;
void countWords(const std::string fileName, WCCollection &wcc)
    {
        std::ifstream input( fileName.c_str() );

        if ( input.is_open() ) {
            std::string line;
            std::string word;

            while( std::getline( input, line ) ) {
                // Substitute punctuation symbols with spaces
                for(std::string::const_iterator it = line.begin(); it != line.end(); ++it) {
                    if ( Symbols.find( *it ) != std::string::npos ) {
                        *it = ' ';
                    }

                }

                // Let std::operator>> separate by spaces
                std::istringstream filter( line );
                while( filter >> word ) {
                    ++( wcc[word] );
                }
            }
        }

    }
2 голосов
/ 03 февраля 2011

Вот рабочее решение. Это должно работать с реальным текстом (включая пунктуацию):

#include <iterator>
#include <iostream>
#include <fstream>
#include <map>
#include <string>
#include <cctype>

std::string getNextToken(std::istream &in)
{
    char c;
    std::string ans="";
    c=in.get();
    while(!std::isalpha(c) && !in.eof())//cleaning non letter charachters
    {
        c=in.get();
    }
    while(std::isalpha(c))
    {
        ans.push_back(std::tolower(c));
        c=in.get();
    }
    return ans;
}

int main()
{
    std::map<std::string,int> words;
    std::ifstream fin("input.txt");

    std::string s;
    std::string empty ="";
    while((s=getNextToken(fin))!=empty )
            ++words[s];

    for(std::map<std::string,int>::iterator iter = words.begin(); iter!=words.end(); ++iter)
        std::cout<<iter->first<<' '<<iter->second<<std::endl;
}

Редактировать: Теперь мой код вызывает tolower для каждой буквы.

1 голос
/ 14 октября 2015

Perl, возможно, не такой элегантный, но очень эффективный.
Я разместил решение здесь: Обработка огромных текстовых файлов

В двух словах

1) При необходимости удалите знаки препинания и преобразуйте прописные буквы в строчные:
perl -pe "s/[^a-zA-Z \t\n']/ /g; tr/A-Z/a-z/" file_raw > file

2) Подсчитайте вхождение каждого слова. Результаты печати сортируются сначала по частоте, а затем по алфавиту:
perl -lane '$h{$_}++ for @F; END{for $w (sort {$h{$b}<=>$h{$a} || $a cmp $b} keys %h) {print "$h{$w}\t$w"}}' file > freq

Я запустил этот код в текстовом файле объемом 3,3 ГБ с 580 000 000 слов.
Perl 5.22 завершен менее чем за 3 минуты.

1 голос
/ 28 августа 2013
  1. Определитесь, что именно вы подразумеваете под "английским словом". Определение должно охватывать такие вещи, как «трудоспособное» - одно слово или два, как справляться с апострофами («Не верь им!»), Является ли капитализация значимой и т. Д.

  2. Создайте набор контрольных примеров, чтобы вы могли быть уверены, что все решения на шаге 1 верны.

  3. Создайте токенизатор, который читает следующее слово (как определено в шаге 1) из ввода и возвращает его в стандартной форме. В зависимости от вашего определения это может быть простой конечный автомат, регулярное выражение или просто использование операторов извлечения (например, std::cin >> word;). Протестируйте свой токенизатор со всеми тестовыми примерами из шага 2.

  4. Выберите структуру данных для хранения слов и количества. В современном C ++ вы, вероятно, получите что-то вроде std::map<std::string, unsigned> или std::unordered_map<std::string, int>.

  5. Напишите цикл, который получает следующее слово от токенизатора и увеличивает его количество в гистограмме до тех пор, пока на входе не останется больше слов.

1 голос
/ 03 февраля 2011

Псевдокод для алгоритма, который, я считаю, близок к тому, что вы хотите:

counts = defaultdict(int)
for line in file:
  for word in line.split():
    if any(x.isalpha() for x in word):
      counts[word.toupper()] += 1

freq = sorted(((count, word) for word, count in counts.items()), reversed=True)
for count, word in freq:
  print "%d\t%s" % (count, word)

Сравнение без учета регистра выполняется наивно и, вероятно, объединяет слова, которые вы не хотите объединять, в абсолютно общем смысле. Будьте осторожны с не-ASCII символами в вашей реализации выше. Ложные срабатывания могут включать «1-800-555-TELL», «0xDEADBEEF» и «42 км», в зависимости от того, что вы хотите. Пропущенные слова включают «911 экстренных служб» (я бы, наверное, хотел, чтобы это считалось тремя словами).

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

0 голосов
/ 05 января 2018
string mostCommon( string filename ) {

    ifstream input( filename );
    string line;
    string mostFreqUsedWord;
    string token;
    map< string, int > wordFreq;

    if ( input.is_open() ) {

        while ( true ) {
            input >> token;
            if( input ) {
                wordFreq[ token ]++;
                if ( wordFreq[ token] > wordFreq[ mostFreqUsedWord ] )
                    mostFreqUsedWord = token;
            } else
                break;
        }
        input.close();
    } else {
        cout << "Unable to ope file." << endl;
    }
    return mostFreqUsedWord;
}
0 голосов
/ 03 февраля 2011

Еще один простой способ - подсчитать количество пробелов в файле, пока не будет найдено более одного пробела, если учесть только один пробел между словами ...

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...