Bir vektörden öğeleri silme


101

Silme yöntemini kullanarak bir vektörden bir öğeyi temizlemek istiyorum. Ancak buradaki sorun, elementin vektörde yalnızca bir kez meydana gelmesinin garanti edilmemesidir. Birden çok kez mevcut olabilir ve hepsini temizlemem gerekiyor. Kodum şuna benzer:

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

Bu kod açıkça çöküyor çünkü içinde yineleme yaparken vektörün sonunu değiştiriyorum. Bunu başarmanın en iyi yolu nedir? Yani, vektörü birden çok kez yinelemeden veya vektörün bir kopyasını daha oluşturmadan bunu yapmanın bir yolu var mı?

Yanıtlar:


167

Kaldır / sil deyimini kullanın :

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

Olan şey, başlangıçtaki removekaldırılacak değerden ( number_in) farklı olan öğeleri sıkıştırır ve vectoryineleyiciyi bu aralıktan sonraki ilk öğeye döndürür. Daha sonra erase(değeri belirtilmemiş olan) bu öğeleri kaldırır.


2
std::remove()öğeleri, kaldırılacak öğelerin üzerine yazılacak şekilde kaydırır. Algoritma, kabın boyutunu değiştirmez ve nöğeler kaldırılırsa, son nöğelerin ne olduğu tanımsızdır .
wilhelmtell

20
sil-kaldır deyimi Scott Meyers'in "Etkili STL: Standart Şablon Kitaplığını Kullanımınızı Geliştirmenin 50 Spesifik Yolları" kitabındaki Madde 32'de açıklanmaktadır.
Alessandro Jacopson 01

1
@Benjin güncelleme gerekmez, varsa nesnelerin yıkıcılarını çağırır.
Motti

54
Bunun gibi STL 'deyimleri' Python'u küçük projeler için kullanmamı sağlıyor.
Johannes Overmann

1
@LouisDionne, bir yineleyici aşırı yüklemesine atıfta bulunuyor, iki yineleyici aşırı yüklemesini kullanıyorum
Motti

53

Silme çağrısı yineleyicileri geçersiz kılar, şunları kullanabilirsiniz:

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

}

Ya da std :: remove_if'i bir functor ve std :: vector :: erase ile birlikte kullanabilirsiniz :

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

Bu durumda kendi functor'unuzu yazmak yerine std :: remove :

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

C ++ 11'de functor yerine lambda kullanabilirsiniz:

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'de std :: experimental :: erase ve std :: experimental :: erase_if de mevcuttur, C ++ 20'de bunlar (sonunda) std :: erase ve std :: erase_if olarak yeniden adlandırılır :

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

veya:

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

2
Equ_to kullanabilecekken neden kendi functor'unuzu kullanasınız? :-P sgi.com/tech/stl/equal_to.html
Chris Jester-Young

3
Bu arada, eraseile removearamak bunu yapmanın kanonik yoludur.
Konrad Rudolph

1
bence tam olarak bunu yapıyor. ancak kendi iirc işlevini kullanıyorsa remove_if kullanmalıdır. veya sadece işlevsiz kaldır seçeneğini kullanın
Johannes Schaub - litb

3
+1 Yazılan kod bir programlama yarışmasında bana yardımcı oldu, "sadece kaldır-sil deyimini kullan" bunu yapmadı.

13
  1. İndeks erişimini kullanarak yineleyebilirsiniz,

  2. O (n ^ 2) karmaşıklığından kaçınmak için iki indeks kullanabilirsiniz, i - mevcut test indeksi, j - indeksi sonraki öğeyi saklamak için ve döngünün sonunda vektörün yeni boyutu.

kod:

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

Böyle bir durumda yineleyicileri geçersiz kılmıyorsunuz, karmaşıklık O (n) ve kod çok kısa ve bazı yardımcı sınıflar yazmanıza gerek yok, ancak bazı durumlarda yardımcı sınıfları kullanmak daha esnek kodda fayda sağlayabilir.

Bu kod eraseyöntemi kullanmaz , ancak görevinizi çözer.

Pure stl kullanarak bunu şu şekilde yapabilirsiniz (bu Motti'nin cevabına benzer):

#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

Bunu neden yaptığınıza bağlı olarak, std :: set kullanmak std :: vector kullanmaktan daha iyi bir fikir olabilir.

Her elemanın yalnızca bir kez oluşmasına izin verir. Birden çok kez eklerseniz, yine de silinecek yalnızca bir örnek olacaktır. Bu, silme işlemini önemsiz hale getirecektir. Silme işlemi de vektöre göre daha düşük zaman karmaşıklığına sahip olacaktır, ancak sette eleman eklemek daha yavaştır, bu yüzden pek bir avantaj olmayabilir.

Vektörünüze bir öğenin kaç kez eklendiği veya öğelerin eklendiği sırayla ilgileniyorsanız, bu elbette işe yaramayacaktır.


-1

1. elementi silmek için şunları kullanabilirsiniz:

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

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

1
Soru bu değildi. 11 yıl sonraki cevabın alakalı görünmüyor Amit!
Kanatlı Asteroitler
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.