Làm cách nào để xóa một mục khỏi vectơ stl với một giá trị nhất định?


145

Tôi đã xem tài liệu API cho stl vector và nhận thấy không có phương thức nào trên lớp vectơ cho phép loại bỏ một phần tử có giá trị nhất định. Đây có vẻ như là một hoạt động phổ biến, và có vẻ kỳ lạ là không có cách nào được thực hiện để làm điều này.


2
Tôi biết rằng tôi đã đề cập đến điều này nhiều lần trước đây nhưng cuốn sách STL hiệu quả của Scott Meyer đã nói về những vấn đề này một cách rõ ràng.
Rob Wells


Đây có thể là một bài đọc thú vị cho bạn: en.wikipedia.org/wiki/Erase%E2%80%93remove_idiom
sergiol

Câu trả lời:


165

std::removekhông thực sự xóa phần tử khỏi vùng chứa, nhưng nó trả về trình lặp kết thúc mới có thể được chuyển qua container_type::eraseđể thực hiện loại bỏ THỰC SỰ của các phần tử bổ sung hiện có ở cuối vùng chứa:

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
Biểu mẫu tất cả trong một này có phụ thuộc vào thứ tự trình biên dịch đánh giá các đối số hay được vec.end()đảm bảo giống nhau ở hai bên của lệnh gọi std::removekhông? Theo tôi, việc đọc các phần khác của web như thế này là an toàn, nhưng nó cần được nêu rõ.
dmckee --- ex-moderator mèo con

1
Tốt rồi. Kết quả của vec.end()không cần phải giống nhau; nó chỉ cần chính xác (đó là).
Jim Buck

8
vec.end()không cần phải giống nhau, nhưng điều đó std::removekhông sao vì không thay đổi nó. Nếu nó đã thay đổi nó (và làm mất hiệu lực giá trị cũ) thì sẽ có một vấn đề: thứ tự đánh giá các tham số là không xác định và do đó bạn sẽ không biết liệu thứ hai vec.end()có còn hiệu lực vào thời điểm nó được sử dụng hay không. Lý do nó giống nhau là đơn giản, std::removekhông thay đổi kích thước của container, nó chỉ di chuyển các nội dung xung quanh.
Steve Jessop

2
Tôi đã tìm thấy câu hỏi này quan trọng, vì tôi có cùng một vấn đề. Nhưng, phòng thu hình ảnh của tôi std::removechỉ có một đối số; đó là const char *_Filename. Tôi cần gọi phương thức nào?
Victor

15
Đó là phiên bản removexóa một tập tin. Bạn cần bao gồm <algorithm>để truy cập phiên bản removegiao dịch với container.
Jim Buck

64

Nếu bạn muốn loại bỏ một mục, sau đây sẽ hiệu quả hơn một chút.

std::vector<int> v;


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

hoặc bạn có thể tránh chi phí di chuyển các mặt hàng nếu đơn hàng không quan trọng với bạn:

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
Lưu ý điều này sẽ không loại bỏ các bản sao của mục nếu chúng tồn tại, trong khi phương pháp std :: remove_if thì có.
Den-Jason

15

Sử dụng phương thức toàn cầu std :: remove với trình lặp bắt đầu và kết thúc, sau đó sử dụng std :: vector.erase để thực sự loại bỏ các phần tử.

Liên kết tài liệu
std :: remove http://www.cppreference.com/cppalacticm/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

Cảm ơn Jim Buck đã chỉ ra lỗi của tôi.


Điều này có khả năng ngăn chặn sự di chuyển của các yếu tố khác khi xóa một phần tử ở giữa vectơ do đó việc này nhanh hơn. Nó di chuyển chúng đến cuối của vectơ trước sau đó bạn có thể loại bỏ từ cuối vectơ.
Etherealone

5

Các câu trả lời khác bao gồm cách làm tốt điều này, nhưng tôi nghĩ tôi cũng chỉ ra rằng nó không thực sự kỳ lạ khi điều này không có trong API vectơ: nó không hiệu quả, tìm kiếm tuyến tính thông qua vectơ cho giá trị, theo sau là một bó sao chép để loại bỏ nó.

Nếu bạn đang thực hiện thao tác này mạnh mẽ, có thể đáng để xem xét std :: set thay vì lý do này.


5

Nếu bạn có một vectơ chưa sắp xếp, thì bạn có thể trao đổi với phần tử vectơ cuối cùng sau đó resize().

Với một container ra lệnh, bạn sẽ được tắt tốt nhất với std::vector::erase(). Lưu ý rằng có một std::remove()định nghĩa trong <algorithm>, nhưng điều đó không thực sự xóa. (Đọc tài liệu cẩn thận).



3

Từ c ++ 20 :

Một hàm không phải thành viên được giới thiệu std::erase, lấy vectơ và giá trị được loại bỏ làm đầu vào.

Ví dụ:

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

2
Không chỉ vậy, nó quá tải cho từng container cụ thể!
HolyBlackCat

... và tận dụng các thuộc tính dành riêng cho container, như map::erase!
LF

2

Xem thêm std :: remove_if để có thể sử dụng một vị ngữ ...

Đây là ví dụ từ liên kết trên:

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
Vui lòng thêm chi tiết cho bài viết này. Như hiện tại, phần lớn nội dung của nó đến từ một liên kết và sẽ bị mất nếu liên kết bị hỏng.
Mick MacCallum

những gì về thực tế bind2nd là - (không dùng nữa trong C ++ 11) (bị xóa trong C ++ 17)
Idan Banani

0

Nếu bạn muốn làm điều đó mà không có thêm bất kỳ bao gồm:

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

Có hai cách để bạn có thể sử dụng để xóa một mục cụ thể. hãy lấy một vectơ

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) Cách không hiệu quả: Mặc dù có vẻ khá hiệu quả nhưng không phải vì chức năng xóa sẽ xóa các phần tử và dịch chuyển tất cả các phần tử sang trái 1. vì vậy độ phức tạp của nó sẽ là 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) Cách hiệu quả (KHUYẾN NGHỊ) : Nó còn được gọi là XÓA - XÓA các thành ngữ .

  • std :: remove biến đổi phạm vi đã cho thành một phạm vi với tất cả các phần tử so sánh không bằng với phần tử đã cho được chuyển sang phần đầu của container.
  • Vì vậy, thực sự không loại bỏ các yếu tố phù hợp. Nó chỉ chuyển cái không khớp sang bắt đầu và đưa ra một trình vòng lặp đến kết thúc hợp lệ mới. Nó chỉ đòi hỏi độ phức tạp O (n).

đầu ra của thuật toán loại bỏ là:

10 20 30 50 40 50 

như kiểu trả về của remove là iterator đến phần cuối mới của phạm vi đó.

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

Bây giờ sử dụng chức năng xóa của vectơ để xóa các phần tử từ đầu mới đến đầu cũ của vectơ. Nó đòi hỏi O (1) thời gian.

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

vì vậy phương pháp này hoạt động trong O (n)


0

*

Cộng đồng C ++ đã nghe yêu cầu của bạn :)

*

C ++ 20 cung cấp một cách dễ dàng để làm điều đó ngay bây giờ. Nó đơn giản như:

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

Bạn nên kiểm tra std :: erasestd :: erase_if .

Nó không chỉ loại bỏ tất cả các yếu tố của giá trị (ở đây '0'), nó sẽ thực hiện nó trong độ phức tạp thời gian O (n) . Đó là điều tốt nhất bạn có thể nhận được.

Nếu trình biên dịch của bạn không hỗ trợ C ++ 20, bạn nên sử dụng thành ngữ erase-remove :

#include <algorithm>
...
vec.erase(std::remove(vec.begin(), vec.end(), 0), vec.end());
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.