如何返回包含不在集合中的元素的向量的副本?

Posted

技术标签:

【中文标题】如何返回包含不在集合中的元素的向量的副本?【英文标题】:How can I return a copy of a vector containing elements not in a set? 【发布时间】:2010-06-22 14:39:49 【问题描述】:

假设我有以下两种数据结构:

std::vector<int> all_items;
std::set<int> bad_items;

all_items 向量包含所有已知项目,bad_items 向量包含一个坏项目列表。这两种数据结构完全相互独立地填充。

编写一个返回std::vector&lt;int&gt; 的方法的正确方法是什么,该方法包含all_items 的所有元素,而不是bad_items

目前,我有一个笨拙的解决方案,我认为可以更简洁地完成。我缺乏对 STL 函数适配器的理解。因此问题。我目前的解决方案是:

struct is_item_bad 
    std::set<int> const* bad_items;
    bool operator() (int const i) const 
        return bad_items.count(i) > 0;
    
;

std::vector<int> items() const 
    is_item_bad iib =  &bad_items; ;
    std::vector<int> good_items(all_items.size());
    std::remove_copy_if(all_items.begin(),  all_items.end(), 
                        good_items.begin(), is_item_bad);
    return good_items; 

假设all_itemsbad_itemsis_item_baditems() 都是某些包含类的一部分。有没有办法写items() getter 这样:

方法中不需要临时变量? 不需要自定义函子struct is_item_bad?

我曾希望只使用std::set 上的count 方法作为函子,但我无法用remove_copy_if 算法预测正确的表达方式。

编辑: 修复了items() 中的逻辑错误。实际代码没有问题,是转录错误。

编辑:我接受了一个不使用std::set_difference 的解决方案,因为它更通用,即使std::vector 未排序也可以工作。我选择在我的代码中使用 C++0x lambda 表达式语法。我的最终items() 方法如下所示:

std::vector<int> items() const 
    std::vector<int> good_items;
    good_items.reserve(all_items.size());
    std::remove_copy_if(all_items.begin(), all_items.end(),
                        std::back_inserter(good_items),
                        [&bad_items] (int const i) 
                            return bad_items.count(i) == 1;
                        );

在大约 800 万个项目的向量上,上述方法在 3.1 秒内运行。我在工作台上标记了std::set_difference 方法,它运行了大约 2.1 秒。感谢所有提供出色答案的人。

【问题讨论】:

也许你可以使用std::set_difference? cplusplus.com/reference/algorithm/set_difference 如果您没有重复的对象,您可以为此使用两个集合和集合操作。但我猜你需要一个向量。 【参考方案1】:

正如 jeffamaphone 所建议的,如果您可以对任何输入向量进行排序,则可以使用std::set_difference,这样既高效又减少了代码:

#include <algorithm>
#include <set>
#include <vector>

std::vector<int> 
get_good_items( std::vector<int> const & all_items,
                std::set<int> const & bad_items )

    std::vector<int> good_items;

    // Assumes all_items is sorted.
    std::set_difference( all_items.begin(),
                         all_items.end(),
                         bad_items.begin(),
                         bad_items.end(),
                         std::back_inserter( good_items ) );

    return good_items;

【讨论】:

@dribeas:我很确定这就是他所说的意思:“......如果你可以对任何输入向量进行排序......” 向量是有序的。数据以有序的方式到达我这里,然后我将其插入到向量中。我没有为此使用集合,因为我不想为已经自然排序的数据添加额外的集合比较器开销。是不是搞错了? 不,Lrm,这很好,只要向量中的顺序与您使用集合而不是向量时它们的顺序相同。 【参考方案2】:

由于您的函数将返回一个向量,因此无论如何您都必须创建一个新向量(即复制元素)。在这种情况下,std::remove_copy_if 没问题,但你应该正确使用它:

#include <iostream>
#include <vector>
#include <set>
#include <iterator>
#include <algorithm>
#include <functional>
std::vector<int> filter(const std::vector<int>& all, const std::set<int>& bad)

        std::vector<int> result;
        remove_copy_if(all.begin(), all.end(), back_inserter(result),
                  [&bad](int i)return bad.count(i)==1;);
        return result;


int main()

        std::vector<int> all_items = 4,5,2,3,4,8,7,56,4,2,2,2,3;
        std::set<int> bad_items = 2,8,4;
        std::vector<int> filtered_items = filter(all_items, bad_items);
        copy(filtered_items.begin(), filtered_items.end(), std::ostream_iterator<int>(std::cout, " "));
        std::cout << std::endl;

要在 C++98 中执行此操作,我想您可以使用 mem_fun_refbind1st 将 set::count 转为内联函子,但这样做存在问题(导致 bind1st 被弃用在 C++0x 中)这意味着取决于您的编译器,您最终可能会使用 std::tr1::bind :

remove_copy_if(all.begin(), all.end(), back_inserter(result),
     bind(&std::set<int>::count, bad, std::tr1::placeholders::_1)); // or std::placeholders in C++0x

在任何情况下,我认为显式函数对象会更具可读性:

struct IsMemberOf 
        const std::set<int>& bad;
        IsMemberOf(const std::set<int>& b) : bad(b) 
        bool operator()(int i) const  return bad.count(i)==1;
;
std::vector<int> filter(const std::vector<int>& all, const std::set<int>& bad)

        std::vector<int> result;
        remove_copy_if(all.begin(), all.end(), back_inserter(result), IsMemberOf(bad));
        return result;

【讨论】:

+1... 很明显,因为这和我自己写的差不多,而且更完整! Cubbi,感谢您的详细回答。我已经修复了逻辑错误——它是一个转录错误,并且不存在于原始代码中。我认为您对可读性的看法是正确的。你能解释一下你的第一个解决方案吗?我不熟悉仿函数 [&bad](int i)... 的语法是创建某种 lambda 表达式吗? @lrm:是的,第一个示例使用 C++0x lambda 表达式。它构建了一个函数对象,通过引用捕获“坏”并公开bool operator()(int i) const——就像你最初写的一样,除了使用引用而不是指针。它使用 gcc 4.5+(我想是 MSVC10)编译。第二位使用 C++TR1 通用绑定器(在 c++0x 模式下 gcc 4.4+ 的 std 命名空间中可用,也可作为所有 C++98 编译器的 boost.bind 使用),第三位使用基本 C++98 .【参考方案3】:

冒着显得过时的风险:


std::set<int> badItems;
std::vector<int> items;
std::vector<int> goodItems;
for ( std::vector<int>::iterator iter = items.begin();
      iter != items.end();
      ++iter)

   int& item = *iter;
   if ( badItems.find(item) == badItems.end() )
   
      goodItems.push_back(item);
   

【讨论】:

感谢克雷格的回答。我正在尝试使用适当的 STL 算法,因为它是 Meyer 有效 STL 的第 43 项。 @lrm: "适当的 STL 算法" ... STL 算法/等太多了,普通人甚至无法记住其中的一半。我通常会写和克雷格一样的东西。这也是K.I.S.S。合规。然而,当输入向量被排序时,上面的代码是 O(log(n)),而它可以通过简单地并行迭代结构在 O(n) 中完成。【参考方案4】:

std::remove_copy_if 返回一个指向目标集合的迭代器。在这种情况下,它将返回good_items.end()(或类似的东西)。 good_items 在方法结束时超出范围,因此会导致一些内存错误。您应该返回good_items 或通过引用传入new vector&lt;int&gt;,然后clearresize 并填充它。这将摆脱临时变量。

我相信您必须定义自定义仿函数,因为该方法取决于对象 bad_items,如果没有它获得 hackey AFAIK,您将无法指定该对象。

【讨论】:

谢谢戴夫。这是我的输入错误。实际代码确实返回good_items。我已经在问题中解决了它。

以上是关于如何返回包含不在集合中的元素的向量的副本?的主要内容,如果未能解决你的问题,请参考以下文章

Python如何在一个集合里查找除了我提供的元素以外的元素?

JAVA判断list不在某个list并输出

数据结构 已知两个链表A和B分别表示两个集合,其元素递增排列。请设计算法求出两个集合A和B的差集(即仅由在A中出现而不在B中出现的元素所构成的集合),并以同样的形式存储,同时返回该集合的元素个数。(代

布隆过滤器

怎样判断哪个集合属于哪个集合

是否可以交换集合中的两个元素?