web-dev-qa-db-fra.com

remove_if équivalent pour std :: map

J'essayais d'effacer une série d'éléments de la carte en fonction de conditions particulières. Comment puis-je le faire en utilisant des algorithmes STL?

Au départ, j'ai pensé à utiliser remove_if mais ce n'est pas possible, car remove_if ne fonctionne pas pour le conteneur associatif.

Existe-t-il un algorithme équivalent à "remove_if" qui fonctionne pour map?

Comme une option simple, j'ai pensé à faire une boucle sur la carte et effacer. Mais la boucle sur la carte et l’effacement sont-ils une option sûre (les itérateurs devenant invalides après l’effacement)?

J'ai utilisé l'exemple suivant:

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;
}
102
aJ.

Presque. 

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

Ce que vous aviez à l’origine incrémenterait l’itérateur deux fois si vous effaciez un élément; vous pouvez potentiellement ignorer des éléments à effacer.

C'est un algorithme commun que j'ai vu utilisé et documenté à plusieurs endroits.

[EDIT] Vous avez raison de dire que les itérateurs sont invalidés après un effacement, mais que seuls les itérateurs référençant l'élément effacé, les autres itérateurs sont toujours valides. D'où l'utilisation de iter ++ dans l'appel erase ().

105
Steve Folly

erase_if pour std :: map (et autres conteneurs)

J'utilise le modèle suivant pour cette chose même.

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

Cela ne retournera rien, mais supprimera les éléments de std :: map.

Exemple d'utilisation:

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

Deuxième exemple (vous permet de transmettre une valeur de test):

// '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
});
64
Iron Savior

J'ai obtenu cette documentation de excellente référence SGI STL :

La carte a la propriété importante que insertion d'un nouvel élément dans une carte n'invalide pas les itérateurs que pointez sur des éléments existants. Effacer un L'élément d'une carte ne le fait pas non plus. invalider tous les itérateurs, à l'exception de Bien sûr, pour les itérateurs qui en fait pointez sur l'élément qui est en cours effacé.

Ainsi, l'itérateur que vous avez et qui pointe sur l'élément à effacer sera bien sûr invalidé. Faites quelque chose comme ça:

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

Le code d'origine n'a qu'un problème:

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

Ici, la iter est incrémentée une fois dans la boucle for et une autre fois dans erase, ce qui aboutira probablement dans une boucle infinie.

2
partha biswas

Premier

Map a la propriété importante que l'insertion d'un nouvel élément dans une carte n'invalide pas les itérateurs qui pointent sur des éléments existants. Effacer un élément d'une carte n'invalide également aucun itérateur, à l'exception bien sûr des itérateurs qui pointent réellement sur l'élément en cours d'effacement.

Deuxièmement, le code suivant est bon

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

Lors de l'appel d'une fonction, les paramètres sont évalués avant l'appel de cette fonction.

Ainsi, lorsque iter ++ est évalué avant l'appel à effacer, l'opérateur ++ de l'itérateur renvoie l'élément en cours et pointe vers l'élément suivant après l'appel.

1
Vincent

Basé sur La réponse d'Iron Savior Pour ceux qui voudraient fournir une gamme plus proche des itérateurs de prise de fonction fonctionnelle.

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

Curieux de savoir s’il existe un moyen de perdre les éléments ContainerT et de le récupérer à partir de l’itérateur.

1
Greg Domjan

IMHO il n'y a pas d'équivalent remove_if().
Vous ne pouvez pas réorganiser une carte.
Donc, remove_if() ne peut pas mettre vos paires d’intérêts à la fin sur laquelle vous pouvez appeler erase().

1
user109134

À partir des notes de bas de:

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

un conteneur associatif par paire ne peut pas fournir d'itérateurs mutables (tels que définis dans les exigences de l'Itérateur trivial), car le type de valeur d'un itérateur mutable doit être Assignable, et Pair n'est pas assignable. Cependant, un conteneur associatif par paire peut fournir des itérateurs qui ne sont pas complètement constants: des itérateurs tels que l'expression (* i) .second = d est valide. 

1
piotr

Désormais, std::experimental::erase_if est disponible dans l'en-tête <experimental/map>.

Voir: http://fr.cppreference.com/w/cpp/experimental/map/erase_if

1
user1633272

La réponse de Steve Folly Je me sens plus efficace.

Voici une autre solution facile mais moins efficace

La solution utilise remove_copy_if pour copier les valeurs souhaitées dans un nouveau conteneur, puis échange le contenu du conteneur d'origine avec ceux du nouveau:

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);
0
aJ.

Si vous souhaitez effacer tous les éléments dont la clé est supérieure à 2, le meilleur moyen est

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

Ne fonctionne que pour les gammes, pas pour les prédicats.

0
Tadeusz Kopec

J'utilise comme ça

 std::map<int, std::string> users;    
 for(auto it = users.begin(); it <= users.end()) {
    if(<condition>){
      it = users.erase(it);
    } else {
    ++it;
    }
 }
0
voltento