如何在std :: vector <string>中查找重复项并返回它们的列表?</string>

时间:2013-07-27 00:11:06

标签: c++ stl functor

所以,如果我有一个像以下词的向量:

Vec1 = "words", "words", "are", "fun", "fun"

结果列表:“fun”,“words”

我正在尝试确定哪些单词是重复的,并返回其中1个副本的按字母顺序排列的向量。我的问题是我甚至不知道从哪里开始,我发现唯一接近它的是std::unique_copy并不完全符合我的需要。具体来说,我输入std::vector<std::string>但输出std::list<std::string>。如果需要,我可以使用仿函数。

有人至少可以把我推向正确的方向吗?我已经尝试过阅读stl文档,但我现在只是“大脑”被阻止了。

6 个答案:

答案 0 :(得分:6)

  1. 制作一个空的std::unordered_set<std::string>
  2. 迭代你的向量,检查每个项目是否是集合的成员
  3. 如果它已经在集合中,则这是重复的,因此添加到结果列表
  4. 否则,请添加到设置。
  5. 由于您希望每个副本仅在结果中列出一次,因此您也可以对结果使用哈希集(非列表)。

答案 1 :(得分:5)

IMO,Ben Voigt以一个很好的基本想法开始,但我会提醒他不要过于字面地采用他的措辞。

特别是,我不喜欢在集合中搜索字符串的想法,如果它不存在则将其添加到您的集合中,如果它存在则将其添加到输出中。这基本上意味着每当我们遇到一个新单词时,我们会搜索两次现有单词,一次检查一个单词是否存在,然后再插入它,因为它不是。大多数搜索基本上是相同的 - 除非其他一些线程在临时中改变了结构(这可能会产生竞争条件)。

相反,我首先尝试将其添加到您看过的单词集中。这将返回pair<iterator, bool>bool设置为true当且仅当插入了值时 - 即先前不存在。这使我们可以整合搜索现有字符串并将新字符串一起插入到单个插入中:

while (input >> word)
    if (!(existing.insert(word)).second)
        output.insert(word);

这也足以清理流量,因此很容易将测试转换为仿函数,然后我们可以使用std::remove_copy_if直接生成结果:

#include <set>
#include <iterator>
#include <algorithm>
#include <string>
#include <vector>
#include <iostream>

class show_copies {
    std::set<std::string> existing;
public:
    bool operator()(std::string const &in) {
        return existing.insert(in).second;
    }
};

int main() {
    std::vector<std::string> words{ "words", "words", "are", "fun", "fun" };
    std::set<std::string> result;

    std::remove_copy_if(words.begin(), words.end(),
        std::inserter(result, result.end()), show_copies());

    for (auto const &s : result)
        std::cout << s << "\n";
}

根据我是否更关心代码简单性或执行速度,我可能会使用std::vector而不是set作为结果,并使用std::sort后跟std::unique_copy产生最终结果。在这种情况下,我可能还会用std::set代替show_copies内的std::unordered_set

#include <unordered_set>
#include <iterator>
#include <algorithm>
#include <string>
#include <vector>
#include <iostream>

class show_copies {
    std::unordered_set<std::string> existing;
public:
    bool operator()(std::string const &in) {
        return existing.insert(in).second;
    }
};

int main() {
    std::vector<std::string> words{ "words", "words", "are", "fun", "fun" };
    std::vector<std::string> intermediate;

    std::remove_copy_if(words.begin(), words.end(),
        std::back_inserter(intermediate), show_copies());

    std::sort(intermediate.begin(), intermediate.end());
    std::unique_copy(intermediate.begin(), intermediate.end(),
        std::ostream_iterator<std::string>(std::cout, "\n"));
}

这稍微复杂一点(整行更长一些!)但是当/如果单词的数量变得非常大时,可能会大大加快。另请注意,我主要使用std::unique_copy来生成可见输出。如果您只想在集合中使用结果,则可以使用标准的唯一/擦除惯用法来获取intermediate中的唯一项。

答案 2 :(得分:5)

在3行中(不计算向量和列表创建,也不计算可读性名称中多余的换行符):

vector<string> vec{"words", "words", "are", "fun", "fun"};

sort(vec.begin(), vec.end());

set<string> uvec(vec.begin(), vec.end());

list<string> output;

set_difference(vec.begin(), vec.end(),
               uvec.begin(), uvec.end(),
               back_inserter(output));

修改

解决方案的说明:

  1. 需要对矢量进行排序才能在以后使用set_difference()

  2. uvec集会自动对元素进行排序,并消除重复项。

  3. output列表将由vec - uvec的元素填充。

答案 3 :(得分:1)

到位(无额外存储空间)。没有字符串复制(结果列表除外)。一种+一次传递:

#include <string>
#include <vector>
#include <list>
#include <iostream>
#include <algorithm>
using namespace std;

int main() {
        vector<string> vec{"words", "words", "are", "fun", "fun"};
        list<string> dup;

        sort(vec.begin(), vec.end());

        const string  empty{""};
        const string* prev_p = &empty;

        for(const string& s: vec) {
                if (*prev_p==s) dup.push_back(s);
                prev_p = &s;
        }

        for(auto& w: dup) cout << w << ' '; 
        cout << '\n';
}

答案 4 :(得分:0)

你可以使用std :: map计算出现次数,然后依靠std :: list :: sort来对结果列表进行排序。例如:

std::list<std::string> duplicateWordList(const std::vector<std::string>& words) {
    std::map<std::string, int> temp;
    std::list<std::string> ret;
    for (std::vector<std::string>::const_iterator iter = words.begin(); iter != words.end(); ++iter) {
        temp[*iter] += 1;
        // only add the word to our return list on the second copy
        // (first copy doesn't count, third and later copies have already been handled)
        if (temp[*iter] == 2) {
            ret.push_back(*iter);
        }
    }
    ret.sort();
    return ret;
}

使用std :: map似乎有点浪费,但它完成了工作。

答案 5 :(得分:0)

这是一个比其他人提出的更好的算法:

#include <algorithm>
#include <vector>

template<class It> It unique2(It const begin, It const end)
{
    It i = begin;
    if (i != end)
    {
        It j = i;
        for (++j; j != end; ++j)
        {
            if (*i != *j)
            { using std::swap; swap(*++i, *j); }
        }
        ++i;
    }
    return i;
}
int main()
{
    std::vector<std::string> v;
    v.push_back("words");
    v.push_back("words");
    v.push_back("are");
    v.push_back("fun");
    v.push_back("words");
    v.push_back("fun");
    v.push_back("fun");
    std::sort(v.begin(), v.end());
    v.erase(v.begin(), unique2(v.begin(), v.end()));
    std::sort(v.begin(), v.end());
    v.erase(unique2(v.begin(), v.end()), v.end());
}

它更好,因为它只需要swap而不需要辅助vector来存储,这意味着它对于早期版本的C ++会表现得最佳,并且它不需要元素可以复制。

如果你更聪明,我认为你可以避免两次对矢量进行排序。