计算文件中单词频率的优雅方法

时间:2011-02-03 16:40:03

标签: c++ file-io

计算每个"英语"的频率的优雅和有效方法是什么?文件中的单词?

8 个答案:

答案 0 :(得分:15)

首先,我定义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;
     }
 }

答案 1 :(得分:2)

这是工作解决方案。这应该与真实文本(包括标点符号)一起使用:

#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。

答案 2 :(得分:2)

我的解决方案如下。首先,所有符号都转换为空格。然后,基本上使用此前提供的相同解决方案来提取单词:

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] );
                }
            }
        }

    }

答案 3 :(得分:1)

算法的伪代码,我相信它接近你想要的算法:

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 km”,具体取决于您的需求。错过的单词包括“911紧急服务”(我可能希望将其视为三个单词)。

简而言之,自然语言解析很难:根据您的实际使用情况,您可能会根据某些近似值来制作。

答案 4 :(得分:1)

  1. 确定“英语单词”的确切含义。定义应涵盖诸如“健全”是一个字还是两个字,如何处理撇号(“不要相信它们!”),大写是否重要等等。

  2. 创建一组测试用例,这样您就可以确保在步骤1中做出正确的所有决定。

  3. 创建一个tokenizer,从输入中读取下一个单词(如步骤1中所定义)并以标准形式返回。根据您的定义,这可能是一个简单的状态机,一个正则表达式,或者只是依赖于&lt; istream&gt;的提取运算符(例如std::cin >> word;)。使用步骤2中的所有测试用例测试令牌化程序。

  4. 选择用于保留单词和计数的数据结构。在现代C ++中,您最终可能会遇到类似std::map<std::string, unsigned>std::unordered_map<std::string, int>的内容。

  5. 编写一个循环,从标记生成器获取下一个单词,并在直方图中增加其计数,直到输入中没有单词为止。

答案 5 :(得分:1)

Perl可以说不是那么优雅,但非常有效 我在这里发布了一个解决方案:Processing huge text files

简而言之,

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.3GB文本文件上运行此代码,文本为580,000,000个字 Perl 5.22在3分钟内完成。

答案 6 :(得分:0)

另一种更简单的方法是计算文件中的空格数,直到找到多于一个空格,如果你只考虑单词之间的单个空格......

答案 7 :(得分:0)

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;
}