【问题标题】:Removing a known subset of items from a c++ vector从 C++ 向量中删除已知的项目子集
【发布时间】:2016-10-27 00:49:48
【问题描述】:

给定:

  • struct Item { int id; ... };
  • std::vector<Item> items;
  • std::vector<int> idsToRemove;

编写执行删除(同时保持顺序)的代码的最有效/最简洁的方法是什么?

使用remove_if,这可能是:

items.erase(std::remove_if(items.begin(), items.end(),
    [&](const Item& i) {
        return std::find(idsToRemove.begin(), idsToRemove.end(), i.id)
            != idsToRemove.end();
    }), items.end());

另一种可能是:

for (auto id : idsToRemove)
{
    items.erase(std::remove(items.begin(), items.end(), id), items.end());
    // or items.erase(std::find(items.begin(), items.end(), id));
    // provided that we know id always exists in items
}

这两种感觉都不是特别好(而且它们看起来都O(N*M)),尽管第二个看起来比第一个更整洁。有没有更好的办法?

(如果有帮助,虽然两个向量都没有排序,但已知idsToRemove 是 id 的子集,其顺序与它们在items 中出现的顺序相同,并且两个数组都很小。我可以使用 Boost如果那里有合适的算法。)

【问题讨论】:

  • 您可以将擦除删除限制在由匹配 next ID 的第一个项目分隔的子范​​围内。如果你从后面这样做,你可以最大限度地减少移动量。
  • 不,如前所述,阵列通常很小。我主要是在寻找代码的简洁性,但我也不希望它不必要地低效。
  • 如果它是大的任意列表,我会制作 idsToRemove 的哈希或布隆过滤器,因此您可以在迭代项目时在 O(1) 时间内检查它们。但鉴于相同的订单子集保证,这完全没有必要。既然你说向量很小,O(NM) 并不是那么糟糕,但我投票支持 1201ProgramAlarm 的答案。它是 O(N) 空间和时间,但您必须自己编写。我认为上述快速解决方案的真正问题是从向量中删除一堆单个项目本身就是一个缓慢的过程。

标签: c++ vector boost stl


【解决方案1】:

由于 idsToRemove 中的 id 已知在 items 中且顺序相同,因此您可以在 items 中使用几个迭代器来跟踪当前比较元素、当前目的地和 walk通过idsToRemoveitems,比较两个元素,移动你想要保留的元素。在该过程结束时,将 items 调整为新的较小尺寸。

【讨论】:

  • 这听起来像是在写一个自定义的remove_if
  • 我主要是想看看是否存在已经从向量中删除向量的现有 STL 或 Boost 算法或复合(这似乎应该是常见的,不是吗?)。数组的大小并不足以证明做任何复杂的事情是合理的。
  • 您可以使用自定义remove_if,但比较会有些复杂(一旦您到达idsToRemove 的末尾,您将比较items 中的尾部元素并且您需要在取消迭代器之前检查该端)。
  • 这里的关键见解是您可以在项目之间迭代一次,并且您可以通过迭代一次来与 idsToRemove 进行比较。您永远不会备份,因为您知道它是相同顺序的子集。我对多个迭代器感到困惑,但是您正在谈论使用原始向量将事物打乱到位,因此您永远不会受到向量删除的性能影响。 (我花了一分钟才明白你在说什么。)
  • @Miral:实际上 STL 并没有提供这样的功能,因为它并不常见。有一个向量是另一个向量的子集,并额外保证元素与第一个向量的顺序相同,这是一种极端情况。无论如何,在几行代码中保留两个迭代器(如此高效)是非常简单的。
【解决方案2】:

我不认为这是对所述问题的真正答案(因为它会移动球门柱),但这是我在调查时发现的,它可能对未来的搜索有用。

如果您没有从外部传入idsToRemove,但无论如何都需要遍历items 来决定删除哪个,那么在O(N) 中有一个相当不错的方法来执行此操作:

#include <boost/range/algorithm_ext/erase.hpp>

boost::range::remove_erase_if(items, [&](const Item& item)
{
    // do whatever else you want to item
    // return true to erase the item, or
    return false; // to keep it
});

在内部它基于std::remove_if,但它更整洁,类似于基于范围的for。

【讨论】:

    【解决方案3】:

    我想您的元素具有唯一的 ID,因此不要将要删除的元素存储在 std::vector 中,而是将它们存储在 std::unordered_set 中。

    这种方式,std::remove_if 方式真的很干净:

    struct Item {
        int id;
    };
    
    // ...
    std::vector<Item> items;
    std::unordered_set<int> idsToRemove;
    items.erase(
        std::remove_if(std::begin(items), std::end(items), [&](Item const& it) {
                return (idsToRemove.find(it.id) != std::end(idsToRemove));
            }),
        std::end(items));
    

    复杂度(摊销)将为O(N),其中N 是向量中的元素数。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-11-08
      相关资源
      最近更新 更多