web-dev-qa-db-fra.com

Fonction Sequence-Zip pour c ++ 11?

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)

84
Hooked

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.

82
kennytm

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.

16
Alexandre C.

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

15
Jonathan Wakely

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());
}
13
aaronman

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.

7
Venki

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

7
csguth

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).

6
cshelton
// declare a, b
BOOST_FOREACH(boost::tie(a, b), boost::combine(list_of_a, list_of_b)){
    // your code here.
}
6
squid

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

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
}
2
knedlsepp

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

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.

0
Cat Plus Plus