【问题标题】:Erasing elements from a vector从向量中擦除元素
【发布时间】:2021-03-05 17:21:51
【问题描述】:

我想使用擦除方法从向量中清除一个元素。但这里的问题是,不能保证元素在向量中只出现一次。它可能出现多次,我需要清除所有这些。我的代码是这样的:

void erase(std::vector<int>& myNumbers_in, int number_in)
{
    std::vector<int>::iterator iter = myNumbers_in.begin();
    std::vector<int>::iterator endIter = myNumbers_in.end();
    for(; iter != endIter; ++iter)
    {
        if(*iter == number_in)
        {
            myNumbers_in.erase(iter);
        }
    }
}

int main(int argc, char* argv[])
{
    std::vector<int> myNmbers;
    for(int i = 0; i < 2; ++i)
    {
        myNmbers.push_back(i);
        myNmbers.push_back(i);
    }

    erase(myNmbers, 1);

    return 0;
}

这段代码显然会崩溃,因为我在迭代它时更改了向量的结尾。实现这一目标的最佳方法是什么? IE。有什么方法可以做到这一点,而无需多次迭代向量或创建一个向量的更多副本?

【问题讨论】:

    标签: c++ vector stl erase


    【解决方案1】:

    使用remove/erase idiom

    std::vector<int>& vec = myNumbers; // use shorter name
    vec.erase(std::remove(vec.begin(), vec.end(), number_in), vec.end());
    

    发生的情况是 remove 压缩与 vector 开头的要删除的值 (number_in) 不同的元素,并将迭代器返回到该范围之后的第一个元素。然后erase 移除这些元素(其值未指定)。

    【讨论】:

    • std::remove() 移动元素,以便覆盖要删除的元素。该算法不会改变容器的大小,如果n 元素被移除,那么最后的n 元素是什么是不确定的。
    • erase-remove idiom 在 Scott Meyers 所著的“Effective STL: 50 Specific Ways to Improvement Your Use of the Standard Template Library”一书中的第 32 项中进行了描述。
    • 像这样的 STL 'idioms' 让我在小型项目中使用 Python。
    • @LouisDionne 指的是一个迭代器重载,我使用的是两个迭代器重载
    • @TamaMcGlinn,此代码不会删除end(),它会删除begin()end() 之间的范围。如果begin() 等于end(),则范围内的元素为零,并且不会删除任何内容(erase 同上)。
    【解决方案2】:

    调用erase会使迭代器失效,你可以使用:

    void erase(std::vector<int>& myNumbers_in, int number_in)
    {
        std::vector<int>::iterator iter = myNumbers_in.begin();
        while (iter != myNumbers_in.end())
        {
            if (*iter == number_in)
            {
                iter = myNumbers_in.erase(iter);
            }
            else
            {
               ++iter;
            }
        }
    
    }
    

    或者您可以将std::remove_if 与仿函数和 std::vector::erase 一起使用:

    struct Eraser
    {
        Eraser(int number_in) : number_in(number_in) {}
        int number_in;
        bool operator()(int i) const
        {
            return i == number_in;
        }
    };
    
    std::vector<int> myNumbers;
    myNumbers.erase(std::remove_if(myNumbers.begin(), myNumbers.end(), Eraser(number_in)), myNumbers.end());
    

    在这种情况下,您可以使用std::remove,而不是编写自己的函子:

    std::vector<int> myNumbers;
    myNumbers.erase(std::remove(myNumbers.begin(), myNumbers.end(), number_in), myNumbers.end());
    

    在 C++11 中,您可以使用 lambda 代替仿函数:

    std::vector<int> myNumbers;
    myNumbers.erase(std::remove_if(myNumbers.begin(), myNumbers.end(), [number_in](int number){ return number == number_in; }), myNumbers.end());
    

    在 C++17 中,std::experimental::erasestd::experimental::erase_if 也可用,在 C++20 中,它们(最终)重命名为 std::erasestd::erase_if注意:在 Visual Studio 2019 中,您'您需要将您的 C++ 语言版本更改为最新的实验版本以获得支持):

    std::vector<int> myNumbers;
    std::erase_if(myNumbers, Eraser(number_in)); // or use lambda
    

    或:

    std::vector<int> myNumbers;
    std::erase(myNumbers, number_in);
    

    【讨论】:

    • 既然可以使用equal_to,为什么还要使用自己的函子? :-P sgi.com/tech/stl/equal_to.html
    • 顺便说一句,使用remove 调用erase 是执行此操作的规范方法。
    • 我认为他正是这样做的。但如果使用自己的仿函数 iirc,他应该使用 remove_if。或者只使用没有仿函数的 remove
    • +1 拼写出来的代码只是在编程比赛中帮助了我,而“只使用 remove-erase idiom”没有。
    【解决方案3】:
    1. 您可以使用索引访问进行迭代,

    2. 为了避免 O(n^2) 复杂度 您可以使用两个索引,i - 当前测试索引,j - 索引 存储下一项并在循环结束时新的向量大小。

    代码:

    void erase(std::vector<int>& v, int num)
    {
      size_t j = 0;
      for (size_t i = 0; i < v.size(); ++i) {
        if (v[i] != num) v[j++] = v[i];
      }
      // trim vector to new size
      v.resize(j);
    }
    

    在这种情况下,您没有使迭代器失效,复杂度为 O(n),并且代码非常简洁,您不需要编写一些辅助类,尽管在某些情况下使用辅助类可以使代码更灵活.

    此代码不使用erase 方法,但解决了您的任务。

    使用纯 stl 您可以通过以下方式执行此操作(这类似于 Motti 的答案):

    #include <algorithm>
    
    void erase(std::vector<int>& v, int num) {
        vector<int>::iterator it = remove(v.begin(), v.end(), num);
        v.erase(it, v.end());
    }
    

    【讨论】:

      【解决方案4】:

      根据您这样做的原因,使用 std::set 可能比 std::vector 更好。

      它允许每个元素只出现一次。如果您多次添加它,无论如何都只会删除一个实例。这将使擦除操作变得微不足道。 擦除操作的时间复杂度也低于向量,但是,在集合上添加元素的速度较慢,因此可能没有太大优势。

      如果您对元素被添加到向量中的次数或元素添加的顺序感兴趣,那么这当然行不通。

      【讨论】:

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