C ++ 11 용 Sequence-zip 함수?


100

새로운 범위 기반 for 루프를 사용하면 다음과 같은 코드를 작성할 수 있습니다.

for(auto x: Y) {}

어떤 IMO가 (예를 들어) 크게 개선되었습니다.

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

Pythons zip함수 처럼 두 개의 동시 루프를 반복하는 데 사용할 수 있습니까 ? 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은 하나의 변수에만 사용할 수 있으므로 아니요. 한 번에 두 개의 값을 액세스하고 싶었다면, 당신은 같은 것을 사용해야 할 것std::pair
세스 카네기

4
@SethCarnegie : 직접적으로는 아니지만 zip()튜플을 반환하고 튜플 목록을 반복 하는 함수를 생각 해낼 수 있습니다 .
André Caron 2011

2
@ AndréCaron 맞습니다. 내 "아니오"는 두 개의 변수를 사용할 수 없다는 뜻이 아니라 한 번에 두 개의 컨테이너를 반복 할 수 없다는 뜻입니다.
Seth Carnegie

분명 for(;;)그래서 질문은 정말 긴 손 불구하고이 동작을 얻을 수 있습니다 : 그것은 한 번에 두 개의 객체를 통해 "자동차"에 대한 수 있습니까?

향후 개정 (C ++ 17)에서는 STL의 정밀 검사에 범위 가 포함됩니다 . 그런 다음 view :: zip 이 선호하는 솔루션을 제공 할 수 있습니다.
John McFarlane

답변:


89

주의 : boost::zip_iteratorboost::combine상기 입력 용기의 길이가 동일하지 않으면 정의되지 않은 동작이 발생할 부스트 1.63.0 (2,016 십이 26 개)의 등 (그 끝을 넘어 충돌이나 반복 처리 할 수있다).


Boost 1.56.0 (2014 년 8 월 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

@gnzlbg : 아니요 .
kennytm

나는 유혹 할 것이다 optional과거 - 더 - 엔드 반복 가능성을위한 요소 ...
Yakk - 아담 Nevraumont

3
std :: make_tuple 및 std :: tie로 이것을 할 수 있습니까? 부스트 종속성을 최소화하면서 이것을 사용하려고했지만 작동하지 못했습니다.
Carneiro

@kennytm 무리에서 가장 짧은 범위의 끝에서 끝나는 대신 UB를 사용하기로 결정한 이유는 무엇입니까?
Catskul 19.07.06

18

그래서 지루할 때 이전에이 zip을 썼는데, 부스트를 사용하지 않고 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;
    }

사용 예 :

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 2013 년

1
모든 @Xeo 범위는 제 이상과 같은 크기이어야
aaronman

어떻게 [](int i,int j,float k,float l)작동 하는지 설명해 주 시겠습니까? 이것은 람다 함수입니까?
Hooked

@Hooked 네는 람다, 그것은 기본적으로 그냥 작동입니다 std::for_each하지만 당신은 기능을 제공 얼마나 많은 반복자는, 람다의 매개 변수에 따라 범위의 임의의 번호를 사용할 수 있습니다
aaronman

1
일반적인 요구 사항은 크기가 다르거 나 범위가 무한한 범위를 압축하는 것입니다.
Xeo

18

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;

두 번째 시퀀스가 ​​더 짧으면 내 구현이 기본 초기화 값을 제공하는 것 같습니다.


1
두 번째 시퀀스가 ​​더 짧으면 b.
Adrian

16

.NET 기반 솔루션을 사용할 수 있습니다 boost::zip_iterator. 컨테이너에 대한 참조를 유지 zip_iterator하고 beginend멤버 함수 에서 반환되는 가짜 컨테이너 클래스를 만듭니다 . 이제 쓸 수 있습니다

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

1
boost::zip_iterator서로 다른 길이의 범위가 작동하지 않습니다
조나단 Wakely에게

1
이것은 튜플 대신 쌍을 사용하는 깨끗한 C ++ 03에서도 작동합니다. 그래도 길이가 같지 않을 때도 문제가 발생합니다. 가장 작은 컨테이너의 해당 end ()를 사용하여 end ()로 무언가를 수행 할 수 있습니다. 이것은 OP 질문에서와 같이 사양에있는 것 같습니다.
Paul

15

range-base 와 함께 작동하고 rvalue 또는 lvalue 일 수 있고 다른 길이 일 수있는 범위의 수를 허용 <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.hppcout << i;
kirill_igum

이것이 나를 위해 일한 것입니다. 그러나 내 코드에서는 boost::get<0>(i)boost::get<1>(i). 원본 샘플을 직접 적용 할 수없는 이유를 잘 모르겠습니다. 내 코드가 컨테이너에 대한 지속적인 참조를 사용한다는 사실과 관련이있을 수 있습니다.
YitzikC

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"이름 지정)을위한 것입니다.


이것은 깔끔합니다! 임의의 수의 인수를 취할 수 있습니까?이 모든 인수는 영리한 템플릿에 의해 유한 수로 제한됩니까?
Hooked

현재 최대 9 개의 병렬 컨테이너 만 처리합니다. 진행하는 것은 간단합니다. 가변 매크로는 단일 "zipfor"매크로가 다른 수의 매개 변수를 처리 할 수 ​​있도록 허용하지만, 하나는 여전히 각각에 대해 별도의 매크로를 코딩해야합니다. groups.google.com/forum/?fromgroups=#!topic/comp.std.c/…stackoverflow.com/questions/15847837/…
cshelton

크기가 다른 인수를 잘 처리합니까? (OP에 설명 된대로)
coyotte508

@ 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

연산자 오버로딩을 좋아한다면 여기에 세 가지 가능성이 있습니다. 처음 두 사용 std::pair<>std::tuple<>반복자로서, 각각; 세 번째는 이것을 범위 기반으로 확장합니다 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

A의 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
link broken ... 포스트가 그것을 사용하는 방법을 보여주는 경우 유용 할 것입니다. 예 : main ()?
javaLover

@javaLover : @knedlsepp의 답변에서 cppitertools와 같은 방식으로 사용할 수 있습니다. 한 가지 주목할만한 차이점은 위의 솔루션을 사용하면 operator*for seq::iteratorstd::tupleconst 참조 를 반환 하므로 기본 컨테이너를 수정할 수 없다는 것 입니다.
winnetou

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
Lambda Does Not Make std :: for_each 유용한 캠페인을 시작해야합니다 . :)
UncleBens 2011

2
@Xeo : 이것은 아마도 별도의 질문이어야하지만 왜 오 왜 ??
UncleBens

-2

다음은 부스트가 필요하지 않은 간단한 버전입니다. 임시 값을 생성하고 목록 이외의 컨테이너에 대해 일반화하지 않기 때문에 특히 효율적이지 않지만 종속성이 없으며 가장 일반적인 압축 사례를 해결합니다.

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.