Il modo migliore per estrarre un subvector da un vettore?


295

Supponiamo di avere una dimensione std::vector(chiamiamola così myVec) N. Qual è il modo più semplice per costruire un nuovo vettore costituito da una copia degli elementi da X a Y, dove 0 <= X <= Y <= N-1? Ad esempio, myVec [100000]attraverso myVec [100999]in un vettore di dimensioni 150000.

Se ciò non può essere fatto in modo efficiente con un vettore, esiste invece un altro tipo di dati STL che dovrei usare?


7
dici che vuoi estrarre un subvector, ma mi sembra che quello che vuoi davvero sia una vista / accesso al subvector - la differenza è che una vista non verrà copiata - il C ++ della vecchia scuola sarebbe usare il puntatore iniziale e il puntatore finale, dato che mem su uno std :: vector è contiguo, allora dovrebbe essere possibile iterare usando i puntatori ed evitare quindi la copia, tuttavia se non ti dispiace copiare, quindi inizializza un nuovo vettore con lo scopo del tuo precedente vettore
serup,

C'è .data () ( cplusplus.com/reference/vector/vector/data ) da c ++ 11. Tuttavia, utilizzando puntatori è scoraggiato all'interno di contenitori STL, vedere stackoverflow.com/questions/31663770/...
David Tóth

Risposte:


371
vector<T>::const_iterator first = myVec.begin() + 100000;
vector<T>::const_iterator last = myVec.begin() + 101000;
vector<T> newVec(first, last);

È un'operazione O (N) per costruire il nuovo vettore, ma non c'è davvero un modo migliore.


12
+1, inoltre è O (YX), che è minore o uguale a O (N) (e nel suo esempio molto meno)
orip

74
@orip Bene, allora dopo tutto è O (N).
Johann Gerell,

55
@GregRogers: non ha senso usare la notazione big-O in cui N è un numero specifico. Big-O comunica il tasso di crescita rispetto a come N cambia. Johann: è meglio non usare un nome di variabile in due modi. Normalmente lo diremmo O(Y-X)o lo diremmo O(Z) where Z=Y-X.
Mooing Duck,

2
@GregRogers Usando questo modo, dobbiamo dichiarare un nuovo vettore. C'è un modo per cambiare il vettore originale? qualcosa come myVec (primo, ultimo)? So che è sbagliato, ma ho davvero bisogno della soluzione in quanto desidero utilizzare la ricorsione nei miei codici e ho bisogno di utilizzare ripetutamente lo stesso vettore (anche se modificato). Grazie!
ulisse 2

13
Perché non solo vector<T> newVec(myVec.begin() + 100000, myVec.begin() + 101000);?
aquirdturtle,

88

Basta usare il costruttore vettoriale.

std::vector<int>   data();
// Load Z elements into data so that Z > Y > X

std::vector<int>   sub(&data[100000],&data[101000]);

2
Ok, non mi rendevo conto che fosse così semplice ottenere un iteratore da un elemento vettoriale arbitrario.
An̲̳̳drew

5
Prendere l'indirizzo di quegli elementi vettoriali è un trucco non portabile che si romperà se l'archiviazione vettoriale non è contigua. Usa begin () + 100000 ecc.
j_random_hacker

2
Mio male, apparentemente lo standard garantisce che l'archiviazione vettoriale sia contigua. Tuttavia è una cattiva pratica lavorare con indirizzi come questo in quanto non è certo che funzioni per tutti i contenitori che supportano l'accesso casuale, mentre begin () + 100000 lo è.
j_random_hacker

33
@j_random_hacker: mi dispiace non essere d'accordo. Le specifiche STL per std :: vector sono state esplicitamente modificate per supportare questo tipo di procedura. Anche un puntatore è un tipo valido di iteratore. Cerca iterator_traits <>
Martin York,

6
@ taktak004 No. Ricorda che operator[]restituisce un riferimento. È solo nel momento in cui leggi o scrivi il riferimento che diventerebbe una violazione di accesso. Dal momento che non otteniamo nessuno dei due ma invece otteniamo l'indirizzo non abbiamo invocato UB.
Martin York,

28

std::vector<T>(input_iterator, input_iterator), nel tuo caso foo = std::vector<T>(myVec.begin () + 100000, myVec.begin () + 150000);, vedi ad esempio qui


1
Dato che Andrew sta cercando di costruire un nuovo vettore, consiglierei "std :: vector foo (..." invece di copiare "foo = std :: vector (..."
Drew Dormann

4
Sì, certo, ma non importa se digiti std :: vector <int> foo = std :: vector (...) o std :: vector <int> foo (...).
Anteru,

19

In questi giorni, usiamo spans! Quindi scriveresti:

#include <gsl/span>

...
auto start_pos = 100000;
auto length = 1000;
auto span_of_myvec = gsl::make_span(myvec);
auto my_subspan = span_of_myvec.subspan(start_pos, length);

per ottenere un arco di 1000 elementi dello stesso tipo di myvec. O una forma più concisa:

auto my_subspan = gsl::make_span(myvec).subspan(1000000, 1000);

(ma non mi piace così tanto, poiché il significato di ogni argomento numerico non è del tutto chiaro; e peggiora se la lunghezza e start_pos sono dello stesso ordine di grandezza.)

Ad ogni modo, ricorda che questa non è una copia, è solo una vista dei dati nel vettore, quindi fai attenzione. Se desideri una copia effettiva, puoi fare:

std::vector<T> new_vec(my_subspan.cbegin(), my_subspan.cend());

Appunti:


userebbe cbegine cendsolo per il principio;) std::cbeginecc. anche.
JHBonarius,

1
@JHBonarius: Vedendo come questo codice non è basato sulla scelta del contenitore, non vedo che ci sia un vantaggio particolare; una questione di gusti suppongo.
einpoklum,

10

Se entrambi non verranno modificati (nessuna aggiunta / eliminazione di elementi - la modifica di quelli esistenti va bene finché si presta attenzione ai problemi di threading), è possibile semplicemente passare in giro data.begin() + 100000e data.begin() + 101000, e fingere che siano i begin()eend() di un vettore più piccolo.

Oppure, poiché è garantito che l'archiviazione vettoriale sia contigua, è possibile semplicemente passare un array di 1000 elementi:

T *arrayOfT = &data[0] + 100000;
size_t arrayOfTLength = 1000;

Entrambe queste tecniche richiedono tempo costante, ma richiedono che la lunghezza dei dati non aumenti, innescando una riallocazione.


Questo è utile anche se si desidera collegare il vettore originale e il sottosettore.
PyRulez

7

Questa discussione è piuttosto vecchia, ma la più semplice non è ancora menzionata, con inizializzazione elenco :

 vector<int> subvector = {big_vector.begin() + 3, big_vector.end() - 2}; 

Richiede c ++ 11 o versioni successive.

Esempio di utilizzo:

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

using namespace std;

int main(){

    vector<int> big_vector = {5,12,4,6,7,8,9,9,31,1,1,5,76,78,8};
    vector<int> subvector = {big_vector.begin() + 3, big_vector.end() - 2};

    cout << "Big vector: ";
    for_each(big_vector.begin(), big_vector.end(),[](int number){cout << number << ";";});
    cout << endl << "Subvector: ";
    for_each(subvector.begin(), subvector.end(),[](int number){cout << number << ";";});
    cout << endl;
}

Risultato:

Big vector: 5;12;4;6;7;8;9;9;31;1;1;5;76;78;8;
Subvector: 6;7;8;9;9;31;1;1;5;76;

6

Non hai menzionato il tipo std::vector<...> myVec, ma se si tratta di un tipo o struttura / classe semplice che non include i puntatori e desideri la massima efficienza, puoi fare una copia diretta della memoria (che penso sarà più veloce della altre risposte fornite). Ecco un esempio generale di std::vector<type> myVecdove typein questo caso è int:

typedef int type; //choose your custom type/struct/class
int iFirst = 100000; //first index to copy
int iLast = 101000; //last index + 1
int iLen = iLast - iFirst;
std::vector<type> newVec;
newVec.resize(iLen); //pre-allocate the space needed to write the data directly
memcpy(&newVec[0], &myVec[iFirst], iLen*sizeof(type)); //write directly to destination buffer from source buffer

2
Mi chiedo se con -O3, "usando il costruttore" di @Anteru, std::vector(myVec.begin () + 100000, myVec.begin () + 150000);la versione più lunga di questo non produca esattamente nello stesso assieme?
sandthorn,

1
MSVC ++ 2015, per esempio, compilazione std::vector<>(iter, iter)di memmove(), se del caso (se costruttore è banale per un adeguato definizione di banale).
Pablo H,

1
Non chiamare memcpy. Fare un std::copyo di un costruttore che accetta un intervallo (due iteratori), e il compilatore ed lo std.library sarà cospirano per chiamare memcpyquando opportuno.
Bulletmagnet,

4

Potresti semplicemente usare insert

vector<type> myVec { n_elements };

vector<type> newVec;

newVec.insert(newVec.begin(), myVec.begin() + X, myVec.begin() + Y);

3

È possibile utilizzare la copia STL con prestazioni O (M) quando M è la dimensione del subvector.


È stato votato perché mi ha indicato la giusta direzione, ma posso capire perché @LokiAstari suggerisce che non è la scelta corretta, poiché STL :: copy funziona con due array std :: vector <T> della stessa dimensione e tipo. Qui, l'OP vuole copiare una sottosezione in un nuovo array più piccolo, come indicato qui nel post dell'OP: "0 <= X <= Y <= N-1"
Andrew,

@Andrew, vedi esempio usando std :: copy e std :: back_inserter
chrisg

@LokiAstari perché no?
chrisg,

2
@LokiAstari Mi riferivo a una modifica che non è sopravvissuta alla peer review, che ha dato l'esempio <br/> vector <T> newvec; std :: copy (myvec.begin () + 10000, myvec.begin () +10100, std :: back_inserter (newvec)); <br/> in questo caso, non è necessario creare prima la destinazione, ma sicuramente l'inizializzazione diretta è più ... diretta.
Chrisg,

1
@chrisg: sono anche due righe. Inoltre, è necessario inserire una terza riga per assicurarsi che sia efficiente. newvec.reserve(10100 - 10000);. È sicuramente un'opzione e tecnicamente funzionerà. Ma tra i due che hai intenzione di raccomandare?
Martin York,

1

L'unico modo per proiettare una collezione che non è tempo lineare è farlo pigramente, dove il "vettore" risultante è in realtà un sottotipo che delega alla collezione originale. Ad esempio, il List#subseqmetodo Scala crea una sottosequenza in tempo costante. Tuttavia, questo funziona solo se la raccolta è immutabile e se la lingua sottostante mette in mostra la spazzatura.


in modo c ++ fare ciò sarebbe avere il vettore di shared_ptr su X anziché il vettore di X e quindi copiare gli SP, ma sfortunatamente non penso che sia più veloce perché l'operazione atomica è coinvolta con l'SP di cpying. Oppure il vettore originale potrebbe essere una const shared_ptr di vettore invece e fai semplicemente riferimento a subrange in esso. OFC non avete bisogno di farne una shared_ptr di vettore ma poi avete problemi tutta la vita ... tutto questo è fuori dalla parte superiore della mia testa, potrebbe essere sbagliato ...
NoSenseEtAl

0

Pubblicando questo post in ritardo solo per gli altri ... Scommetto che il primo programmatore è già pronto. Per semplici tipi di dati non è necessaria alcuna copia, è sufficiente ripristinare i vecchi metodi del codice C.

std::vector <int>   myVec;
int *p;
// Add some data here and set start, then
p=myVec.data()+start;

Quindi passa il puntatore p e una len a tutto ciò che necessita di un subvector.

notelen deve essere !! len < myVec.size()-start


Questo non esegue una copia.
Trilarion

0

Forse array_view / span nella libreria GSL è una buona opzione.

Ecco anche un'implementazione di un singolo file: array_view .


Si prega di aggiungere la risposta qui insieme al collegamento. Poiché il collegamento esterno potrebbe cambiare in futuro
Pantera,

0

Copia elementi da un vettore all'altro facilmente
In questo esempio, sto usando un vettore di coppie per facilitarne la comprensione
`

vector<pair<int, int> > v(n);

//we want half of elements in vector a and another half in vector b
vector<pair<lli, lli> > a(v.begin(),v.begin()+n/2);
vector<pair<lli, lli> > b(v.begin()+n/2, v.end());


//if v = [(1, 2), (2, 3), (3, 4), (4, 5), (5, 6)]
//then a = [(1, 2), (2, 3)]
//and b = [(3, 4), (4, 5), (5, 6)]

//if v = [(1, 2), (2, 3), (3, 4), (4, 5), (5, 6), (6, 7)]
//then a = [(1, 2), (2, 3), (3, 4)]
//and b = [(4, 5), (5, 6), (6, 7)]

"
Come puoi vedere, puoi facilmente copiare elementi da un vettore all'altro, se ad esempio vuoi copiare elementi dall'indice 10 al 16, allora useremmo

vector<pair<int, int> > a(v.begin()+10, v.begin+16);

e se vuoi elementi dall'indice 10 a qualche indice dalla fine, allora in quel caso

vector<pair<int, int> > a(v.begin()+10, v.end()-5);

spero che questo aiuti, ricordati solo nell'ultimo caso v.end()-5 > v.begin()+10


0

Ancora un'altra opzione: utile ad esempio quando ci si sposta tra a thrust::device_vectore a thrust::host_vector, dove non è possibile utilizzare il costruttore.

std::vector<T> newVector;
newVector.reserve(1000);
std::copy_n(&vec[100000], 1000, std::back_inserter(newVector));

Dovrebbe anche essere la complessità O (N)

È possibile combinare questo con il codice anwer superiore

vector<T>::const_iterator first = myVec.begin() + 100000;
vector<T>::const_iterator last = myVec.begin() + 101000;
std::copy(first, last, std::back_inserter(newVector));
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.