¿La mejor manera de extraer un subvector de un vector?

295

Supongamos que tengo un std::vector(llamémoslo myVec) de tamaño N. ¿Cuál es la forma más simple de construir un nuevo vector que consiste en una copia de los elementos X a Y, donde 0 <= X <= Y <= N-1? Por ejemplo, a myVec [100000]través myVec [100999]de un vector de tamaño 150000.

Si esto no se puede hacer de manera eficiente con un vector, ¿hay otro tipo de datos STL que debería usar en su lugar?

Andrés
fuente
77
dices que quieres extraer un subvector, pero me parece que lo que realmente quieres es una vista / acceso al subvector, la diferencia es que una vista no se copiaría, la vieja escuela C ++ sería usar el puntero de inicio y el puntero de fin, dado el hecho de que mem en un std :: vector es contiguo, entonces debería ser posible iterar usando punteros y evitar la copia, sin embargo, si no le importa copiar, simplemente inicialice un nuevo vector con el alcance de su anterior vector
serup
Hay .data () ( cplusplus.com/reference/vector/vector/data ) desde c ++ 11. Sin embargo, se desaconseja el uso de punteros dentro de los contenedores stl, consulte stackoverflow.com/questions/31663770/…
David Tóth el

Respuestas:

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

Es una operación O (N) para construir el nuevo vector, pero en realidad no hay una mejor manera.

Greg Rogers
fuente
12
+1, también es O (YX), que es menor o igual que O (N) (y en su ejemplo mucho menos)
orip
74
@orip Bueno, entonces es O (N) después de todo.
Johann Gerell
55
@GregRogers: no tiene sentido usar la notación big-O donde N es un número específico. Big-O comunica la tasa de crecimiento con respecto a cómo N cambia. Johann: Es mejor no usar un nombre de variable de dos maneras. Normalmente diríamos cualquiera O(Y-X), o diríamos O(Z) where Z=Y-X.
Mooing Duck
2
@GregRogers Al usar esta forma, necesitamos declarar un nuevo vector. ¿Hay alguna manera de cambiar el vector original? algo como myVec (primero, último)? Sé que esto está mal, pero realmente necesito la solución, ya que quiero usar la recursividad en mis códigos, y necesito usar repetidamente el mismo vector (aunque haya cambiado). ¡Gracias!
ulyssis2
13
¿Por qué no solo vector<T> newVec(myVec.begin() + 100000, myVec.begin() + 101000);?
aquirdturtle
88

Solo usa el constructor de vectores.

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

std::vector<int>   sub(&data[100000],&data[101000]);
Martin York
fuente
2
Ok, no me di cuenta de que era tan simple obtener un iterador de un elemento vectorial arbitrario.
An̲̳̳drew
55
Tomar la dirección de esos elementos vectoriales es un truco no portable que se romperá si el almacenamiento del vector no es contiguo. Use begin () + 100000 etc.
j_random_hacker
2
Mi mal, aparentemente el estándar garantiza que el almacenamiento de vectores es contiguo. Sin embargo, es una mala práctica trabajar con direcciones como esta, ya que no está garantizado que funcione para todos los contenedores que admiten acceso aleatorio, mientras que begin () + 100000 sí lo es.
j_random_hacker
33
@j_random_hacker: Lo siento, no estoy de acuerdo. La especificación STL para std :: vector se cambió explícitamente para admitir este tipo de procedimiento. También un puntero es un tipo válido de iterador. Busque iterator_traits <>
Martin York
66
@ taktak004 No. Recuerda que operator[]devuelve una referencia. Es solo en el punto donde lee o escribe la referencia que se convertiría en una violación de acceso. Como no hacemos ninguno de los dos, sino que obtenemos la dirección, no hemos invocado a UB.
Martin York
28

std::vector<T>(input_iterator, input_iterator), en su caso foo = std::vector<T>(myVec.begin () + 100000, myVec.begin () + 150000);, vea por ejemplo aquí

Anteru
fuente
1
Como Andrew está tratando de construir un nuevo vector, recomendaría "std :: vector foo (..." en lugar de copiar con "foo = std :: vector (..."
Drew Dormann el
44
Sí, por supuesto, pero si escribe std :: vector <int> foo = std :: vector (...) o std :: vector <int> foo (...) no debería importar.
Anteru
19

En estos días, usamos spans! Entonces escribirías:

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

para obtener un lapso de 1000 elementos del mismo tipo que myvec's. O una forma más concisa:

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

(pero esto no me gusta tanto, ya que el significado de cada argumento numérico no está del todo claro; y empeora si la longitud y start_pos son del mismo orden de magnitud).

De todos modos, recuerde que esto no es una copia, es solo una vista de los datos en el vector, así que tenga cuidado. Si desea una copia real, puede hacer:

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

Notas:

einpoklum
fuente
usaría cbeginy cendsolo por el principio;) std::cbeginetc incluso.
JHBonarius
1
@JHBonarius: al ver cómo este código no está basado en la elección del contenedor, no veo que haya un beneficio particular; una cuestión de gustos, supongo.
einpoklum
10

Si no se van a modificar ambos (no se deben agregar / eliminar elementos; modificar los existentes está bien siempre que preste atención a los problemas de subprocesamiento), simplemente puede pasar data.begin() + 100000y data.begin() + 101000, y pretender que son el begin()y end()de un vector más pequeño.

O, dado que se garantiza que el almacenamiento vectorial sea contiguo, simplemente puede pasar una matriz de 1000 elementos:

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

Ambas técnicas toman tiempo constante, pero requieren que la longitud de los datos no aumente, lo que desencadena una reasignación.

Eclipse
fuente
Esto también es bueno si desea vincular el vector original y el subvector.
PyRulez
7

Esta discusión es bastante antigua, pero la más simple aún no se menciona, con la inicialización de la lista :

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

Requiere c ++ 11 o superior.

Ejemplo de uso:

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

Resultado:

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
fuente
6

No mencionó qué tipo std::vector<...> myVeces, pero si es un tipo simple o estructura / clase que no incluye punteros, y desea la mejor eficiencia, puede hacer una copia de memoria directa (que creo que será más rápido que el otras respuestas proporcionadas). Aquí hay un ejemplo general de std::vector<type> myVecdónde typeen este caso es 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
fuente
2
Me pregunto si con -O3, el constructor "anterior" de @ Anteru std::vector(myVec.begin () + 100000, myVec.begin () + 150000);, ¿la versión más larga de este producto no produciría exactamente el mismo ensamblaje?
Sandthorn
1
MSVC ++ 2015, por ejemplo, compila std::vector<>(iter, iter)para memmove(), en su caso (si constructor es trivial, para una definición adecuada de trivial).
Pablo H
1
No llame memcpy. Haga un std::copyo un constructor que acepte un rango (dos iteradores), y el compilador y el std.library conspirarán para llamar memcpycuando sea apropiado.
Bulletmagnet
4

Podrías usar insert

vector<type> myVec { n_elements };

vector<type> newVec;

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

Puede usar la copia STL con rendimiento O (M) cuando M es el tamaño del subvector.

Yuval F
fuente
Voté porque me señaló en la dirección correcta, pero puedo ver por qué @LokiAstari sugiere que no es la opción correcta, ya que STL :: copy funciona con dos matrices std :: vector <T> del mismo tamaño y tipo. Aquí, el OP quiere copiar una subsección en una nueva matriz más pequeña como se describe aquí en la publicación del OP: "0 <= X <= Y <= N-1"
Andrew
@ Andrew, ver ejemplo usando std :: copy y std :: back_inserter
chrisg
@LokiAstari ¿por qué no?
chrisg
2
@LokiAstari Me refería a una edición de esto que no sobrevivió a la revisión por pares, que planteó el ejemplo <br/> vector <T> newvec; std :: copy (myvec.begin () + 10000, myvec.begin () +10100, std :: back_inserter (newvec)); <br/> en este caso, no necesita construir el destino primero, pero claro, la inicialización directa es más ... directa.
chrisg
1
@chrisg: También son dos líneas. Además, debe pegar una tercera línea para asegurarse de que sea eficiente. newvec.reserve(10100 - 10000);. Definitivamente es una opción y técnicamente funcionará. Pero de los dos, ¿qué vas a recomendar?
Martin York
1

La única forma de proyectar una colección que no es tiempo lineal es hacerlo perezosamente, donde el "vector" resultante es en realidad un subtipo que delega en la colección original. Por ejemplo, el List#subseqmétodo de Scala crea una subsecuencia en tiempo constante. Sin embargo, esto solo funciona si la colección es inmutable y si el idioma subyacente tiene una recolección de basura.

Daniel Spiewak
fuente
en la forma c ++ de hacer eso sería tener un vector de shared_ptr a X en lugar de un vector de X y luego copiar los SP, pero desafortunadamente no creo que sea más rápido porque la operación atómica está involucrada con el cifrado de SP. O el vector original podría ser un const shared_ptr de vector en su lugar y solo tiene que hacer referencia a subrango en él. ofc no necesita convertirlo en un shared_ptr de vector pero tiene problemas de por vida ... todo esto está fuera de mi
alcance
0

Publicar esto tarde solo para otros ... Apuesto a que el primer codificador ya ha terminado. Para los tipos de datos simples, no se necesita copia, simplemente vuelva a los viejos métodos de código C.

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

Luego pase el puntero p y un len a cualquier cosa que necesite un subvector.

notelen debe ser !! len < myVec.size()-start

mrrgu
fuente
Esto no realiza una copia.
Trilarion
0

Quizás el array_view / span en la biblioteca GSL es una buena opción.

Aquí también hay una implementación de archivo único: array_view .

myd7349
fuente
Agregue amablemente la respuesta aquí junto con el enlace. Como el enlace externo podría cambiar en el futuro
Panther
0

Copie elementos de un vector a otro fácilmente
En este ejemplo, estoy usando un vector de pares para que sea fácil de entender
`

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

'
Como puede ver, puede copiar fácilmente elementos de un vector a otro, si desea copiar elementos del índice 10 al 16, por ejemplo, usaríamos

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

y si quieres elementos del índice 10 a algún índice desde el final, entonces en ese caso

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

Espero que esto ayude, solo recuerda en el último caso v.end()-5 > v.begin()+10

Jishu Dohare
fuente
0

Otra opción más: útil, por ejemplo, cuando se mueve entre ay thrust::device_vectora thrust::host_vector, donde no se puede usar el constructor.

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

También debe ser complejidad O (N)

Puedes combinar esto con el código de respuesta superior

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
fuente