remove_if equivalente para std :: map

118

Estaba tratando de borrar una variedad de elementos del mapa en función de una condición particular. ¿Cómo lo hago usando algoritmos STL?

Inicialmente pensé en usar, remove_ifpero no es posible ya que remove_if no funciona para el contenedor asociativo.

¿Existe algún algoritmo equivalente "remove_if" que funcione para el mapa?

Como una opción simple, pensé en recorrer el mapa y borrar. Pero, ¿recorrer el mapa y borrar es una opción segura? (Ya que los iteradores se vuelven inválidos después del borrado)

Usé el siguiente ejemplo:

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;
}
aJ.
fuente
¿Qué quieres decir con que remove_if no funciona?
dirkgently
No puedo usar remove_if para encontrar un elemento en el mapa, ¿verdad? Dio un error de tiempo de compilación. ¿Me estoy perdiendo de algo?
aJ.
No, no funciona como remove_if funciona reordenando una secuencia, moviendo elementos que fallan en la condición hacia el final. Por tanto, funciona en un T [n], pero no en un mapa <T, U>.
MSalters
2
Con C + 11, puede utilizarlo for(auto iter=aMap.begin(); iter!=aMap.end(); ){ ....}para reducir el desorden. El descanso es como decían otros. Esta pregunta me salvó un poco de división del cabello en este momento ;-)
Atul Kumar

Respuestas:

111

Casi.

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

Lo que tenía originalmente incrementaría el iterador dos veces si borrara un elemento de él; potencialmente podría omitir elementos que deben borrarse.

Este es un algoritmo común que he visto utilizado y documentado en muchos lugares.

[EDITAR] Tiene razón en que los iteradores se invalidan después de un borrado, pero solo los iteradores que hacen referencia al elemento que se borra, otros iteradores siguen siendo válidos. Por lo tanto, usar iter++en la erase()llamada.

Steve Folly
fuente
4
Estoy confundido; ¿Por qué usarías for (; ...;) en lugar de while (...)? Además, aunque esto probablemente funcione, ¿no devuelve .erase un iterador del siguiente? Así que parece que el blog if (Some Condition) debería ser iter = aMap.erase (iter) para ser el más compatible. ¿Quizás me estoy perdiendo algo? Me falta la experiencia que tienen algunos de ustedes.
taxilian
86
Tenga en cuenta que en C ++ 11 todos los contenedores asociativos, incluido map, devuelven el siguiente iterador de erase(iter). Es mucho más limpio de hacer iter = erase( iter ).
Potatoswatter
10
@taxilian (años tarde) while () o for () funcionarían, pero semánticamente, la gente suele usar for () para iterar sobre un rango conocido, y while () para un número desconocido de bucles. Dado que el rango se conoce en este caso (desde el principio hasta endIter ), for () no sería una elección inusual y probablemente sería más común. Pero nuevamente, ambos serían aceptables.
Jamin Gray
4
@taxilian Más importante: con 'for', puede tener la definición de su iterador DENTRO del alcance del bucle, para que no interfiera con el resto de su programa.
Sanchises
1
@athos La pregunta está formulada en voz pasiva, "se recomienda". No existe una recomendación universal. Creo que mi último comentario es la forma más sencilla. Implica dos copias de la variable iteradora, que pierde un poco de eficiencia como alguien señaló aquí. Es tu decisión lo que es apropiado para ti.
Potatoswatter
75

erase_if para std :: map (y otros contenedores)

Utilizo la siguiente plantilla para esto mismo.

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

Esto no devolverá nada, pero eliminará los elementos del std :: map.

Ejemplo de uso:

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

Segundo ejemplo (le permite pasar un valor de prueba):

// '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
});
Salvador de hierro
fuente
3
@CodeAngry Gracias, siempre me pareció extraño que esto no existiera en std. Entiendo por qué no es miembro de std::map, pero creo que algo así debería estar en la biblioteca estándar.
Iron Savior
3
Se agregará en C ++ 20 parastd::map y otros.
Roi Danton
3

Obtuve esta documentación de la excelente referencia SGI STL :

Map tiene la propiedad importante de que insertar un nuevo elemento en un mapa no invalida los iteradores que apuntan a elementos existentes. Borrar un elemento de un mapa tampoco invalida ningún iterador, excepto, por supuesto, para los iteradores que realmente apuntan al elemento que se está borrando.

Por lo tanto, el iterador que tiene que apunta al elemento que se va a borrar, por supuesto, se invalidará. Haz algo como esto:

if (some condition)
{
  iterator here=iter++;
  aMap.erase(here)
}
1800 INFORMACIÓN
fuente
3
Este no es diferente al código original. iter ++ incrementa el iterador y luego devuelve un iterador que apunta al elemento antes del incremento.
Steve Folly
Pero iter no será invalidado ya que luego borramos en la posición de aquí
1800 INFORMACIÓN
@ 1800INFORMACIÓN: ingresar una llamada de función es un punto de secuencia, el efecto secundario del incremento se evalúa antes de erasellamar. Entonces, de hecho, son equivalentes. Aún así, preferiría su versión sobre la original.
Peterchen
Funciona para matrices o vectores, pero provocará un resultado inesperado en el mapa stl.
hunter_tech
2

El código original tiene un solo problema:

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

Aquí iterse incrementa una vez en el ciclo for y otra vez en el borrado, lo que probablemente terminará en un ciclo infinito.

partha biswas
fuente
2

Aquí tienes una solución elegante.

for (auto it = map.begin(); it != map.end();)
{   
    (SomeCondition) ? map.erase(it++) : (++it);
}
Raíz de mandrágora
fuente
1

De las notas inferiores de:

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

un contenedor asociativo de pares no puede proporcionar iteradores mutables (como se define en los requisitos del iterador trivial), porque el tipo de valor de un iterador mutable debe ser asignable y el par no es asignable. Sin embargo, un contenedor asociativo de pares puede proporcionar iteradores que no son completamente constantes: iteradores tales que la expresión (* i) .second = d sea válida.

piotr
fuente
1

primero

Map tiene la propiedad importante de que insertar un nuevo elemento en un mapa no invalida los iteradores que apuntan a elementos existentes. Borrar un elemento de un mapa tampoco invalida ningún iterador, excepto, por supuesto, los iteradores que realmente apuntan al elemento que se está borrando.

En segundo lugar, el siguiente código es bueno

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

Al llamar a una función, los parámetros se evalúan antes de la llamada a esa función.

Entonces, cuando iter ++ se evalúa antes de la llamada a borrar, el operador ++ del iterador devolverá el elemento actual y apuntará al siguiente elemento después de la llamada.

Vincent
fuente
1

En mi humilde opinión, no hay remove_if()equivalente.
No puede reordenar un mapa.
Así remove_if()que no puedes poner tus pares de interés al final al que puedes llamar erase().

usuario109134
fuente
Eso es realmente lamentable.
allyourcode
1

Basado en la respuesta de Iron Savior Para aquellos que deseen proporcionar un rango más similar a los iteradores de toma funcionales estándar.

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

Es curioso si hay alguna forma de perder los ContainerTelementos y obtenerlos del iterador.

Greg Domjan
fuente
1
"Los identificadores que comienzan con un guión bajo seguido de una letra mayúscula están reservados para todo uso por parte de la implementación".
YSC
0

La respuesta de Steve Folly me siento más eficiente.

Aquí hay otra solución fácil pero menos eficiente :

La solución usa remove_copy_ifpara copiar los valores que queremos en un nuevo contenedor, luego intercambia el contenido del contenedor original con los del nuevo:

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);
aJ.
fuente
2
Eso parece ineficaz.
allyourcode
0

Si desea borrar todos los elementos con clave mayor que 2, entonces la mejor manera es

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

Sin embargo, solo funciona para rangos, no para ningún predicado.

Tadeusz Kopec
fuente
0

Yo uso así

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