【问题标题】:faster erase-remove idiom when I don't care about order and don't have duplicates?当我不关心订单并且没有重复项时,更快的擦除删除习语?
【发布时间】:2019-12-30 03:30:55
【问题描述】:

我有一个对象向量,想按值删除。但是,该值只出现一次,我不关心排序。

显然,如果这种按值删除非常普遍,和/或数据集相当大,那么向量就不是最好的数据结构。但假设我已经确定不是这种情况。

明确地说,如果我的代码是 C,我会对以下内容感到满意:

void delete_by_value( int* const piArray, int& n, int iValue ) {
    for ( int i = 0; i < n; i++ ) {
        if ( piArray[ i ] == iValue ) {
            piArray[ i ] = piArray[ --n ];
            return;
        }
    }
}

似乎使用 std::algos 和容器方法的“现代成语”方法是:

v.erase(std::remove(v.begin(), v.end(), iValue), v.end());

但这应该慢得多,因为对于随机存在的元素,它是 n/2 移动和 n 比较。我的版本是 1 步,n/2 比较。

在“现代成语”中肯定有比擦除-删除-成语更好的方法吗?如果不是,为什么不呢?

【问题讨论】:

  • std::find 为您找到该值,如果它存在,那么它几乎就像在 C 中发生的那样。这一切都归结为,几乎与 C 相同。
  • 这两个代码示例不等价。一个使用无法调整大小的数组,而 vector 版本会调整大小。
  • 非常感谢;我有点发疯了;通过引用传递 n,因此调用者对数组的视图实际上已调整大小,并且发现我认为比“开明”更好的措辞
  • 您是否考虑过使用std::list 而不是std::vector?因为std::list 是一个链接列表,您可以删除列表中的任何项目而无需进行任何复制。缺点是访问元素是线性时间。
  • @SwissFrank 其中两个答案都使用erase()resize() 删除最后一个元素,使用pop_back() 可以更简单地完成

标签: c++ algorithm erase-remove-idiom


【解决方案1】:

使用std::find 替换循环。从end 迭代器的前身中获取替换值,并将该迭代器用于erase 那个元素。由于这个迭代器是最后一个元素,erase 很便宜。奖励:bool 返回成功检查,templateing over int

template<typename T>
bool delete_by_value(std::vector<T> &v, T const &del) {
    auto final = v.end();
    auto found = std::find(v.begin(), final, del);
    if(found == final) return false;
    *found = *--final;
    v.erase(final);
    return true;
}

【讨论】:

  • 谢谢,看来我可以把它放到生产代码中了。甚至缓存最终! :-D 尽管如此,对于如此普遍的需求,没有比(基本上)拼出 C 版本更现代的习语了吗(除了你的版本实际上更长,尽管是类型通用的)?
  • @SwissFrank,标准库为您提供了构建块,您可以使用它们来构建您需要的东西。如果它提供所有通用算法,那将是一个不可思议的怪物。
  • 你可以像使用T一样使用移动赋值。
【解决方案2】:

在“现代成语”中肯定有比擦除-删除-成语更好的方法吗?

标准库中的每个小众用例都没有现成的函数。 Unstable remove 是未提供的功能之一。不过,这已经是proposed (p0041r0) 了。同样,对于不包含重复的向量的特殊情况,也没有特殊版本的算法。

因此,如果您希望使用最优算法,则需要自己实现该算法。有std::find 用于线性搜索。之后,你只需要从最后一个元素开始赋值,最后将其弹出即可。

【讨论】:

    【解决方案3】:

    如果您减小向量的大小,std::vector::resize 的大多数实现都不会重新分配。因此,下面的示例可能与 C 示例具有相似的性能。

    void find_and_delete(std::vector<int>& v, int value) {
        auto it = std::find(v.begin(), v.end(), value);
        if (it != v.end()) {
            *it = v.back();
            v.resize(v.size() - 1);
        }
    }
    

    【讨论】:

    • 谢谢,但仍然没有现代成语来做这种极其常见的操作吗?
    • 通常情况下,如果您有一组唯一值并且不关心顺序,您会使用std::unordered_set 而不会真正关心实现。
    • 当然,但是我编写的软件关注性能,所以我确实关心实现。软件跟踪了大量数据集,这些数据集足够小,以至于向量中的线性搜索优于集合、无序集合等。
    • &lt;algorithm&gt; 很少提供超高性能的解决方案。它提供了非常通用的、抽象的解决方案,适用于多个容器,并且足以满足大多数情况。在瓶颈情况下需要打破并编写自己的循环是很正常的。
    • 很多很多很多很多文章和书籍都说 确实提供了超高性能的解决方案。我开始同意你的观点(作为一个被拖入 21 世纪的老 C++ 程序员),这是非常具体的......
    【解决方案4】:

    C++ 方式与std::vector 基本相同:

    template <typename T>
    void delete_by_value(std::vector<T>& v, const T& value) {
        auto it = std::find(v.begin(), v.end(), value);
    
        if (it != v.end()) {
            *it = std::move(v.back());
            v.pop_back();
        }
    }
    

    【讨论】:

      猜你喜欢
      • 2011-04-17
      • 1970-01-01
      • 1970-01-01
      • 2015-11-05
      • 2021-08-07
      • 1970-01-01
      • 1970-01-01
      • 2011-03-17
      • 1970-01-01
      相关资源
      最近更新 更多