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


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.