124 votes

Fonction Sequence-zip pour C++11 ?

Avec la nouvelle boucle for basée sur la gamme, nous pouvons écrire du code comme :

for(auto x: Y) {}

ce qui, à mon avis, est une énorme amélioration par rapport à (par exemple)

for(std::vector::iterator x=Y.begin(); x!=Y.end(); ++x) {}

Peut-il être utilisé pour parcourir deux boucles simultanément, comme la fonction zip de Python? 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)

0 votes

La balise for basée sur la plage ne peut être utilisée qu'avec une seule variable, donc non. Si vous voulez accéder à deux valeurs à la fois, vous devriez utiliser quelque chose comme std::pair

4 votes

@ SethCarnegie: pas directement, mais vous pourriez créer une fonction zip() qui renvoie des tuples et itérer sur la liste de tuples.

2 votes

@AndréCaron vous avez raison, mon "non" voulait dire que vous ne pouvez pas utiliser deux variables, pas que vous ne pouvez pas itérer sur deux conteneurs en même temps.

94voto

KennyTM Points 232647

Attention : boost::zip_iterator et boost::combine à partir de Boost 1.63.0 (26 décembre 2016) provoqueront un comportement indéfini si la longueur des conteneurs d'entrée n'est pas la même (cela peut provoquer un crash ou itérer au-delà de la fin).


À partir de Boost 1.56.0 (7 août 2014), vous pouvez utiliser boost::combine (la fonction existe dans les versions antérieures mais n'est pas documentée) :

#include 
#include 
#include 
#include 

int main() {
    std::vector a {4, 5, 6};
    double b[] = {7, 8, 9};
    std::list 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 afficherait

4 7 a 4
5 8 b 5
6 9 c 6

Dans les versions antérieures, vous pouviez définir une plage vous-même de cette manière :

#include 
#include 

template 
auto zip(T&&... containers) -> boost::iterator_range>
{
    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'utilisation est la même.

1 votes

Pouvez-vous utiliser ceci pour le tri? c'est-à-dire std::sort(zip(a.begin(),...),zip(a.end(),...),[](tup a, tup b){a.get<0>() > b.get<0>()}); ?

0 votes

0 votes

Je serais tenté par les éléments optionnels pour des possibilités d'itération au-delà de la fin...

22voto

aaronman Points 8034

Alors j'ai écrit ce zip avant quand j'étais ennuyé, j'ai décidé de le poster parce qu'il est différent des autres en ce sens qu'il n'utilise pas boost et ressemble plus à la stdlib c++.

template 
    void advance_all (Iterator & iterator) {
        ++iterator;
    }
template 
    void advance_all (Iterator & iterator, Iterators& ... iterators) {
        ++iterator;
        advance_all(iterators...);
    } 
template 
    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;
    }

Utilisation de l'exemple:

int main () {
    std::vector v1{1,2,3};
    std::vector v2{3,2,1};
    std::vector v3{1.2,2.4,9.0};
    std::vector 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());
}

4 votes

Vous devriez vérifier si l'un des itérateurs est à la fin.

1 votes

@Xeo toutes les plages doivent être de la même taille que la première ou plus grandes

0 votes

Pouvez-vous expliquer comment fonctionne [](int i, int j, float k, float l)? S'agit-il d'une fonction lambda?

16voto

Alexandre C. Points 31758

Vous pouvez utiliser une solution basée sur boost::zip_iterator. Créez une classe de conteneur fictive maintenant des références à vos conteneurs, et qui retourne des zip_iterator à partir des fonctions membres begin et end. Maintenant, vous pouvez écrire

for (auto p: zip(c1, c2)) { ... }

Implémentation d'exemple (veuillez tester):

#include 
#include 

template 
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 iterator;

    iterator begin() const
    {
         return iterator(std::begin(*c1), std::begin(*c2));
    }

    iterator end() const
    {
         return iterator(std::end(*c1), std::end(*c2));
    }
};

template 
zip_container zip(C1& c1, C2& c2)
{
    return zip_container(c1, c2);
}

Je laisse la version variadique comme un excellent exercice pour le lecteur.

3 votes

+1: Boost.Range devrait probablement intégrer ceci. En fait, je vais leur envoyer une demande de fonctionnalité à ce sujet.

2 votes

@NicolBolas: Vous faites bien. Cela devrait être assez facile à implémenter avec boost::iterator_range + boost::zip_iterator, même la version variadique.

1 votes

Je crois que cela ne se terminera jamais (et aura un comportement indéfini) si les plages n'ont pas la même longueur.

15voto

Jonathan Wakely Points 45593

Consultez pour une fonction zip qui fonctionne avec la boucle for basée sur les plages et accepte un nombre quelconque de plages, qui peuvent être des rvalues ou des lvalues et peuvent avoir des longueurs différentes (l'itération s'arrêtera à la fin de la plage la plus courte).

std::vector vi{ 0, 2, 4 };
std::vector vs{ "1", "3", "5", "7" };
for (auto i : redi::zip(vi, vs))
  std::cout << i.get<0>() << ' ' << i.get<1>() << ' ';

Affiche 0 1 2 3 4 5

2 votes

Vous pouvez également utiliser boost/tuple/tuple_io.hpp pour cout << i;

0 votes

Voici ce qui a fonctionné pour moi. Cependant, dans mon code, j'ai dû utiliser l'équivalent de boost::get<0>(i) et boost::get<1>(i). Je ne suis pas sûr pourquoi l'exemple original n'a pas pu être adapté directement, cela pourrait être dû au fait que mon code prend des références constantes aux conteneurs.

6voto

cshelton Points 86

J'ai rencontré cette même question indépendamment et je n'aimais pas la syntaxe de celles mentionnées ci-dessus. J'ai donc un petit fichier d'en-tête qui fait essentiellement la même chose que le zip_iterator de boost mais contient quelques macros pour rendre la syntaxe plus agréable pour moi :

https://github.com/cshelton/zipfor

Par exemple, vous pouvez faire

vector a {1,2,3};
array b {"bonjour","tout le monde","codeurs"};

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 la même chose, mais pour les maps (pour nommer le ".first" et le ".second" de l'élément).

0 votes

C'est génial! Peut-il prendre un nombre arbitraire d'arguments ou sont-ils tous limités par votre système de modélisation intelligent à un nombre fini?

0 votes

Actuellement, il ne gère que jusqu'à 9 conteneurs parallèles. Ce serait simple à faire avancer. Bien que les macros variadiques permettent à une seule macro "zipfor" de gérer différents nombres de paramètres, il faut toujours coder une macro séparée pour chacun (à dispatcher vers). Voir groups.google.com/forum/?fromgroups=#!topic/comp.std.c/… et stackoverflow.com/questions/15847837/…

0 votes

Est-ce qu'il prend bien en charge les arguments de tailles différentes ? (comme décrit dans le message d'origine)

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X