Fungsi Sequence-zip untuk c ++ 11?

100

Dengan loop for berbasis rentang baru kita dapat menulis kode seperti

for(auto x: Y) {}

IMO mana yang merupakan peningkatan besar dari (misalnya)

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

Bisakah itu digunakan untuk mengulang dua loop simultan, seperti zipfungsi Pythons ? Bagi mereka yang tidak terbiasa dengan Python, kode:

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

Memberikan sebagai keluaran (1,4) (2,5) (3,6)

Doyan
sumber
Berbasis rentang forhanya dapat digunakan dengan satu variabel, jadi tidak. Jika Anda ingin mengakses dua nilai sekaligus, Anda harus menggunakan sesuatu sepertistd::pair
Seth Carnegie
4
@SethCarnegie: tidak secara langsung, tetapi Anda bisa mendapatkan zip()fungsi yang mengembalikan tupel dan mengulang daftar tupel.
André Caron
2
@ AndréCaron Anda benar, "tidak" saya dimaksudkan untuk mengatakan bahwa Anda tidak dapat menggunakan dua variabel, bukan berarti Anda tidak dapat mengulang lebih dari dua kontainer sekaligus.
Seth Carnegie
Jelas for(;;)bisa mendapatkan perilaku ini, meskipun jangka panjang, jadi pertanyaannya sebenarnya: Apakah mungkin untuk "otomatis" pada dua objek sekaligus?
Dalam revisi mendatang (semoga C ++ 17), perbaikan STL akan mencakup rentang . Kemudian view :: zip dapat memberikan solusi yang lebih disukai.
John McFarlane

Jawaban:

89

Peringatan: boost::zip_iterator dan boost::combinepada Boost 1.63.0 (26 Des 2016) akan menyebabkan perilaku tidak terdefinisi jika panjang wadah masukan tidak sama (mungkin macet atau berulang setelah akhir).


Mulai dari Boost 1.56.0 (2014 Agustus 7), Anda dapat menggunakanboost::combine (fungsi tersebut ada di versi sebelumnya tetapi tidak terdokumentasi):

#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);
    }
}

Ini akan mencetak

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

Di versi sebelumnya, Anda dapat menentukan sendiri rentang seperti ini:

#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);
}

Penggunaannya sama.

kennytm
sumber
1
dapatkah Anda menggunakan ini untuk menyortir? yaitu std :: sort (zip (a.begin (), ...), zip (a.end (), ...), [] (tup a, tup b) {a.get <0> () > b. dapatkan <0> ()}); ?
gnzlbg
@ ggnzlbg: Tidak, Anda tidak bisa .
kennytm
Saya akan tergoda oleh optionalelemen untuk kemungkinan iterasi terakhir ...
Yakk - Adam Nevraumont
3
Adakah kemungkinan Anda dapat melakukan ini dengan std :: make_tuple dan std :: tie? Saya mencoba menggunakan ini sambil meminimalkan ketergantungan dorongan tetapi saya tidak dapat membuatnya berfungsi.
Carneiro
@kennytm ada ide mengapa mereka memutuskan untuk pergi dengan UB daripada hanya berakhir di akhir rentang terpendek dalam kelompok?
Catskul
18

Jadi saya menulis zip ini sebelumnya ketika saya bosan, saya memutuskan untuk mempostingnya karena berbeda dari yang lain karena tidak menggunakan boost dan lebih mirip c ++ stdlib.

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;
    }

Contoh penggunaan:

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());
}
aaronman
sumber
4
Anda harus memeriksa apakah ada iterator di akhir.
Xeo
1
@Xeo semua rentang harus berukuran sama dengan yang pertama atau lebih besar
aaronman
Bisakah Anda menjelaskan cara [](int i,int j,float k,float l)kerjanya? Apakah ini fungsi lambda?
Ketagihan
@Hooked ya itu lambda, pada dasarnya berfungsi hanya std::for_eachtetapi Anda dapat menggunakan jumlah rentang yang sewenang-wenang, parameter di lambda bergantung pada berapa banyak iterator yang Anda berikan fungsi
aaronman
1
Kebutuhan umum adalah menggunakan zip range dengan ukuran berbeda, atau bahkan dengan range tak terbatas.
Xeo
18

std :: transform dapat melakukan ini dengan mudah:

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;

Jika urutan kedua lebih pendek, implementasi saya tampaknya memberikan nilai yang diinisialisasi default.

Venki
sumber
1
Jika urutan ke-2 lebih pendek, maka saya berharap ini adalah UB karena Anda akan mengulang-ulang di akhir b.
Adrian
16

Anda dapat menggunakan solusi berdasarkan boost::zip_iterator. Buat kelas container palsu yang memelihara referensi ke container Anda, dan yang mengembalikan zip_iteratordari fungsi anggota begindan end. Sekarang Anda bisa menulis

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

Contoh implementasi (harap diuji):

#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);
}

Saya tinggalkan versi variadic sebagai latihan yang sangat baik untuk pembaca.

Alexandre C.
sumber
3
+1: Boost. Rentang mungkin harus memasukkan ini. Bahkan, saya akan memberikan permintaan fitur kepada mereka tentang hal ini.
Nicol Bolas
2
@NicolBolas: Anda melakukannya dengan baik. Ini seharusnya cukup mudah diimplementasikan dengan boost::iterator_range+ boost::zip_iterator, bahkan versi variadic.
Alexandre C.
1
Saya yakin ini tidak akan pernah berakhir (dan memiliki perilaku tidak terdefinisi) jika rentangnya tidak sama panjangnya.
Jonathan Wakely
1
boost::zip_iteratortidak bekerja dengan rentang dengan panjang yang berbeda
Jonathan Wakely
1
Ini juga harus bekerja bahkan di clean c ++ 03 dengan pair bukan tuple. Namun hal ini juga akan menimbulkan masalah ketika panjangnya tidak sama. Sesuatu mungkin dilakukan dengan end () dengan mengambil end () yang sesuai dari container terkecil. Ini tampaknya dalam spesifikasi seperti pada pertanyaan OP.
Paul
15

Lihat <redi/zip.h>untuk zipfungsi yang bekerja dengan rentang-base fordan menerima sejumlah rentang, yang dapat rvalues atau lvalues dan dapat panjang yang berbeda (iterasi akan berhenti pada akhir rentang terpendek).

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>() << ' ';

Cetakan 0 1 2 3 4 5

Jonathan Wakely
sumber
2
Anda juga dapat menggunakan boost/tuple/tuple_io.hppuntukcout << i;
kirill_igum
Inilah yang berhasil bagi saya. Namun, dalam kode saya, saya harus menggunakan yang setara dengan boost::get<0>(i)dan boost::get<1>(i). Saya tidak yakin mengapa sampel asli tidak dapat diadaptasi secara langsung, mungkin ada hubungannya dengan fakta bahwa kode saya mengambil referensi konstan ke container.
YitzikC
11

Dengan range-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; 
}

Hasil:

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

csguth
sumber
@ einpoklum-reinstateMonica sekarang!
yuyoyuppe
6

Saya mengalami pertanyaan yang sama ini secara independen dan tidak menyukai sintaks dari semua yang disebutkan di atas. Jadi, saya memiliki file header pendek yang pada dasarnya melakukan hal yang sama dengan boost zip_iterator tetapi memiliki beberapa makro untuk membuat sintaksnya lebih enak bagi saya:

https://github.com/cshelton/zipfor

Misalnya Anda bisa melakukannya

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

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

Gula sintaksis utama adalah saya dapat memberi nama elemen dari setiap wadah. Saya juga menyertakan "mapfor" yang melakukan hal yang sama, tetapi untuk peta (untuk menamai elemen ".first" dan ".second").

cshelton.dll
sumber
Ini rapi! Bisakah itu mengambil jumlah argumen yang sewenang-wenang, apakah semua itu dibatasi oleh kecerdasan Anda yang membuat template ke jumlah yang terbatas?
Hooked
Saat ini hanya menangani hingga 9 kontainer paralel. Itu akan mudah untuk dimajukan. Sementara makro variadic memungkinkan makro "zipfor" tunggal untuk menangani jumlah parameter yang berbeda, kita masih harus membuat kode makro terpisah untuk masing-masing (untuk dikirim ke). Lihat groups.google.com/forum/?fromgroups=#!topic/comp.std.c/… dan stackoverflow.com/questions/15847837/…
cshelton
Apakah itu menangani argumen dengan ukuran berbeda dengan baik? (seperti yang dijelaskan dalam OP)
coyotte508
@ coyotte508, ini mengasumsikan bahwa wadah pertama memiliki jumlah elemen paling sedikit (dan mengabaikan elemen ekstra di wadah lain). Mudah untuk memodifikasi untuk tidak membuat asumsi ini, tetapi itu akan memperlambatnya (saat ini tidak lebih lambat dari tulisan tangan) ketika jumlah elemen cocok.
cshelton
6
// declare a, b
BOOST_FOREACH(boost::tie(a, b), boost::combine(list_of_a, list_of_b)){
    // your code here.
}
cumi-cumi
sumber
6

Jika Anda suka kelebihan beban operator, berikut tiga kemungkinannya. Dua yang pertama menggunakan std::pair<>dan std::tuple<>, masing-masing, sebagai iterator; yang ketiga memperluas ini ke berbasis jangkauan for. Perhatikan bahwa tidak semua orang akan menyukai definisi operator ini, jadi yang terbaik adalah menyimpannya di namespace terpisah dan memiliki using namespacefungsi (bukan file!) Di mana Anda ingin menggunakannya.

#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;
    }
}
lorro
sumber
3

Untuk pustaka pemrosesan aliran C ++ yang saya tulis, saya sedang mencari solusi yang tidak bergantung pada pustaka pihak ketiga dan bekerja dengan jumlah wadah yang sewenang-wenang. Saya berakhir dengan solusi ini. Ini mirip dengan solusi yang diterima yang menggunakan dorongan (dan juga menghasilkan perilaku yang tidak ditentukan jika panjang wadah tidak sama)

#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)...));
}
foges
sumber
1
link broken ... akan berguna jika postingan menunjukkan bagaimana cara menggunakannya misal main ()?
javaLover
@javaLover: Anda dapat menggunakannya dengan cara yang sama seperti cppitertools di jawaban @ knedlsepp. Satu perbedaan penting adalah bahwa dengan solusi di atas Anda tidak dapat mengubah wadah yang mendasari sebagai operator*untuk seq::iteratormengembalikan std::tuplereferensi konstanta.
winnetou
2

Jika Anda memiliki kompilator yang mendukung C ++ 14 (misalnya gcc5), Anda dapat menggunakan yang zipdisediakan di cppitertoolsperpustakaan oleh Ryan Haining, yang terlihat sangat menjanjikan:

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
}
knedlsepp.dll
sumber
0

Boost.Iterator zip_iteratordapat Anda gunakan (contoh ada di dokumen). Ini tidak akan berfungsi dengan range for, tetapi Anda dapat menggunakan std::for_eachdan lambda.

Cat Plus Plus
sumber
Mengapa itu tidak bekerja dengan berbasis jangkauan? Gabungkan dengan Boost.Range dan Anda harus disetel.
Xeo
@ Xeo: Saya tidak terlalu mengenal Range. Saya kira Anda bisa melibatkan beberapa boilerplate dan membuatnya berfungsi, tetapi IMO hanya menggunakan for_eachakan lebih mudah.
Cat Plus Plus
Anda berarti sesuatu seperti ini tidak merepotkan: 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
2
Saya harus memulai kampanye Lambda Does Not Make std :: for_each Berguna. :)
UncleBens
2
@Xeo: Ini mungkin pertanyaan yang terpisah, tapi kenapa oh kenapa ??
UncleBens
-2

Ini adalah versi sederhana yang tidak membutuhkan dorongan. Ini tidak akan sangat efisien karena membuat nilai sementara, dan tidak menggeneralisasi container selain daftar, tetapi tidak memiliki dependensi dan menyelesaikan kasus paling umum untuk pembuatan 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;
}

Meskipun versi lain lebih fleksibel, seringkali tujuan penggunaan operator daftar adalah membuat satu baris sederhana. Versi ini memiliki kelebihan karena kasus umumnya sederhana.

Andrew
sumber