有哪些优雅有效的方法可以在文件中统计每个“英语”单词的频率?
首先,我定义了 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];
}
};
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;
}
}
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;
}
}
"end_of_sentence."
和"end_of_sentence!"
视为__两个不同的单词__,这__不是__OP想要的。 - sbiwhile (input >> word)
?虽然其他标志位没有被检查,但原文写法仍然是错误的。 - James McNellisperl -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
我在一个有580,000,000个单词的3.3GB文本文件上运行了这段代码。
Perl 5.22在不到3分钟内完成了操作。#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函数。
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] );
}
}
}
}
以下是我认为接近您所需的算法的伪代码:
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 emergency services”(我可能希望将其视为三个单词)。
简而言之,自然语言解析很难:根据您的实际用例,您可能可以使用一些近似值。
确定“英语单词”的确切含义。定义应涵盖以下内容,如“able-bodied”是一个单词还是两个单词,如何处理撇号(“Don't trust 'em!”),大写是否重要等。
创建一组测试用例,以确保正确执行步骤1中的所有决策。
创建一个标记生成器,从输入中读取下一个单词(由步骤1定义),并以标准形式返回它。根据您的定义,这可能是一个简单的状态机、正则表达式或仅依赖于<istream>的提取运算符(例如,std::cin >> word;
)。使用步骤2中的所有测试用例测试您的标记生成器。
选择一种数据结构来保存单词和计数。在现代C++中,您可能会得到像std::map<std::string, unsigned>
或std::unordered_map<std::string, int>
这样的东西。
编写循环,从标记生成器获取下一个单词,并递增其直方图中的计数,直到输入中没有更多单词为止。
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;
}
另一种简单的方法是计算文件中空格的数量,直到发现超过一个空格,如果您只考虑单词之间的单个空格...
can't
和The cat's toy.
。 - Thomas Matthews