C + + 11的序列 -zip 函数?

使用新的基于范围的 for-loop,我们可以编写如下代码:

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)

92822 次浏览

加油。迭代器有 zip_iterator,你可以使用(例如在文档中)。它不适用于 range for,但是可以使用 std::for_each和 lambda。

您可以使用基于 boost::zip_iterator的解决方案。创建一个虚假的容器类,维护对容器的引用,并从 beginend成员函数返回 zip_iterator。现在你可以写作了

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

我把可变版本留给读者作为一个很好的练习。

警告: 从 Boost 1.63.0(2016年12月26日)开始,如果输入容器的长度不相同,则 boost::zip_iteratorboost::combine会引起未定义行为(它可能会崩溃或者在结束后重复)。


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

用法是一样的。

有关 zip函数,请参阅 <redi/zip.h>,该函数使用基于范围的 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

所以我之前无聊的时候写了这个 zip,我决定发布它,因为它不同于其他的,它不使用 ost,而且看起来更像 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());
}

我独立地遇到了同样的问题,并且不喜欢上述任何一个问题的语法。因此,我有一个简短的头文件,它实际上与 boostzip _ 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.
}

如果你有一个 C + + 14兼容的编译器(例如 gcc5) ,你可以使用 Ryan Hining 在 cppitertools库中提供的 zip,它看起来很有前途:

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
}

这里有一个简单的版本,不需要提高。它不会特别有效,因为它创建临时值,它不会泛化除列表以外的容器,但它没有依赖关系,它解决了最常见的压缩情况。

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

虽然其他版本更加灵活,但通常使用列表运算符的目的是创建一个简单的一行程序。这个版本的好处是通用情况很简单。

如果你喜欢运算符重载,这里有三种可能性。前两个分别使用 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 + + 流处理库,我正在寻找一个不依赖于第三方库的解决方案,并且可以使用任意数量的容器。我最终得到了这个解决方案。它类似于公认的解决方案,使用的升级(也导致未定义行为,如果容器长度不相等)

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

距离 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)]

转换 可以做到这一点:

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;

如果第二个序列更短,我的实现似乎给出了默认的初始化值。

对阿伦曼 解决方案的改进:

  • 还是 C + + 11。
  • 没有递归模板展开。
  • 支持容器压缩。
  • 利用肖恩父母的方法著名的 for_each_arg()
// Includes only required for the example main() below!
#include <vector>
#include <iostream>


namespace detail {


struct advance {
template <typename T> void operator()(T& t) const { ++t; }
};


// Adaptation of for_each_arg, see:
// https://isocpp.org/blog/2015/01/for-each-argument-sean-parent
template <class... Iterators>
void advance_all(Iterators&... iterators) {
[](...){}((advance{}(iterators), 0)...);
}


} // namespace detail


template <typename F, typename Iterator, typename ... ExtraIterators>
F for_each_zipped(
F func,
Iterator begin,
Iterator end,
ExtraIterators ... extra_iterators)
{
for(;begin != end; ++begin, detail::advance_all(extra_iterators...))
func(*begin, *(extra_iterators)... );
return func;
}
template <typename F, typename Container, typename... ExtraContainers>
F for_each_zipped_containers(
F func,
Container& container,
ExtraContainers& ... extra_containers)
{
return for_each_zipped(
func, std::begin(container), std::end(container), std::begin(extra_containers)...);
}


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};
auto print_quartet =
[](int i,int j,float k,float l) {
std::cout << i << " " << j << " " << k << " " << l << '\n';
};
std::cout << "Using zipped iterators:\n";
for_each_zipped(print_quartet, v1.begin(), v1.end(), v2.begin(), v3.begin(), v4.begin());
std::cout << "\nUsing zipped containers:\n";
for_each_zipped_containers(print_quartet, v1, v2, v3, v4);
}

看到它 正在研究 GodBolt

我会提出这一个。我发现它是相当优雅的,正是我(和你)需要的。

Https://github.com/committhis/zip-iterator

以防这里有密码拷贝。注意,它是根据麻省理工学院许可证发行的,也不要忘记写作者姓名。

Zip.hpp

/***
* MIT License
* Author: G Davey
*/


#pragma once


#include <cassert>
#include <functional>
#include <iomanip>
#include <iostream>
#include <list>
#include <string>
#include <vector>
#include <typeinfo>


namespace c9 {


template <typename Iter>
using select_access_type_for = std::conditional_t<
std::is_same_v<Iter, std::vector<bool>::iterator> ||
std::is_same_v<Iter, std::vector<bool>::const_iterator>,
typename Iter::value_type,
typename Iter::reference
>;




template <typename ... Args, std::size_t ... Index>
auto any_match_impl(std::tuple<Args...> const & lhs,
std::tuple<Args...> const & rhs,
std::index_sequence<Index...>) -> bool
{
auto result = false;
result = (... | (std::get<Index>(lhs) == std::get<Index>(rhs)));
return result;
}




template <typename ... Args>
auto any_match(std::tuple<Args...> const & lhs, std::tuple<Args...> const & rhs)
-> bool
{
return any_match_impl(lhs, rhs, std::index_sequence_for<Args...>{});
}






template <typename ... Iters>
class zip_iterator
{
public:


using value_type = std::tuple<
select_access_type_for<Iters>...
>;


zip_iterator() = delete;


zip_iterator(Iters && ... iters)
: m_iters {std::forward<Iters>(iters)...}
{
}


auto operator++() -> zip_iterator&
{
std::apply([](auto && ... args){ ((args += 1), ...); }, m_iters);
return *this;
}


auto operator++(int) -> zip_iterator
{
auto tmp = *this;
++*this;
return tmp;
}


auto operator!=(zip_iterator const & other)
{
return !(*this == other);
}


auto operator==(zip_iterator const & other)
{
auto result = false;
return any_match(m_iters, other.m_iters);
}


auto operator*() -> value_type
{
return std::apply([](auto && ... args){
return value_type(*args...);
}, m_iters);
}


private:
std::tuple<Iters...> m_iters;
};




/* std::decay needed because T is a reference, and is not a complete type */
template <typename T>
using select_iterator_for = std::conditional_t<
std::is_const_v<std::remove_reference_t<T>>,
typename std::decay_t<T>::const_iterator,
typename std::decay_t<T>::iterator>;






template <typename ... T>
class zipper
{
public:
using zip_type = zip_iterator<select_iterator_for<T> ...>;


template <typename ... Args>
zipper(Args && ... args)
: m_args{std::forward<Args>(args)...}
{
}


auto begin() -> zip_type
{
return std::apply([](auto && ... args){
return zip_type(std::begin(args)...);
}, m_args);
}
auto end() -> zip_type
{
return std::apply([](auto && ... args){
return zip_type(std::end(args)...);
}, m_args);
}


private:
std::tuple<T ...> m_args;


};




template <typename ... T>
auto zip(T && ... t)
{
return zipper<T ...>{std::forward<T>(t)...};
}


}


例子

#include "zip.hpp"
#include <vector>


std::vector<int> a, b, c;


void foo() {
for (auto && [x, y] : zip(a, b))
c.push_back(x + z);
}




C + + 23,我们可以在 std::views::zip上迭代。 下面是一个简单的例子。

#include <iostream>
#include <ranges>
#include <vector>
 

int main() {
std::vector<int> x {4, 5, 6};
double y[] = {7, 8, 9};


for (auto [elem1,elem2] : std::views::zip(x, y))
std::cout << "[" << elem1 << "," << elem2 << "]" << " ";
}

输出可以在下面(在线编译器)进行验证。不确定链接存在多少天。

Https://godbolt.org/z/kjje4eegy