Hàm Sequence-zip cho c ++ 11?


98

Với vòng lặp for dựa trên phạm vi mới, chúng ta có thể viết mã như

for(auto x: Y) {}

IMO là một cải tiến lớn từ (ví dụ:)

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

Nó có thể được sử dụng để lặp qua hai vòng lặp đồng thời, giống như ziphàm Pythons không? Đối với những người không quen thuộc với Python, mã:

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

Cung cấp dưới dạng đầu ra (1,4) (2,5) (3,6)


Dựa trên phạm vi forchỉ có thể được sử dụng với một biến, vì vậy không. Nếu bạn muốn truy cập vào hai giá trị cùng một lúc, bạn sẽ phải sử dụng một cái gì đó giống nhưstd::pair
Seth Carnegie

4
@SethCarnegie: không trực tiếp, nhưng bạn có thể nghĩ ra một zip()hàm trả về các bộ giá trị và lặp lại danh sách các bộ giá trị.
André Caron

2
@ AndréCaron, bạn nói đúng, "không" của tôi có nghĩa là bạn không thể sử dụng hai biến, không phải là bạn không thể lặp lại hai vùng chứa cùng một lúc.
Seth Carnegie

Rõ ràng for(;;)có thể nhận được hành vi này, mặc dù lâu dài, vì vậy câu hỏi thực sự là: Có thể cho "tự động" trên hai đối tượng cùng một lúc không?

Trong một bản sửa đổi trong tương lai (hy vọng là C ++ 17), một bản đại tu của STL sẽ bao gồm các phạm vi . Sau đó, xem :: zip có thể cung cấp giải pháp ưu tiên.
John McFarlane

Câu trả lời:


88

Cảnh báo: boost::zip_iteratorboost::combinekể từ Boost 1.63.0 (ngày 26 tháng 12 năm 2016) sẽ gây ra hành vi không xác định nếu độ dài của các vùng chứa đầu vào không giống nhau (nó có thể bị lỗi hoặc lặp lại quá cuối).


Bắt đầu từ Boost 1.56.0 (ngày 7 tháng 8 năm 2014), bạn có thể sử dụngboost::combine (chức năng tồn tại trong các phiên bản trước đó nhưng không có giấy tờ):

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

Điều này sẽ in

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

Trong các phiên bản trước đó, bạn có thể tự xác định một phạm vi như sau:

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

Cách sử dụng cũng vậy.


1
bạn có thể sử dụng cái này để phân loại không? tức là std :: sort (zip (a.begin (), ...), zip (a.end (), ...), [] (tup a, tup b) {a.get <0> () > b.get <0> ()}); ?
gnzlbg 13/12/12


Tôi sẽ bị cám dỗ bởi optionalcác yếu tố cho khả năng qua-the-end lặp ...
Yakk - Adam Nevraumont

3
Bất kỳ cơ hội nào bạn có thể làm điều này với std :: make_tuple và std :: tie? Tôi đã cố gắng sử dụng điều này trong khi giảm thiểu sự phụ thuộc vào tăng cường nhưng tôi không thể làm cho nó hoạt động.
Carneiro

@kennytm có bất kỳ ý kiến ​​nào tại sao họ quyết định đi với UB thay vì chỉ kết thúc ở cuối phạm vi ngắn nhất trong nhóm không?
Catskul

18

Vì vậy, tôi đã viết zip này trước khi cảm thấy buồn chán, tôi quyết định đăng nó vì nó khác với những cái khác ở chỗ nó không sử dụng boost và trông giống stdlib c ++ hơn.

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

Ví dụ sử dụng:

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
Bạn nên kiểm tra xem có bất kỳ trình vòng lặp nào ở cuối hay không.
Xeo

1
@Xeo tất cả các dãy nên có cùng kích thước như người đầu tiên hoặc cao hơn
aaronman

Bạn có thể giải thích làm thế nào [](int i,int j,float k,float l)hoạt động? Đây có phải là một hàm lambda không?
Hooked

@Hooked vâng đó là một lambda, nó về cơ bản hoạt động giống std::for_eachnhưng bạn có thể sử dụng một số tùy ý của dãy, các thông số trong lambda phụ thuộc vào có bao nhiêu vòng lặp bạn cung cấp cho các chức năng
aaronman

1
Nhu cầu phổ biến là zip các phạm vi có kích thước khác nhau hoặc thậm chí với phạm vi vô hạn.
Xeo

16

Bạn có thể sử dụng một giải pháp dựa trên boost::zip_iterator. Tạo một lớp vùng chứa giả duy trì các tham chiếu đến vùng chứa của bạn và các tham chiếu này trả về zip_iteratortừ các hàm beginendthành viên. Bây giờ bạn có thể viết

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

Triển khai ví dụ (vui lòng kiểm tra):

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

Tôi để lại phiên bản đa dạng như một bài tập tuyệt vời cho người đọc.


3
+1: Boost.Range có lẽ nên kết hợp điều này. Trên thực tế, tôi sẽ gửi cho họ một yêu cầu tính năng về điều này.
Nicol Bolas

2
@NicolBolas: Bạn làm tốt. Điều này sẽ khá dễ thực hiện với boost::iterator_range+ boost::zip_iterator, ngay cả phiên bản khác nhau.
Alexandre C.

1
Tôi tin rằng điều này sẽ không bao giờ kết thúc (và có hành vi không xác định) nếu các phạm vi không có cùng độ dài.
Jonathan Wakely

1
boost::zip_iteratorkhông làm việc với các dãy có độ dài khác nhau
Jonathan Wakely

1
Điều này cũng sẽ hoạt động ngay cả trong c ++ 03 sạch với cặp thay vì tuple. Tuy nhiên, wil này cũng tạo ra vấn đề khi độ dài không bằng nhau. Điều gì đó có thể được thực hiện với end () bằng cách lấy end () tương ứng của vùng chứa nhỏ nhất. Điều này dường như nằm trong thông số kỹ thuật như trong câu hỏi của OP.
Paul

16

std :: biến đổi có thể làm điều này một cách đáng kể:

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;

Nếu chuỗi thứ hai ngắn hơn, việc triển khai của tôi dường như đang cung cấp các giá trị khởi tạo mặc định.


1
Nếu chuỗi thứ 2 ngắn hơn, thì tôi hy vọng rằng đây là UB như bạn sẽ lặp lại ở cuối b.
Adrian

15

Tìm <redi/zip.h>một ziphàm hoạt động với cơ sở phạm vi forvà chấp nhận bất kỳ số lượng phạm vi nào, có thể là giá trị hoặc giá trị và có thể có độ dài khác nhau (quá trình lặp sẽ dừng lại ở cuối phạm vi ngắn nhất).

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

Bản in 0 1 2 3 4 5


2
bạn cũng có thể sử dụng boost/tuple/tuple_io.hppđểcout << i;
kirill_igum

Đây là những gì làm việc cho tôi. Tuy nhiên, trong mã của tôi, tôi phải sử dụng tương đương với boost::get<0>(i)boost::get<1>(i). Tôi không chắc tại sao mẫu ban đầu không thể được điều chỉnh trực tiếp, nó có thể liên quan đến thực tế là mã của tôi có tham chiếu liên tục đến các vùng chứa.
YitzikC

11

Với 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; 
}

Đầu ra:

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


@ einpoklum-reinstateMonica bây giờ là vậy!
yuyoyuppe

6

Tôi đã gặp cùng câu hỏi này một cách độc lập và không thích cú pháp của bất kỳ câu nào ở trên. Vì vậy, tôi có một tệp tiêu đề ngắn về cơ bản giống như boost zip_iterator nhưng có một số macro để làm cho cú pháp trở nên dễ hiểu hơn đối với tôi:

https://github.com/cshelton/zipfor

Ví dụ bạn có thể làm

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

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

Cú pháp chính là tôi có thể đặt tên cho các phần tử từ mỗi vùng chứa. Tôi cũng bao gồm một "mapfor" làm tương tự, nhưng đối với bản đồ (để đặt tên cho ".first" và ".second" của phần tử).


Điều này là gọn gàng! Có thể nhận một số lượng tùy ý đối số là tất cả những đối số bị giới hạn bởi cách tạo mẫu thông minh của bạn thành một số hữu hạn không?
Hooked

Hiện tại nó chỉ xử lý tối đa 9 container song song. Đó sẽ là đơn giản để thăng tiến. Mặc dù macro đa dạng cho phép một macro "zipfor" xử lý các số lượng thông số khác nhau, người ta vẫn phải viết mã một macro riêng biệt cho từng macro (được gửi đến). Xem groups.google.com/forum/?fromgroups=#!topic/comp.std.c/…stackoverflow.com/questions/15847837/…
cshelton

Nó có xử lý tốt các đối số có kích thước khác nhau không? (như được mô tả trong OP)
coyotte508

@ coyotte508, nó giả định rằng vùng chứa đầu tiên có ít phần tử nhất (và bỏ qua các phần tử phụ trong các vùng chứa khác). Sẽ dễ dàng sửa đổi để không đưa ra giả định này, nhưng điều đó sẽ làm chậm nó (hiện tại nó không chậm hơn so với viết tay) khi số phần tử khớp nhau.
cshelton

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

6

Nếu bạn thích nạp chồng toán tử, đây là ba khả năng. Hai đầu tiên đang sử dụng std::pair<>std::tuple<>, tương ứng, làm trình vòng lặp; thứ ba mở rộng điều này sang dựa trên phạm vi for. Lưu ý rằng không phải ai cũng thích các định nghĩa này về các toán tử, vì vậy tốt nhất hãy giữ chúng trong một không gian tên riêng và có using namespacetrong các hàm (không phải tệp!) Nơi bạn muốn sử dụng chúng.

#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

Đối với thư viện xử lý luồng C ++ mà tôi đang viết, tôi đang tìm kiếm một giải pháp không dựa vào thư viện của bên thứ ba và hoạt động với số lượng vùng chứa tùy ý. Tôi đã kết thúc với giải pháp này. Nó tương tự như giải pháp được chấp nhận sử dụng boost (và cũng dẫn đến hành vi không xác định nếu độ dài vùng chứa không bằng nhau)

#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
liên kết bị hỏng ... sẽ hữu ích nếu bài viết hướng dẫn cách sử dụng nó, ví dụ: main ()?
javaLover 15/02/17

@javaLover: bạn có thể sử dụng nó theo cách tương tự như cppitertools trong câu trả lời của @ knedlsepp. Một điểm khác biệt đáng chú ý là với giải pháp trên, bạn không thể sửa đổi các vùng chứa bên dưới như các tham chiếu operator*for seq::iteratortrả về a std::tupleof const.
winnetou

2

Nếu bạn có trình biên dịch tuân thủ C ++ 14 (ví dụ: gcc5), bạn có thể sử dụng zipđược cung cấp trong cppitertoolsthư viện bởi Ryan Haining, trông thực sự hứa hẹn:

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.Iterator có zip_iteratorbạn có thể sử dụng (ví dụ trong tài liệu). Nó sẽ không hoạt động với phạm vi cho, nhưng bạn có thể sử dụng std::for_eachvà lambda.


Tại sao nó sẽ không hoạt động với range-based for? Kết hợp nó với Boost.Range và bạn sẽ được thiết lập.
Xeo

@Xeo: Tôi không biết Range quá rõ. Tôi đoán bạn có thể liên quan đến một số bảng soạn sẵn và làm cho nó hoạt động, nhưng IMO chỉ sử dụng for_eachsẽ ít phức tạp hơn.
Cat Plus Plus

Ý bạn là một cái gì đó như thế này không phức tạp 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
Tôi nên bắt đầu chiến dịch Lambda Does Not Make std :: for_each Hữu ích. :)
UncleBens

2
@Xeo: Đây có lẽ nên là một câu hỏi riêng, nhưng tại sao oh tại sao ??
UncleBens

-2

Đây là một phiên bản đơn giản không yêu cầu tăng cường. Nó sẽ không đặc biệt hiệu quả vì nó tạo ra các giá trị tạm thời và nó không tổng quát hóa qua các vùng chứa ngoài danh sách, nhưng nó không có phụ thuộc và nó giải quyết trường hợp phổ biến nhất để nén.

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

Mặc dù các phiên bản khác linh hoạt hơn, nhưng thường thì điểm của việc sử dụng toán tử danh sách là tạo một lớp lót đơn giản. Phiên bản này có lợi ích là trường hợp chung rất đơn giản.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.