C ++, copia establecida en vector

146

Necesito copiar std::seta std::vector:

std::set <double> input;
input.insert(5);
input.insert(6);

std::vector <double> output;
std::copy(input.begin(), input.end(), output.begin()); //Error: Vector iterator not dereferencable

¿Dónde está el problema?

CocodriloDundee
fuente
55
también hay assign()función:output.assign(input.begin(), input.end());
Gene Bushuyev
Su vector está vacío. Hay una multitud de formas de remediar eso, aunque las personas están señalando a continuación.
AJG85
@Gene: asignar () quiere reservar () la cantidad necesaria de almacenamiento por adelantado. Utilizará los iteradores de entrada para determinar cuánto se necesita, a menos que los iteradores sean estrictamente InputIterator, en cuyo caso se saltará la reserva y dará lugar a reasignaciones en cada push_back (). En el extremo opuesto del espectro, los iteradores bidireccionales le permitirían restar el extremo, comenzar. Sin embargo, los iteradores de std :: set no son ninguno (son ForwardIterator), y eso es desafortunado: en este caso, asignar () solo recorrerá todo el conjunto para determinar su tamaño: mal rendimiento en conjuntos grandes.
Sergey Shevchenko

Respuestas:

213

Necesitas usar un back_inserter:

std::copy(input.begin(), input.end(), std::back_inserter(output));

std::copyno agrega elementos al contenedor en el que está insertando: no puede; solo tiene un iterador en el contenedor. Debido a esto, si pasa un iterador de salida directamente std::copy, debe asegurarse de que apunte a un rango que sea al menos lo suficientemente grande como para contener el rango de entrada.

std::back_insertercrea un iterador de salida que llama push_backa un contenedor para cada elemento, por lo que cada elemento se inserta en el contenedor. Alternativamente, podría haber creado un número suficiente de elementos en el std::vectorpara mantener el rango que se copia:

std::vector<double> output(input.size());
std::copy(input.begin(), input.end(), output.begin());

O bien, puede usar el std::vectorconstructor de rango:

std::vector<double> output(input.begin(), input.end()); 
James McNellis
fuente
3
Hola James, en lugar de tu línea std :: copy (el primer bloque de código en tu respuesta), ¿no podría simplemente hacerlo output.insert(output.end(), input.begin(), input.end());?
user2015453
o simplemente use la versión cbegin y cend: output.insert(output.cend(), input.cbegin(), input.cend());¿Qué le parece? Gracias.
user2015453
2
¿Debería output.reserve (input.size ()); solo o puedo esperar que algún compilador lo haga por mí?
jimifiki
@ jimifiki, no tengo miedo.
Alexis Wilke
Su primera inicialización de vector es incorrecta. Crea una matriz de input,size()entradas vacías y luego agrega los anexos después de eso. Creo que te refieres a usar std::vector<double> output; output.reserve(input.size()); std::copy(...);.
Alexis Wilke
121

Simplemente use el constructor para el vector que toma iteradores:

std::set<T> s;

//...

std::vector v( s.begin(), s.end() );

Asume que solo desea el contenido de s en v, y no hay nada en v antes de copiar los datos.

Jacob
fuente
42

Aquí hay otra alternativa usando vector::assign:

theVector.assign(theSet.begin(), theSet.end());
PelucheC
fuente
24

No ha reservado suficiente espacio en su objeto vectorial para contener el contenido de su conjunto.

std::vector<double> output(input.size());
std::copy(input.begin(), input.end(), output.begin());
Marlon
fuente
1
Esto no merece -1. En particular, esto permite que el vector solo haga una asignación (ya que no puede determinar la distancia de los iteradores establecidos en O (1)) y, si no se definió para el vector poner a cero cada elemento cuando se construye, esto podría merece la pena permitir que la copia se reduzca a una memoria. Esto último aún podría valer la pena si la implementación descubre que se puede eliminar el bucle en el ctor del vector. Por supuesto, lo primero también se puede lograr con reserva.
Fred Nurk
No lo sé. Dejame ayudarte con eso.
wilhelmtell
Te di un -1, pero fue un thinko de mi parte. Realice una pequeña edición para que pueda deshacer mi voto, y le daré un +1: esta es en realidad una solución muy limpia debido a la propiedad de fallar primero.
Fred Foo
Me acabo de dar cuenta de que si edito la respuesta yo mismo, puedo hacer un voto a favor. Hizo eso, le dio un +1 para la asignación de memoria de fallar primero. ¡Lo siento!
Fred Foo
3

Creo que la forma más eficiente es preasignar y luego colocar elementos:

template <typename T>
std::vector<T> VectorFromSet(const std::set<T>& from)
{
    std::vector<T> to;
    to.reserve(from.size());

    for (auto const& value : from)
        to.emplace_back(value);

    return to;
}

De esa manera, solo invocaremos el constructor de copia para cada elemento en lugar de llamar primero al constructor predeterminado y luego al operador de asignación de copia para otras soluciones enumeradas anteriormente. Más aclaraciones a continuación.

  1. se puede usar back_inserter pero invocará push_back () en el vector ( https://en.cppreference.com/w/cpp/iterator/back_insert_iterator ). emplace_back () es más eficiente porque evita crear un temporal cuando se usa push_back () . No es un problema con los tipos construidos trivialmente, pero será una implicación de rendimiento para los tipos construidos no trivialmente (por ejemplo, std :: string).

  2. Tenemos que evitar construir un vector con el argumento de tamaño que hace que todos los elementos se construyan por defecto (para nada). Al igual que con la solución usando std :: copy () , por ejemplo.

  3. Y, finalmente, el método vector :: asignar () o el constructor que toma el rango del iterador no son buenas opciones porque invocarán std :: distance () (para conocer el número de elementos) en los iteradores establecidos . Esto hará que la iteración adicional no deseada a través de los todos los establecidos los elementos, porque el conjunto es la estructura de datos de árbol de búsqueda binaria y no implementa iteradores de acceso aleatorio.

Espero que ayude.

dshvets1
fuente
agregue una referencia a una autoridad por qué esto es rápido y algo así como por qué back_inserterno es necesario utilizar a
Tarick Welling
Se agregaron más aclaraciones en la respuesta.
dshvets1
1

std::copyno se puede usar para insertar en un contenedor vacío. Para hacer eso, necesita usar un insert_iterator así:

std::set<double> input;
input.insert(5);
input.insert(6);

std::vector<double> output;
std::copy(input.begin(), input.end(), inserter(output, output.begin())); 
Bradley Swain
fuente
3
Esto falla la primera vez que el vector se reasigna: el iterador de output.begin () se invalida.
Fred Nurk