计算每个"英语"的频率的优雅和有效方法是什么?文件中的单词?
答案 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];
}
};
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;
}
}
答案 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中做出正确的所有决定。
创建一个tokenizer,从输入中读取下一个单词(如步骤1中所定义)并以标准形式返回。根据您的定义,这可能是一个简单的状态机,一个正则表达式,或者只是依赖于&lt; istream&gt;的提取运算符(例如std::cin >> word;
)。使用步骤2中的所有测试用例测试令牌化程序。
选择用于保留单词和计数的数据结构。在现代C ++中,您最终可能会遇到类似std::map<std::string, unsigned>
或std::unordered_map<std::string, int>
的内容。
编写一个循环,从标记生成器获取下一个单词,并在直方图中增加其计数,直到输入中没有单词为止。
答案 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;
}