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

how to find duplicates in std::vector<string> and return a list of them?

本文关键字:gt string 返回 列表 lt vector std      更新时间:2023-10-16

所以如果我有一个单词向量,比如:

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

结果列表:"有趣","单词"

我试图确定哪些单词是重复的,并返回一个按字母顺序排列的矢量,其中有一个副本。我的问题是,我甚至不知道从哪里开始,我发现唯一接近它的是std::unique_copy,它并不能完全满足我的需求。具体地说,我输入std::vector<std::string>,但输出std::list<std::string>。如果需要的话,我可以使用函子。

至少有人能把我推向正确的方向吗?我已经试过阅读stl文档,但我现在只是"大脑"堵塞了。

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

vector<string> vec{"words", "words", "are", "fun", "fun"};
list<string> output;
sort(vec.begin(), vec.end());
set<string> uvec(vec.begin(), vec.end());
set_difference(vec.begin(), vec.end(),
               uvec.begin(), uvec.end(),
               back_inserter(output));

编辑

解决方案说明:

  1. 为了以后使用set_difference(),需要对矢量进行排序。

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

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

  1. 制作一个空std::unordered_set<std::string>
  2. 迭代向量,检查每个项是否都是集合的成员
  3. 如果它已经在集合中,则这是重复的,因此添加到您的结果列表中
  4. 否则,添加到集合中

由于您希望每个重复项在结果中只列出一次,因此您也可以对结果使用哈希集(而不是列表)。

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::sortstd::unique_copy来产生最终结果。在这种情况下,我可能还会将show_copies内部的std::set替换为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中的唯一项。

就位(无额外存储)。没有字符串复制(结果列表除外)。一次分拣+一次通过:

#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';
}

您可以使用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似乎有点浪费,但它完成了任务。

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

#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++中表现最佳,而且它不需要元素是可复制的。

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