如何从stl向量中删除具有特定值的项目?


145

我查看了stl vector的API文档,并注意到vector类上没有允许删除具有特定值的元素的方法。这似乎是一个普通的操作,而且似乎没有内置的方法可以执行此操作。


2
我知道我之前已经提到过几次,但是Scott Meyer的书《Effective STL》以清晰的方式涵盖了这些陷阱。
罗伯·威尔斯


Answers:


165

std::remove实际上并没有从容器中删除该元素,但是它确实返回了新的结束迭代器,可以将该迭代器传递给它container_type::erase以真正的方式移除容器末尾的多余元素:

std::vector<int> vec;
// .. put in some values ..
int int_to_remove = n;
vec.erase(std::remove(vec.begin(), vec.end(), int_to_remove), vec.end());

3
这种多合一语句的形式取决于编译器评估参数的顺序,还是vec.end()保证在调用的任一端都相同std::remove?在我看来,在阅读网络的其他部分时,这是安全的,但应明确说明。
dmckee ---前主持人小猫,

1
没关系。的结果vec.end()不必相同。它只需要是正确的(就是如此)。
Jim Buck

8
vec.end()确实需要相同,但这没关系,因为std::remove不会更改它。如果确实更改了它(并使旧值无效),则会出现问题:未指定参数的评估顺序,因此您将不知道第二个参数在使用时是否vec.end()仍然有效。相同的原因很简单,std::remove不更改容器的大小,它只是移动内容。
Steve Jessop 2013年

2
我发现这个问题很重要,因为我有同样的问题。但是,我的视觉工作室std::remove只接受一个论点。那就是const char *_Filename。我需要调用什么方法?
维克多

15
那是remove删除文件的版本。您需要包含<algorithm>才能访问remove与容器打交道的版本。
Jim Buck

64

如果你想删除项目,下面会有多一点的效率。

std::vector<int> v;


auto it = std::find(v.begin(), v.end(), 5);
if(it != v.end())
    v.erase(it);

或者,如果订单对您来说无关紧要,则可以避免移动这些物品的开销:

std::vector<int> v;

auto it = std::find(v.begin(), v.end(), 5);

if (it != v.end()) {
  using std::swap;

  // swap the one to be removed with the last element
  // and remove the item at the end of the container
  // to prevent moving all items after '5' by one
  swap(*it, v.back());
  v.pop_back();
}

3
请注意,如果存在重复项,则不会删除重复项,而std :: remove_if方法会删除重复项。
Den-Jason

15

使用带有begin和end迭代器的全局方法std :: remove,然后使用std :: vector.erase实际删除元素。

文档链接
std ::删除http://www.cppreference.com/cppalgorithm/remove.html
std :: vector.erase http://www.cppreference.com/cppvector/erase.html

std::vector<int> v;
v.push_back(1);
v.push_back(2);

//Vector should contain the elements 1, 2

//Find new end iterator
std::vector<int>::iterator newEnd = std::remove(v.begin(), v.end(), 1);

//Erase the "removed" elements.
v.erase(newEnd, v.end());

//Vector should now only contain 2

感谢Jim Buck指出我的错误。


这具有在擦除矢量中间的元素时防止其他元素移动的能力,因此速度更快。它将它们首先移动到向量的末尾,然后您可以从向量的末尾丢弃。
Etherealone 2015年

5

其他答案涵盖了如何做到这一点,但我想我也要指出,向量API中没有这个并不是很奇怪:在向量中线性查找效率低下,然后是一堆复制将其删除。

如果您正在密集地执行此操作,则出于这个原因,值得考虑使用std :: set。


5

如果您有未排序的向量,则只需与最后一个向量元素交换即可resize()

有了一个有序的容器,你会用最好关闭std::vector::erase()。请注意,中有一个std::remove()定义<algorithm>,但实际上并没有进行擦除。(请仔细阅读文档)。



3

c ++ 20

引入了一个非成员函数std::erase,该函数将要删除的向量和值作为输入。

例如:

std::vector<int> v = {90,80,70,60,50};
std::erase(v,50);

2
不仅如此,每个特定的容器都超载了!
HolyBlackCat

...,并利用容器特有的属性,例如map::erase
LF

2

也可以使用std :: remove_if来使用谓词...

这是上面链接中的示例:

vector<int> V;
V.push_back(1);
V.push_back(4);
V.push_back(2);
V.push_back(8);
V.push_back(5);
V.push_back(7);

copy(V.begin(), V.end(), ostream_iterator<int>(cout, " "));
    // The output is "1 4 2 8 5 7"

vector<int>::iterator new_end = 
    remove_if(V.begin(), V.end(), 
              compose1(bind2nd(equal_to<int>(), 0),
                       bind2nd(modulus<int>(), 2)));
V.erase(new_end, V.end()); [1]

copy(V.begin(), V.end(), ostream_iterator<int>(cout, " "));
    // The output is "1 5 7".

2
请在此帖子中添加更多详细信息。就其现状而言,其大部分内容来自链接,如果链接中断,它将丢失。
Mick MacCallum

关于bind2nd的事实是-(在C ++ 11中已弃用)(在C ++ 17中已删除)
Idan Banani

0

如果您想不做任何额外的准备,则包括:

vector<IComponent*> myComponents; //assume it has items in it already.
void RemoveComponent(IComponent* componentToRemove)
{
    IComponent* juggler;

    if (componentToRemove != NULL)
    {
        for (int currComponentIndex = 0; currComponentIndex < myComponents.size(); currComponentIndex++)
        {
            if (componentToRemove == myComponents[currComponentIndex])
            {
                //Since we don't care about order, swap with the last element, then delete it.
                juggler = myComponents[currComponentIndex];
                myComponents[currComponentIndex] = myComponents[myComponents.size() - 1];
                myComponents[myComponents.size() - 1] = juggler;

                //Remove it from memory and let the vector know too.
                myComponents.pop_back();
                delete juggler;
            }
        }
    }
}

0

有两种方法可以用来擦除项目。让我们取一个向量

std :: vector < int > v;
v.push_back(10);
v.push_back(20);
v.push_back(30);
v.push_back(40);
v.push_back(40);
v.push_back(50);

1)非有效方式:尽管它似乎非常有效,但这不是因为擦除功能会删除元素并将所有元素向左移动1, 因此其复杂度将为O(n ^ 2)

std :: vector < int > :: iterator itr = v.begin();
int value = 40;
while ( itr != v.end() )
{
   if(*itr == value)
   { 
      v.erase(itr);
   }
   else
       ++itr;
}

2)高效方式(推荐):也称为ERASE-REMOVE习惯用法

  • std :: remove将给定范围转换成一个范围,所有比较不等于给定元素的元素都移到了容器的开头。
  • 因此,实际上不要删除匹配的元素。它只是将不匹配项转换为开始,并将迭代器赋予新的有效结束。它只需要O(n)复杂度。

删除算法的输出为:

10 20 30 50 40 50 

因为remove的返回类型是迭代器到该范围的新末端。

template <class ForwardIterator, class T>
  ForwardIterator remove (ForwardIterator first, ForwardIterator last, const T& val);

现在使用矢量的擦除功能从矢量的新端到旧端删除元素。它需要O(1)时间。

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

因此该方法适用于O(n)


0

*

C ++社区已听到您的请求:)

*

C ++ 20提供了一种简便的方法。它变得如此简单:

#include <vector>
...
vector<int> cnt{5, 0, 2, 8, 0, 7};
std::erase(cnt, 0);

您应该检出std :: erasestd :: erase_if

它不仅会删除该值的所有元素(此处为“ 0”),而且会以O(n)时间复杂度进行删除。这是您所能得到的最好的。

如果您的编译器不支持C ++ 20,则应使用“ 擦除删除”惯用语

#include <algorithm>
...
vec.erase(std::remove(vec.begin(), vec.end(), 0), vec.end());
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.