Який найкращий спосіб об'єднати два вектори?


189

Я використовую багатопотокове запитання і хочу об'єднати результати. Наприклад:

std::vector<int> A;
std::vector<int> B;
std::vector<int> AB;

Я хочу, щоб AB містив вміст A і вміст B у такому порядку. Який найефективніший спосіб зробити щось подібне?


1
Якщо ви шукаєте ефективність під час роботи з контейнерами великого розміру, можливо, буде більш ефективно використовувати список, де ви можете з'єднати один з одним за допомогою декількох операцій з покажчиком. Але у списку є вільний простір (розгляньте використання одного пов'язаного списку)
Kemin Zhou

Відповіді:


322
AB.reserve( A.size() + B.size() ); // preallocate memory
AB.insert( AB.end(), A.begin(), A.end() );
AB.insert( AB.end(), B.begin(), B.end() );

6
Дякую! Не подумав би про резерв.
jmasterx

10
він повинен скопіювати кожен елемент, тому це О (п)
Кирило В. Лядвінський

1
Не знаєте, ставити нове запитання чи ні, але чи можна покращити цю відповідь, враховуючи семантику руху? Чи я можу очікувати / доручити компілятору зробити один рух пам'яті замість того, щоб перекидатись усіма елементами?
Broes De Cat

2
@boycy Ні. Постійний час амортизації для повернення одного елемента амортизується. Відштовхувати n елементів - це O (n)
Конрад Лінденбах

1
@Konrad Я не мав на увазі інакше, але дякую за пояснення. Зауважте, складність операції з вставкою ніколи не наводиться з точки зору кількості вставлених елементів - що завжди дасть O (n) - але з точки зору кількості елементів, які вже є в контейнері, оскільки це забезпечує міру її масштабованості .
хлопець

65

Це саме те , що функція член std::vector::insertдля

std::vector<int> AB = A;
AB.insert(AB.end(), B.begin(), B.end());

4
@Nick: Повільніше порівняно з чим?
GManNickG

2
Може, це перевіряє достатньо місця на кожній вставці елемента? Попереднє використання резерву пришвидшить його.
RvdK

10
@ Nick: Я б не здивувався, якби кожна сучасна реалізація stdlib спеціалізувалася insertна ітераторах з випадковим доступом та зарезервована наперед .
GManNickG

1
@Gman: Це справедливий момент, оскільки ми знаємо, що джерело також є вектором (де ітератор distanceмає складність O (1)). Тим не менш, гарантії ефективності - insertце те, про що слід пам’ятати, коли часто можна зробити краще, плануючи заздалегідь.
Нік Бастін

2
@RvdK перевірка простору - це лише кілька інструкцій: вантажопідйомність, порівняння з розміром, умовний стрибок; все це є мізерною вартістю для більшості випадків. Оскільки size < capacityбільшу частину часу передбачення гілок, ймовірно, призведе до того, що вказівки нерозподільної гілки будуть вкладені в конвеєр інструкцій, мінімізуючи затримку, викликану гілкою, за винятком низького числа ітерацій. Це передбачає хорошу реалізацію вектора, плюс конвеєр процесорних інструкцій та [хороший] прогнозування галузей, але це досить надійні припущення для сучасної інструментальної та настільної машини. Не знаю, хоча про смартфони ..
хлопець

27

Залежить від того, чи дійсно вам потрібно фізично об'єднати два вектори або ви хочете надати вигляд конкатенації заради ітерації. Функція boost :: join

http://www.boost.org/doc/libs/1_43_0/libs/range/doc/html/range/reference/utilities/join.html

дасть вам це.

std::vector<int> v0;
v0.push_back(1);
v0.push_back(2);
v0.push_back(3);

std::vector<int> v1;
v1.push_back(4);
v1.push_back(5);
v1.push_back(6);
...

BOOST_FOREACH(const int & i, boost::join(v0, v1)){
    cout << i << endl;
}

повинен вам дати

1
2
3
4
5
6

Примітка boost :: join не копіює два вектори в новий контейнер, але генерує пару ітераторів (діапазон), які охоплюють проміжок обох контейнерів. Буде деяка продуктивність, але, можливо, менше, ніж спочатку скопіювати всі дані в новий контейнер.


1
Хороша ідея. Подумавши деякий час, я зрозумів, що ця мета також може бути досягнута без використання прискорених бібліотек. Я опублікував відповідь, де пояснюється як.
Рональд Суза

11

На основі відповіді Кирила В. Лядвінського я склав нову версію. Цей фрагмент використання шаблону та перевантаження. З ним можна писати vector3 = vector1 + vector2і vector4 += vector3. Сподіваюся, це може допомогти.

template <typename T>
std::vector<T> operator+(const std::vector<T> &A, const std::vector<T> &B)
{
    std::vector<T> AB;
    AB.reserve(A.size() + B.size());                // preallocate memory
    AB.insert(AB.end(), A.begin(), A.end());        // add A;
    AB.insert(AB.end(), B.begin(), B.end());        // add B;
    return AB;
}

template <typename T>
std::vector<T> &operator+=(std::vector<T> &A, const std::vector<T> &B)
{
    A.reserve(A.size() + B.size());                // preallocate memory without erase original data
    A.insert(A.end(), B.begin(), B.end());         // add B;
    return A;                                        // here A could be named AB
}

1
Ви маєте на увазі додати елементи кожного вектора один до одного? Або ви хочете додати? Це зрозуміло зараз, але протягом наступних 5 років ..? Ви не повинні перевантажувати оператора, якщо значення неоднозначне.
SR

2
@SR Я маю на увазі збити. Цю відповідь я написав 3 роки тому. Я досі знаю, що це означає. Ніяких проблем там немає. Якщо C ++ зможе забезпечити власну перевантаження, це буде ще краще. (і так ::прийнято;)
aloisdg переходить на codidact.com

Безумовно, не зрозуміло в цілому, що v1 + v2не означає додавання.
Аполліс підтримує Моніку


Альтернативою було б використання, @як у F #
aloisdg, перехід на codidact.com

6

У напрямку відповіді Бредгонесурфінга багато разів не потрібно поєднувати два вектори (O (n)), а натомість просто працювати з ними так, ніби вони з'єднані (O (1)) . Якщо це ваш випадок, це можна зробити без необхідності збільшення бібліотек.

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

ВИКОРИСТАННЯ

std::vector<int> A{ 1, 2, 3, 4, 5};
std::vector<int> B{ 10, 20, 30 };

VecProxy<int> AB(A, B);  // ----> O(1). No copies performed.

for (size_t i = 0; i < AB.size(); ++i)
    std::cout << AB[i] << " ";  // 1 2 3 4 5 10 20 30

ВПРОВАДЖЕННЯ

template <class T>
class VecProxy {
private:
    std::vector<T>& v1, v2;
public:
    VecProxy(std::vector<T>& ref1, std::vector<T>& ref2) : v1(ref1), v2(ref2) {}
    const T& operator[](const size_t& i) const;
    const size_t size() const;
};

template <class T>
const T& VecProxy<T>::operator[](const size_t& i) const{
    return (i < v1.size()) ? v1[i] : v2[i - v1.size()];
};

template <class T>
const size_t VecProxy<T>::size() const { return v1.size() + v2.size(); };

ОСНОВНА ПЕРЕВАГА

Це O (1) (постійний час) для його створення та з мінімальним додатковим розподілом пам'яті.

ДЕЯКІ ПЛАТИ, ЩО ВІДМОВИТИ

  • Вам слід зайнятися цим, лише якщо ви дійсно знаєте, чим займаєтесь, коли маєте справу з посиланнями . Це рішення призначене для конкретної мети поставленого питання, для якого воно працює досить добре . Використовувати його в будь-якому іншому контексті може призвести до несподіваної поведінки, якщо ви не впевнені в тому, як працюють посилання.
  • У цьому прикладі AB не забезпечує оператора доступу без дозволу ([]). Не соромтеся включати його, але пам’ятайте: оскільки АВ містить посилання, присвоєння ним значень також вплине на оригінальні елементи в межах А та / або В. Чи це бажана особливість, чи ні, це питання, яке стосується додатків, слід уважно розглянути.
  • Будь-які зміни, безпосередньо внесені до A або B (наприклад, присвоєння значень, сортування тощо), також "модифікують" AB. Це не обов'язково погано (насправді це може бути дуже зручно: AB ніколи не потрібно чітко оновлювати, щоб він був синхронізований як до А, так і до В), але це, безумовно, поведінка, якої слід знати. Важливий виняток: зміни розміру A і / або B на sth більше може призвести до їх перерозподілу в пам'яті (для необхідності суміжного простору), а це, в свою чергу, призведе до недійсності AB.
  • Оскільки кожному доступу до елемента передує тест (а саме "i <v1.size ()"), час доступу до VecProxy, хоча і постійний, також трохи повільніше, ніж у векторів.
  • Цей підхід можна узагальнити до n векторів. Я не пробував, але це не повинно бути великою справою.

2

Ще один простий варіант, про який ще не говорилося:

copy(A.begin(),A.end(),std::back_inserter(AB));
copy(B.begin(),B.end(),std::back_inserter(AB));

І за допомогою алгоритму злиття:

#include <algorithm> #include <vector> #include <iterator> #include <iostream> #include <sstream> #include <string> template<template<typename, typename...> class Container, class T> std::string toString(const Container<T>& v) { std::stringstream ss; std::copy(v.begin(), v.end(), std::ostream_iterator<T>(ss, "")); return ss.str(); }; int main() { std::vector<int> A(10); std::vector<int> B(5); //zero filled std::vector<int> AB(15); std::for_each(A.begin(), A.end(), [](int& f)->void { f = rand() % 100; }); std::cout << "before merge: " << toString(A) << "\n"; std::cout << "before merge: " << toString(B) << "\n"; merge(B.begin(),B.end(), begin(A), end(A), AB.begin(), [](int&,int&)->bool {}); std::cout << "after merge: " << toString(AB) << "\n"; return 1; }


-1

Якщо ваші вектори відсортовані *, перевірте set_union з <алгоритму>.

set_union(A.begin(), A.end(), B.begin(), B.end(), AB.begin());

Є більш ретельний приклад за посиланням

* спасибі rlbond


4
Крім того, це не робить те ж саме, що і пряме додавання - елементи в діапазоні виходу є унікальними, що може бути не тим, чого хотів ОП (вони можуть бути навіть не порівнянні). Це, звичайно, не найефективніший спосіб зробити це.
Пітер

-1

Усі рішення правильні, але мені було легше просто написати функцію, щоб реалізувати це. подобається це:

template <class T1, class T2>
void ContainerInsert(T1 t1, T2 t2)
{
    t1->insert(t1->end(), t2->begin(), t2->end());
}

Таким чином ви можете уникнути тимчасового розміщення, як це:

ContainerInsert(vec, GetSomeVector());
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.