Функция Sequence-zip для c++11?
С Новым диапазона на основе цикла for, мы можем написать код типа
for(auto x: Y) {}
который ИМО является огромный улучшение (например.)
for(std::vector<int>::iterator x=Y.begin(); x!=Y.end(); ++x) {}
может ли он использоваться для циклического перебора двух одновременных циклов, таких как Pythons ? Для тех, кто не знаком с 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)
13 ответов:
предупреждение:
boost::zip_iterator
иboost::combine
начиная с Boost 1.63.0 (2016 Dec 26) вызовет неопределенное поведение, если длина входных контейнеров не одинакова (это может привести к сбою или итерации за пределами конца).
начиная с Boost 1.56.0 (2014 Aug 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); }
использование.
вы можете использовать решение на основе
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); }
Я оставляю вариативную версию как отличное упражнение для читателя.
посмотреть
<redi/zip.h>
наzip
функция, которая работает с range-basefor
и принимает любое количество диапазонов, которые могут быть rvalues или lvalues и могут быть разной длины (итерация остановится в конце самого короткого диапазона).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
поэтому я написал этот zip раньше, когда мне было скучно, я решил опубликовать его, потому что он отличается от других тем, что он не использует boost и больше похож на 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()); }
С диапазон-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)]
я столкнулся с этим же вопросом независимо и мне не понравился синтаксис любого из вышеперечисленных. Итак, у меня есть короткий файл заголовка, который по существу делает то же самое, что и 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", что делает то же самое, но для карт (чтобы назвать ".сначала" и".второго" элемента).
// declare a, b BOOST_FOREACH(boost::tie(a, b), boost::combine(list_of_a, list_of_b)){ // your code here. }
Если вам нравится перегрузка оператора, вот три возможности. Первые два используют
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; } }
Если у вас есть компилятор, совместимый с 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 }
на библиотека обработки потока C++ Я пишу, что искал решение, которое не полагается на сторонние библиотеки и работает с произвольным количеством контейнеров. Я закончил с этим решением. Это похоже на принятое решение, которое использует boost (а также приводит к неопределенному поведению, если длины контейнеров не равны)
#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)...)); }
импульс.Итераторы имеет
zip_iterator
вы можете использовать (пример в документах). Он не будет работать с диапазоном, но вы можете использоватьstd::for_each
и лямбду.
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;
если вторая последовательность короче, моя реализация, кажется, дает инициализированные значения по умолчанию.
вот простая версия, которая не требует повышения. Он не будет особенно эффективным, поскольку он создает временные значения и не обобщает контейнеры, отличные от списков, но у него нет зависимостей, и он решает наиболее распространенный случай для архивирования.
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; }
хотя другие версии являются более гибкими, часто смысл использования оператора списка-сделать простой однострочный. Эта версия имеет то преимущество, что общий случай прост.