Лучший способ извлечь подвектор из вектора?

Предположим, у меня есть std::vector (назовем его myVec) размера N. Как проще всего построить новый вектор, состоящий из копии элементов с X по Y, где 0 ‹= X‹ = Y ‹= N-1? Например, от myVec [100000] до myVec [100999] в векторе размером 150000.

Если это не может быть эффективно сделано с вектором, есть ли другой тип данных STL, который я должен использовать вместо этого?


person An̲̳̳drew    schedule 07.01.2009    source источник
comment
вы говорите, что хотите извлечь подвектор, но мне кажется, что на самом деле вам нужно представление / доступ к подвектору - разница в том, что представление не будет копироваться - старый школьный C ++ будет использовать начальный указатель и конечный указатель, учитывая тот факт, что mem в std :: vector является смежным, тогда у вас должна быть возможность выполнять итерацию с использованием указателей и, таким образом, избегать копирования, однако, если вы не против копирования, просто инициализируйте новый вектор с областью действия вашего предыдущего вектор   -  person serup    schedule 13.02.2017
comment
Существует .data () (cplusplus.com/reference/vector/vector/data) начиная с C ++ 11. Однако использование указателей в контейнерах stl не рекомендуется, см. stackoverflow.com/questions/31663770/   -  person David Tóth    schedule 06.11.2019
comment
@serup, возможно, не заинтересован в OP, но мне нужно знать, как инициализировать новый вектор с областью вашего предыдущего вектора.   -  person meolic    schedule 11.04.2021


Ответы (14)


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

Это операция O (N) для создания нового вектора, но на самом деле лучшего способа нет.

person Greg Rogers    schedule 07.01.2009
comment
+1, также это O (Y-X), что меньше или равно O (N) (а в его примере намного меньше) - person orip; 08.01.2009
comment
@orip Ну, тогда все-таки О (Н). - person Johann Gerell; 08.01.2009
comment
@GregRogers: Нет смысла использовать нотацию большого O, где N - конкретное число. Big-O сообщает скорость роста относительно того, как изменяется N. Иоганн: Лучше не использовать одно имя переменной двумя способами. Обычно мы говорим либо O(Y-X), либо O(Z) where Z=Y-X. - person Mooing Duck; 11.09.2013
comment
@GregRogers Используя этот способ, нам нужно объявить новый вектор. Есть ли способ изменить исходный вектор? что-то вроде myVec (первый, последний)? Я знаю, что это неправильно, но мне действительно нужно решение, поскольку я хочу использовать рекурсию в своих кодах, и мне нужно многократно использовать один и тот же вектор (хотя и измененный). Спасибо! - person ulyssis2; 05.11.2015
comment
@ ulyssis2 Создать новый вектор и поменять местами? - person AturSams; 27.01.2016
comment
Почему не просто vector<T> newVec(myVec.begin() + 100000, myVec.begin() + 101000);? - person aquirdturtle; 21.03.2017
comment
Кроме того, если newVec уже существует и вы хотите заменить его содержимое, используйте newVec.asign(first, last). Он имеет ту же семантику, что и конструктор в ответе. - person Elvis Teixeira; 07.06.2017
comment
что произойдет, если последний итератор выйдет за пределы границ? - person Dr Deo; 20.08.2017
comment
Вы также можете использовать auto вместо vector<T>::const_iterator - person Ahmed Karaman; 14.12.2017
comment
Просто для моей справки, результирующий вектор - это неглубокая копия, верно? субвектор не является указателем на исходные векторы, лежащие в основе структуры данных, верно? Я в основном хочу знать, что если я вернусь обратно к стеку, внутренние указатели на результирующий субвектор все еще действительны, потому что они отделены от исходного супер-вектора. - person searchengine27; 20.02.2018
comment
Строка 3 - копия? Можно ли таким образом построить ссылочный подвектор? - person Sandburg; 05.10.2018
comment
В качестве побочного примечания конструктор диапазона std :: vector constructs the container with the contents of the range [first, last). - person AZTECCO; 28.02.2020
comment
Отличное предложение. Можно ли использовать auto cbegin вместо begin? auto first = myVec.cbegin() + 100000; auto last = myVec.cbegin() + 101000; - person pamplemousse_mk2; 01.03.2021

Просто используйте конструктор векторов.

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

std::vector<int>   sub(&data[100000],&data[101000]);
person Martin York    schedule 07.01.2009
comment
Хорошо, я не понимал, что получить итератор из произвольного векторного элемента так просто. - person An̲̳̳drew; 07.01.2009
comment
Что ж, вы фактически получаете указатели на элементы, которые в большинстве случаев эквивалентны итераторам. Однако я предполагаю, что они не будут проверяться как итераторы, если у вас есть реализация проверенных итераторов. Может быть какая-то магия, о которой я не подозреваю, хотя это тоже помогает ... - person Niklas; 07.01.2009
comment
Получение адреса этих векторных элементов - это непереносимый взлом, который сломается, если хранилище векторов на самом деле не является непрерывным. Используйте begin () + 100000 и т. Д. - person j_random_hacker; 08.01.2009
comment
Мое плохое, по-видимому, стандарт гарантирует непрерывность векторного хранилища. Тем не менее, работать с подобными адресами - плохая практика, поскольку, конечно, не гарантируется работа для всех контейнеров, поддерживающих произвольный доступ, в то время как begin () + 100000 работает. - person j_random_hacker; 08.01.2009
comment
@j_random_hacker: Извините, что не согласен. Спецификация STL для std :: vector была явно изменена для поддержки этого типа процедуры. Также указатель является допустимым типом итератора. Найдите iterator_traits ‹› - person Martin York; 08.01.2009
comment
Да, указатели не обязательно являются действительными std :: vector ‹T› :: iterator, но они являются действительными итераторами для массива в непрерывном хранилище, как оно и используется. - person Greg Rogers; 08.01.2009
comment
@Uri: Это всегда будет работать. Но, как и весь код, он ограничен системными ограничениями. Если вы попытаетесь сделать что-то, что физически невозможно, вы получите исключение. - person Martin York; 17.08.2015
comment
Как бы вы с помощью этого разрезали последний элемент? Если вы сделаете sub (& data [100000], & data [data.size ()]); Это не всегда будет работать и может вызвать нарушение прав доступа? - person taktak004; 01.10.2016
comment
@ taktak004 Нет. Помните, что operator[] возвращает ссылку. Только в том месте, где вы читаете или записываете ссылку, это может стать нарушением доступа. Поскольку мы не делаем ни того, ни другого, а вместо этого получаем адрес, который мы не вызывали, UB ,. - person Martin York; 01.10.2016
comment
Это не сработает, если вам нужно скопировать данные до конца исходного вектора. - person bialix; 06.12.2017
comment
@bialix: Будет. Я предполагаю, что вы говорите о том, что взяли адрес одного из прошлых, и это уже проблема. Это предусмотрено стандартом и разрешено. UB разыменовывать эти адреса, но не принимать их адреса. Обратите внимание, что определение X[Y] определяется как *(X + Y) в стандарте. И операторы &*, помещенные вместе, отменяют друг друга (при условии, что они не были переопределены (что не может быть для указателей)). = ›&X[Y] =› &*(X + Y) = ›(X + Y). По этой же причине 10000[data] является допустимым выражением для доступа к массиву. - person Martin York; 07.12.2017

std::vector<T>(input_iterator, input_iterator), в вашем случае foo = std::vector<T>(myVec.begin () + 100000, myVec.begin () + 150000);, см., Например, здесь

person Anteru    schedule 07.01.2009
comment
Поскольку Эндрю пытается построить новый вектор, я бы порекомендовал std :: vector foo (... вместо копирования с помощью foo = std :: vector (... - person Drew Dormann; 07.01.2009
comment
Да, конечно, но набираете ли вы std :: vector ‹int› foo = std :: vector (...) или std :: vector ‹int› foo (...), не имеет значения. - person Anteru; 07.01.2009


В наши дни мы используем spans! Итак, вы бы написали:

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

чтобы получить диапазон из 1000 элементов того же типа, что и myvec. Или более сжатую форму:

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

(но мне это не очень нравится, поскольку значение каждого числового аргумента не совсем понятно; и становится хуже, если length и start_pos имеют одинаковый порядок величины.)

В любом случае помните, что это не копия, это просто представление данных в векторе, поэтому будьте осторожны. Если вам нужна настоящая копия, вы можете сделать:

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

Примечания:

person einpoklum    schedule 09.08.2017
comment
использовал бы cbegin и cend только по принципу;) даже std::cbegin и т. д. - person JHBonarius; 29.06.2018
comment
@JHBonarius: Видя, что этот код не основан на шаблоне выбора контейнера, я не вижу особого преимущества; полагаю, дело вкуса. - person einpoklum; 30.06.2018

Если оба элемента не будут изменены (без добавления / удаления элементов - изменение существующих - это нормально, если вы обращаете внимание на проблемы потоковой передачи), вы можете просто передать data.begin() + 100000 и data.begin() + 101000 и притвориться, что они являются begin() и end() из меньший вектор.

Или, поскольку векторное хранилище гарантированно непрерывно, вы можете просто передать массив из 1000 элементов:

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

Оба эти метода требуют постоянного времени, но требуют, чтобы длина данных не увеличивалась, вызывая перераспределение.

person Eclipse    schedule 07.01.2009
comment
Это также хорошо, если вы хотите, чтобы исходный вектор и подвектор были связаны. - person PyRulez; 01.03.2015

Вы не упомянули, что такое тип std::vector<...> myVec, но если это простой тип или структура / класс, который не включает указатели, и вам нужна максимальная эффективность, вы можете сделать прямое копирование в память (что, я думаю, будет быстрее, чем другие предоставленные ответы). Вот общий пример для std::vector<type> myVec, где type в данном случае 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
person MasterHD    schedule 16.10.2015
comment
Интересно, если с -O3, @ Anteru использует конструктор std::vector(myVec.begin () + 100000, myVec.begin () + 150000);, не будет ли более длинная версия этого преобразована в точно такую ​​же сборку? - person sandthorn; 14.02.2018
comment
Например, MSVC ++ 2015 компилирует std::vector<>(iter, iter) в memmove(), если это необходимо (если конструктор тривиальный, для подходящего определения тривиального). - person Pablo H; 26.06.2018
comment
Не звоните memcpy. Создайте std::copy или конструктор, который принимает диапазон (два итератора), и компилятор и std.library сговорились вызвать memcpy, когда это необходимо. - person Bulletmagnet; 12.04.2019

Вы можете просто использовать insert

vector<type> myVec { n_elements };

vector<type> newVec;

newVec.insert(newVec.begin(), myVec.begin() + X, myVec.begin() + Y);
person Matheus Vinícius de Andrade    schedule 10.06.2019

Вы можете использовать копию STL с производительностью O (M), когда M - размер подвектора.

person Yuval F    schedule 07.01.2009
comment
Проголосовали за, потому что это указывало мне в правильном направлении, но я понимаю, почему @LokiAstari предлагает неправильный выбор - поскольку STL :: copy работает с двумя массивами std :: vector ‹T› одинакового размера и типа. Здесь OP хочет скопировать подраздел в новый, меньший массив, как описано здесь в сообщении OP: 0 ‹= X‹ = Y ‹= N-1 - person Andrew; 01.01.2017
comment
@Andrew, см. Пример использования std :: copy и std :: back_inserter - person chrisg; 26.07.2017
comment
@LokiAstari, почему бы и нет? - person chrisg; 26.07.2017
comment
@chrisg: разгадка кроется в имени copy. Чтобы использовать std :: copy, вам нужно сначала создать место назначения (что означает инициализацию всех членов их значением по умолчанию). Затем вы можете скопировать их. Итак, это две разные операции. Почему бы просто не использовать конструктор вектора и не инициализировать элементы непосредственно их конечными значениями. Посмотрите красивый ответ, за который проголосовало +50. - person Martin York; 26.07.2017
comment
@LokiAstari Я имел в виду правку этого, которая не выдержала экспертной оценки, в которой представлен пример ‹br/› vector ‹T› newvec; std :: copy (myvec.begin () + 10000, myvec.begin () +10100, std :: back_inserter (newvec)); ‹Br/› в этом случае вам не нужно сначала создавать место назначения, но, конечно, прямая инициализация более ... прямая. - person chrisg; 26.07.2017
comment
@chrisg: Это тоже две строки. Кроме того, вам нужно вставить третью строку, чтобы убедиться, что она эффективна. newvec.reserve(10100 - 10000);. Это определенно вариант, и технически он будет работать. Но какой из двух вы порекомендуете? - person Martin York; 27.07.2017
comment
@LokiAstari а, хорошо, я продана. Потребность в резерве на больших сетах подтолкнула меня к черту. Мне также не нравилось скрытое ощущение использования голых указателей, зависимости от базовой детали (или гарантии, что вектор является смежным) и неконтролируемого доступа, подобного массиву, потому что они кажутся очень похожими на C и не-C ++ - как , но потом я решил, что это не хуже, чем арифметика итератора, используемая для подхода std :: copy - person chrisg; 27.07.2017
comment
@chrisg: в ответе, получившем наибольшее количество голосов, используются стандартные итераторы и конструктор. - person Martin York; 27.07.2017

Единственный способ спроецировать коллекцию, которая не является линейной по времени, - это делать это лениво, когда результирующий «вектор» на самом деле является подтипом, который делегируется исходной коллекции. Например, метод List#subseq в Scala создает подпоследовательность за постоянное время. Однако это работает только в том случае, если сборка неизменна и если базовый язык поддерживает сборку мусора.

person Daniel Spiewak    schedule 07.01.2009
comment
в С ++ способ сделать это будет иметь вектор shared_ptr в X вместо вектора X, а затем скопировать SP, но, к сожалению, я не думаю, что это быстрее, потому что атомарная операция связана с cpying SP. Или исходный вектор может быть const shared_ptr of vector, и вы просто берете в нем ссылку на поддиапазон. конечно, вам не нужно делать его shared_ptr вектора, но тогда у вас есть проблемы со временем жизни ... все это не в моей голове, может быть неправильно ... - person NoSenseEtAl; 22.10.2013

Возможно, array_view / span В библиотеке GSL хороший вариант.

Вот также реализация одного файла: array_view.

person myd7349    schedule 27.04.2017
comment
Пожалуйста, добавьте сюда ответ вместе со ссылкой. Поскольку внешняя ссылка может измениться в будущем - person Panther; 27.04.2017

Легко копировать элементы из одного вектора в другой
В этом примере я использую вектор пар, чтобы упростить понимание
`

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

'
Как видите, вы можете легко копировать элементы из одного вектора в другой, если вы хотите скопировать элементы, например, из индекса 10 в 16, тогда мы будем использовать

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

и если вам нужны элементы с индекса 10 до некоторого индекса с конца, то в этом случае

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

надеюсь, это поможет, просто помните в последнем случае v.end()-5 > v.begin()+10

person Jishu Dohare    schedule 24.06.2018

Еще один вариант: полезен, например, при перемещении между thrust::device_vector и thrust::host_vector, когда вы не можете использовать конструктор.

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

Также должна быть сложность O (N)

Вы можете комбинировать это с верхним кодом ответа

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

Публикация так поздно только для других .. Держу пари, что первый кодировщик уже готов. Для простых типов данных копирование не требуется, просто вернитесь к старым добрым методам кода C.

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

Затем передайте указатель p и len всем, кому нужен подвектор.

нотелен должно быть !! len < myVec.size()-start

person mrrgu    schedule 18.11.2013
comment
Копирование не выполняется. - person Trilarion; 30.01.2017