Найкращий спосіб дістати субвектор з вектора?


295

Припустимо, я маю std::vector(назвемо так myVec) розмір N. Який найпростіший спосіб побудувати новий вектор, що складається з копії елементів X через Y, де 0 <= X <= Y <= N-1? Наприклад, myVec [100000]через myVec [100999]вектор у розмірі 150000.

Якщо це неможливо зробити ефективно з вектором, чи є інший тип даних STL, який я повинен використовувати замість цього?


7
ви кажете, що хочете витягнути субвектор, але мені здається, що ви дійсно хочете - це перегляд / доступ до субвектора. Різниця полягає в тому, що подання не буде копіювати - стара школа C ++ полягатиме в тому, щоб використовувати покажчик початку та кінця, враховуючи той факт, що mem на std :: vector є суміжним, то вам слід мати можливість повторити, використовуючи покажчики, і тим самим уникнути копіювання, однак, якщо ви не заперечуєте над копією, то просто ініціалізуйте новий вектор із обсягом попереднього вектор
серум

Є .data () ( cplusplus.com/reference/vector/vector/data ), оскільки c ++ 11. Однак використання покажчиків не забороняється в контейнерах stl, див. Stackoverflow.com/questions/31663770/…
Девід Тот

Відповіді:


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

Це операція O (N) для побудови нового вектора, але насправді кращого способу немає.


12
+1, також це O (YX), що менше або дорівнює O (N) (а в його прикладі набагато менше)
orip

74
@orip Ну, тоді це O (N).
Йоганн Герелл

55
@GregRogers: Немає сенсу використовувати позначення big-O, де N - це конкретне число. Big-O повідомляє швидкість зростання щодо того, як змінюється N. Йоган: Найкраще не використовувати одне ім’я змінної двома способами. Ми зазвичай говоримо або O(Y-X), або ми говоримо O(Z) where Z=Y-X.
Mooing Duck

2
@GregRogers Використовуючи цей спосіб, нам потрібно оголосити новий вектор. Чи є спосіб змінити вихідний вектор? щось на кшталт myVec (перший, останній)? Я знаю, що це неправильно, але мені дуже потрібне рішення, оскільки я хочу використовувати рекурсію в своїх кодах, і мені потрібно багаторазово використовувати один і той же вектор (хоча і змінений). Дякую!
ulyssis2

13
Чому б не просто vector<T> newVec(myVec.begin() + 100000, myVec.begin() + 101000);?
aquirdturtle

88

Просто використовуйте векторний конструктор.

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

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

2
Гаразд, я не усвідомлював, що отримати ітератор з довільного векторного елемента так просто.
An̲̳̳drew

5
Прийняття адреси цих векторних елементів - це нерепортажний злом, який зламається, якщо зберігання векторів насправді не буде суміжним. Використовуйте
start

2
Мій поганий, мабуть, стандарт гарантує, що зберігання векторів є суміжним. Тим не менш, це погана практика працювати з подібними адресами, оскільки це точно не гарантується для всіх контейнерів, що підтримують випадковий доступ, в той час як start () + 100000 є.
j_random_hacker

33
@j_random_hacker: На жаль, не погоджуюсь. Специфікація STL для std :: vector була явно змінена для підтримки цього типу процедури. Також покажчик є дійсним типом ітератора. Знайдіть iterator_traits <>
Мартін Йорк

6
@ taktak004 Ні. Пам'ятайте, що operator[]повертає посилання. Лише в момент, коли ви читаєте або пишете посилання, це стане порушенням доступу. Оскільки ми не робимо жодного, але натомість отримуємо адресу, ми не викликали UB,.
Мартін Йорк

28

std::vector<T>(input_iterator, input_iterator), у вашому випадку foo = std::vector<T>(myVec.begin () + 100000, myVec.begin () + 150000);дивіться, наприклад, тут


1
Оскільки Ендрю намагається побудувати новий вектор, я рекомендував би "std :: vector foo (..." замість копіювання з "foo = std :: vector (..."
Дрю Дорманн

4
Так, звичайно, але чи вводити std :: vector <int> foo = std :: vector (...) або std :: vector <int> foo (...) не має значення.
Антера

19

У ці дні ми використовуємо 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 елементів того ж типу, що і myvecs. Або більш лаконічна форма:

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

(але мені це не дуже подобається, оскільки значення кожного числового аргументу не зовсім зрозуміло; і стає гірше, якщо довжина та start_pos мають однаковий порядок.)

У будь-якому випадку пам’ятайте, що це не копія, це лише перегляд даних у векторі, тому будьте обережні. Якщо ви хочете отримати фактичну копію, ви можете зробити:

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

Примітки:


використовував би cbeginі cendпросто за принципом;) std::cbeginтощо навіть.
JHBonarius

1
@JHBonarius: бачачи, як цей код не шаблонований на виборі контейнера, я не бачу особливої ​​переваги; Я думаю, питання смаку.
einpoklum

10

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

Або оскільки векторне зберігання гарантовано є суміжним, ви можете просто пройти навколо масиву 1000 елементів:

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

Обидві ці методи займають постійний час, але вимагають, щоб довжина даних не збільшувалася, викликаючи перерозподіл.


Це також добре, якщо ви хочете, щоб вихідний вектор і субвектор були пов'язані.
PyRulez

7

Ця дискусія досить стара, але найпростіша ще не згадується із ініціалізацією списку :

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

Він вимагає c ++ 11 або вище.

Приклад використання:

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

Результат:

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

Ви не згадали, що це за тип 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

2
Цікаво, якби з -O3, "використовуючим конструктором" @ Anteru std::vector(myVec.begin () + 100000, myVec.begin () + 150000);, чи не вийшла б більш дрібна версія цього продукту в абсолютно ту ж збірку?
пісочниця

1
MSVC ++ 2015 року, наприклад, компілює , std::vector<>(iter, iter)щоб memmove(), в разі необхідності (якщо конструктор тривіально, для відповідного визначення тривіально).
Пабло Н

1
Не дзвоніть memcpy. Зробіть a std::copyабо конструктор, який приймає діапазон (два ітератори), і компілятор і std.library змовляться викликати, memcpyколи це доречно.
Bulletmagnet


3

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


Оновлений, тому що він вказував мені в правильному напрямку, але я можу зрозуміти, чому @LokiAstari припускає, що це не правильний вибір - оскільки копія STL :: працює з двома масивами std :: vector <T> однакового розміру та типу. Тут ОП хоче скопіювати підрозділ у новий, менший масив, як зазначено тут у дописі ОП: "0 <= X <= Y <= N-1"
Андрій

@Andrew, див. Приклад із використанням std :: copy та std :: back_inserter
chrisg

@LokiAstari чому б і ні?
chrisg

2
@LokiAstari Я мав на увазі редагування цього, що не пережив експертної оцінки, яка наводила приклад <br/> вектор <T> newvec; std :: копія (myvec.begin () + 10000, myvec.begin () +10100, std :: back_inserter (newvec)); <br/> У цьому випадку вам не потрібно спочатку будувати призначення, але, звичайно, пряма ініціалізація - це більше ... пряма.
chrisg

1
@chrisg: Його також два рядки. Крім того, вам потрібно дотримуватися третього рядка, щоб переконатися, що це ефективно. newvec.reserve(10100 - 10000);. Це, безумовно, варіант, і технічно це буде працювати. Але з двох, які ви збираєтесь рекомендувати?
Мартін Йорк

1

Єдиний спосіб спроектувати колекцію, яка не є лінійним часом, - це зробити ліниво, де отриманий "вектор" насправді є підтипом, який делегує оригінальній колекції. Наприклад, List#subseqметод Скали створює підряд в постійному часі. Однак це працює лише в тому випадку, якщо збір є непорушним і якщо основний мовний спортивний збір сміття.


у c ++ способом зробити це було б мати вектор shared_ptr до X замість вектора X, а потім копіювати SP, але, на жаль, я не думаю, що це швидше, тому що атомна операція пов'язана з копіюванням SP. Або оригінальний вектор може бути const shared_ptr вектора замість цього, і ви просто посилаєтесь на підрядку в ньому. ofc вам не потрібно робити це спільним_ptr вектора, але тоді у вас є проблеми з життям ... все це в моїй голові, могло помилятися ...
NoSenseEtAl

0

Розміщення цього пізнього лише для інших. Я ставлю ставку, що перший кодер вже зроблений. Для простих типів даних копія не потрібна, просто поверніться до старих хороших методів коду 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


Це копія не виконує.
Триларіон

0

Можливо, array_view / span у бібліотеці GSL є хорошим варіантом.

Ось також реалізація одного файлу: array_view .


Будь ласка, додайте тут відповідь разом із посиланням Оскільки зовнішнє посилання може змінитися в майбутньому
Panther

0

Копіювати елементи з одного вектора в інший Легко
У цьому прикладі я використовую вектор пар, щоб полегшити розуміння
`

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


0

Ще один варіант: корисний, наприклад, при переміщенні між a thrust::device_vectorі a 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));
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.