2012-03-05 18 views
5

Quiero eliminar algunos elementos de un vector y estoy usando el algoritmo remove_if para hacer esto. Pero quiero hacer un seguimiento de los elementos eliminados para poder realizar alguna operación sobre ellos más tarde. He intentado esto con el siguiente código:Realizar un seguimiento de los elementos eliminados usando std :: remove_if

#include <vector> 
#include <algorithm> 
#include <iostream> 

using namespace std; 


struct IsEven 
{ 
    bool operator()(int n) 
    { 
     if(n % 2 == 0) 
     { 
      evens.push_back(n); 
      return true; 
     } 

     return false; 
    } 

    vector<int> evens; 
}; 

int main(int argc, char **argv) 
{ 

    vector<int> v; 
    for(int i = 0; i < 10; ++i) 
    { 
     v.push_back(i); 
    } 

    IsEven f; 
    vector<int>::iterator newEnd = remove_if(v.begin(), v.end(), f); 
    for(vector<int>::iterator it = f.evens.begin(); it != f.evens.end(); ++it) 
    { 
     cout<<*it<<"\n"; 
    } 

    v.erase(newEnd, v.end()); 

    return 0; 
} 

Pero esto no funciona como remove_if acepta la copia de mi objeto funtor, por lo que el vector de la evens almacenada no es accesible. ¿Cuál es la forma correcta de lograr esto?

P.S. : El ejemplo, con par y probabilidades es solo por ejemplo sake, mi código real es algo diferente. Por lo tanto, no sugiera una forma de identificar pares o probabilidades de manera diferente.

+0

Puede pasar su functor por referencia. ¿Es aceptable una solución usando 'boost' o' C++ 11' (para pasar con 'ref')? – nabulke

+0

Puede hacer lo siguiente 'for (vector :: iterator it = newEnd; it! = V.end(); ++ it) { cout << * it <<" \ n "; } v.erase (newEnd, v.end()); 'para obtener resultados correctos sin incluir' evens' – megabyte1024

+0

@ megabyte1024: No, eso no funciona. No se garantiza que los elementos que están más allá de 'newEnd' sean los elementos eliminados. – Asha

Respuesta

9

La solución no es remove_if, pero es primo partial_sort partition. La diferencia es que remove_if solo garantiza que [begin, middle) contiene los elementos coincidentes, pero partition también garantiza que [middle, end) contiene los elementos que no coinciden con el predicado.

lo tanto, su ejemplo se convierte simplemente (tenga en cuenta que evens ya no es necesario):

vector<int>::iterator newEnd = partition(v.begin(), v.end(), f); 
for(vector<int>::iterator it = newEnd; it != v.end(); ++it) 
{ 
    cout<<*it<<"\n"; 
} 
v.erase(newEnd, v.end()); 
+0

¿'partial_sort' devuelve algo? Esto no se está compilando en mi sistema. – Asha

+0

Eh, ¿cómo lo logré? Reparando ... Lectura cruzada, creo. – MSalters

+0

Gracias ... No sabía esto ... Solo revertí mi condición de predicado y está funcionando bien ahora ... – Asha

0

El problema que veo con el código es que el vector evens que crea dentro de la estructura se crea cada vez que lo llama el algoritmo remove_if. Así que no importa si transfiere un funtor para eliminarlo, si crea un vector nuevo cada vez. Entonces, una vez que se elimina el último elemento y cuando la llamada a la función finaliza y sale de la función, los f.evens siempre buscarán un vector vacío. Esto podría ser clasificada en dos formas,

  1. Vuelva a colocar la estructura con una clase y declaran iguala como estática (si eso es lo que quería)
  2. o usted podría hacer que iguala global. Personalmente no lo recomendaría (hace que el código sea malo, digamos que no a los globales a menos que realmente los necesites).

Editar:

Como sugiere nabulke usted podría también algo std :: ref likke esto, std :: ref (f). Esto evita que el vector sea global y evita la estática innecesaria.

Una muestra de lo que es mundial, es el siguiente,

#include <vector> 
#include <algorithm> 
#include <iostream> 

using namespace std; 
vector<int> evens; 

struct IsEven 
{ 
    bool operator()(int n) 
    { 
     if(n % 2 == 0) 
     { 
      evens.push_back(n); 
      return true; 
     } 

     return false; 
    } 


}; 

int main(int argc, char **argv) 
{ 

    vector<int> v; 
    for(int i = 0; i < 10; ++i) 
    { 
     v.push_back(i); 
    } 

    IsEven f; 
    vector<int>::iterator newEnd = remove_if(v.begin(), v.end(), f); 
    for(vector<int>::iterator it = evens.begin(); it != evens.end(); ++it) 
    { 
     cout<<*it<<"\n"; 
    } 

    v.erase(newEnd, v.end()); 

    return 0; 
} 

Este código parece funcionar muy bien para mí. Avísame si esto no es lo que querías.

+0

¿No pasaría el funtor con la ayuda 'std :: ref'? – nabulke

+0

@nabulke: Hhhhmmm, supongo. Eso podría agregarse a la posible solución. – Ajai

1

Un nivel adicional de direccionamiento indirecto. Declare el vector localmente y tenga IsEven que contenga una copia. También es posible que IsEven a sean propietarios del vector, siempre que esté asignado dinámicamente y administrado por a shared_ptr. En la práctica, generalmente he encontrado la variable local más la solución de puntero más conveniente. Algo así como:

class IsEven 
{ 
    std::vector<int>* myEliminated; 
public: 
    IsEven(std::vector<int>* eliminated = NULL) 
     : myEliminated(eliminated) 
    { 
    } 
    bool 
    operator()(int n) const 
    { 
     bool results = n % 2 == 0; 
     if (results && myEliminated != NULL) { 
      myEliminated->push_back(n); 
     } 
     return results; 
    } 
} 

Tenga en cuenta que esto también permite que la función sea operator()()const. I creo que esto es formalmente necesario (aunque no estoy seguro).

+0

Le daría +1 si estaba usando referencia y no puntero. Y probablemente lo deje en plain-old-data e inicialice la variable local con el inicializador, cuando de todos modos tenga que crear uno para el vector. –

+0

@JanHudec Es un comportamiento indefinido si utiliza una referencia; los objetos funcionales deben ser CopyAssignable. Y dejándolo POD significa que no puede construir un temporal: mi patrón de uso sería declarar el vector, luego usar 'IsEven (& v)' como argumento para 'remove_if'. –

+0

Hm, no puedo encontrar la declaración definitiva de que tiene que ser asignable y no solo CopyConstructible, así que estoy algo confundido ahora. La referencia obviamente es CopyConstructible, pero no se puede asignar. –

2

Usted puede evitar copiar el funtor (es decir paso por valor) si pasa ist por referencia como esto:

vector<int>::iterator newEnd = remove_if(v.begin(), v.end(), 
    boost::bind<int>(boost::ref(f), _1)); 

Si no puede utilizar boost, lo mismo es posible con std::ref. Probé el código anterior y funciona como se esperaba.

+1

+1, pero tenga en cuenta que 'boost :: ref' es el predecesor de' std :: ref'. Primero se introdujo en boost, que en TR1 (muchos compiladores lo tienen como 'std :: tr1 :: ref' y finalmente se estandarizó en C++ 11. –

3

Su mejor apuesta es std::partition() que reordenará todos los elts en la secuencia, como todos los élts para los cuales su retorno de predicado true precederá aquellos para los que devuelve falso.

Ejemplo:

vector<int>::iterator bound = partition (v.begin(), v.end(), IsEven); 
std::cout << "Even numbers:" << std::endl; 
for (vector<int>::iterator it = v.begin(); it != bound; ++it) 
    std::cout << *it << " "; 

std::cout << "Odd numbers:" << std::endl; 
for (vector<int>::iterator it = bound; it != v.end(); ++it) 
    std::cout << *it << " "; 
0

Es posible que tenga otra solución; solo si no necesitas eliminar los elts al mismo tiempo (¿verdad?). Con std::for_each() que devuelve una copia de su functor. Ejemplo:

IsEven result = std::for_each(v.begin(), v.end(), IsEven()); 

// Display the even numbers. 
std::copy(result.evens.begin(), result.evens.end(), std::ostream_iterator<int> (cout, "\n")); 

tomar en cuenta que siempre es mejor para crear las variables identificadas en C++ cuando sea posible. Aquí, esa solución no responde exactamente a su problema principal (eliminar los elts del contenedor de origen), pero le recuerda a todos que std :: for_each() devuelve una copia de su functor. :-)

Cuestiones relacionadas