Об'єднання двох std :: векторів


687

Як з'єднати два std::vectorс?


5
Надані відповіді насправді не поєднуються. Вони додають копію. Можливо, може бути використане (з точки зору ефективності) створення методу std :: vector concatenate, однак це вимагатиме дещо складного обміну управліннями вузлами, і, мабуть, тому це не було зроблено.
Fauhhristian

8
@FauChristian: Ні, використання точки зору ефективності може не бути корисним. Векторна пам'ять повинна бути безперервною, тому те, що вам пропонують, неможливо. Якби ви хотіли "дещо складного обміну управліннями вузлами", і якби ви змінили векторний клас таким чином, ви б закінчилися з декею. Навіть тоді дуже важко повторно використовувати пам'ять у запропонованому способом, хоч це і стане трохи більш здійсненним. Я не думаю, що він зараз реалізований. Головне, що при такому спільному використанні вузлів управління (deque) кінцевий вузол може бути частково порожнім.
Печиво

4
@lecaruyer Ви розумієте, що щойно ви позначили питання, яке було задано два роки до цього, як дублікат
eshirima

9
Мене єдине цікавить, чому це не реалізовано як a + bабо a.concat(b)у стандартній бібліотеці? Можливо, реалізація за замовчуванням була б неоптимальною, але кожне об'єднання масивів не потребує мікрооптимізації
oseiskar

9
роки еволюції, найсучасніший оператор-перевантаження будь-якої основної мови, шаблонна система, що подвоює складність мови, і все ж відповідь не v = v1 + v2;
Spike0xff

Відповіді:


727
vector1.insert( vector1.end(), vector2.begin(), vector2.end() );

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

34
Я маю питання. Чи буде це працювати, якщо вектор1 і вектор2 - однакові вектори?
Олександр Рафферті

6
Якщо ви об'єднали кілька векторів до одного, чи корисно спочатку зателефонувати reserveна вектор призначення?
Faheem Mitha

33
@AlexanderRafferty: Тільки якщо vector1.capacity() >= 2 * vector1.size(). Що нетипово, якщо ви не телефонували std::vector::reserve(). В іншому випадку вектор буде перерозподілятися, виключаючи ітератори, передані як параметри 2 і 3.
Дрю Дорманн,

28
Це дуже погано, що в стандартній бібліотеці немає виразнішого виразу. .concatабо що- +=небудь
nmr

193

Якщо ви використовуєте C ++ 11 і хочете перемістити елементи, а не просто їх копіювання, ви можете використовувати std::move_iteratorразом із вставкою (або копією):

#include <vector>
#include <iostream>
#include <iterator>

int main(int argc, char** argv) {
  std::vector<int> dest{1,2,3,4,5};
  std::vector<int> src{6,7,8,9,10};

  // Move elements from src to dest.
  // src is left in undefined but safe-to-destruct state.
  dest.insert(
      dest.end(),
      std::make_move_iterator(src.begin()),
      std::make_move_iterator(src.end())
    );

  // Print out concatenated vector.
  std::copy(
      dest.begin(),
      dest.end(),
      std::ostream_iterator<int>(std::cout, "\n")
    );

  return 0;
}

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

#include <vector>
#include <iostream>
#include <iterator>

int main(int argc, char** argv) {
  std::vector<std::vector<int>> dest{{1,2,3,4,5}, {3,4}};
  std::vector<std::vector<int>> src{{6,7,8,9,10}};

  // Move elements from src to dest.
  // src is left in undefined but safe-to-destruct state.
  dest.insert(
      dest.end(),
      std::make_move_iterator(src.begin()),
      std::make_move_iterator(src.end())
    );

  return 0;
}

Після переміщення елемент src залишається у невизначеному, але безпечному для руйнування стані, а його колишні елементи були перенесені безпосередньо до нового елемента dest у кінці.


6
Метод std :: make_move_iterator () допоміг мені при спробі об'єднання std :: vectors of std :: unique_ptr.
Knitschi

Яка різниця між цим і std::move(src.begin(), src.end(), back_inserter(dest))?
кшеной


77

Або ви можете використовувати:

std::copy(source.begin(), source.end(), std::back_inserter(destination));

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


7
метод копіювання - це не такий вдалий спосіб. Він буде викликати push_back кілька разів, що означає, що якщо потрібно вставити багато елементів, це може означати кілька перерозподілів. краще використовувати вставку, оскільки векторна реалізація може зробити оптимізацію, щоб уникнути перерозподілу. він може зарезервувати пам'ять перед початком копіювання
Йогеш Арора

7
@Yogesh: надано, але ніщо не заважає тобі reserveспочатку телефонувати . Причина std::copyіноді корисна в тому, якщо ви хочете використовувати щось інше, ніж back_inserter.
Роджер Ліпскомб

Коли ви говорите "кілька розподілів", це правда - але кількість асигнувань знаходиться в гіршому випадку (кількість записів додано) - це означає, що вартість додавання запису є постійною в кількості доданих записів. (В основному, не хвилюйтеся з цього приводу, якщо профілювання не показує, що вам потрібен резерв).
Мартін Боннер підтримує Моніку

Ви можете використовувати для цього std :: transform.
Мартін Бродхерст

1
копія смокче багато, навіть із резервом. vector :: insert уникне всіх перевірок: quick-bench.com/bLJO4OfkAzMcWia7Pa80ynwmAIA
Денис Ярошевський,

63

З C ++ 11 я вважаю за краще додати вектор b до:

std::move(b.begin(), b.end(), std::back_inserter(a));

коли aі bне перекриваються, і bбільше не використовуватимуться.


Це std::moveз <algorithm>, а не звичайне std::move з <utility>.


10
Не визначена поведінка, якщо насправді є b (що нормально, якщо ви знаєте, що цього ніколи не може відбутися, - але варто пам’ятати про них у загальному коді цілей).
Мартін Боннер підтримує Моніку

1
@MartinBonner Дякую, що згадуєте про це. Напевно, я повинен повернутися до старого insertспособу, який є більш безпечним.
Декінг

15
Ах, ІНШИЙ std :: ход. Досить заплутаний перший раз, коли ви його бачите.
xaxxon

1
Це відрізняється від insert()з move_iteratorс? Якщо так, то як?
GPhilo

1
Я додав замітку про те, що std::moveми тут говоримо, оскільки більшість людей не знають цього перевантаження. Сподіваюся, це поліпшення.
YSC


24

Я віддаю перевагу тому, про яке вже говорилося:

a.insert(a.end(), b.begin(), b.end());

Але якщо ви використовуєте C ++ 11, є ще один загальний спосіб:

a.insert(std::end(a), std::begin(b), std::end(b));

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


Отже, в основному, що вам потрібно:

template <typename T>
void Append(std::vector<T>& a, const std::vector<T>& b)
{
    a.reserve(a.size() + b.size());
    a.insert(a.end(), b.begin(), b.end());
}

2
std::виводиться за допомогою аргументованого пошуку . end(a)буде достатньо
Асу

4
@Asu ADL додасть лише std::тоді, коли aпоходить тип std, який перемагає загальний аспект.
Potatoswatter

гарна думка. у цьому випадку це вектор, тому він би працював у будь-якому випадку, але так, це краще рішення.
Asu

std :: begin () / end () були додані для колекцій (як масиви), які не мають їх як член-функції. Але масиви також не мають функції вставки (), і викликає питання "Чи існує колекція зі вставкою (), але без start () (яка працює з std :: begin ())?"
Джеймс Курран


12

Ви повинні використовувати вектор :: insert

v1.insert(v1.end(), v2.begin(), v2.end());

3
Хіба це не те саме, що відповіли Том Ріттер та Роберт Гембл у 2008 році?
IgNite

9

Загальний приріст продуктивності для СЦЕПИТЬ, щоб перевірити розмір векторів. І об'єднайте / вставте менший з більшим.

//vector<int> v1,v2;
if(v1.size()>v2.size()) {
    v1.insert(v1.end(),v2.begin(),v2.end());
} else {
    v2.insert(v2.end(),v1.begin(),v1.end());
}

Так просто, але я ніколи про це не думав!
Zimano

2
Зразок коду неправильний. v1.insert(v2.end()...використовує ітератор, v2щоб вказати позицію в v1.
Девід Стоун

Ви також можете скористатися швидким свопом. @DavidStone Я відредагував це так, щоб порядок лаконічності мінявся. Чи можна додати до початку вектора?
qwr

Можна вставити на початку, але це буде повільніше. Однак, щоб по-справжньому "об'єднати", порядок, як правило, має значення, так що саме вам потрібно зробити.
Девід Стоун

7

Якщо ви хочете мати можливість стисло зв'язати вектори, ви можете перевантажити +=оператора.

template <typename T>
std::vector<T>& operator +=(std::vector<T>& vector1, const std::vector<T>& vector2) {
    vector1.insert(vector1.end(), vector2.begin(), vector2.end());
    return vector1;
}

Тоді ви можете назвати це так:

vector1 += vector2;

6

Якщо вас цікавить сильна гарантія виключення (коли конструктор копій може викинути виняток):

template<typename T>
inline void append_copy(std::vector<T>& v1, const std::vector<T>& v2)
{
    const auto orig_v1_size = v1.size();
    v1.reserve(orig_v1_size + v2.size());
    try
    {
        v1.insert(v1.end(), v2.begin(), v2.end());
    }
    catch(...)
    {
        v1.erase(v1.begin() + orig_v1_size, v1.end());
        throw;
    }
}

Подібне append_moveз високою гарантією взагалі не може бути реалізовано, якщо конструктор переміщення векторного елемента може кинути (що малоймовірно, але все ж).


Хіба не можна v1.erase(...також кидати?
Скелет класу

insertвже справляється з цим. Також цей дзвінок eraseеквівалентний а resize.
Potatoswatter

Мені це подобається - дякую!
natersoz

5

Додайте його до файлу заголовка:

template <typename T> vector<T> concat(vector<T> &a, vector<T> &b) {
    vector<T> ret = vector<T>();
    copy(a.begin(), a.end(), back_inserter(ret));
    copy(b.begin(), b.end(), back_inserter(ret));
    return ret;
}

і використовувати його таким чином:

vector<int> a = vector<int>();
vector<int> b = vector<int>();

a.push_back(1);
a.push_back(2);
b.push_back(62);

vector<int> r = concat(a, b);

r міститиме [1,2,62]


Не знаю, чому це було знято. Це може бути не найефективнішим способом зробити це, але це не так і є ефективним.
leeor_net

4

Ось загальне рішення із використанням семантики переміщення C ++ 11:

template <typename T>
std::vector<T> concat(const std::vector<T>& lhs, const std::vector<T>& rhs)
{
    if (lhs.empty()) return rhs;
    if (rhs.empty()) return lhs;
    std::vector<T> result {};
    result.reserve(lhs.size() + rhs.size());
    result.insert(result.cend(), lhs.cbegin(), lhs.cend());
    result.insert(result.cend(), rhs.cbegin(), rhs.cend());
    return result;
}

template <typename T>
std::vector<T> concat(std::vector<T>&& lhs, const std::vector<T>& rhs)
{
    lhs.insert(lhs.cend(), rhs.cbegin(), rhs.cend());
    return std::move(lhs);
}

template <typename T>
std::vector<T> concat(const std::vector<T>& lhs, std::vector<T>&& rhs)
{
    rhs.insert(rhs.cbegin(), lhs.cbegin(), lhs.cend());
    return std::move(rhs);
}

template <typename T>
std::vector<T> concat(std::vector<T>&& lhs, std::vector<T>&& rhs)
{
    if (lhs.empty()) return std::move(rhs);
    lhs.insert(lhs.cend(), std::make_move_iterator(rhs.begin()), std::make_move_iterator(rhs.end()));
    return std::move(lhs);
}

Зверніть увагу, чим це відрізняється від appending до a vector.


4

Ви можете підготувати власний шаблон для + оператора:

template <typename T> 
inline T operator+(const T & a, const T & b)
{
    T res = a;
    res.insert(res.end(), b.begin(), b.end());
    return res;
}

Наступне - просто використовувати +:

vector<int> a{1, 2, 3, 4};
vector<int> b{5, 6, 7, 8};
for (auto x: a + b)
    cout << x << " ";
cout << endl;

Цей приклад дає вихід:

1 2 3 4 5 6 7 8

3
Використовувати T operator+(const T & a, const T & b)небезпечно, краще використовувати vector<T> operator+(const vector<T> & a, const vector<T> & b).
Matthieu H

4

Існує алгоритм std::mergeіз C ++ 17 , яка дуже проста у використанні,

Нижче наведено приклад:

#include <iostream>
#include <vector>
#include <algorithm>

int main()
{
    //DATA
    std::vector<int> v1{2,4,6,8};
    std::vector<int> v2{12,14,16,18};

    //MERGE
    std::vector<int> dst;
    std::merge(v1.begin(), v1.end(), v2.begin(), v2.end(), std::back_inserter(dst));

    //PRINT
    for(auto item:dst)
        std::cout<<item<<" ";

    return 0;
}

3
Я не думаю, що це легше використовувати std::vector::insert, але це робить щось інше: об'єднання двох діапазонів у новий діапазон проти вставлення одного вектора в кінці іншого. Варто згадати у відповіді?
jb

4

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

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

VecProxy<int> AB(A, B);  // ----> O(1)!

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

Докладнішу інформацію див. На https://stackoverflow.com/a/55838758/2379625 , включаючи реалізацію "VecProxy", а також плюси та мінуси.


3
vector<int> v1 = {1, 2, 3, 4, 5};
vector<int> v2 = {11, 12, 13, 14, 15};
copy(v2.begin(), v2.end(), back_inserter(v1));

6
Хоча цей фрагмент коду може вирішити проблему, він не пояснює, чому або як він відповідає на питання. Будь ласка, додайте пояснення до свого коду , оскільки це дійсно допомагає покращити якість вашої публікації. Флаггери / рецензенти: Для відповідей, що стосуються лише коду, таких як ця, downvote, не видаляйте! (Примітка. Ця відповідь насправді може бути досить простою, щоб зробити пояснення, і, таким чином, зворотним шляхом, непотрібним. Ви все ще можете додати пояснення, щоб запобігти появі більше прапорів NAA / VLQ.)
Скотт Уелдон,

2

Я реалізував цю функцію, яка об'єднує будь-яку кількість контейнерів, переходячи від rvalue-посилань і копіюючи інакше

namespace internal {

// Implementation detail of Concatenate, appends to a pre-reserved vector, copying or moving if
// appropriate
template<typename Target, typename Head, typename... Tail>
void AppendNoReserve(Target* target, Head&& head, Tail&&... tail) {
    // Currently, require each homogenous inputs. If there is demand, we could probably implement a
    // version that outputs a vector whose value_type is the common_type of all the containers
    // passed to it, and call it ConvertingConcatenate.
    static_assert(
            std::is_same_v<
                    typename std::decay_t<Target>::value_type,
                    typename std::decay_t<Head>::value_type>,
            "Concatenate requires each container passed to it to have the same value_type");
    if constexpr (std::is_lvalue_reference_v<Head>) {
        std::copy(head.begin(), head.end(), std::back_inserter(*target));
    } else {
        std::move(head.begin(), head.end(), std::back_inserter(*target));
    }
    if constexpr (sizeof...(Tail) > 0) {
        AppendNoReserve(target, std::forward<Tail>(tail)...);
    }
}

template<typename Head, typename... Tail>
size_t TotalSize(const Head& head, const Tail&... tail) {
    if constexpr (sizeof...(Tail) > 0) {
        return head.size() + TotalSize(tail...);
    } else {
        return head.size();
    }
}

}  // namespace internal

/// Concatenate the provided containers into a single vector. Moves from rvalue references, copies
/// otherwise.
template<typename Head, typename... Tail>
auto Concatenate(Head&& head, Tail&&... tail) {
    size_t totalSize = internal::TotalSize(head, tail...);
    std::vector<typename std::decay_t<Head>::value_type> result;
    result.reserve(totalSize);
    internal::AppendNoReserve(&result, std::forward<Head>(head), std::forward<Tail>(tail)...);
    return result;
}

1

Якщо те, що ви шукаєте, це спосіб додавання вектора до іншого після створення, vector::insert- це найкраща ставка, на яку було відповідено кілька разів, наприклад:

vector<int> first = {13};
const vector<int> second = {42};

first.insert(first.end(), second.cbegin(), second.cend());

На жаль, немає способу побудувати const vector<int>, як вище, ви повинні побудувати і потім insert.


Якщо ви насправді шукаєте контейнер для з'єднання цих двох vector<int>с, можливо, вам стане щось краще, якщо:

  1. У вас vectorє примітиви
  2. Ваші вміщені примітиви мають розмір 32-бітовий або менший розмір
  3. Ви хочете constконтейнер

Якщо вищезазначене все вірно, я б запропонував використовувати, basic_stringхто char_typeвідповідає розміру примітиву, що міститься у вашому vector. Ви повинні включити static_assertу свій код, щоб підтвердити, що ці розміри залишаються послідовними:

static_assert(sizeof(char32_t) == sizeof(int));

З цим правдою ви просто можете:

const u32string concatenation = u32string(first.cbegin(), first.cend()) + u32string(second.cbegin(), second.cend());

Більш детальну інформацію про відмінності між stringта vectorможна подивитися тут: https://stackoverflow.com/a/35558008/2642059

Живий приклад цього коду ви можете подивитися тут: http://ideone.com/7Iww3I


0

Це рішення може бути дещо складним, але boost-rangeможе запропонувати ще деякі приємні речі.

#include <iostream>
#include <vector>
#include <boost/range/algorithm/copy.hpp>

int main(int, char**) {
    std::vector<int> a = { 1,2,3 };
    std::vector<int> b = { 4,5,6 };
    boost::copy(b, std::back_inserter(a));
    for (auto& iter : a) {
        std::cout << iter << " ";
    }
    return EXIT_SUCCESS;
}

Найчастіше цією метою є поєднання вектора aі bпросто повторення над ним, виконуючи якусь операцію. У цьому випадку є смішна проста joinфункція.

#include <iostream>
#include <vector>
#include <boost/range/join.hpp>
#include <boost/range/algorithm/copy.hpp>

int main(int, char**) {
    std::vector<int> a = { 1,2,3 };
    std::vector<int> b = { 4,5,6 };
    std::vector<int> c = { 7,8,9 };
    // Just creates an iterator
    for (auto& iter : boost::join(a, boost::join(b, c))) {
        std::cout << iter << " ";
    }
    std::cout << "\n";
    // Can also be used to create a copy
    std::vector<int> d;
    boost::copy(boost::join(a, boost::join(b, c)), std::back_inserter(d));
    for (auto& iter : d) {
        std::cout << iter << " ";
    }
    return EXIT_SUCCESS;
}

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

Чомусь немає нічого подібного boost::join(a,b,c), що могло б бути розумним.


0

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

#include <iostream>
#include <vector>
#include <algorithm>

template<typename T>

void concat(std::vector<T>& valuesa, std::vector<T>& valuesb){

     for_each(valuesb.begin(), valuesb.end(), [&](int value){ valuesa.push_back(value);});
}

int main()
{
    std::vector<int> values_p={1,2,3,4,5};
    std::vector<int> values_s={6,7};

   concat(values_p, values_s);

    for(auto& it : values_p){

        std::cout<<it<<std::endl;
    }

    return 0;
}

Ви можете очистити другий вектор, якщо не хочете його більше використовувати ( clear()метод).


-1

З’єднайте два std::vector-sз forпетлею в однуstd::vector .

Приклад:

std::vector <int> v1 {1, 2, 3};//declare vector1
std::vector <int> v2 {4, 5};//declare vector2
std::vector <int> suma;//declare vector suma

for(auto i = 0; i < v1.size()-1;i++)//for loop 1
{
     suma.push_back(v1[i]);
}

for(auto i = 0; i< v2.size()-1;i++)/for loop 2
{
     suma.push_back(v2[i]);
}

for(auto i = 0; i < suma.size(); i++)//for loop 3-output
{
     std::cout<<suma[i];
}

Напишіть цей код у main().


Ви forпетлі помиляєтесь. Дійсні індекси у векторі від 0 до size()-1. Ви повинні виконувати умови припинення циклу i < v1.size(), <не використовуючи <=. Використання неправильного стану отримує доступ до пам'яті поза контейнером.
Доменна піч

окрім того, що він не працює, цей код сильно не ідіоматичний. Ви повинні принаймні використовувати autoітератори замість ручної індексації. Вам байдуже, який індекс ви об'єднує лише те, що це робиться послідовно.
Tarick Веллінг

Чи можете ви пояснити, чому ви використовуєте size()-1в двох своїх умовах циклу? Це пропускає останні векторні елементи. Третя петля - єдино правильна зараз.
Доменна піч

-3

Якщо чесно, ви можете швидко об'єднати два вектори, скопіювавши елементи з двох векторів в інший або просто додати один з двох векторів !. Це залежить від вашої мети.

Спосіб 1: Призначте новий вектор за його розміром - це сума розміру двох вихідних векторів.

vector<int> concat_vector = vector<int>();
concat_vector.setcapacity(vector_A.size() + vector_B.size());
// Loop for copy elements in two vectors into concat_vector

Спосіб 2: Додайте вектор A шляхом додавання / вставки елементів вектора В.

// Loop for insert elements of vector_B into vector_A with insert() 
function: vector_A.insert(vector_A .end(), vector_B.cbegin(), vector_B.cend());

4
Що додає ваша відповідь, що ще не було вказано в інших відповідях?
Мат

13
@Mat: Сміливі символи.
marcv81

Якщо вихідні вектори після цього більше не потрібні, можливо, краще використовувати std::move_iteratorтак, щоб елементи переміщувалися замість скопійованих. (див. en.cppreference.com/w/cpp/iterator/move_iterator ).
tmlen

Що таке setcapacity? Що таке function: ?
LF

@LF Я думаю, що він говорить про resizeметод.
Матьє H
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.