Zip
Python zip能够让我们:
1 Parallel iteration
NOTE: 这是 realpython Using the Python zip() Function for Parallel Iteration 中的说法
2 Loop over multiple arrays simultaneously
NOTE: 这是 rosettacode Loop over multiple arrays simultaneously 中的说法
C++中如何实现zip呢?本文对此进行讨论。
NOTE: 关于Python zip,参见:
1 realpython Using the Python zip() Function for Parallel Iteration
rosettacode Loop over multiple arrays simultaneously
std::vector
#include <iostream>
#include <vector>
int main(int argc, char *argv[])
{
std::vector<char> ls(3);
ls[0] = 'a';
ls[1] = 'b';
ls[2] = 'c';
std::vector<char> us(3);
us[0] = 'A';
us[1] = 'B';
us[2] = 'C';
std::vector<int> ns(3);
ns[0] = 1;
ns[1] = 2;
ns[2] = 3;
std::vector<char>::const_iterator lIt = ls.begin();
std::vector<char>::const_iterator uIt = us.begin();
std::vector<int>::const_iterator nIt = ns.begin();
for (; lIt != ls.end() && uIt != us.end() && nIt != ns.end(); ++lIt, ++uIt, ++nIt)
{
std::cout << *lIt << *uIt << *nIt << "\n";
}
}
// g++ test.cpp
Static array
#include <iostream>
int main(int argc, char *argv[])
{
char ls[] = { 'a', 'b', 'c' };
char us[] = { 'A', 'B', 'C' };
int ns[] = { 1, 2, 3 };
for (size_t li = 0, ui = 0, ni = 0; li < sizeof(ls) && ui < sizeof(us) && ni < sizeof(ns) / sizeof(int); ++li, ++ui, ++ni)
{
std::cout << ls[li] << us[ui] << ns[ni] << "\n";
}
}
C++11
std::vector
#include <iostream>
#include <vector>
int main(int argc, char *argv[])
{
auto lowers = std::vector<char>( { 'a', 'b', 'c' });
auto uppers = std::vector<char>( { 'A', 'B', 'C' });
auto nums = std::vector<int>( { 1, 2, 3 });
auto ilow = lowers.cbegin();
auto iup = uppers.cbegin();
auto inum = nums.cbegin();
for (; ilow != lowers.end() and iup != uppers.end() and inum != nums.end(); ++ilow, ++iup, ++inum)
{
std::cout << *ilow << *iup << *inum << "\n";
}
}
Static array
#include <iostream>
#include <iterator>
int main(int argc, char *argv[])
{
char lowers[] = { 'a', 'b', 'c' };
char uppers[] = { 'A', 'B', 'C' };
int nums[] = { 1, 2, 3 };
auto ilow = std::begin(lowers);
auto iup = std::begin(uppers);
auto inum = std::begin(nums);
for (; ilow != std::end(lowers) and iup != std::end(uppers) and inum != std::end(nums); ++ilow, ++iup, ++inum)
{
std::cout << *ilow << *iup << *inum << "\n";
}
}
stackoverflow Sequence-zip function for c++11?
A: boost
Warning: boost::zip_iterator
and boost::combine
as of Boost 1.63.0 (2016 Dec 26) will cause undefined behavior if the length of the input containers are not the same (it may crash or iterate beyond the end).
Starting from Boost 1.56.0 (2014 Aug 7) you could use boost::combine
(the function exists in earlier versions but undocumented):
#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);
}
}
This would print
4 7 a 4
5 8 b 5
6 9 c 6
In earlier versions, you could define a range yourself like this:
#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);
}
A: variadic template + first class function
So I wrote this zip
before when I was bored, I decided to post it because it's different than the others in that it doesn't use boost and looks more like the c++ stdlib.
#include <vector>
#include <iostream>
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());
}
// g++ --std=c++11 test.cpp
Example use:
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());
}
A: std::transform
NOTE: 局限是: 最多只支持两个iterator
#include <vector>
#include <algorithm>
#include <iostream>
int main()
{
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;
}
// g++ --std=c++14 test.cpp
#include <vector>
#include <algorithm>
#include <iostream>
int main()
{
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 int &aa, const int &bb)
{
return aa*bb;
});
for (auto cc : c)
std::cout << cc << std::endl;
}
// g++ --std=c++11 test.cpp
A: range-v3
A: tuple
If you like operator overloading, here are three possibilities. The first two are using std::pair<>
and std::tuple<>
, respectively, as iterators; the third extends this to range-based for
. Note that not everyone will like these definitions of the operators, so it's best to keep them in a separate namespace and have a using namespace
in the functions (not files!) where you'd like to use these.
#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;
}
}
// g++ --std=c++11 test.cpp
#include <iostream>
#include <utility>
#include <vector>
#include <tuple>
namespace tuple_iterators
{
// you might want to make this generic (via param pack)
template<typename ... Args>
auto operator++(std::tuple<Args...> &it)
{
int size = std::tuple_size<std::tuple<Args...> >::value;
for (int i = 0; i < size; ++i)
{
++(std::get<i>(it));
}
return it;
}
}
int main()
{
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 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;
}
}