C ++ 11 için sıra-zip işlevi?


104

Yeni aralık tabanlı for döngüsü ile aşağıdaki gibi kod yazabiliriz

for(auto x: Y) {}

Hangi IMO bir olan dev gelen gelişme (ex.)

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

Pythons zipişlevi gibi iki eşzamanlı döngü üzerinden döngü yapmak için kullanılabilir mi? Python'a aşina olmayanlar için kod:

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

Çıktı olarak verir (1,4) (2,5) (3,6)


Menzile dayalı foryalnızca tek bir değişkenle kullanılabilir, bu nedenle hayır. Aynı anda iki değere erişmek istiyorsanız, şöyle bir şey kullanmanız gerekirstd::pair
Seth Carnegie

4
@SethCarnegie: doğrudan değil, ancak zip()tuples döndüren ve tuple listesi üzerinde yineleyen bir işlev bulabilirsin.
André Caron

2
@ AndréCaron haklısınız, benim "hayır" ifadem iki değişken kullanamayacağınızı, aynı anda iki kapsayıcı üzerinde yineleme yapamayacağınızı söylemekti.
Seth Carnegie

Açıkça for(;;)bu davranışı uzun vadeli de olsa elde edebilirsiniz, soru gerçekten de şu: Aynı anda iki nesne üzerinde "otomatik" yapmak mümkün mü?

Gelecekteki bir revizyonda (umarız C ++ 17), STL'nin revizyonu aralıkları içerecektir . Bu durumda, view :: zip tercih edilen çözümü sağlayabilir.
John McFarlane

Yanıtlar:


90

Uyarı: boost::zip_iterator ve boost::combineBoost 1.63.0 (2016 Aralık 26) itibariyle, giriş kaplarının uzunluğu aynı değilse (çökebilir veya sonun ötesinde yinelenebilir) tanımsız davranışlara neden olacaktır.


Boost 1.56.0'dan (2014 Ağustos 7) başlayarak kullanabilirsinizboost::combine (işlev önceki sürümlerde mevcuttur ancak belgelenmemiş):

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

Bu yazdıracak

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

Önceki sürümlerde, aşağıdaki gibi bir aralığı kendiniz tanımlayabilirdiniz:

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

Kullanım aynıdır.


1
bunu sıralama için kullanabilir misin? örneğin std :: sort (zip (a.begin (), ...), zip (a.end (), ...), [] (tup a, tup b) {a.get <0> () > b.get <0> ()}); ?
gnzlbg


Ben tarafından cazip olacaktır optionalgeçmiş uç yineleme olasılıklar için elemanlar ...
Yakk - Adam Nevraumont

3
Bunu std :: make_tuple ve std :: tie ile yapma şansınız var mı? Arttırma bağımlılığını en aza indirirken bunu kullanmaya çalışıyordum ama çalışmasını sağlayamadım.
Carneiro

@kennytm Gruptaki en kısa menzil sonunda bitmek yerine neden UB ile gitmeye karar verdiklerine dair bir fikriniz var mı?
Catskul

21

Bu zip'i daha önce sıkıldığım zaman yazdım, göndermeye karar verdim çünkü diğerlerinden farklı, çünkü boost kullanmıyor ve daha çok c ++ stdlib'e benziyor.

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

Örnek kullanım:

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
Yineleyicilerden herhangi birinin sonunda olup olmadığını kontrol etmelisiniz .
Xeo

1
@Xeo tüm aralıklar, ilk veya daha büyük aralıklarla aynı boyutta olmalıdır
aaronman

Nasıl [](int i,int j,float k,float l)çalıştığını açıklayabilir misin ? Bu bir lambda işlevi mi?
Hooked

@Hooked evet, bu bir lambda, temelde işe yarıyor, std::for_eachancak keyfi sayıda aralık kullanabilirsiniz,
lambda'daki

1
Yaygın bir ihtiyaç, farklı boyuttaki aralıkları veya hatta sonsuz aralıklarla sıkıştırmaktır.
Xeo

18

std :: transform bunu önemsiz bir şekilde yapabilir:

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;

İkinci sıra daha kısaysa, benim uygulamam varsayılan olarak başlatılmış değerler veriyor gibi görünüyor.


1
2. sıra daha kısaysa, sonundan itibaren yinelediğiniz için bunun UB olmasını beklerim b.
Adrian

16

Dayalı bir çözüm kullanabilirsiniz boost::zip_iterator. Kapsayıcılarınıza referansları koruyan zip_iteratorve beginve endüye işlevlerinden dönen sahte bir kapsayıcı sınıfı oluşturun . Şimdi yazabilirsin

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

Örnek uygulama (lütfen test edin):

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

Varyadik versiyonu mükemmel bir alıştırma olarak okuyucuya bırakıyorum.


3
+1: Boost.Range muhtemelen bunu içermelidir. Aslında, bu konuda onlara bir özellik isteği bırakacağım.
Nicol Bolas

2
@NicolBolas: İyi gidiyorsun. Bunun boost::iterator_range+ ile boost::zip_iterator, hatta değişken sürümle bile uygulanması oldukça kolay olmalıdır .
Alexandre C.

1
Aralıklar aynı uzunlukta değilse bunun asla sona ermeyeceğine (ve tanımsız davranışa sahip olmayacağına) inanıyorum.
Jonathan Wakely

1
boost::zip_iteratorfarklı uzunluklardaki aralıklarla çalışmıyor
Jonathan Wakely

1
Bu aynı zamanda temiz c ++ 03'te bile tuple yerine pair ile çalışmalıdır. Yine de uzunluklar eşit olmadığında bu da sorun yaratacaktır. En küçük kabın karşılık gelen ucu () alınarak end () ile bir şeyler yapılabilir. Bu, OP'lerin sorusundaki gibi spesifikasyonda gibi görünüyor.
Paul

15

Aralık tabanı ile çalışan ve herhangi bir sayıda aralığı kabul eden, bu değerler, değerler veya değerler olabilen ve farklı uzunluklarda olabilen <redi/zip.h>bir zipişlev için bakın for(yineleme en kısa aralığın sonunda duracaktır).

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

Baskılar 0 1 2 3 4 5


2
boost/tuple/tuple_io.hppcout << i;
kirill_igum

Bu benim için çalıştı. Ancak, benim kodda eşdeğer kullanmak zorunda kaldı boost::get<0>(i)ve boost::get<1>(i). Orijinal örneğin neden doğrudan uyarlanamayacağından emin değilim, kodumun kaplara sürekli referanslar alması gerçeğiyle ilgili olabilir.
YitzikC

11

İle aralık-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; 
}

Çıktı:

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


@ einpoklum-reinstateMonica şimdi!
yuyoyuppe

6

Aynı soruyla bağımsız olarak karşılaştım ve yukarıdakilerin hiçbirinin sözdizimini beğenmedim. Bu yüzden, temelde boost zip_iterator ile aynı şeyi yapan ancak sözdizimini benim için daha lezzetli hale getirmek için birkaç makro içeren kısa bir başlık dosyam var:

https://github.com/cshelton/zipfor

Örneğin yapabilirsin

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

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

Ana sözdizimsel şeker, her kaptaki öğeleri adlandırabilmemdir. Ayrıca aynı şeyi yapan, ancak haritalar için (öğenin ".first" ve ".second" adlarını vermek için) bir "mapfor" ekliyorum.


Bu harika! Zeki şablonunuzla sınırlı sayıda olan keyfi sayıda argüman alabilir mi?
Hooked

Şu anda yalnızca 9 paralel konteyneri işleyebilir. Bunu ilerletmek kolay olurdu. Değişken makrolar tek bir "zipfor" makrosunun farklı sayıda parametreyi işlemesine izin verirken, yine de her biri için ayrı bir makro (gönderilmek üzere) kodlamak gerekir. Bkz groups.google.com/forum/?fromgroups=#!topic/comp.std.c/... ve stackoverflow.com/questions/15847837/...
cshelton

Farklı büyüklükteki argümanları iyi ele alıyor mu? (OP'de açıklandığı gibi)
coyotte508

@ coyotte508, ilk kabın en az sayıda öğeye sahip olduğunu varsayar (ve diğer kaplardaki fazladan öğeleri yok sayar). Bu varsayımı yapmamak için değişiklik yapmak kolay olurdu, ancak bu, eleman sayısı eşleştiğinde yavaşlatır (şu anda elle yazılandan daha yavaş değildir).
cshelton

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

6

Operatörün aşırı yüklenmesini seviyorsanız, işte üç olasılık. İlk ikisi yineleyiciler olarak sırasıyla std::pair<>ve kullanıyor std::tuple<>; üçüncüsü bunu menzile dayalı olarak genişletir for. Herkesin bu operatör tanımlarından hoşlanmayacağına dikkat edin, bu nedenle bunları ayrı bir ad alanında tutmak ve bunları using namespacekullanmak istediğiniz işlevlerde (dosyalar değil!) A sahip olmak en iyisidir .

#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

Yazdığım bir C ++ akış işleme kitaplığı için, üçüncü taraf kitaplıklara dayanmayan ve rastgele sayıda kapsayıcıyla çalışan bir çözüm arıyordum. Bu çözümü buldum. Destek kullanan kabul edilen çözüme benzer (ve ayrıca kap uzunlukları eşit değilse tanımsız davranışla sonuçlanır)

#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
bağlantı bozuk ... gönderi nasıl kullanılacağını gösteriyorsa yararlı olur, örneğin main ()?
javaLover

@javaLover: @ knedlsepp'in cevabındaki cppitertools ile aynı şekilde kullanabilirsiniz. Dikkate değer bir fark, yukarıdaki çözümle temeldeki kapsayıcıları değiştiremeyeceğinizdir, çünkü operator*for seq::iterator, bir std::tuplesabit referans döndürür .
winnetou

2

C ++ 14 uyumlu bir derleyiciniz varsa (örn. Gcc5) zip, cppitertoolskütüphanede Ryan Haining tarafından sağlanan , gerçekten umut verici görünen kullanabilirsiniz:

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_iteratorkullanabilirsiniz (örnekler belgelerde). Range for ile çalışmaz, ancak std::for_eachbir lambda kullanabilirsiniz .


Neden aralık tabanlı ile çalışmıyor? Boost.Range ile birleştirin ve ayarlanmalısınız.
Xeo

@Xeo: Range'i çok iyi bilmiyorum. Sanırım bazı standart metinler dahil edip çalışmasını sağlayabilirsiniz, ancak sadece IMO kullanmak for_eachdaha az güçlük olacaktır.
Cat Plus Plus

Bunun gibi bir şeyin güçlük olmadığını 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)); });mı söylüyorsunuz:
UncleBens

2
Bir Lambda mu başlamalı Değil olun std :: for_each Faydalı bir kampanya. :)
UncleBens

2
@Xeo: Bu muhtemelen ayrı bir soru olmalı, ama neden oh neden ??
UncleBens

-2

İşte destek gerektirmeyen basit bir versiyon. Geçici değerler yarattığı için özellikle verimli olmayacak ve listeler dışındaki kapsayıcılar üzerinde genelleme yapmayacaktır, ancak hiçbir bağımlılığı yoktur ve sıkıştırma için en yaygın durumu çözer.

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

Diğer sürümler daha esnek olsa da, genellikle bir liste operatörü kullanmanın amacı basit bir tek satırlık yapmaktır. Bu sürüm, ortak durumun basit olması avantajına sahiptir.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.