我还在处理这篇文章中提到的问题: Sorting vector of strings with leading numbers
原始问题如下:
编写一个完整的C ++程序,输出文件input.txt中最常用的k个单词,每行按频率降序排列一个,其中k是从输入读取的非负整数。关系是任意打破的,如果input.txt中只有u个不同的单词,则u< k,那么输出只有u个条目。 对于此问题,除了vector和string之外,您不能使用任何STL类或算法。单词是非空白字符的最大块,其中删除了标点符号。每个输出行由一个单词后跟其频率计数组成。 (给出输入和k值)
感谢那些建议使用结构的人,我最终得到了一个更有效的解决方案,而且代码更少。
然而,问题是,对于相对较大的文本文件(由> 400000字组成),我的程序可以继续运行超过5分钟并且不会产生任何结果。该程序在小文件输入上运行完美。我不确定是不是因为文件太大,或者算法本身存在导致内存溢出/损坏的问题。
这是我的程序代码:
struct word_freq {
int freq;
string word;
};
bool operator<(const word_freq& a, const word_freq& b) {
return a.freq < b.freq;
}
void word_frequencies(ifstream& inf, int k)
{
vector <string> input;
string w;
while (inf >> w)
{
remove_punc(w);
input.push_back(w);
}
sort(input.begin(), input.end());
// initialize frequency vector
vector <int> freq;
for (size_t i = 0; i < input.size(); ++i) freq.push_back(1);
// count actual frequencies
int count = 0;
for (size_t i = 0; i < input.size()-1; ++i)
{
if (input[i] == input[i+1])
{
++count;
} else
{
freq[i] += count;
count = 0;
}
}
// words+frequencies
vector <word_freq> wf;
for (int i = 0; i < freq.size(); ++i)
{
if (freq[i] > 1 || is_unique(input, input[i]))
{
word_freq st = {freq[i], input[i]};
wf.push_back(st);
}
}
// printing
sort(wf.begin(), wf.end());
if (wf.size() < k)
{
for (int i = wf.size()-1; i >= 0; --i)
{
cout << wf[i].word << " " << wf[i].freq << endl;
}
} else
{
for (int i = wf.size()-1; i >= wf.size()-1-k; --i)
{
cout << wf[i].word << " " << wf[i].freq << endl;
}
}
}
如果有人能指出所犯的错误,我们将不胜感激。
答案 0 :(得分:1)
如果在分配载体后使用reserve(int)
,
表现会好得多。
推回向量不断导致内存碎片化。
原因是向量不断超出其分配的边界,并经常重新分配。重新分配小对象通常很昂贵,并且会对性能产生直接影响。
最初使用足够大的内存块调用reserve,并在向量的大小与其容量匹配时再次调用它,有助于避免此问题。
更多信息:
在这里:
Should I worry about memory fragmentation with std::vector?
带有性能测量的小型演示:
#include <chrono>
#include <vector>
#include <iostream>
int main()
{
std::vector<std::string> slow;
std::string d = "divide and conquer";
std::chrono::time_point<std::chrono::system_clock> start, end;
start = std::chrono::system_clock::now();
// I get reallocated all the time
for ( int i=0; i < 100000; i++ )
{
slow.push_back(d);
}
end = std::chrono::system_clock::now();
std::chrono::duration<double> elapsed_seconds = end-start;
std::time_t end_time = std::chrono::system_clock::to_time_t(end);
std::cout << "elapsed time v1: " << elapsed_seconds.count() << "s\n";
start = std::chrono::system_clock::now();
//I don't move around
slow.reserve(100000);
slow.clear();
for ( int i=0; i < 100000; i++ )
{
slow.push_back(d);
}
end = std::chrono::system_clock::now();
elapsed_seconds = end-start;
end_time = std::chrono::system_clock::to_time_t(end);
std::cout << "elapsed time v2: " << elapsed_seconds.count() << "s\n";
return 0;
}
输出:
elapsed time v1: 0.014085s
elapsed time v2: 0.004597s
答案 1 :(得分:1)
你使程序按内存和计算方式匹配。首先,您将所有单词读入内存并对其进行排序。然后你计算频率并填充另一个向量。首先应该有std::vector<word_freq>
,保持按字排序(通过将元素插入到适当的位置)并插入新元素或增加现有元素。然后按频率和打印方式使用此向量。
例如,如何重写循环:
struct word_freq {
int freq;
std::string word;
word_freq( const std::string &w ) : word( w ), freq( 0 ) {}
};
void addWord( std::vector<word_freq> &v, const std::string &word )
{
word_freq tmp( word );
auto p = std::equal_range( v.begin(), v.end(), tmp,
[]( const word_freq &w1, const word_freq &w2 ) {
return w1.word < w2.word;
} );
if( p.first == p.second ) // not found
p.first = v.insert( p.second, tmp ); // insert into proper place
p.first->freq++; // increase freq counter
}
// ......
std::vector<word_freq> words;
string w;
while (inf >> w)
{
remove_punc(w);
addWord( words, w );
}
// here your vector sorted by words, there are no dups and counters have proper value already
// just resort it by freq and print
有关如何在此处找到保持向量排序的详细信息how do you insert the value in a sorted vector?
另一方面,保持std::vector<word_freq>
排序将需要过多匹配插入到向量的中间或开头,这可能非常昂贵且缓慢。因此,如果您实现所描述的逻辑并使其适用于小示例,并且对于您的大输入仍然太慢 - 您应该排序索引的向量而不是word_freq
本身的向量。这仍然需要插入整数向量的开始或中间,但这种操作明显更便宜和更快。有关如何在此处找到排序索引而不是向量本身的详细信息:compare function of sort in c++ for index sort