2つのベクトルを連結する最良の方法は何ですか?


189

マルチトレッドを使用しており、結果をマージしたいと考えています。例えば:

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

ABにAの内容とBの内容をこの順序で持たせたい。このようなことをする最も効率的な方法は何ですか?


1
大きなサイズのコンテナで作業するときに効率を求める場合は、リストを使用する方が効率的かもしれません。リストでは、いくつかのポインタ操作を使用して、一方を他方に接続できます。ただし、リストにはスペースのオーバーヘッドがあります(単一のリンクリストを使用することを検討してください)。
Kemin Zhou

回答:


316
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
各要素をコピーする必要があるため、O(n)になります
Kirill V. Lyadvinsky 2013

1
新しい質問をするかどうかはわかりませんが、移動のセマンティクスを考慮に入れると、この回答を改善できますか?すべての要素をループするのではなく、単一のメモリ移動を行うようにコンパイラに期待/指示できる方法はありますか?
De Cat

2
@boycyいいえ。1つの要素をpush_backするために一定の時間で償却されます。n個の要素をプッシュバックすることはO(n)です
Konrad Lindenbach 2016年

1
@Konrad私は他のことを示唆していませんでしたが、明確化に感謝します。挿入操作の複雑さは、挿入される要素の数(決してO(n)を与える)によって与えられることはありませんが、スケーラビリティーの測定値を提供するため、既にコンテナーにある要素の数に関して与えられることに注意してください。
ボイシー2016年

64

これがまさにメンバー関数のstd::vector::insert目的です

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

4
@ニック:何に比べて遅い?
GManNickG

2
要素の挿入ごとに十分なスペースをチェックしているのでしょうか?事前に予約しておくとスピードアップします。
RvdK 2010

10
@ニック:insertランダムアクセスイテレータに特化し、事前に予約されたすべての最新のstdlib実装が驚くに値しません。
GManNickG

1
@Gman:ソースもベクトルであることを知っているので、これは公平な点です(イテレータdistanceはO(1)の複雑さを持っています)。それでも、のパフォーマンスの保証は、insert事前に計画することでより良い結果が得られる場合があることに留意する必要があります。
Nick Bastin、2010

2
スペースの@RvdKチェックは、ほんの一部の指示です。負荷容量、サイズと比較、条件付きジャンプ。ほとんどの場合、これらのコストはすべて無視できます。以来size < capacity、ほとんどの時間、分岐予測は、おそらく低反復回数を除き、分岐によって誘発される遅延を最小限に抑え、命令パイプラインになるように非再割り当てブランチの指示を引き起こします。これは、優れたベクトル実装と、CPU命令パイプラインおよび[適切な]分岐予測を前提としていますが、これらは、最新のツールチェーンとデスクトップマシンにとってかなり信頼できる前提です。しかし、スマートフォンについては知らない..
Boycy

27

2つのベクトルを物理的に連結する必要があるか、反復のために連結のように見せたいかによって異なります。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は2つのベクトルを新しいコンテナーにコピーしないことに注意してください。両方のコンテナーのスパンをカバーするイテレーターのペア(範囲)を生成します。パフォーマンスのオーバーヘッドは多少ありますが、最初にすべてのデータを新しいコンテナにコピーするよりは少ないかもしれません。


1
良いアイデア。しばらく考えた後、この目標はboostライブラリを使用しなくても達成できることに気付きました。方法を説明する回答を投稿しました。
Ronald Souza

11

Kiril V. Lyadvinsky answerに基づいて、新しいバージョンを作成しました。このスニペットでは、テンプレートとオーバーロードを使用しています。これを使うと、書くことができるvector3 = vector1 + vector2vector4 += 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に移動します2017

v1 + v2加算を表さないことは、一般的にはっきりとはわかりません。
Apollysは、


@F#のように使用することもできます
aloisdgがcodidact.comに移動します

5

Bradgonesurfingの回答の方向では、多くの場合、実際に 2つのベクトルを連結する必要はありません(O(n))、代わりにそれらを連結したかのように操作します(O(1))。これが当てはまる場合は、Boostライブラリがなくても実行できます。

コツは、ベクトルプロキシを作成することです。外部的には単一の連続したベクトルとして見られる、両方のベクトルへの参照を操作するラッパークラスです。

使用法

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はいません非constアクセス演算子([])を提供して。含めてもかまいませんが、覚えておいてください。ABには参照が含まれているため、ABに値を割り当てると、AやB内の元の要素にも影響します。これが望ましい機能であるかどうかにかかわらず、アプリケーション固有の問題です。慎重に検討してください。
  • AまたはBのいずれかに直接加えられた変更(値の割り当て、並べ替えなど)もABを「変更」します。これは必ずしも悪いことではありません(実際には非常に便利です。ABを明示的に更新してAとBの両方に同期させる必要はありません)。しかし、これは確かに注意すべき動作です。重要な例外:AやBのサイズをsth より大きいサイズに変更すると、これらがメモリ内で再割り当てされ(連続するスペースが必要になるため)、ABが無効になります。
  • 要素へのすべてのアクセスの前にはテストが続くため(つまり、 "i <v1.size()")、VecProxyのアクセス時間は一定ですが、ベクトルよりも少し遅くなります。
  • このアプローチは、n個のベクトルに一般化できます。私は試していませんが、大したことではありません。

2

まだ言及されていないもう1つの単純なバリアント:

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

ベクトルがソートされている場合は、<algorithm>のset_unionをチェックしてください。

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

リンクにはより完全な例があります

* rlbondに感謝


4
また、ストレートアペンドと同じことは行いません。出力範囲の要素は一意であり、OPが必要とするものとは異なる場合があります(比較できない場合もあります)。それは確かにそれを行う最も効率的な方法ではありません。
Peter

-1

すべてのソリューションは正しいですが、これを実装する関数を記述するだけの方が簡単であることがわかりました。このような:

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

これにより、次のような一時的な配置を回避できます。

ContainerInsert(vec, GetSomeVector());
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.