2つ以上のコンテナーを同時に反復処理する最良の方法は何ですか


114

C ++ 11は、コンテナーを反復処理する複数の方法を提供します。例えば:

範囲ベースのループ

for(auto c : container) fun(c)

std :: for_each

for_each(container.begin(),container.end(),fun)

ただし、次のようなことを達成するために同じサイズの2つ(またはそれ以上)のコンテナーを反復処理するための推奨される方法は何ですか?

for(unsigned i = 0; i < containerA.size(); ++i) {
  containerA[i] = containerB[i];
}

1
でのtransform存在は#include <algorithm>どうですか
Ankit Acharya、2015

割り当てループについて:両方がベクトルまたは類似している場合containerA = containerB;は、ループの代わりに使用します。
emlai 2015年


回答:


53

むしろパーティーに遅れる。しかし:私はインデックスを反復します。しかし、古典的なforループではなくfor、インデックスの範囲ベースのループを使用します。

for(unsigned i : indices(containerA)) {
    containerA[i] = containerB[i];
}

indicesインデックスの(遅延評価)範囲を返す単純なラッパー関数です。実装は単純ですが、ここで投稿するには少し長すぎるため、GitHubで実装を見つけることができます

このコードは、手動の古典的なループを使用するのと同じくらい効率的forです。

このパターンがデータで頻繁に発生する場合は、zip2つのシーケンスを使用して、ペアの要素に対応する一連のタプルを生成する別のパターンの使用を検討してください。

for (auto& [a, b] : zip(containerA, containerB)) {
    a = b;
}

の実装はzip読者の練習問題として残されていますが、の実装から簡単に実行できindicesます。

(C ++ 17以前は、代わりに次のように記述する必要があります。)

for (auto items&& : zip(containerA, containerB))
    get<0>(items) = get<1>(items);

2
ブースティングcounting_rangeと比較して、インデックス実装の利点はありますか?簡単に使用できるboost::counting_range(size_t(0), containerA.size())
SebastianK

3
@SebastianKこの場合の最大の違いは構文です。私は(私が主張するところ)このケースでは客観的に使用する方が優れています。さらに、ステップサイズを指定できます。リンクされたGithubページ、特にREADMEファイルを例として参照してください。
Konrad Rudolph

あなたのアイデアは非常に素晴らしく、counting_rangeの使用を思いついたのは、それを確認した後だけです。例えば、パフォーマンスに関して。もちろん、より良い構文でも同意しますが、この欠点を補うために単純なジェネレーター関数を作成するだけで十分です。
SebastianK

@SebastianK私がコードを書いたとき、私はそれを、ライブラリを使用せずに孤立して生きるのに十分単純であると考えていたことを認めます(そしてそれはそうです!)。今私はおそらくそれをBoost.Rangeのラッパーとして書くでしょう。そうは言っても、私のライブラリのパフォーマンスはすでに最適です。つまり、私のindices実装を使用すると、手動ループを使用する場合と同じコンパイラ出力が生成されforます。オーバーヘッドは一切ありません。
Konrad Rudolph

とにかく私はブーストを使用するので、私の場合はそれが簡単になります。このラッパーはブースト範囲について既に記述しました。必要なのは、コードが1行の関数だけです。ただし、ブースト範囲のパフォーマンスも最適であるかどうかに興味があります。
SebastianK

38

特定の例については、単に使用してください

std::copy_n(contB.begin(), contA.size(), contA.begin())

より一般的なケースでzip_iteratorは、小さな関数でBoost.Iteratorを使用して、範囲ベースのforループで使用できるようにすることができます。ほとんどの場合、これは機能します。

template<class... Conts>
auto zip_range(Conts&... conts)
  -> decltype(boost::make_iterator_range(
  boost::make_zip_iterator(boost::make_tuple(conts.begin()...)),
  boost::make_zip_iterator(boost::make_tuple(conts.end()...))))
{
  return {boost::make_zip_iterator(boost::make_tuple(conts.begin()...)),
          boost::make_zip_iterator(boost::make_tuple(conts.end()...))};
}

// ...
for(auto&& t : zip_range(contA, contB))
  std::cout << t.get<0>() << " : " << t.get<1>() << "\n";

実例。

しかし、本格的な汎用性のために、あなたはおそらくもっと何かしたい、このメンバーを持っていない配列やユーザー定義型のために正しく動作しますが、begin()/ end()しかし持っているbegin/ end自分の名前空間の機能を。また、これにより、ユーザーconstzip_c...関数を介して具体的にアクセスできます。

そして、私のように素敵なエラーメッセージの擁護者であれば、おそらくこれが必要になります。これは、一時的なコンテナがzip_...関数のいずれかに渡されたかどうかをチェックし、そうであれば素敵なエラーメッセージを出力します。


1
ありがとう!1つの質問ですが、なぜauto &&を使用するのですか、それは&&の意味を教えてください。
memecs 2012

@memecs:私はこの質問に目を通すことをお勧めします。また、控除と参照の折りたたみがどのように行われるかを説明している私の私の回答もお読みください。注auto正確テンプレートパラメータと同じように機能し、そしてT&&テンプレートでは、その最初のリンクで説明したように普遍的参照であるauto&& v = 42と推測されるint&&auto&& w = v;、その後のように推測されますint&。コピーを作成せずに、左辺値と右辺値を一致させ、両方を変更可能にすることができます。
Xeo

@Xeo:しかし、foreachループでauto &&がauto&より優れている点は何ですか?
Viktor Sehr 2013

@ViktorSehr:によって生成されるような一時的な要素にバインドできますzip_range
Xeo

23
@Xeo例へのリンクはすべて壊れています。
kynan 2015年

34

なぜ誰もこれに言及しなかったのかしら。

auto ItA = VectorA.begin();
auto ItB = VectorB.begin();

while(ItA != VectorA.end() || ItB != VectorB.end())
{
    if(ItA != VectorA.end())
    {
        ++ItA;
    }
    if(ItB != VectorB.end())
    {
        ++ItB;
    }
}

PS:コンテナーのサイズが一致しない場合は、ifステートメント内にコードを配置する必要があります。


9

ヘッダーで提供されるように、複数のコンテナーで特定のことを行う方法はたくさんありますalgorithm。たとえば、指定した例std::copyでは、明示的なforループの代わりに使用できます。

一方、通常のforループ以外に、複数のコンテナーを総称的に反復する組み込みの方法はありません。反復する方法はたくさんあるので、これは当然のことです。考えてみてください。1つのコンテナーを1つのステップで繰り返し、1つのコンテナーを別のステップで繰り返します。または、一方のコンテナを最後まで通過し、挿入を開始しながら、もう一方のコンテナの最後まで進みます。または、最初のコンテナの1つのステップで他のコンテナを完全に通過するたびに、最初からやり直します。または他のパターン; 一度に3つ以上のコンテナ。など...

ただし、2つのコンテナを最短の長さまで反復する独自の「for_each」スタイルの関数を作成する場合は、次のようにします。

template <typename Container1, typename Container2>
void custom_for_each(
  Container1 &c1,
  Container2 &c2,
  std::function<void(Container1::iterator &it1, Container2::iterator &it2)> f)
{
  Container1::iterator begin1 = c1.begin();
  Container2::iterator begin2 = c2.begin();
  Container1::iterator end1 = c1.end();
  Container2::iterator end2 = c2.end();
  Container1::iterator i1;
  Container1::iterator i2;
  for (i1 = begin1, i2 = begin2; (i1 != end1) && (i2 != end2); ++it1, ++i2) {
    f(i1, i2);
  }
}

当然のことながら、同様の方法で任意の種類の反復戦略を作成できます。

もちろん、内部forループを直接実行する方が、このようなカスタム関数を作成するよりも簡単であると主張するかもしれません。しかし、これは非常に再利用可能であるというのが良い点です。=)


ループの前にイテレータを宣言する必要があるようです?私はこれを試しました:for (Container1::iterator i1 = c1.begin(), Container2::iterator i2 = c2.begin(); (i1 != end1) && (i2 != end2); ++it1, ++i2)しかしコンパイラは大声で叫びました。これが無効である理由を誰かが説明できますか?
David Doria

@DavidDoria forループの最初の部分は単一のステートメントです。同じステートメントで異なる型の2つの変数を宣言することはできません。なぜfor (int x = 0, y = 0; ...機能するか考えてみてくださいfor (int x = 0, double y = 0; ...)
wjl

1
..ただし、std :: pair <Container1 :: iterator、Container2 :: iterator> its = {c1.begin()、c2.begin()};を使用できます。
lorro

1
もう1つ注意すべき点は、これはC ++ 14を使用して簡単に可変にすることができるということですtypename...
wjl

8

2つのコンテナのみを同時に反復処理する必要がある場合は、ブースト範囲ライブラリに標準のfor_eachアルゴリズムの拡張バージョンがあります。例:

#include <vector>
#include <boost/assign/list_of.hpp>
#include <boost/bind.hpp>
#include <boost/range/algorithm_ext/for_each.hpp>

void foo(int a, int& b)
{
    b = a + 1;
}

int main()
{
    std::vector<int> contA = boost::assign::list_of(4)(3)(5)(2);
    std::vector<int> contB(contA.size(), 0);

    boost::for_each(contA, contB, boost::bind(&foo, _1, _2));
    // contB will be now 5,4,6,3
    //...
    return 0;
}

1つのアルゴリズムで3つ以上のコンテナを処理する必要がある場合は、zipで遊ぶ必要があります。


素晴らしい!どうやって見つけたの?どこにも記載されていないようです。
ミハイル、

4

別の解決策は、ラムダ内の他のコンテナのイテレータの参照をキャプチャし、その上でポストインクリメント演算子を使用することです。たとえば、単純なコピーは次のようになります。

vector<double> a{1, 2, 3};
vector<double> b(3);

auto ita = a.begin();
for_each(b.begin(), b.end(), [&ita](auto &itb) { itb = *ita++; })

ラムダ内では、何でもできるしita、それをインクリメントできます。これは、複数のコンテナのケースに簡単に拡張できます。


3

範囲ライブラリは、この機能やその他の非常に役立つ機能を提供します。次の例では、Boost.Rangeを使用していますエリックニーブラーのrangev3は良い代替品となるはずです。

#include <boost/range/combine.hpp>
#include <iostream>
#include <vector>
#include <list>

int main(int, const char*[])
{
    std::vector<int> const v{0,1,2,3,4};
    std::list<char> const  l{'a', 'b', 'c', 'd', 'e'};

    for(auto const& i: boost::combine(v, l))
    {
        int ti;
        char tc;
        boost::tie(ti,tc) = i;
        std::cout << '(' << ti << ',' << tc << ')' << '\n';
    }

    return 0;
}

C ++ 17は、構造化バインディングを使用してこれをさらに改善します。

int main(int, const char*[])
{
    std::vector<int> const v{0,1,2,3,4};
    std::list<char> const  l{'a', 'b', 'c', 'd', 'e'};

    for(auto const& [ti, tc]: boost::combine(v, l))
    {
        std::cout << '(' << ti << ',' << tc << ')' << '\n';
    }

    return 0;
}

このプログラムはg ++ 4.8.0でコンパイルされていません。 delme.cxx:15:25: error: no match for 'operator=' (operand types are 'std::tuple<int&, char&>' and 'const boost::tuples::cons<const int&, boost::tuples::cons<const char&, boost::tuples::null_type> >') std::tie(ti,tc) = i; ^
2016年

std :: tieをboost:tieに変更した後、コンパイルされた。
サイアム

私はバインディング(MSVC使用して構造化されたバージョンについて、次のコンパイルエラーを取得19.13.26132.0し、Windows SDKのバージョン10.0.16299.0:) error C2679: binary '<<': no operator found which takes a right-hand operand of type 'const boost::tuples::cons<const char &,boost::fusion::detail::build_tuple_cons<boost::fusion::single_view_iterator<Sequence,boost::mpl::int_<1>>,Last,true>::type>' (or there is no acceptable conversion)
pooya13を

構造化バインディングが動作しないようですboost::combinestackoverflow.com/q/55585723/8414561
Dev Null

2

私も少し遅れています。これを使用できます(Cスタイルの可変関数):

template<typename T>
void foreach(std::function<void(T)> callback, int count, ...) {
    va_list args;
    va_start(args, count);

    for (int i = 0; i < count; i++) {
        std::vector<T> v = va_arg(args, std::vector<T>);
        std::for_each(v.begin(), v.end(), callback);
    }

    va_end(args);
}

foreach<int>([](const int &i) {
    // do something here
}, 6, vecA, vecB, vecC, vecD, vecE, vecF);

またはこれ(関数パラメーターパックを使用):

template<typename Func, typename T>
void foreach(Func callback, std::vector<T> &v) {
    std::for_each(v.begin(), v.end(), callback);
}

template<typename Func, typename T, typename... Args>
void foreach(Func callback, std::vector<T> &v, Args... args) {
    std::for_each(v.begin(), v.end(), callback);
    return foreach(callback, args...);
}

foreach([](const int &i){
    // do something here
}, vecA, vecB, vecC, vecD, vecE, vecF);

またはこれ(中かっこで囲まれたイニシャライザリストを使用):

template<typename Func, typename T>
void foreach(Func callback, std::initializer_list<std::vector<T>> list) {
    for (auto &vec : list) {
        std::for_each(vec.begin(), vec.end(), callback);
    }
}

foreach([](const int &i){
    // do something here
}, {vecA, vecB, vecC, vecD, vecE, vecF});

または、次のようなベクターを結合できます。2つのベクターを連結する最良の方法は何ですか?そして、大きなベクトルを繰り返します。


0

ここに1つのバリアントがあります

template<class ... Iterator>
void increment_dummy(Iterator ... i)
    {}

template<class Function,class ... Iterator>
void for_each_combined(size_t N,Function&& fun,Iterator... iter)
    {
    while(N!=0)
        {
        fun(*iter...);
        increment_dummy(++iter...);
        --N;
        }
    }

使用例

void arrays_mix(size_t N,const float* x,const float* y,float* z)
    {
    for_each_combined(N,[](float x,float y,float& z){z=x+y;},x,y,z);    
    }
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.