【问题标题】:elegant way to remove all elements of a vector that are contained in another vector?删除包含在另一个向量中的向量的所有元素的优雅方法?
【发布时间】:2014-02-07 08:00:15
【问题描述】:

在查看一些代码时,我发现 std::set_difference 的实现循环和算法缓慢 :

 for(int i = 0; i < a.size(); i++)
 {
  iter = std::find(b.begin(),b.end(),a[i]);
  if(iter != b.end())
  {
     b.erase(iter);
  }
 }

它可以很容易地替换为排序(向量未排序)+ set_difference,但这需要分配新内存(请参阅我最近的 Q Can output of set difference be stored in first input? 为什么不能“就地”完成)。
所以我的解决方案是这样的:

sort(a.begin(), a.end());
for(size_t i = 0; i < b.size(); i++)
{
 if (binary_search(a.begin(), a.end(), b[i]))
 {
     swap(b[i], b[b.size()-1]); //remove current element by swapping with last
     b.pop_back();     // and removing new last by shrinking
 }
}

可以做得更优雅吗?
优雅是主观的,所以在这个 Q 的范围内被定义为更清晰的代码(理想情况下来自 STL 算法,但我认为它不能完成)但没有内存分配,也没有增加算法复杂度。

【问题讨论】:

  • 定义“优雅”。代码少?执行时间更短?内存少?
  • “看看我最近的 Q 21191631”——在哪里?
  • @JohnDibling 在最后一段中定义
  • @NoSenseEtAl:谢谢,错过了第一次通读。
  • @NoSenseEtAl 在第二个示例中,bset_difference 的输入之一吗?如果是这样,您正在修改已传递给您的范围,调用者可能不会对此感到兴奋。当然,您可以制作本地副本,但现在该解决方案的吸引力开始下降。

标签: c++ stl


【解决方案1】:

假设两个数组都已排序,这一操作在 O(N+M) 中完成。

  auto ib = std::begin(two);
  auto iter = std::remove_if (
       std::begin(one), std::end(one),
       [&ib](int x) -> bool {
                       while  (ib != std::end(two) && *ib < x) ++ib;
                       return (ib != std::end(two) && *ib == x);
                     });

【讨论】:

  • 我觉得这需要C++1y的增强返回类型推演。
  • @dyp 为什么?看不到它(它可以在 gcc 和 clang 中使用 -std=c++11)。
  • IIRC 都在其 C++11 模式中实现了 C++1y 的返回类型推导作为扩展。然而,[expr.prim.lambda]/4 声明你的 lambda 应该有返回类型void
  • 我必须说我通常讨厌在 lambda 中混合循环的代码,这是 stl 算法 + 引用捕获的功能,但您的代码非常漂亮。 :) 在我的情况下,数组没有排序,但是更改是微不足道的,只需对它们都进行排序......再次很好的代码。 :)
  • 我认为它不会编译,需要添加[&ib, &two]
【解决方案2】:

b 进行排序,以便您可以对它进行二进制搜索以降低时间复杂度。然后使用erase-remove 习惯用法来丢弃a 中包含在b 中的所有元素:

sort( begin(b), end(b) );
a.erase( remove_if( begin(a),end(a),
    [&](auto x){return binary_search(begin(b),end(b),x);}), end(a) );

当然,您仍然可以通过删除sort() 并将binary_search() 替换为find() 来简化代码并减少代码:

a.erase( remove_if( begin(a),end(a),
    [&](auto x){return find(begin(b),end(b),x)!=end(b);}), end(a) );

这是一个品味问题。在这两种情况下,您都不需要堆分配。顺便说一句,我使用的是 C++14 的 lambda 自动参数。一些编译器已经实现了该功能,例如 clang。如果您没有这样的编译器,而只有 C++11,则将 auto 替换为容器的元素类型。

顺便说一句,这段代码没有提到任何类型!您可以编写一个模板函数,使其适用于所有类型的类型。第一个变体需要b 的随机访问迭代,而第二个代码则不需要。

【讨论】:

  • 我更喜欢 &b 在那个捕获列表中,但这是我个人的偏好。 +1
  • @NoSenseEtAl 对于维护,最好只写[&amp;][=],因为如果其他代码发生更改,您不必更改该部分。如果有特殊原因不允许使用其他变量并因此会导致有用的编译时错误,那么显式捕获就很有意义。您指定默认捕获并添加一些要以不同方式捕获的变量的情况也是一个重要的用例。
【解决方案3】:

想到的一个解决方案是结合remove_ifbinary_search。它实际上与您的手动循环解决方案相同,但可能会更“优雅”一些,因为它使用了更多 STL 功能。

sort(begin(b), end(b));
auto iter = remove_if(begin(a), end(a), 
                      [](auto x) { 
                          return binary_search(begin(b), end(b), x); 
                      });
// Now [begin(a), iter) defines a new range, and you can erase them however
// you see fit, based on the type of a.

【讨论】:

  • 再次擦除删除罢工...从可读性的角度来看,我讨厌它,但仍然是一个不错的 A
  • 我同意,但考虑到不同的容器可能有不同的删除元素的专业化,这是一个公平的权衡,imo。
  • remove_ifbinary_search 在不同的范围内运行。
  • @Praetorian 如果我理解正确,则删除是否触摸 a,而不是 b
  • 你理解正确。 binary_search 只看元素:它是一个非修改算法,remove_if 根本不接触 b。
【解决方案4】:

当前的代码非常清晰,任何程序员都应该清楚发生了什么。

当前性能为 O(a.size() * b.size()),根据实际大小,可能会很差。

描述它的更简洁和类似 STL 的方式是使用 remove_if 和一个谓词,告诉您一个值是否在 a 中。

b.erase(std::remove_if(b.begin(), b.end(), [](const auto&x) {
  return std::find(a.begin(), a.end(), x) != a.end();
}), b.end());

(未测试,所以我可能犯了语法错误。)我使用了一个 lambda,但如果你不使用 C++11 编译器,你可以创建一个仿函数。

请注意,原始代码仅删除了b 中的一个值实例,该实例也在a 中。我的解决方案将从b 中删除此类值的所有实例。

请注意,find 操作会一次又一次地发生,因此最好在较小的向量上执行此操作以获得更好的参考局部性。

【讨论】:

    【解决方案5】:

    想了一会儿,我想到了这个
    (注意:通过回答我自己的 Q 我并没有声称这是优于提供的 A):

    vector<int64_t> a{3,2,7,5,11,13}, b{2,3,13,5};
    set<int64_t> bs(b.begin(), b.end());
    for (const auto& num: bs)
        cout << num << " ";
    cout  << endl;
    for (const auto& num: a)
        bs.erase(num);
    vector<int64_t> result(bs.begin(), bs.end());
    for (const auto& num: result)
        cout << num << " ";
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2021-08-03
      • 1970-01-01
      • 1970-01-01
      • 2019-05-10
      • 1970-01-01
      • 1970-01-01
      • 2015-07-19
      • 1970-01-01
      相关资源
      最近更新 更多