Avec la nouvelle boucle for basée sur une plage, nous pouvons écrire du code comme
for(auto x: Y) {}
Quel IMO est une énorme amélioration de (par exemple)
for(std::vector<int>::iterator x=Y.begin(); x!=Y.end(); ++x) {}
Peut-il être utilisé pour boucler sur deux boucles simultanées, comme la fonction Pythons Zip
? Pour ceux qui ne connaissent pas Python, le code:
Y1 = [1,2,3]
Y2 = [4,5,6,7]
for x1,x2 in Zip(Y1,Y2):
print x1,x2
Donne en sortie (1,4) (2,5) (3,6)
Avertissement: boost::Zip_iterator
et boost::combine
à compter de Boost 1.63.0 (26 décembre 2016) entraînera un comportement indéfini si la longueur des conteneurs d'entrée n'est pas la même (elle peut se bloquer ou itérer au-delà de la fin).
À partir de Boost 1.56.0 (7 août 2014), vous pourriez tilisez boost::combine
(la fonction existe dans les versions antérieures mais non documentée):
#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);
}
}
Cela imprimerait
4 7 a 4 5 8 b 5 6 9 c 6
Dans les versions antérieures, vous pouviez définir vous-même une plage comme ceci:
#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);
}
L'usage est le même.
Vous pouvez utiliser une solution basée sur boost::Zip_iterator
. Créez une classe de conteneurs bidon en conservant les références à vos conteneurs et qui renvoient Zip_iterator
à partir des fonctions membres begin
et end
. Vous pouvez maintenant écrire
for (auto p: Zip(c1, c2)) { ... }
Exemple d'implémentation (veuillez tester):
#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);
}
Je laisse la version variadique au lecteur comme un excellent exercice.
Voir <redi/Zip.h>
pour une fonction Zip
qui fonctionne avec range-base for
et accepte n'importe quel nombre de plages, qui peuvent être rvalues ou lvalues et peuvent être de longueurs différentes (l'itération s'arrêtera à la fin de la plage la plus courte).
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>() << ' ';
Impressions 0 1 2 3 4 5
J'ai donc écrit ce Zip avant de m'ennuyer, j'ai décidé de le poster car il est différent des autres en ce qu'il n'utilise pas boost et ressemble plus au stdlib 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;
}
Exemple d'utilisation:
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());
}
std :: transform peut faire cela trivialement:
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;
Si la deuxième séquence est plus courte, mon implémentation semble donner des valeurs initialisées par défaut.
Avec range-v :
#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;
}
Le résultat:
[(4, 7), (5, 8), (6, 9)]
J'ai rencontré cette même question indépendamment et je n'ai pas aimé la syntaxe de l'une des réponses ci-dessus. Donc, j'ai un fichier d'en-tête court qui fait essentiellement la même chose que le boost Zip_iterator mais a quelques macros pour me rendre la syntaxe plus agréable au goût:
https://github.com/cshelton/zipfor
Par exemple, vous pouvez faire
vector<int> a {1,2,3};
array<string,3> b {"hello","there","coders"};
zipfor(i,s eachin a,b)
cout << i << " => " << s << endl;
Le principal sucre syntaxique est que je peux nommer les éléments de chaque conteneur. J'inclus également un "mapfor" qui fait de même, mais pour les maps (pour nommer les ".first" et ".second" de l'élément).
// declare a, b
BOOST_FOREACH(boost::tie(a, b), boost::combine(list_of_a, list_of_b)){
// your code here.
}
Si vous aimez la surcharge de l'opérateur, voici trois possibilités. Les deux premiers utilisent std::pair<>
et std::Tuple<>
, respectivement, en tant qu'itérateurs; le troisième étend cela à la plage for
. Notez que tout le monde n'aimera pas ces définitions des opérateurs, il est donc préférable de les conserver dans un espace de noms séparé et d'avoir un using namespace
dans les fonctions (pas les fichiers!) où vous souhaitez les utiliser.
#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;
}
}
Si vous avez un compilateur compatible C++ 14 (par exemple gcc5), vous pouvez utiliser Zip
fourni dans la bibliothèque cppitertools
de Ryan Haining, ce qui semble vraiment prometteur:
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
}
Pour une bibliothèque de traitement de flux C++ J'écris Je cherchais une solution qui ne repose pas sur des bibliothèques tierces et fonctionne avec un nombre arbitraire de conteneurs. Je me suis retrouvé avec cette solution. Elle est similaire à la solution acceptée qui utilise le boost (et entraîne également un comportement indéfini si les longueurs de conteneur ne sont pas égales)
#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)...));
}
Boost.Iterators a Zip_iterator
vous pouvez utiliser (exemple dans les documents). Cela ne fonctionnera pas avec la plage pour, mais vous pouvez utiliser std::for_each
et un lambda.