La meilleure façon d'extraire un sous-vecteur d'un vecteur?

295

Supposons que j'ai une std::vector(appelons-la myVec) de taille N. Quelle est la façon la plus simple de construire un nouveau vecteur composé d'une copie des éléments X à Y, où 0 <= X <= Y <= N-1? Par exemple, myVec [100000]grâce à myVec [100999]un vecteur de taille 150000.

Si cela ne peut pas être fait efficacement avec un vecteur, existe-t-il un autre type de données STL que je devrais utiliser à la place?

An̲̳̳drew
la source
7
vous dites que vous voulez extraire un sous-vecteur, mais il me semble que ce que vous voulez vraiment, c'est une vue / accès au sous-vecteur - la différence étant qu'une vue ne copiera pas - le C ++ old school serait d'utiliser le pointeur de début et le pointeur de fin, étant donné que mem sur un vecteur std :: est contigu, il devrait être possible pour vous d'itérer en utilisant des pointeurs et ainsi éviter la copie, cependant si cela ne vous dérange pas de copier, alors initialisez simplement un nouveau vecteur avec la portée de votre précédent vecteur
Serup
Il y a .data () ( cplusplus.com/reference/vector/vector/data ) depuis c ++ 11. Cependant, l'utilisation de pointeurs est déconseillée dans les conteneurs stl, voir stackoverflow.com/questions/31663770/…
David Tóth

Réponses:

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

C'est une opération O (N) pour construire le nouveau vecteur, mais il n'y a pas vraiment de meilleure façon.

Greg Rogers
la source
12
+1, c'est aussi O (YX), qui est inférieur ou égal à O (N) (et dans son exemple beaucoup moins)
orip
74
@orip Eh bien, c'est O (N) après tout.
Johann Gerell
55
@GregRogers: Cela n'a pas de sens d'utiliser la notation big-O où N est un nombre spécifique. Big-O communique le taux de croissance par rapport à la façon dont N change. Johann: Il vaut mieux ne pas utiliser un nom de variable de deux manières. Nous dirions normalement non plus O(Y-X), ou nous dirions O(Z) where Z=Y-X.
Mooing Duck
2
@GregRogers En utilisant cette méthode, nous devons déclarer un nouveau vecteur. Existe-t-il un moyen de changer le vecteur d'origine? quelque chose comme myVec (premier, dernier)? Je sais que c'est faux, mais j'ai vraiment besoin de la solution car je veux utiliser la récursivité dans mes codes, et j'ai besoin d'utiliser à plusieurs reprises le même vecteur (bien que modifié). Merci!
ulyssis2
13
Pourquoi pas juste vector<T> newVec(myVec.begin() + 100000, myVec.begin() + 101000);?
aquirdturtle
88

Utilisez simplement le constructeur vectoriel.

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

std::vector<int>   sub(&data[100000],&data[101000]);
Martin York
la source
2
Ok, je ne savais pas que c'était aussi simple d'obtenir un itérateur à partir d'un élément vectoriel arbitraire.
An̲̳̳drew
5
Prendre l'adresse de ces éléments vectoriels est un hack non transférable qui se brisera si le stockage vectoriel n'est en fait pas contigu. Utilisez begin () + 100000 etc.
j_random_hacker
2
Mon mauvais, apparemment, la norme garantit que le stockage vectoriel est contigu. Néanmoins, c'est une mauvaise pratique de travailler avec des adresses comme celle-ci car il n'est certainement pas garanti de fonctionner pour tous les conteneurs prenant en charge l'accès aléatoire, alors que begin () + 100000 l'est.
j_random_hacker
33
@j_random_hacker: Désolé de ne pas être d'accord. La spécification STL pour std :: vector a été explicitement modifiée pour prendre en charge ce type de procédure. Un pointeur est également un type d'itérateur valide. Recherchez iterator_traits <>
Martin York
6
@ taktak004 Non. N'oubliez pas que operator[]renvoie une référence. Ce n'est qu'au point où vous lisez ou écrivez la référence que cela deviendrait une violation d'accès. Puisque nous ne faisons ni l'un ni l'autre mais obtenons l'adresse, nous n'avons pas invoqué UB ,.
Martin York
28

std::vector<T>(input_iterator, input_iterator), dans votre cas foo = std::vector<T>(myVec.begin () + 100000, myVec.begin () + 150000);, voir par exemple ici

Anteru
la source
1
Comme Andrew essaie de construire un nouveau vecteur, je recommanderais "std :: vector foo (..." au lieu de copier avec "foo = std :: vector (..."
Drew Dormann
4
Oui, bien sûr, mais que vous tapiez std :: vector <int> foo = std :: vector (...) ou std :: vector <int> foo (...) ne devrait pas avoir d'importance.
Anteru
19

De nos jours, nous utilisons spans! Vous écririez donc:

#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);

pour obtenir une plage de 1000 éléments du même type que ceux myvecde. Ou une forme plus concise:

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

(mais je n'aime pas autant cela, car la signification de chaque argument numérique n'est pas entièrement claire; et cela empire si la longueur et start_pos sont du même ordre de grandeur.)

Quoi qu'il en soit, n'oubliez pas que ce n'est pas une copie, c'est juste une vue des données dans le vecteur, alors soyez prudent. Si vous voulez une copie réelle, vous pouvez faire:

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

Remarques:

einpoklum
la source
utiliserait cbeginet cendjuste pour le principe;) std::cbeginetc même.
JHBonarius
1
@JHBonarius: En voyant comment ce code n'est pas basé sur le choix du conteneur, je ne vois pas qu'il y ait un avantage particulier; une question de goût je suppose.
einpoklum
11

Si les deux ne vont pas être modifiés (pas d'ajout / suppression d'éléments - la modification des éléments existants est très bien tant que vous tenez compte des problèmes de thread), vous pouvez simplement contourner data.begin() + 100000et data.begin() + 101000, et prétendre qu'ils sont le begin()et end()d'un vecteur plus petit.

Ou, comme le stockage vectoriel est garanti contigu, vous pouvez simplement contourner un tableau de 1000 éléments:

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

Ces deux techniques prennent un temps constant, mais nécessitent que la longueur des données n'augmente pas, ce qui déclenche une réallocation.

Éclipse
la source
Cela est également utile si vous souhaitez que le vecteur d'origine et le sous-vecteur soient liés.
PyRulez
8

Cette discussion est assez ancienne, mais la plus simple n'est pas encore mentionnée, avec initialisation de liste :

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

Il nécessite c ++ 11 ou supérieur.

Exemple d'utilisation:

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

Résultat:

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;
David Tóth
la source
6

Vous n'avez pas mentionné le type std::vector<...> myVec, mais s'il s'agit d'un type simple ou d'une structure / classe qui n'inclut pas de pointeurs et que vous voulez la meilleure efficacité, vous pouvez faire une copie de mémoire directe (qui, je pense, sera plus rapide que la autres réponses fournies). Voici un exemple général de l' std::vector<type> myVecendroit où se trouve typedans ce cas 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
MasterHD
la source
2
Je me demande si avec -O3, le "constructeur utilisant" d'Anteru std::vector(myVec.begin () + 100000, myVec.begin () + 150000);, la version plus longue de ce produit ne produirait-elle pas exactement le même assemblage?
sandthorn
1
MSVC ++ 2015, par exemple, compiles std::vector<>(iter, iter)à memmove(), le cas échéant (si le constructeur est trivial, pour une définition appropriée de trivial).
Pablo H
1
N'appelle pas memcpy. Faites un std::copyconstructeur ou un constructeur qui accepte une plage (deux itérateurs), et le compilateur et std.library conspireront pour appeler memcpylorsque cela sera approprié.
Bulletmagnet
4

Vous pouvez simplement utiliser insert

vector<type> myVec { n_elements };

vector<type> newVec;

newVec.insert(newVec.begin(), myVec.begin() + X, myVec.begin() + Y);
Matheus Vinícius de Andrade
la source
3

Vous pouvez utiliser la copie STL avec des performances O (M) lorsque M est la taille du sous-vecteur.

Yuval F
la source
Surévalué car il m'a dirigé dans la bonne direction mais je peux voir pourquoi @LokiAstari suggère que ce n'est pas le bon choix - puisque la copie STL :: fonctionne avec deux tableaux std :: vector <T> de même taille et de même type. Ici, l'OP souhaite copier une sous-section dans un nouveau tableau plus petit, comme indiqué ici dans le message de l'OP: "0 <= X <= Y <= N-1"
Andrew
@Andrew, voir l'exemple utilisant std :: copy et std :: back_inserter
chrisg
@LokiAstari pourquoi pas?
chrisg
2
@LokiAstari Je faisais référence à une modification de ce document qui n'a pas survécu à l'examen par les pairs, ce qui posait l'exemple <br/> vecteur <T> newvec; std :: copy (myvec.begin () + 10000, myvec.begin () +10100, std :: back_inserter (newvec)); <br/> dans ce cas, vous n'avez pas besoin de construire la destination en premier, mais bien sûr, l'initialisation directe est plus ... directe.
chrisg
1
@chrisg: C'est aussi deux lignes. De plus, vous devez coller une troisième ligne pour vous assurer qu'elle est efficace. newvec.reserve(10100 - 10000);. Les TI sont définitivement une option et techniquement, cela fonctionnera. Mais sur les deux, qu'allez-vous recommander?
Martin York
1

La seule façon de projeter une collection qui n'est pas du temps linéaire est de le faire paresseusement, où le "vecteur" résultant est en fait un sous-type qui délègue à la collection d'origine. Par exemple, la List#subseqméthode de Scala crée une sous-séquence en temps constant. Cependant, cela ne fonctionne que si la collection est immuable et si la langue sous-jacente est garbage collection.

Daniel Spiewak
la source
de la manière c ++, ce serait d'avoir le vecteur de shared_ptr vers X au lieu du vecteur de X et ensuite de copier les SP, mais malheureusement je ne pense pas que ce soit plus rapide car l'opération atomique est impliquée avec cpying SP. Ou le vecteur d'origine pourrait être un const shared_ptr de vecteur à la place et vous faites simplement référence à une sous-gamme. ofc vous n'avez pas besoin d'en faire un shared_ptr de vecteur mais alors vous avez des problèmes à vie ... tout cela est hors de ma tête, pourrait être faux ...
NoSenseEtAl
0

Publier ce retard juste pour les autres ... Je parie que le premier codeur est terminé maintenant. Pour les types de données simples, aucune copie n'est nécessaire, il suffit de revenir aux bonnes vieilles méthodes de code C.

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

Passez ensuite le pointeur p et un len à tout ce qui nécessite un sous-vecteur.

notelen doit être !! len < myVec.size()-start

mrrgu
la source
Cela n'effectue pas de copie.
Trilarion
0

Peut-être que array_view / span dans la bibliothèque GSL est une bonne option.

Voici également une implémentation d'un seul fichier: array_view .

myd7349
la source
Veuillez ajouter la réponse ici avec le lien. Comme le lien externe pourrait changer à l'avenir
Panther
0

Copier facilement des éléments d'un vecteur à un autre
Dans cet exemple, j'utilise un vecteur de paires pour le rendre facile à comprendre
`

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)]

'
Comme vous pouvez le voir, vous pouvez facilement copier des éléments d'un vecteur à un autre, si vous souhaitez copier des éléments de l'index 10 à 16 par exemple, nous utiliserions

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

et si vous voulez des éléments de l'index 10 vers un index de la fin, alors dans ce cas

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

j'espère que cela aide, rappelez-vous juste dans le dernier cas v.end()-5 > v.begin()+10

Jishu Dohare
la source
0

Encore une autre option: utile par exemple lors du déplacement entre a thrust::device_vectoret a thrust::host_vector, où vous ne pouvez pas utiliser le constructeur.

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

Doit également être la complexité O (N)

Vous pouvez combiner cela avec le code de réponse supérieur

vector<T>::const_iterator first = myVec.begin() + 100000;
vector<T>::const_iterator last = myVec.begin() + 101000;
std::copy(first, last, std::back_inserter(newVector));
JHBonarius
la source