remove_if equivalente per std :: map


118

Stavo cercando di cancellare una serie di elementi dalla mappa in base a condizioni particolari. Come lo faccio utilizzando algoritmi STL?

Inizialmente pensavo di utilizzare remove_ifma non è possibile in quanto remove_if non funziona per i container associativi.

Esiste un algoritmo equivalente "remove_if" che funziona per map?

Come semplice opzione, ho pensato di scorrere la mappa e cancellare. Ma scorrere la mappa e cancellare è un'opzione sicura? (Poiché gli iteratori non sono più validi dopo la cancellazione)

Ho usato il seguente esempio:

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

Cosa vuoi dire che remove_if non funziona?
direttamente il

Non posso usare remove_if per trovare un elemento nella mappa, giusto? Ha dato un errore in fase di compilazione. Mi sto perdendo qualcosa?
aJ.

No, non funziona poiché remove_if funziona riordinando una sequenza, spostando gli elementi che non soddisfano la condizione verso la fine. Quindi funziona su un T [n], ma non su una mappa <T, U>.
MSalters

2
Con C + 11, puoi usare for(auto iter=aMap.begin(); iter!=aMap.end(); ){ ....}per ridurre il disordine. Il riposo è come altri hanno detto. Questa domanda mi ha risparmiato un po 'di spaccature solo ora ;-)
Atul Kumar

Risposte:


111

Quasi.

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

Ciò che avevi originariamente incrementerebbe l'iteratore due volte se cancellassi un elemento da esso; potresti potenzialmente saltare gli elementi che dovevano essere cancellati.

Questo è un algoritmo comune che ho visto usato e documentato in molti posti.

[EDIT] Hai ragione sul fatto che gli iteratori vengono invalidati dopo una cancellazione, ma solo gli iteratori che fanno riferimento all'elemento che è stato cancellato, gli altri iteratori sono ancora validi. Quindi usando iter++nella erase()chiamata.


4
Non ho capito bene; perché dovresti usare for (; ...;) invece di while (...)? Inoltre, sebbene questo probabilmente funzioni, .erase non restituisce un iteratore di quello successivo? Quindi sembra che il blog if (Some Condition) dovrebbe essere iter = aMap.erase (iter) per essere il più compatibile. Forse mi manca qualcosa? Mi manca l'esperienza che alcuni di voi hanno.
taxilian

86
Nota, in C ++ 11 tutti i contenitori associativi, incluso map, restituiscono l'iteratore successivo da erase(iter). È molto più pulito da fare iter = erase( iter ).
Potatoswatter

10
@taxilian (anni di ritardo) while () o for () funzionerebbero, ma semanticamente, le persone spesso usano for () per iterare su un intervallo noto e while () per un numero sconosciuto di cicli. Poiché l'intervallo è noto in questo caso (dall'inizio, a endIter ), for () non sarebbe una scelta insolita e sarebbe probabilmente più comune. Ma ancora una volta, entrambi sarebbero accettabili.
Jamin Grey

4
@taxilian Ancora più importante: con 'for', puoi avere la definizione dell'iteratore ALL'INTERNO dell'ambito del ciclo, in modo che non interferisca con il resto del programma.
Sanchises

1
@athos La domanda è formulata con voce passiva, "è consigliato". Non esiste una raccomandazione universale. Penso che il mio ultimo commento sia il modo più diretto. Coinvolge due copie della variabile iteratore, che perde un po 'di efficienza come qualcuno ha sottolineato qui. È la tua chiamata ciò che è appropriato per te.
Potatoswatter

75

erase_if per std :: map (e altri contenitori)

Uso il seguente modello proprio per questa cosa.

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

Questo non restituirà nulla, ma rimuoverà gli elementi da std :: map.

Esempio di utilizzo:

// '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 */;
});

Secondo esempio (consente di passare un valore di prova):

// '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 Grazie - mi è sempre sembrato strano che questo non esistesse già in std. Capisco perché non è un membro di std::map, ma penso che qualcosa di simile dovrebbe essere nella libreria standard.
Iron Savior

3
Verrà aggiunto in C ++ 20 perstd::map e altri.
Roi Danton


3

Ho ottenuto questa documentazione dall'eccellente riferimento SGI STL :

La mappa ha l'importante proprietà che l'inserimento di un nuovo elemento in una mappa non invalida gli iteratori che puntano a elementi esistenti. Anche la cancellazione di un elemento da una mappa non invalida alcun iteratore, tranne, ovviamente, per gli iteratori che puntano effettivamente all'elemento che viene cancellato.

Quindi, l'iteratore che hai che punta all'elemento da cancellare sarà ovviamente invalidato. Fai qualcosa di simile:

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

3
Questo non è diverso dal codice originale. iter ++ incrementa l'iteratore quindi restituisce un iteratore che punta all'elemento prima dell'incremento.
Steve Folly

Ma l'iter non verrà invalidato poiché cancelleremo nella posizione di qui
1800 INFORMAZIONI

@ 1800INFORMATION: l'immissione di una chiamata di funzione è un punto della sequenza, l'effetto collaterale dell'incremento viene valutato prima di eraseessere chiamato. Quindi sono davvero equivalenti. Tuttavia, preferirei fortemente la tua versione all'originale.
Peterchen

Funziona per array o vettore, ma causerà risultati imprevisti nella mappa stl.
hunter_tech

2

Il codice originale ha un solo problema:

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

Qui iterviene incrementato una volta nel ciclo for e un'altra volta in erase, che probabilmente finirà in un ciclo infinito.


2

Ecco una soluzione elegante.

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

1

Dalle note di fondo di:

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

un contenitore associativo di coppia non può fornire iteratori modificabili (come definito nei requisiti di Trivial Iterator), perché il tipo di valore di un iteratore modificabile deve essere assegnabile e la coppia non è assegnabile. Tuttavia, un contenitore associativo di coppia può fornire iteratori che non sono completamente costanti: iteratori tali che l'espressione (* i) .second = d sia valida.


1

Primo

La mappa ha l'importante proprietà che l'inserimento di un nuovo elemento in una mappa non invalida gli iteratori che puntano a elementi esistenti. Anche la cancellazione di un elemento da una mappa non invalida alcun iteratore, tranne, ovviamente, per gli iteratori che puntano effettivamente all'elemento che viene cancellato.

In secondo luogo, il codice seguente è buono

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

Quando si chiama una funzione, i parametri vengono valutati prima della chiamata a quella funzione.

Quindi, quando l'iter ++ viene valutato prima della chiamata da cancellare, l'operatore ++ dell'iteratore restituirà l'elemento corrente e punterà all'elemento successivo dopo la chiamata.


1

IMHO non c'è remove_if()equivalente.
Non puoi riordinare una mappa.
Quindi remove_if()non puoi mettere le tue coppie di interesse alla fine su cui puoi chiamare erase().


È davvero un peccato.
allyourcode

1

Basato sulla risposta di Iron Savior Per coloro che desiderano fornire una gamma più simile agli iteratori di presa funzionale standard.

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

Curioso se c'è un modo per perdere gli ContainerTelementi e ottenerli dall'iteratore.


1
"Gli identificatori che iniziano con un trattino basso seguito da una lettera maiuscola sono riservati per tutto l'utilizzo da parte dell'implementazione."
YSC

0

La risposta di Steve Folly mi sembra la più efficiente.

Ecco un'altra soluzione facile ma meno efficiente :

La soluzione utilizza remove_copy_ifper copiare i valori che vogliamo in un nuovo contenitore, quindi scambia il contenuto del contenitore originale con quelli di quello nuovo:

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
Sembra inefficiente.
allyourcode

0

Se vuoi cancellare tutti gli elementi con chiave maggiore di 2, il modo migliore è

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

Tuttavia, funziona solo per intervalli, non per alcun predicato.


0

Lo uso in questo modo

 std::map<int, std::string> users;    
 for(auto it = users.begin(); it <= users.end()) {
    if(<condition>){
      it = users.erase(it);
    } else {
    ++it;
    }
 }
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.