Xóa các phần tử khỏi một vectơ


101

Tôi muốn xóa một phần tử khỏi vectơ bằng phương pháp xóa. Nhưng vấn đề ở đây là phần tử không được đảm bảo chỉ xảy ra một lần trong vector. Nó có thể xuất hiện nhiều lần và tôi cần phải xóa tất cả chúng. Mã của tôi như thế này:

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;
}

Mã này rõ ràng bị treo bởi vì tôi đang thay đổi phần cuối của vectơ trong khi lặp lại qua nó. cách tốt nhất để đạt được điều này là gì? Tức là có cách nào để thực hiện việc này mà không cần lặp lại véc tơ nhiều lần hoặc tạo thêm một bản sao của véc tơ không?

Câu trả lời:


166

Sử dụng thành ngữ xóa / xóa :

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

Điều xảy ra là việc removenén các phần tử khác với giá trị cần loại bỏ ( number_in) ở đầu vectorvà trả về trình vòng lặp về phần tử đầu tiên sau phạm vi đó. Sau đó, eraseloại bỏ các phần tử này (có giá trị không xác định).


2
std::remove()dịch chuyển các phần tử sao cho các phần tử cần xóa bị ghi đè. Thuật toán không thay đổi kích thước của vùng chứa và nếu ncác phần tử bị xóa thì sẽ không xác định được đâu là nphần tử cuối cùng .
wilhelmtell

20
Thành ngữ xóa-xóa được mô tả trong Mục 32 trong cuốn sách "STL hiệu quả: 50 cách cụ thể để cải thiện việc sử dụng thư viện mẫu chuẩn" của Scott Meyers.
Alessandro Jacopson

1
@Benjin không cần cập nhật, nó sẽ gọi hàm hủy của đối tượng nếu chúng tồn tại.
Motti

53
STL 'thành ngữ' như thế này khiến tôi sử dụng Python cho các dự án nhỏ.
Johannes Overmann

1
@LouisDionne đó đề cập đến tình trạng quá tải một iterator, Tôi đang sử dụng quá tải hai iterator
Motti

53

Gọi xóa sẽ làm mất hiệu lực các trình vòng lặp, bạn có thể sử dụng:

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;
        }
    }

}

Hoặc bạn có thể sử dụng std :: remove_if cùng với functor và std :: vector :: delete:

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());

Thay vì viết functor của riêng bạn trong trường hợp này, bạn có thể sử dụng std :: remove :

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

Trong C ++ 11, bạn có thể sử dụng lambda thay vì functor:

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

Trong C ++ 17 std :: nghiệm :: erasestd :: nghiệm :: erase_if cũng có sẵn, trong C ++ 20 đây là (cuối cùng) đổi tên để std :: erasestd :: erase_if :

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

hoặc là:

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

2
Tại sao lại sử dụng functor của riêng bạn khi bạn có thể sử dụng equal_to? :-P sgi.com/tech/stl/equal_to.html
Chris Jester-Young

3
Nhân tiện, gọi erasevới removelà cách chuẩn để làm điều này.
Konrad Rudolph,

1
tôi nghĩ cô ấy làm chính xác điều đó. nhưng anh ta nên sử dụng remove_if nếu sử dụng một iirc functor riêng. hoặc chỉ sử dụng loại bỏ mà không có trình điều khiển
Johannes Schaub - litb

3
+1 Mã viết sai chính tả chỉ giúp tôi trong một cuộc thi lập trình, trong khi "chỉ sử dụng thành ngữ xóa-xóa" thì không.

13
  1. Bạn có thể lặp lại bằng cách sử dụng quyền truy cập chỉ mục,

  2. Để tránh độ phức tạp O (n ^ 2), bạn có thể sử dụng hai chỉ số, i - chỉ số thử nghiệm hiện tại, j - chỉ số để lưu trữ mục tiếp theo và vào cuối chu kỳ kích thước mới của vectơ.

mã:

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);
}

Trong trường hợp như vậy, bạn không làm mất hiệu lực của các trình vòng lặp, độ phức tạp là O (n) và mã rất ngắn gọn và bạn không cần phải viết một số lớp trợ giúp, mặc dù trong một số trường hợp, sử dụng các lớp trợ giúp có thể có lợi trong mã linh hoạt hơn.

Mã này không sử dụng erasephương pháp, nhưng giải quyết nhiệm vụ của bạn.

Sử dụng pure stl, bạn có thể làm điều này theo cách sau (cách này tương tự như câu trả lời của 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

Tùy thuộc vào lý do tại sao bạn làm điều này, sử dụng std :: set có thể là một ý tưởng tốt hơn std :: vector.

Nó cho phép mỗi phần tử chỉ xảy ra một lần. Nếu bạn thêm nó nhiều lần, sẽ chỉ có một trường hợp để xóa. Điều này sẽ làm cho thao tác xóa trở nên tầm thường. Thao tác xóa cũng sẽ có độ phức tạp về thời gian thấp hơn so với trên vector, tuy nhiên, việc thêm các phần tử trên tập hợp sẽ chậm hơn nên có thể không có nhiều lợi thế.

Tất nhiên điều này sẽ không hoạt động nếu bạn quan tâm đến số lần một phần tử đã được thêm vào vectơ của bạn hoặc thứ tự các phần tử được thêm vào.


-1

Để xóa phần tử đầu tiên, bạn có thể sử dụng:

vector<int> mV{ 1, 2, 3, 4, 5 }; 
vector<int>::iterator it; 

it = mV.begin(); 
mV.erase(it); 

1
Đó không phải là câu hỏi. Câu trả lời của bạn 11 năm sau có vẻ không liên quan, Amit!
Tiểu hành tinh có cánh
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.