std :: map için remove_if eşdeğeri


118

Belirli bir duruma göre haritadan bir dizi öğeyi silmeye çalışıyordum. STL algoritmalarını kullanarak bunu nasıl yaparım?

Başlangıçta kullanmayı düşündüm remove_ifama bu mümkün değil, çünkü remove_if ilişkisel kapsayıcı için çalışmıyor.

Harita için çalışan herhangi bir "remove_if" eşdeğeri algoritması var mı?

Basit bir seçenek olarak, haritayı dolaşıp silmeyi düşündüm. Ancak haritada döngü yapmak ve güvenli bir seçeneği silmek mi? (Silindikten sonra yineleyiciler geçersiz hale geldikçe)

Aşağıdaki örneği kullandım:

bool predicate(const std::pair<int,std::string>& x)
{
    return x.first > 2;
}

int main(void) 
{

    std::map<int, std::string> aMap;

    aMap[2] = "two";
    aMap[3] = "three";
    aMap[4] = "four";
    aMap[5] = "five";
    aMap[6] = "six";

//      does not work, an error
//  std::remove_if(aMap.begin(), aMap.end(), predicate);

    std::map<int, std::string>::iterator iter = aMap.begin();
    std::map<int, std::string>::iterator endIter = aMap.end();

    for(; iter != endIter; ++iter)
    {
            if(Some Condition)
            {
                            // is it safe ?
                aMap.erase(iter++);
            }
    }

    return 0;
}

Remove_if çalışmıyorsa ne demek istiyorsun?
Dirkgently

Haritadaki bir öğeyi bulmak için remove_if kullanamıyorum, değil mi? Derleme zamanı hatası verdi. Bir şey mi kaçırıyorum?
aJ.

Hayır - remove_if, bir diziyi yeniden sıralayarak, koşulu sona erdiren öğeleri taşıyarak çalıştığı için çalışmaz. Dolayısıyla bir T [n] üzerinde çalışır, ancak <T, U> haritasında çalışmaz.
MSalters

2
C + 11 ile for(auto iter=aMap.begin(); iter!=aMap.end(); ){ ....}dağınıklığı azaltmak için kullanabilirsiniz . Diğerlerinin söylediği gibi dinlenme. Bu soru az önce saçlarımı kırmaktan kurtardı ;-)
Atul Kumar

Yanıtlar:


111

Neredeyse.

for(; iter != endIter; ) {
     if (Some Condition) {
          iter = aMap.erase(iter);
     } else {
          ++iter;
     }
}

Eğer ondan bir eleman silseydiniz, orijinalde yineleyiciyi iki kez artıracaktı ; silinmesi gereken öğeleri potansiyel olarak atlayabilirsiniz.

Bu, birçok yerde kullanıldığını ve belgelendiğini gördüğüm yaygın bir algoritmadır.

[DÜZENLE] Yinelemelerin bir silme işleminden sonra geçersiz kılındığı konusunda haklısınız, ancak yalnızca silinen öğeye başvuran yineleyiciler, diğer yineleyiciler hala geçerli. Dolayısıyla kullanarak iter++içinde erase()çağrı.


4
Kafam karıştı; neden while (...) yerine (; ...;) kullanasın? Ayrıca, bu muhtemelen işe yarasa da, .erase bir sonrakinin yineleyicisini döndürmez mi? Bu nedenle, if (Some Condition) blogu en uyumlu olması için iter = aMap.erase (iter) olmalı gibi görünüyor. Belki bir şeyi kaçırıyorum? Bazılarınızın sahip olduğu deneyimden yoksunum.
Taxilian

86
C ++ 11'de, dahil olmak üzere tüm ilişkili kapsayıcıların mapsonraki yineleyiciyi erase(iter). Yapması çok daha temiz iter = erase( iter ).
Potatoswatter

10
@taxilian (yıllar geç) while () veya for () çalışır, ancak anlamsal olarak, insanlar genellikle for () 'u bilinen bir aralıkta yineleme için ve while () bilinmeyen sayıda döngü için kullanır. Bu durumda aralık bilindiğinden (baştan endIter'e kadar ), for () alışılmadık bir seçim olmayacak ve muhtemelen daha yaygın olacaktır. Ama yine, her ikisi de kabul edilebilir.
Jamin Grey

4
@taxilian Daha da önemlisi: 'for' ile yineleyici tanımınızı döngünün kapsamının İÇİNDE alabilirsiniz, böylece programınızın geri kalanıyla karışmaz.
Sanchises

1
@athos Soru pasif sesle ifade edilir, "tavsiye edilir." Evrensel bir öneri yok. Son yorumumun en basit yol olduğunu düşünüyorum. Yineleyici değişkeninin iki kopyasını içerir ve bu, birinin burada belirttiği gibi biraz verimliliğini kaybeder. Sizin için uygun olan sizin kararınız.
Potatoswatter

75

std :: map (ve diğer kapsayıcılar) için erase_if

Bunun için aşağıdaki şablonu kullanıyorum.

namespace stuff {
  template< typename ContainerT, typename PredicateT >
  void erase_if( ContainerT& items, const PredicateT& predicate ) {
    for( auto it = items.begin(); it != items.end(); ) {
      if( predicate(*it) ) it = items.erase(it);
      else ++it;
    }
  }
}

Bu hiçbir şey döndürmez, ancak öğeleri std :: map'den kaldırır.

Kullanım örneği:

// 'container' could be a std::map
// 'item_type' is what you might store in your container
using stuff::erase_if;
erase_if(container, []( item_type& item ) {
  return /* insert appropriate test */;
});

İkinci örnek (bir test değerini geçmenize izin verir):

// 'test_value' is value that you might inject into your predicate.
// 'property' is just used to provide a stand-in test
using stuff::erase_if;
int test_value = 4;  // or use whatever appropriate type and value
erase_if(container, [&test_value]( item_type& item ) {
  return item.property < test_value;  // or whatever appropriate test
});

3
@CodeAngry Teşekkürler - bunun zaten mevcut olmaması bana her zaman garip geldi std. Neden üye olmadığını anlıyorum std::mapama bence standart kitaplıkta buna benzer bir şey olmalı.
Iron Savior

3
Ve diğerleri için C ++ 20'destd::map eklenecektir .
Roi Danton


3

Bu dokümantasyonu mükemmel SGI STL referansından aldım :

Harita, haritaya yeni bir öğe eklemenin mevcut öğeleri gösteren yineleyicileri geçersiz kılmaması gibi önemli bir özelliğe sahiptir. Bir öğenin haritadan silinmesi, elbette silinen öğeye gerçekten işaret eden yineleyiciler dışında hiçbir yineleyiciyi geçersiz kılmaz.

Dolayısıyla, silinecek öğeye işaret eden sahip olduğunuz yineleyici elbette geçersiz kılınacaktır. Bunun gibi bir şey yapın:

if (some condition)
{
  iterator here=iter++;
  aMap.erase(here)
}

3
Bu, orijinal koddan farklı değildir. iter ++ yineleyiciyi artırır ve ardından artımdan önceki öğeye işaret eden bir yineleyici döndürür.
Steve Folly

Ancak daha sonra buradaki konumunda sildiğimiz için yineleme geçersiz olmayacak
1800 BİLGİ

@ 1800 BİLGİ: bir işlev çağrısı girmek bir sıra noktasıdır, artma yan etkisi eraseçağrılmadan önce değerlendirilir . Yani gerçekten eşdeğerler. Yine de, versiyonunuzu orijinaline tercih ederim.
peterchen

Dizi veya vektör için çalışır, ancak stl eşleminde beklenmedik sonuçlara neden olur.
hunter_tech

2

Orijinal kodun yalnızca bir sorunu vardır:

for(; iter != endIter; ++iter)
{
    if(Some Condition)
    {
        // is it safe ?
        aMap.erase(iter++);
    }
}

Burada, iterbir kez for döngüsünde ve başka bir silme işleminde artırılır, bu muhtemelen sonsuz bir döngüde sona erecektir.


2

İşte bazı zarif çözüm.

for (auto it = map.begin(); it != map.end();)
{   
    (SomeCondition) ? map.erase(it++) : (++it);
}

1

Alt notlardan:

http://www.sgi.com/tech/stl/PairAssociativeContainer.html

Bir Çift İlişkilendirilebilir Konteyner, değiştirilebilir yineleyiciler sağlayamaz (Trivial Yineleyici gereksinimlerinde tanımlandığı gibi), çünkü değiştirilebilir bir yineleyicinin değer türü Atanabilir olmalıdır ve çift Atanabilir değildir. Bununla birlikte, bir Çift İlişkilendirilebilir Konteyner, tamamen sabit olmayan yineleyiciler sağlayabilir: (* i) .second = d ifadesi geçerli olacak şekilde yineleyiciler.


1

İlk

Harita, haritaya yeni bir öğe eklemenin mevcut öğeleri gösteren yineleyicileri geçersiz kılmaması gibi önemli bir özelliğe sahiptir. Bir öğenin haritadan silinmesi, elbette silinen öğeye gerçekten işaret eden yineleyiciler dışında hiçbir yineleyiciyi geçersiz kılmaz.

İkincisi, aşağıdaki kod iyidir

for(; iter != endIter; )
{
    if(Some Condition)
    {
        aMap.erase(iter++);
    }
    else
    {
        ++iter;
    }
}

Bir işlev çağrılırken, parametreler o işlev çağrılmadan önce değerlendirilir.

Bu nedenle, iter ++ silme çağrısından önce değerlendirildiğinde, yineleyicinin ++ operatörü geçerli öğeyi döndürür ve çağrıdan sonra bir sonraki öğeyi işaret eder.


1

IMHO remove_if()muadili yoktur .
Bir haritayı yeniden sıralayamazsınız.
Böylece remove_if()ilgilendiğiniz çiftleri arayabileceğiniz sonuna koyamazsınız erase().


Bu gerçekten talihsiz bir durum.
allyourcode

1

Dayanarak Demir Kurtarıcımız'ın cevap std fonksiyonel alma Yineleyicilerin çizgisinde daha bir dizi sağlamak istiyorum olanlar için.

template< typename ContainerT, class FwdIt, class Pr >
void erase_if(ContainerT& items, FwdIt it, FwdIt Last, Pr Pred) {
    for (; it != Last; ) {
        if (Pred(*it)) it = items.erase(it);
        else ++it;
    }
}

ContainerTÖğeleri kaybetmenin ve bunu yineleyiciden almanın bir yolu olup olmadığını merak ediyorum .


1
"Alt çizgiyle başlayan ve ardından büyük harfle gelen tanımlayıcılar, uygulama tarafından tüm kullanım için ayrılmıştır."
YSC

0

Steve Folly'nin cevabı Daha verimli hissediyorum.

İşte başka bir kolay ama daha az verimli çözüm :

Çözüm, remove_copy_ifistediğimiz değerleri yeni bir kaba kopyalamak için kullanır , ardından orijinal kabın içeriğini yenisininkilerle değiştirir:

std::map<int, std::string> aMap;

...
//Temporary map to hold the unremoved elements
std::map<int, std::string> aTempMap;

//copy unremoved values from aMap to aTempMap
std::remove_copy_if(aMap.begin(), aMap.end(), 
                    inserter(aTempMap, aTempMap.end()),
                    predicate);

//Swap the contents of aMap and aTempMap
aMap.swap(aTempMap);

2
Bu verimsiz görünüyor.
allyourcode

0

2'den büyük anahtara sahip tüm öğeleri silmek istiyorsanız, en iyi yol

map.erase(map.upper_bound(2), map.end());

Herhangi bir yüklem için değil, yalnızca aralıklar için çalışır.


0

Ben böyle kullanıyorum

 std::map<int, std::string> users;    
 for(auto it = users.begin(); it <= users.end()) {
    if(<condition>){
      it = users.erase(it);
    } else {
    ++it;
    }
 }
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.