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

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

首先,我定义了letter_only std::locale ,以便忽略来自stream的标点符号,并从inputstream中只读取有效的“英文”字母。 这样,这个stream将会把"ways""ways."这些单词对待"ways.""ways!" 就像同一个词"ways" ,因为这个stream将忽略"."这样的标点符号".""!"

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

解决scheme1

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

解决scheme2

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

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

 #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每个字母。

我的解决scheme是以下一个。 首先,所有的符号都被转换成空格。 然后,基本上使用前面提供的相同的解决scheme来提取单词:

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

一个algorithm的伪代码,我相信是接近你想要的:

 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应急服务”(我可能要这个数字作为三个单词)。

简而言之,自然语言parsing是很难的:根据您的实际使用情况,您可能会做出一些近似解释。

Perl可以说没有那么优雅,但非常有效。
我在这里发布了一个解决scheme: 处理大文本文件

简而言之,

1)如果需要,去掉标点符号并将大写字母转换为小写字母:
perl -pe "s/[^a-zA-Z \t\n']/ /g; tr/AZ/az/" file_raw > file

2)计算每个单词的出现次数。 打印结果按频率sorting,然后按字母顺序sorting:
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

我用一个有5.8亿字的3.3GB文本文件运行这个代码。
Perl 5.22在3分钟内完成。

一个更简单的方法是计算文件中的空格数,直到find多个空格为止,如果只考虑单词之间的单个空格…

  1. 确切地说,你的意思是“一个英文单词”。 定义应该包括“健全”是一个字还是两个字,如何处理撇号(“不要相信他们!”),是否大写等等。

  2. 创build一组testing用例,以确保您能够正确地完成步骤1中的所有决定。

  3. 创build一个标记器,从input中读取下一个单词(如步骤1中定义的),并以标准forms返回。 根据您的定义,这可能是一个简单的状态机,一个正则expression式,或者只依赖<istream>的提取操作符(例如std::cin >> word; )。 使用步骤2中的所有testing用例testing您的标记器。

  4. select一个数据结构来保留单词和计数。 在现代C ++中,你最终可能会得到像std::map<std::string, unsigned>std::unordered_map<std::string, int>

  5. 编写一个循环,从分词器中获取下一个单词,并在直方图中增加其计数,直到input中不再有单词为止。