更好的计算方法?

时间:2009-12-09 17:48:05

标签: c++ stl

在我的一个学校课程中,我使用以下函数来计算字符串中标识符的频率,用换行符和#分隔:

输入:

dog
cat
mouse
#
rabbit
snake
#

功能:

//assume I have the proper includes, and am using namespace std
vector< pair<string,int> > getFreqcounts(string input) {
    vector<string> items = splitString(input,"\n");
    map<string,int> counts;

    for (int i=0; i<items.size(); i++) {
        if (items[i] == "#") continue;
        counts[items[i]] = 0;
    }
    for (int i=0; i<items.size(); i++) {
        if (items[i] == "#") continue;
        counts[items[i]]++;
    }

    return vector< pair<string,int> > (counts.begin(),counts.end());
}

我想至少

  • 删除双循环
  • 找到获得vector< pair<string,int> >
  • 的更好方法

有什么想法吗?

顺便说一句,这不是功课。真正的家庭作业将使用这个功能,但这完全出于我自己的好奇心和渴望拥有“更好”的代码。

4 个答案:

答案 0 :(得分:5)

我对std::map的理解是可以简单地消除整个第一个循环。当您第一次尝试访问不存在的节点时,map将默认为您创建它,将初始计数设置为零(内置类型的默认行为。)这应该是全部您需要对代码进行更改,并且行为应该相同。

更新:在您提供的counts代码的旁注中,将根据为operator<定义的std::string进行排序(您的密钥类型) map),它将按字典顺序对map个节点进行排序。没有必要通过矢量抽取结果并对矢量进行排序 - 地图会自动为您处理。

答案 1 :(得分:2)

只需删除它就可以摆脱第一个for循环。它没有任何用处。当/如果下标进入地图创建一个新项目时,该项目将具有所选择的密钥,并且您关联的int将自动初始化为零。

就个人而言,我可能会做一些不同的事情,使用字符串流而不是SplitString()。我对发布代码犹豫不决,但我想我会相信你......

typedef vector<pair<string, int> > count_vec;

count_vec GetFreqCounts(string const &input) { 
    istringstream in(input);
    string line;
    map<string, int> counts;

    while (getline(in, line))
        if (line != "#")
            ++counts[line];
    return count_vec(counts.begin(), counts.end());
}

编辑:老实说,我写这篇文章的时候并没有全神贯注于效率,但我认为Steve Jessop的评论非常准确。只要输入很小,它就不会产生任何真正的差别。如果输入非常大,那么一次只使用一个单词的额外副本这一事实可以节省足够的内存以使其有意义。

史蒂夫在回答中给出的解决方案看起来也很不错。由于它还会在生成单词时对其进行处理,因此我希望它与上面的代码具有类似的特征。如果你可以将字符串分解为比stringstream更快的单词,那么它无疑会更快。考虑到使用iostream阻碍虚拟函数的数量,这很有可能 - 但除非你处理大量文本,否则它没有太大机会产生显着差异。当然,究竟什么是重要的是值得商榷的问题。为了正确看待,我在一个方便的单词列表中运行了一些类似的代码。使用非常接近上述代码的代码,它每秒处理文本的速度超过10兆字节。

答案 2 :(得分:1)

在对Jerry的回答的评论中,我提到了使用仿函数。这是我的意思(未经测试的代码):

struct StringCounter {
    std::map<std::string, int> counts;
    void operator()(const std::string &s) {
        ++counts[s];
    }
};

template <typename Output>
void splitString(const string &input, const string &separator, Output &out) {
    // do whatever you currently do to get each string, call it "s"...
    out(s);
    // lather, rinse, repeat
}

vector< pair<string,int> > getFreqcounts(const string &input) {
    StringCounter sc;
    splitString(input,"\n",sc);
    return vector< pair<string,int> > (sc.counts.begin(),sc.counts.end());
}

答案 3 :(得分:0)

如果你想保留当前逻辑但是在一个循环中包含所有逻辑,你可以使用find(),如下所示:

map<string,int> counts;
loop on curr ..
map<string,int>::iterator it = counts.find(curr);
if (it != counts.end())
  ++it->second;
else
  counts[curr] = 1;

但那不是我会做的。没有必要在第一次显式初始化地图条目,因为它无论如何都会默认为零,因此您可以使用:

map<string,int> counts;
loop on curr ..
  if (..
    ++map[curr];

我要做的另一件事是使用std::getline(),将'\ n'和'#'作为分隔符。