C ++, copia impostata su vettoriale


146

Devo copiare std::setsu 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

Dov'è il problema?


5
c'è anche una assign()funzione:output.assign(input.begin(), input.end());
Gene Bushuyev,

il tuo vettore è vuoto. Ci sono molti modi per rimediare a ciò, anche se come le persone sottolineano di seguito.
AJG85,

@Gene: assegnare () vuole prenotare () la quantità necessaria di memoria in anticipo. Utilizzerà gli iteratori di input per determinare quanto è necessario, a meno che gli iteratori non siano rigorosamente InputIterator, nel qual caso salterà la prenotazione e si tradurrà in riallocazioni su ogni push_back (). All'estremità opposta dello spettro, BiderectionalIterators gli permetterebbe di sottrarre solo la fine - inizio. Gli iteratori di std :: set, tuttavia, non sono nessuno dei due (sono ForwardIterator), e questo è un peccato: in questo caso, assegnato () camminerà sull'intero set per determinarne le dimensioni - cattive prestazioni su set di grandi dimensioni.
Sergey Shevchenko,

Risposte:


213

Devi usare un back_inserter:

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

std::copynon aggiunge elementi al contenitore in cui si sta inserendo: non può; ha solo un iteratore nel contenitore. Per questo motivo, se si passa direttamente a un iteratore di output std::copy, è necessario assicurarsi che punti a un intervallo almeno sufficientemente grande da contenere l'intervallo di input.

std::back_insertercrea un iteratore di output che chiama push_backun contenitore per ciascun elemento, quindi ogni elemento viene inserito nel contenitore. In alternativa, avresti potuto creare un numero sufficiente di elementi std::vectorper contenere l'intervallo da copiare:

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

In alternativa, è possibile utilizzare il std::vectorcostruttore di intervalli:

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

3
Ciao James, invece della tua riga std :: copy (il primo blocco di codice nella tua risposta), non posso semplicemente fare output.insert(output.end(), input.begin(), input.end());invece?
user2015453

o semplicemente usa la versione cbegin e cend: output.insert(output.cend(), input.cbegin(), input.cend());cosa ne pensi? Grazie.
user2015453

2
Dovrei output.reserve (input.size ()); da solo o posso sperare che qualche compilatore lo faccia per me?
jimifiki,

@jimifiki, nessuna speranza ho paura.
Alexis Wilke,

La tua prima inizializzazione vettoriale non è corretta. Si crea una matrice di input,size()voci vuote e successivamente si aggiungono gli accodamenti. Penso che tu intenda usare std::vector<double> output; output.reserve(input.size()); std::copy(...);.
Alexis Wilke,

121

Basta usare il costruttore per il vettore che accetta gli iteratori:

std::set<T> s;

//...

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

Presuppone che desideri solo il contenuto di s in v e non c'è nulla in v prima di copiarvi i dati.


42

ecco un'altra alternativa usando vector::assign:

theVector.assign(theSet.begin(), theSet.end());

24

Non hai riservato abbastanza spazio nel tuo oggetto vettoriale per contenere il contenuto del tuo set.

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

1
Questo non merita -1. In particolare, ciò consente al vettore di eseguire solo un'allocazione (poiché non è in grado di determinare la distanza dei set iteratori in O (1)) e, se non fosse definito per il vettore, azzerare ogni elemento una volta costruito, questo potrebbe vale la pena lasciare che la copia si riduca a un memcpy. Quest'ultimo potrebbe comunque essere utile se l'implementazione capisce il ciclo nel ctor del vettore può essere rimosso. Naturalmente, il primo può anche essere ottenuto con riserva.
Fred Nurk,

Non lo so. Lascia che ti aiuti con quello.
Wilhelmtell,

Ti ho dato un -1, ma è stato un thinko da parte mia. Apporta una piccola modifica in modo che io possa annullare il mio voto e ti darò un +1: questa è in realtà una soluzione molto pulita a causa della proprietà fail-first.
Fred Foo,

Ho appena capito che se modifico la risposta da solo, posso fare un voto. Fatto ciò, ti ha dato un +1 per l'allocazione di memoria fail-first. Scusa!
Fred Foo,

3

Penso che il modo più efficiente sia quello di preallocare e quindi posizionare gli elementi:

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

In questo modo invocheremo il costruttore di copie solo per ogni elemento anziché chiamare prima il costruttore predefinito, quindi copieremo l'operatore di assegnazione per altre soluzioni sopra elencate. Ulteriori chiarimenti di seguito.

  1. back_inserter può essere utilizzato ma richiamerà push_back () sul vettore ( https://en.cppreference.com/w/cpp/iterator/back_insert_iterator ). emplace_back () è più efficiente perché evita di creare un temporaneo quando si utilizza push_back () . Non è un problema con tipi costruiti in modo banale, ma avrà un impatto sulle prestazioni per tipi non banalmente costruiti (ad es. Std :: string).

  2. Dobbiamo evitare di costruire un vettore con l'argomento size che causa la costruzione predefinita di tutti gli elementi (per niente). Come con la soluzione usando std :: copy () , per esempio.

  3. E, infine, il metodo vector :: assegnato () o il costruttore che prende l'intervallo dell'iteratore non sono buone opzioni perché invoceranno std :: distance () (per conoscere il numero di elementi) su set iteratori. Ciò causerà iterazioni aggiuntive indesiderate attraverso tutti gli elementi dell'insieme poiché l'insieme è struttura dei dati dell'albero di ricerca binario e non implementa iteratori ad accesso casuale.

Spero che aiuti.


per favore, aggiungi un riferimento a un'autorità per cui questo è veloce e qualcosa come il motivo per cui back_inserternon è necessario utilizzarlo
Tarick Welling

Aggiunti ulteriori chiarimenti nella risposta.
dshvets1,

1

std::copynon può essere utilizzato per l'inserimento in un contenitore vuoto. Per fare ciò, è necessario utilizzare un insert_iterator in questo modo:

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

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

3
Ciò fallisce la prima volta che il vettore viene riallocato: l'iteratore da output.begin () viene invalidato.
Fred Nurk,
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.