c ++ 11のシーケンスzip関数?


98

新しい範囲ベースのforループを使用すると、次のようなコードを記述できます

for(auto x: Y) {}

どのIMOが(例)からの大きな改善です

for(std::vector<int>::iterator x=Y.begin(); x!=Y.end(); ++x) {}

Python zip関数のように、2つの同時ループをループするために使用できますか?Pythonに慣れていない人のために、コード:

Y1 = [1,2,3]
Y2 = [4,5,6,7]
for x1,x2 in zip(Y1,Y2):
    print x1,x2

出力として与える (1,4) (2,5) (3,6)


範囲ベースforは1つの変数でのみ使用できるため、使用できません。あなたが一度に2つの値にアクセスしたい場合は、のようなものを使用する必要があるだろうstd::pair
セス・カーネギー

4
@SethCarnegie:直接ではありませんが、zip()タプルを返し、タプルのリストを反復処理する関数を考え出すことができます。
アンドレ・キャノン

2
@AndréCaronそうです、私の「いいえ」は、2つの変数を使用できないことを意味していました。2つのコンテナーを一度に反復できないことを意味していません。
セスカーネギー、

確かにfor(;;)、長い間ではありますが、この振る舞いを得ることができるので、問題は本当にあります:一度に2つのオブジェクトを「自動」にすることは可能ですか?

将来のリビジョン(できればC ++ 17)では、STLのオーバーホールにrangeが含まれる予定です。次に、view :: zipが優先ソリューションを提供する場合があります。
ジョンマクファーレン

回答:


88

警告: boost::zip_iteratorboost::combine入力コンテナの長さが同じでない場合は未定義の動作を引き起こしますブースト1.63.0(2016年12月26日)のように(それは終わりを超えてクラッシュしたり、反復可能)。


Boost 1.56.0(2014 Aug 7)以降、使用boost::combineできます(この機能は以前のバージョンに存在しますが、ドキュメントには記載されていません)。

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

int main() {
    std::vector<int> a {4, 5, 6};
    double b[] = {7, 8, 9};
    std::list<std::string> c {"a", "b", "c"};
    for (auto tup : boost::combine(a, b, c, a)) {    // <---
        int x, w;
        double y;
        std::string z;
        boost::tie(x, y, z, w) = tup;
        printf("%d %g %s %d\n", x, y, z.c_str(), w);
    }
}

これは印刷されます

4 7 a 4
5 8 b 5
6 9 c 6

以前のバージョンでは、次のように自分で範囲を定義できました。

#include <boost/iterator/zip_iterator.hpp>
#include <boost/range.hpp>

template <typename... T>
auto zip(T&&... containers) -> boost::iterator_range<boost::zip_iterator<decltype(boost::make_tuple(std::begin(containers)...))>>
{
    auto zip_begin = boost::make_zip_iterator(boost::make_tuple(std::begin(containers)...));
    auto zip_end = boost::make_zip_iterator(boost::make_tuple(std::end(containers)...));
    return boost::make_iterator_range(zip_begin, zip_end);
}

使い方は同じです。


1
これを並べ替えに使用できますか?すなわちstd :: sort(zip(a.begin()、...)、zip(a.end()、...)、[](tup a、tup b){a.get <0>() > b.get <0>()}); ?
gnzlbg


optional過去の反復の可能性のために要素に誘惑されます...
Yakk-Adam Nevraumont 2013年

3
std :: make_tupleとstd :: tieでこれを行うことができますか?ブーストの依存関係を最小限に抑えながらこれを使用しようとしましたが、機能させることができませんでした。
Carneiro 2014

@kennytm彼らが束の最短距離の終わりで終わるのではなく、なぜUBで行くことに決めたのか?
Catskul

18

退屈する前にこのzipを書いたのですが、ブーストを使用せず、c ++ stdlibに似ているという点で他のzipとは異なるため、投稿することにしました。

template <typename Iterator>
    void advance_all (Iterator & iterator) {
        ++iterator;
    }
template <typename Iterator, typename ... Iterators>
    void advance_all (Iterator & iterator, Iterators& ... iterators) {
        ++iterator;
        advance_all(iterators...);
    } 
template <typename Function, typename Iterator, typename ... Iterators>
    Function zip (Function func, Iterator begin, 
            Iterator end, 
            Iterators ... iterators)
    {
        for(;begin != end; ++begin, advance_all(iterators...))
            func(*begin, *(iterators)... );
        //could also make this a tuple
        return func;
    }

使用例:

int main () {
    std::vector<int> v1{1,2,3};
    std::vector<int> v2{3,2,1};
    std::vector<float> v3{1.2,2.4,9.0};
    std::vector<float> v4{1.2,2.4,9.0};
     zip (
            [](int i,int j,float k,float l){
                std::cout << i << " " << j << " " << k << " " << l << std::endl;
            },
            v1.begin(),v1.end(),v2.begin(),v3.begin(),v4.begin());
}

4
イテレータが最後にあるかどうを確認する必要があります。
Xeo

1
@Xeoすべての範囲は最初と同じかそれ以上である必要があります
aaronman

どのように[](int i,int j,float k,float l)機能するか説明できますか?これはラムダ関数ですか?
フック

@フックええ、それはラムダです。基本的には正しく機能しstd::for_eachますが、任意の数の範囲を使用できます。ラムダのパラメーターは、関数に与えるイテレーターの数に依存します
aaronman

1
一般的なニーズは、さまざまなサイズの範囲を圧縮したり、範囲が無限であったりすることです。
Xeo

16

に基づくソリューションを使用できますboost::zip_iterator。コンテナーへの参照を維持する偽のコンテナークラスを作成します。これzip_iteratorは、beginおよびendメンバー関数から返されます。今、あなたは書くことができます

for (auto p: zip(c1, c2)) { ... }

実装例(テストしてください):

#include <iterator>
#include <boost/iterator/zip_iterator.hpp>

template <typename C1, typename C2>
class zip_container
{
    C1* c1; C2* c2;

    typedef boost::tuple<
        decltype(std::begin(*c1)), 
        decltype(std::begin(*c2))
    > tuple;

public:
    zip_container(C1& c1, C2& c2) : c1(&c1), c2(&c2) {}

    typedef boost::zip_iterator<tuple> iterator;

    iterator begin() const
    {
         return iterator(std::begin(*c1), std::begin(*c2));
    }

    iterator end() const
    {
         return iterator(std::end(*c1), std::end(*c2));
    }
};

template <typename C1, typename C2>
zip_container<C1, C2> zip(C1& c1, C2& c2)
{
    return zip_container<C1, C2>(c1, c2);
}

私は可変長バージョンを読者への優れた演習として残しています。


3
+1:Boost.Rangeはおそらくこれを組み込むべきです。実際、私は彼らに機能リクエストをドロップします。
Nicol Bolas、2011

2
@NicolBolas:元気です。これはboost::iterator_range+ を使用して実装するのが非常に簡単なはずboost::zip_iteratorです。
Alexandre C.

1
範囲が同じ長さでない場合、これは決して終了しない(そして未定義の動作をする)と思います。
Jonathan Wakely 2013年

1
boost::zip_iteratorさまざまな長さの範囲では機能しません
ジョナサンウェイクリー2013

1
これは、タプルではなくペアのクリーンなc ++ 03でも機能するはずです。それでも、このウィルは長さが等しくないときに問題を引き起こします。最小のコンテナの対応するend()を取得することにより、end()で何かが行われる場合があります。これはOPの質問と同様に仕様に含まれているようです。
Paul

16

std :: transformはこれを簡単に行うことができます:

std::vector<int> a = {1,2,3,4,5};
std::vector<int> b = {1,2,3,4,5};
std::vector<int>c;
std::transform(a.begin(),a.end(), b.begin(),
               std::back_inserter(c),
               [](const auto& aa, const auto& bb)
               {
                   return aa*bb;
               });
for(auto cc:c)
    std::cout<<cc<<std::endl;

2番目のシーケンスが短い場合、私の実装はデフォルトの初期化値を提供しているようです。


1
2番目のシーケンスが短い場合は、これがUBであると予想しますb
エイドリアン

15

range-baseで機能<redi/zip.h>し、任意の数の範囲を受け入れるzip関数については、を参照してくださいfor。これは、右辺値または左辺値であり、長さが異なる場合があります(反復は、最短範囲の最後で停止します)。

std::vector<int> vi{ 0, 2, 4 };
std::vector<std::string> vs{ "1", "3", "5", "7" };
for (auto i : redi::zip(vi, vs))
  std::cout << i.get<0>() << ' ' << i.get<1>() << ' ';

プリント 0 1 2 3 4 5


2
次の用途boost/tuple/tuple_io.hppにも使用できますcout << i;
kirill_igum 2014年

これは私のために働いたものです。ただし、私のコードでは、boost::get<0>(i)およびと同等のものを使用する必要が ありましたboost::get<1>(i)。元のサンプルを直接適合させることができなかった理由がわかりません。これは、コードがコンテナーへの一定の参照を取得するという事実に関係している可能性があります。
YitzikC 2016

11

範囲-V3

#include <range/v3/all.hpp>
#include <vector>
#include <iostream>

namespace ranges {
    template <class T, class U>
    std::ostream& operator << (std::ostream& os, common_pair<T, U> const& p)
    {
      return os << '(' << p.first << ", " << p.second << ')';
    }
}

using namespace ranges::v3;

int main()
{
    std::vector<int> a {4, 5, 6};
    double b[] = {7, 8, 9};
    std::cout << view::zip(a, b) << std::endl; 
}

出力:

[(4、7)、(5、8)、(6、9)]


@ einpoklum-reinstateMonicaになりました!
yuyoyuppe

6

私は独立してこの同じ質問に出くわし、上記のいずれの構文も好きではありませんでした。したがって、基本的にはboost zip_iteratorと同じように機能する短いヘッダーファイルがありますが、構文をわかりやすくするためのマクロがいくつかあります。

https://github.com/cshelton/zipfor

例えばあなたはできる

vector<int> a {1,2,3};
array<string,3> b {"hello","there","coders"};

zipfor(i,s eachin a,b)
    cout << i << " => " << s << endl;

主な構文上の砂糖は、各コンテナの要素に名前を付けることができるということです。同じことをしますが、マップ用の「mapfor」も含めます(要素の「.first」と「.second」に名前を付けるため)。


これはすごいです!任意の数の引数を取ることができますか?有限の数にあなたの賢いテンプレートによって制限されているすべてのものですか?
2014年

現在、最大9つの並列コンテナーのみを処理します。簡単に進めることができます。可変個のマクロでは、単一の「zipfor」マクロで異なる数のパラメーターを処理できますが、それぞれに(ディスパッチされる)別のマクロをコーディングする必要があります。参照groups.google.com/forum/?fromgroups=#!topic/comp.std.c/...stackoverflow.com/questions/15847837/...
cshelton

異なるサイズの引数をうまく処理できますか?(OPに記載)
coyotte508 2016年

@ coyotte508では、最初のコンテナの要素数が最も少ないと想定しています(他のコンテナの余分な要素は無視されます)。この仮定を行わないように変更するのは簡単ですが、要素の数が一致すると、速度が遅くなります(現在、手書きより遅くはありません)。
cshelton 2016年

6
// declare a, b
BOOST_FOREACH(boost::tie(a, b), boost::combine(list_of_a, list_of_b)){
    // your code here.
}

6

演算子のオーバーロードが必要な場合は、3つの可能性があります。最初の2つはそれぞれ反復子としてstd::pair<>およびを使用しstd::tuple<>ています。3番目は、これを範囲ベースに拡張しますfor。誰もがこれらの演算子の定義を好むわけではないので、それらを別の名前空間に保持し、using namespaceこれらを使用する関数(ファイルではない!)を持つことをお勧めします。

#include <iostream>
#include <utility>
#include <vector>
#include <tuple>

// put these in namespaces so we don't pollute global
namespace pair_iterators
{
    template<typename T1, typename T2>
    std::pair<T1, T2> operator++(std::pair<T1, T2>& it)
    {
        ++it.first;
        ++it.second;
        return it;
    }
}

namespace tuple_iterators
{
    // you might want to make this generic (via param pack)
    template<typename T1, typename T2, typename T3>
    auto operator++(std::tuple<T1, T2, T3>& it)
    {
        ++( std::get<0>( it ) );
        ++( std::get<1>( it ) );
        ++( std::get<2>( it ) );
        return it;
    }

    template<typename T1, typename T2, typename T3>
    auto operator*(const std::tuple<T1, T2, T3>& it)
    {
        return std::tie( *( std::get<0>( it ) ),
                         *( std::get<1>( it ) ),
                         *( std::get<2>( it ) ) );
    }

    // needed due to ADL-only lookup
    template<typename... Args>
    struct tuple_c
    {
        std::tuple<Args...> containers;
    };

    template<typename... Args>
    auto tie_c( const Args&... args )
    {
        tuple_c<Args...> ret = { std::tie(args...) };
        return ret;
    }

    template<typename T1, typename T2, typename T3>
    auto begin( const tuple_c<T1, T2, T3>& c )
    {
        return std::make_tuple( std::get<0>( c.containers ).begin(),
                                std::get<1>( c.containers ).begin(),
                                std::get<2>( c.containers ).begin() );
    }

    template<typename T1, typename T2, typename T3>
    auto end( const tuple_c<T1, T2, T3>& c )
    {
        return std::make_tuple( std::get<0>( c.containers ).end(),
                                std::get<1>( c.containers ).end(),
                                std::get<2>( c.containers ).end() );
    }

    // implement cbegin(), cend() as needed
}

int main()
{
    using namespace pair_iterators;
    using namespace tuple_iterators;

    std::vector<double> ds = { 0.0, 0.1, 0.2 };
    std::vector<int   > is = {   1,   2,   3 };
    std::vector<char  > cs = { 'a', 'b', 'c' };

    // classical, iterator-style using pairs
    for( auto its  = std::make_pair(ds.begin(), is.begin()),
              end  = std::make_pair(ds.end(),   is.end()  ); its != end; ++its )
    {
        std::cout << "1. " << *(its.first ) + *(its.second) << " " << std::endl;
    }

    // classical, iterator-style using tuples
    for( auto its  = std::make_tuple(ds.begin(), is.begin(), cs.begin()),
              end  = std::make_tuple(ds.end(),   is.end(),   cs.end()  ); its != end; ++its )
    {
        std::cout << "2. " << *(std::get<0>(its)) + *(std::get<1>(its)) << " "
                           << *(std::get<2>(its)) << " " << std::endl;
    }

    // range for using tuples
    for( const auto& d_i_c : tie_c( ds, is, cs ) )
    {
        std::cout << "3. " << std::get<0>(d_i_c) + std::get<1>(d_i_c) << " "
                           << std::get<2>(d_i_c) << " " << std::endl;
    }
}

3

のために C ++ストリーム処理ライブラリの私は、サードパーティのライブラリやコンテナの任意の数の作品に依存しないソリューションを探していました書いています。私はこの解決策に終わりました。これは、ブーストを使用する承認済みのソリューションに似ています(また、コンテナーの長さが等しくない場合は、未定義の動作になります)。

#include <utility>

namespace impl {

template <typename Iter, typename... Iters>
class zip_iterator {
public:
  using value_type = std::tuple<const typename Iter::value_type&,
                                const typename Iters::value_type&...>;

  zip_iterator(const Iter &head, const Iters&... tail)
      : head_(head), tail_(tail...) { }

  value_type operator*() const {
    return std::tuple_cat(std::tuple<const typename Iter::value_type&>(*head_), *tail_);
  }

  zip_iterator& operator++() {
    ++head_; ++tail_;
    return *this;
  }

  bool operator==(const zip_iterator &rhs) const {
    return head_ == rhs.head_ && tail_ == rhs.tail_;
  }

  bool operator!=(const zip_iterator &rhs) const {
    return !(*this == rhs);
  }

private:
  Iter head_;
  zip_iterator<Iters...> tail_;
};

template <typename Iter>
class zip_iterator<Iter> {
public:
  using value_type = std::tuple<const typename Iter::value_type&>;

  zip_iterator(const Iter &head) : head_(head) { }

  value_type operator*() const {
    return value_type(*head_);
  }

  zip_iterator& operator++() { ++head_; return *this; }

  bool operator==(const zip_iterator &rhs) const { return head_ == rhs.head_; }

  bool operator!=(const zip_iterator &rhs) const { return !(*this == rhs); }

private:
  Iter head_;
};

}  // namespace impl

template <typename Iter>
class seq {
public:
  using iterator = Iter;
  seq(const Iter &begin, const Iter &end) : begin_(begin), end_(end) { }
  iterator begin() const { return begin_; }
  iterator end() const { return end_; }
private:
  Iter begin_, end_;
};

/* WARNING: Undefined behavior if iterator lengths are different.
 */
template <typename... Seqs>
seq<impl::zip_iterator<typename Seqs::iterator...>>
zip(const Seqs&... seqs) {
  return seq<impl::zip_iterator<typename Seqs::iterator...>>(
      impl::zip_iterator<typename Seqs::iterator...>(std::begin(seqs)...),
      impl::zip_iterator<typename Seqs::iterator...>(std::end(seqs)...));
}

1
リンクが壊れています... main()などの投稿で使用方法が示されていると便利です。
javaLover 2017

@javaLover:@knedlseppの回答でcppitertoolsと同じように使用できます。注目すべき違いの1つは、上記のソリューションoperator*seq::iteratorは、for がstd::tupleconst参照のa を返すため、基になるコンテナーを変更できないことです。
winnetou 2017

2

C ++ 14準拠のコンパイラ(gcc5など)を使用しているzip場合は、cppitertoolsRyan Hainingによってライブラリで提供されているものを使用できます。

array<int,4> i{{1,2,3,4}};
vector<float> f{1.2,1.4,12.3,4.5,9.9};
vector<string> s{"i","like","apples","alot","dude"};
array<double,5> d{{1.2,1.2,1.2,1.2,1.2}};

for (auto&& e : zip(i,f,s,d)) {
    cout << std::get<0>(e) << ' '
         << std::get<1>(e) << ' '
         << std::get<2>(e) << ' '
         << std::get<3>(e) << '\n';
    std::get<1>(e)=2.2f; // modifies the underlying 'f' array
}

0

Boost.Iteratorsでzip_iterator使用できます(ドキュメント内の例)。範囲では機能しませんがstd::for_each、ラムダを使用できます。


なぜ範囲ベースで動作しないのですか?Boost.Rangeと組み合わせると、設定が完了します。
Xeo

@Xeo:範囲をよく知りません。ボイラープレートを使用して機能させることができると思いますが、IMOを使用for_eachするだけで面倒は少なくなります。
Cat Plus Plus

あなたはこのような何かが面倒ではないことを意味します:std::for_each(make_zip_iterator(make_tuple(Y1.begin(), Y2.begin())), make_zip_iterator(make_tuple(Y1.end(), Y2.end())), [](const tuple<int, int>& t){printf("%d %d\n", get<0>(t), get<1>(t)); });
UncleBens 2011

2
私はラムダがstd :: for_eachを作らないキャンペーンを始めるべきです。:)
UncleBens 2011

2
@Xeo:これはおそらく別の質問になるはずですが、なぜああ、なぜですか?
UncleBens 2011

-2

これは、ブーストを必要としないシンプルなバージョンです。一時的な値を作成するため、特に効率的ではありません。また、リスト以外のコンテナーを一般化しませんが、依存関係はなく、zipの最も一般的なケースを解決します。

template<class L, class R>
std::list< std::pair<L,R> >  zip(std::list<L> left, std::list<R> right)
{
auto l = left.begin();
auto r = right.begin();
std::list< std::pair<L,R> > result;
  while( l!=left.end() && r!=right.end() )
    result.push_back( std::pair<L,R>( *(l++), *(r++) ) );
  return result;
}

他のバージョンはより柔軟ですが、多くの場合、リスト演算子を使用するポイントは、単純なワンライナーにすることです。このバージョンには、一般的なケースが単純であるという利点があります。

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