字频程序 - 文件输入太大?

时间:2017-05-11 18:30:46

标签: c++ file-io text-files fstream word-frequency

我还在处理这篇文章中提到的问题: 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;
    }
}
}

如果有人能指出所犯的错误,我们将不胜感激。

2 个答案:

答案 0 :(得分:1)

如果在分配载体后使用reserve(int), 表现会好得多。

推回向量不断导致内存碎片化。

原因是向量不断超出其分配的边界,并经常重新分配。重新分配小对象通常很昂贵,并且会对性能产生直接影响。

最初使用足够大的内存块调用reserve,并在向量的大小与其容量匹配时再次调用它,有助于避免此问题。

更多信息:

What is memory fragmentation?

在这里:

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