Функция Sequence-zip для С++ 11?

С новым циклом for, основанным на диапазоне, мы можем написать такой код, как

for(auto x: Y) {}

Какая ИМО является огромным улучшением по сравнению с (например)

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

Можно ли использовать его для двух одновременных циклов, например функции Python 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)


person Hooked    schedule 14.12.2011    source источник
comment
for на основе диапазона можно использовать только с одной переменной, поэтому нет. Если вы хотите получить доступ к двум значениям одновременно, вам придется использовать что-то вроде std::pair   -  person Seth Carnegie    schedule 15.12.2011
comment
@SethCarnegie: не напрямую, но вы могли бы придумать функцию zip(), которая возвращает кортежи и перебирает список кортежей.   -  person André Caron    schedule 15.12.2011
comment
@AndréCaron, вы правы, мое «нет» означало, что вы не можете использовать две переменные, а не то, что вы не можете перебирать два контейнера одновременно.   -  person Seth Carnegie    schedule 15.12.2011
comment
Ясно, что for(;;) может получить такое поведение, хотя и запутанное, так что на самом деле вопрос: возможно ли использовать auto для двух объектов одновременно?   -  person    schedule 15.12.2011
comment
В будущей версии (надеюсь, C++17) капитальный ремонт STL будет включать диапазоны. . Затем view::zip может предоставить предпочтительное решение.   -  person John McFarlane    schedule 11.06.2015


Ответы (12)


Предупреждение. boost::zip_iterator и boost::combine в версии Boost 1.63.0 (26 декабря 2016 г.) вызовут неопределенное поведение, если длина входных контейнеров не одинакова (это может привести к сбою или повторению за пределами конца).


Начиная с версии Boost 1.56.0 (7 августа 2014 г.) вы можете use 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);
}

Использование такое же.

person kennytm    schedule 15.12.2011
comment
не могли бы вы использовать это для сортировки? т.е. std::sort(zip(a.begin(),...),zip(a.end(),...),[](tup a, tup b){a.get‹0›() › b.get‹0›()}); ? - person gnzlbg; 13.12.2012
comment
@gnzlbg: Нет, вы не можете. - person kennytm; 13.12.2012
comment
Я бы соблазнился optional элементами для возможности бесконечной итерации... - person Yakk - Adam Nevraumont; 26.06.2013
comment
Есть ли шанс, что вы можете сделать это с помощью std::make_tuple и std::tie ? Я пытался использовать это, сводя к минимуму зависимость от повышения, но я не мог заставить это работать. - person Carneiro; 11.07.2014
comment
@kennytm есть идеи, почему они решили пойти с UB вместо того, чтобы просто закончить в конце кратчайшего диапазона в группе? - person Catskul; 06.07.2019

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;

Если вторая последовательность короче, моя реализация, кажется, дает инициализированные значения по умолчанию.

person Venki    schedule 25.06.2018
comment
Если вторая последовательность короче, то я ожидаю, что это UB, поскольку вы будете повторять конец b. - person Adrian; 06.05.2019

Итак, я написал этот zip раньше, когда мне было скучно, я решил опубликовать его, потому что он отличается от других тем, что он не использует ускорение и больше похож на стандартную библиотеку C++.

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());
}
person aaronman    schedule 12.09.2013
comment
Вы должны проверить, не находится ли какой-либо из итераторов в конце. - person Xeo; 12.09.2013
comment
@Xeo все диапазоны должны быть того же размера, что и первый, или больше - person aaronman; 12.09.2013
comment
Можете ли вы объяснить, как работает [](int i,int j,float k,float l)? Это лямбда-функция? - person Hooked; 12.09.2013
comment
@Hooked, да, это лямбда, в основном она работает только std::for_each, но вы можете использовать произвольное количество диапазонов, параметры в лямбде зависят от того, сколько итераторов вы даете функции - person aaronman; 12.09.2013
comment
@Xeo, не могли бы вы объяснить свою критику, если вы все еще считаете ее справедливой, я хотел бы это исправить. - person aaronman; 12.09.2013
comment
Распространенной потребностью является сжатие диапазонов разного размера или даже с бесконечными диапазонами. - person Xeo; 12.09.2013
comment
@Xeo Я понимаю вашу точку зрения, просто такие функции stdlib обычно просто предполагают, что первый диапазон является наименьшим, это был шаблон, которым я руководствовался. - person aaronman; 12.09.2013
comment
К сожалению, stdlib не всегда делает самый разумный выбор. ;) - person Xeo; 13.09.2013
comment
как стандартная библиотека С++. Не могли бы вы показать пример стандартной библиотеки? Мне любопытно - никогда не видел такой функции. (я новичок в С++) - person javaLover; 16.02.2017

Вы можете использовать решение на основе 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);
}

Я оставляю вариативную версию в качестве отличного упражнения для читателя.

person Alexandre C.    schedule 14.12.2011
comment
+1: Boost.Range, вероятно, должен включать это. На самом деле, я отправлю им запрос на эту функцию. - person Nicol Bolas; 15.12.2011
comment
@NicolBolas: Ты молодец. Это должно быть довольно легко реализовать с помощью boost::iterator_range + boost::zip_iterator, даже в версии с переменным числом переменных. - person Alexandre C.; 15.12.2011
comment
Я считаю, что это никогда не прекратится (и будет иметь неопределенное поведение), если диапазоны не будут одинаковой длины. - person Jonathan Wakely; 17.01.2013
comment
@JonathanWakely: Хороший вопрос. Однако я считаю, что boost::zip_iterator поступает правильно. Если это не так, то код выше может действительно нуждаться в доработке. - person Alexandre C.; 10.04.2013
comment
boost::zip_iterator не работает с диапазонами разной длины - person Jonathan Wakely; 21.04.2013
comment
Это также должно работать даже в чистом С++ 03 с парой вместо кортежа. Тем не менее, это также создаст проблемы, когда длины не равны. Что-то можно сделать с end(), взяв соответствующий end() наименьшего контейнера. Кажется, это есть в спецификации, как и в вопросе ОП. - person Paul; 29.10.2015

См. <redi/zip.h> для функции zip, которая работает с диапазоном- base for и принимает любое количество диапазонов, которые могут быть rvalue или lvalue и могут иметь разную длину (итерация остановится в конце кратчайшего диапазона).

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

person Jonathan Wakely    schedule 17.01.2013
comment
вы также можете использовать boost/tuple/tuple_io.hpp для cout << i; - person kirill_igum; 30.10.2014
comment
Это то, что сработало для меня. Однако в моем коде мне пришлось использовать эквивалент boost::get<0>(i) и boost::get<1>(i). Я не уверен, почему исходный пример нельзя было адаптировать напрямую, возможно, это связано с тем, что мой код использует постоянные ссылки на контейнеры. - person YitzikC; 06.12.2016

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

Выход:

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

person csguth    schedule 14.04.2017
comment
@einpoklum-reinstateМоника теперь есть! - person yuyoyuppe; 19.11.2019

Если вам нравится перегрузка операторов, вот три возможности. Первые два используют 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;
    }
}
person lorro    schedule 12.07.2016

Я столкнулся с этим же вопросом независимо, и мне не понравился синтаксис любого из вышеперечисленных. Итак, у меня есть короткий заголовочный файл, который по сути делает то же самое, что и 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» элемента).

person cshelton    schedule 09.01.2014
comment
Это аккуратно! Может ли он принимать произвольное количество аргументов, все ли они ограничены вашим умным шаблоном до конечного числа? - person Hooked; 10.01.2014
comment
В настоящее время он обрабатывает только до 9 параллельных контейнеров. Было бы просто продвигаться вперед. В то время как вариативные макросы позволяют одному макросу zipfor обрабатывать различное количество параметров, для каждого из них по-прежнему необходимо кодировать отдельный макрос (для отправки). См. groups.google.com/forum/ ?fromgroups=#!topic/comp.std.c/ и заголовок stackoverflow.com/questions/15847837/ - person cshelton; 10.01.2014
comment
Хорошо ли он обрабатывает аргументы разного размера? (как описано в ОП) - person coyotte508; 27.05.2016
comment
@ coyotte508, предполагается, что первый контейнер имеет наименьшее количество элементов (и игнорирует дополнительные элементы в других контейнерах). Было бы легко изменить, чтобы не делать этого предположения, но это замедлило бы его (в настоящее время он не медленнее, чем написанный от руки), когда количество элементов совпадает. - person cshelton; 27.05.2016

Если у вас есть компилятор, совместимый с C++14 (например, gcc5), вы можете использовать zip, предоставленный в cppitertools библиотека Райана Хайнинга, которая выглядит многообещающе:

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
}
person knedlsepp    schedule 05.04.2016

Для библиотеки потоковой обработки 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)...));
}
person foges    schedule 28.12.2016
comment
ссылка не работает ... было бы полезно, если бы в сообщении было показано, как ее использовать, например. главный() ? - person javaLover; 15.02.2017
comment
@javaLover: вы можете использовать его так же, как cppitertools в ответе @knedlsepp. Одно заметное отличие заключается в том, что с помощью приведенного выше решения вы не можете изменять базовые контейнеры, поскольку operator* для seq::iterator возвращает std::tuple ссылок на константы. - person winnetou; 09.12.2017

Boost.Iterators имеет zip_iterator, который вы можете использовать ( пример в документации). Это не будет работать с диапазоном для, но вы можете использовать std::for_each и лямбду.

person Cat Plus Plus    schedule 14.12.2011
comment
Почему это не будет работать с диапазоном для? Объедините его с Boost.Range, и все будет готово. - person Xeo; 15.12.2011
comment
@Xeo: я не слишком хорошо знаю Range. Я думаю, вы могли бы использовать какой-то шаблон и заставить его работать, но IMO, просто используя for_each, было бы меньше хлопот. - person Cat Plus Plus; 15.12.2011
comment
Вы имеете в виду что-то вроде этого не хлопот: 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)); });? - person UncleBens; 15.12.2011
comment
@UncleBens: Меньше хлопот, а не не хлопот. :P Опять же, я не очень хорошо знаю Рендж, может быть, он все-таки будет выглядеть лучше. - person Cat Plus Plus; 15.12.2011
comment
Я должен начать кампанию Lambda Does Not Make std::for_each Useful. :) - person UncleBens; 15.12.2011
comment
@UncleBens: даже гуру С++ (например, Херб Саттер) заявляют, что в большинстве случаев std::for_each с лямбдой предпочтительнее, чем на основе диапазона. - person Xeo; 15.12.2011
comment
@Xeo: Это, вероятно, должен быть отдельный вопрос, но почему, почему ?? - person UncleBens; 15.12.2011

Вот простая версия, которая не требует повышения. Это не будет особенно эффективно, поскольку создает временные значения и не распространяется на другие контейнеры, кроме списков, но не имеет зависимостей и решает наиболее распространенный случай архивирования.

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

Хотя другие версии более гибкие, часто смысл использования оператора списка заключается в том, чтобы сделать его простым однострочным. Преимущество этой версии в том, что общий случай прост.

person Andrew    schedule 21.06.2016
comment
Если вы реализуете итератор, вы можете избежать создания результата и вернуть по мере необходимости следующий элемент. Требуется немного больше кода, так как вам нужно определить ++ * и т. д. (все операторы, используемые for (auto v : containers)) - person Et7f3XIV; 02.04.2021
comment
@ Et7f3XIV Et7f3XIV Верно, но, глядя на этот код, который Эндрю написал в 16 году, я бы взорвал его с орбиты и использовал один из других ответов в качестве отправной точки. - person Andrew; 03.04.2021