web-dev-qa-db-ja.com

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

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];
}
94
memecs

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

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

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

このコードは、手動の古典的なforループを使用するのと同じように効率的です

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

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);
44
Konrad Rudolph

あなたの特定の例については、単に使用する

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

より一般的な場合は、Boost.IteratorのZip_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";

実例

ただし、本格的な汎用性のために、おそらく this のようなものが必要です。これは、メンバーbegin()/end()を持たないがdo名前空間にbegin/end関数があります。また、これにより、ユーザーはZip_c...関数を介してconstアクセスを明確に取得できます。

そして、あなたが私のようなニースのエラーメッセージの擁護者なら、おそらく this が必要です。これは、一時コンテナがZip_...関数のいずれかに渡されたかどうかをチェックし、もしそうなら素晴らしいエラーメッセージ。

37
Xeo

なぜ誰もこれに言及していないのだろうか:

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ステートメント内に配置する必要があります。

29
Joseph

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

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

ただし、最短のコンテナの長さまで2つのコンテナを反復処理するown "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ループを直接実行することは、このようなカスタム関数を記述するよりも簡単であると主張するかもしれません...そして、あなたがそれを1、2回だけ行うつもりなら、あなたは正しいでしょう。しかし、素晴らしいのは、これが非常に再利用可能であるということです。 =)

9
wjl

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で遊ぶ必要があります。

8
czarles

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

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を使用して何でもでき、それをインクリメントできます。これは、複数コンテナの場合にも簡単に拡張できます。

2
Vahid

範囲ライブラリは、これと他の非常に役立つ機能を提供します。次の例では、 Boost.Range を使用しています。 Eric Nieblerのrangev は適切な代替手段です。

#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;
}
1
Jens

ここに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);    
    }
0
user877329

私も少し遅れています。ただし、これを使用できます(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
Szymon Marczak