Sequence-Zip-Funktion für c ++ 11?


97

Mit der neuen bereichsbasierten for-Schleife können wir Code wie schreiben

for(auto x: Y) {}

Welche IMO ist eine enorme Verbesserung von (zum Beispiel)

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

Kann es verwendet werden, um zwei gleichzeitige Schleifen wie die Pythons- zipFunktion zu durchlaufen ? Für diejenigen, die mit Python nicht vertraut sind, gilt der Code:

Y1 = [1,2,3]
Y2 = [4,5,6,7]
for x1,x2 in zip(Y1,Y2):
    print x1,x2

Gibt als Ausgabe (1,4) (2,5) (3,6)


Bereichsbasiert forkann nur mit einer Variablen verwendet werden, also nein. Wenn Sie auf zwei Werte gleichzeitig zugreifen möchten, müssen Sie Folgendes verwendenstd::pair
Seth Carnegie

4
@SethCarnegie: nicht direkt, aber Sie könnten eine zip()Funktion entwickeln, die Tupel zurückgibt und die Liste der Tupel durchläuft.
André Caron

2
@ AndréCaron Sie haben Recht, mein "Nein" sollte sagen, dass Sie nicht zwei Variablen verwenden können, nicht, dass Sie nicht über zwei Container gleichzeitig iterieren können.
Seth Carnegie

Es ist klar, for(;;)dass dieses Verhalten, wenn auch auf lange Sicht, auftreten kann. Ist die Frage also wirklich: Ist es möglich, zwei Objekte gleichzeitig "automatisch" zu bearbeiten?

In einer zukünftigen Revision (hoffentlich C ++ 17) wird eine Überarbeitung der STL Bereiche enthalten . Dann kann view :: zip die bevorzugte Lösung darstellen.
John McFarlane

Antworten:


88

Warnung: boost::zip_iterator und boost::combineab Boost 1.63.0 (26. Dezember 2016) wird ein undefiniertes Verhalten verursacht, wenn die Länge der Eingabecontainer nicht gleich ist (es kann über das Ende hinaus abstürzen oder iterieren).


Ab Boost 1.56.0 (2014 Aug 7) können Sie Folgendes verwendenboost::combine (die Funktion ist in früheren Versionen vorhanden, jedoch nicht dokumentiert):

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

Dies würde drucken

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

In früheren Versionen konnten Sie einen Bereich wie folgt definieren:

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

Die Verwendung ist die gleiche.


1
Könnten Sie dies zum Sortieren verwenden? dh std :: sort (zip (a.begin (), ...), zip (a.end (), ...), [] (tup a, tup b) {a.get <0> () > b.get <0> ()}); ?
Gnzlbg


Ich würde von optionalElementen für Iterationsmöglichkeiten hinter dem Ende in Versuchung geführt werden ...
Yakk - Adam Nevraumont

3
Gibt es eine Chance, dass Sie dies mit std :: make_tuple und std :: tie tun können? Ich habe versucht, dies zu verwenden und gleichzeitig die Boost-Abhängigkeit zu minimieren, aber ich konnte es nicht zum Laufen bringen.
Carneiro

@kennytm Irgendeine Idee, warum sie sich für UB entschieden haben, anstatt nur am Ende des kürzesten Bereichs im Haufen zu enden?
Catskul

18

Also habe ich diesen Zip geschrieben, als ich gelangweilt war. Ich habe beschlossen, ihn zu posten, weil er sich von den anderen darin unterscheidet, dass er keinen Boost verwendet und eher wie die c ++ stdlib aussieht.

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

Anwendungsbeispiel:

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

4
Sie sollten überprüfen, ob sich einer der Iteratoren am Ende befindet.
Xeo

1
@Xeo alle Bereiche sollten die gleiche Größe wie der erste oder größer sein
Aaronon

Können Sie erklären, wie es [](int i,int j,float k,float l)funktioniert? Ist das eine Lambda-Funktion?
Hooked

@Hooked Ja, es ist ein Lambda, es funktioniert im Grunde nur, std::for_eachaber Sie können eine beliebige Anzahl von Bereichen verwenden. Die Parameter im Lambda hängen davon ab, wie viele Iteratoren Sie der Funktion geben
Aaronon

1
Ein häufiger Bedarf besteht darin, Bereiche unterschiedlicher Größe oder sogar mit unendlichen Bereichen zu komprimieren.
Xeo

16

Sie können eine Lösung verwenden, die auf basiert boost::zip_iterator. Erstellen Sie eine falsche Containerklasse, die Verweise auf Ihre Container verwaltet und zip_iteratorvon den Funktionen beginund endmember zurückkehrt. Jetzt kannst du schreiben

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

Beispielimplementierung (bitte testen):

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

Ich überlasse die Variadic-Version dem Leser als hervorragende Übung.


3
+1: Boost.Range sollte dies wahrscheinlich beinhalten. In der Tat werde ich ihnen eine Feature-Anfrage zu diesem Thema.
Nicol Bolas

2
@NicolBolas: Du machst es gut. Dies sollte mit boost::iterator_range+ recht einfach zu implementieren sein boost::zip_iterator, auch in der variadischen Version.
Alexandre C.

1
Ich glaube, dies wird niemals enden (und ein undefiniertes Verhalten haben), wenn die Bereiche nicht gleich lang sind.
Jonathan Wakely

1
boost::zip_iteratorfunktioniert nicht mit Bereichen unterschiedlicher Länge
Jonathan Wakely

1
Dies sollte auch in sauberem c ++ 03 mit Paar anstelle von Tupel funktionieren. Dies führt jedoch auch zu Problemen, wenn die Längen nicht gleich sind. Mit dem Ende () kann etwas getan werden, indem das entsprechende Ende () des kleinsten Containers genommen wird. Dies scheint in der Spezifikation zu sein, wie es in der OP-Frage war.
Paul

16

std :: transform kann dies trivial tun:

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;

Wenn die zweite Sequenz kürzer ist, scheint meine Implementierung standardmäßig initialisierte Werte zu liefern.


1
Wenn die 2. Sequenz kürzer ist, würde ich erwarten, dass dies UB ist, da Sie am Ende von iterieren würden b.
Adrian

15

Suchen Sie <redi/zip.h>nach einer zipFunktion, die mit der Bereichsbasis arbeitet forund eine beliebige Anzahl von Bereichen akzeptiert, die r- oder l-Werte sein können und unterschiedliche Längen haben können (die Iteration stoppt am Ende des kürzesten Bereichs).

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>() << ' ';

Druckt 0 1 2 3 4 5


2
Sie können auch boost/tuple/tuple_io.hpptocout << i;
kirill_igum

Das hat bei mir funktioniert. In meinem Code musste ich jedoch das Äquivalent von boost::get<0>(i)und verwenden boost::get<1>(i). Ich bin mir nicht sicher, warum das Originalbeispiel nicht direkt angepasst werden konnte. Dies hat möglicherweise damit zu tun, dass mein Code ständig auf Container verweist.
YitzikC

11

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

Die Ausgabe:

[(4, 7), (5, 8), (6, 9)]


@ einpoklum-reinstateMonica jetzt ist es!
Yuyoyuppe

6

Ich bin unabhängig auf dieselbe Frage gestoßen und mochte die Syntax der oben genannten Fragen nicht. Ich habe also eine kurze Header-Datei, die im Wesentlichen dasselbe tut wie der boost zip_iterator, aber einige Makros enthält, um die Syntax für mich angenehmer zu machen:

https://github.com/cshelton/zipfor

Zum Beispiel können Sie tun

vector<int> a {1,2,3};
array<string,3> b {"hello","there","coders"};

zipfor(i,s eachin a,b)
    cout << i << " => " << s << endl;

Der wichtigste syntaktische Zucker ist, dass ich die Elemente aus jedem Container benennen kann. Ich füge auch eine "mapfor" hinzu, die dasselbe tut, jedoch für Maps (um die ".first" und ".second" des Elements zu benennen).


Das ist ordentlich! Kann es eine beliebige Anzahl von Argumenten geben, die alle durch Ihre geschickte Vorlage auf eine endliche Anzahl beschränkt sind?
Hooked

Derzeit werden nur bis zu 9 parallele Container verarbeitet. Das wäre einfach voranzutreiben. Während verschiedene Makros es einem einzelnen "zipfor" -Makro ermöglichen, unterschiedliche Anzahlen von Parametern zu verarbeiten, muss immer noch ein separates Makro für jedes (zum Versenden an) codiert werden. Siehe groups.google.com/forum/?fromgroups=#!topic/comp.std.c/… und stackoverflow.com/questions/15847837/…
cshelton

Behandelt es Argumente unterschiedlicher Größe gut? (wie im OP beschrieben)
coyotte508

@ coyotte508 wird davon ausgegangen, dass der erste Container die geringste Anzahl von Elementen enthält (und die zusätzlichen Elemente in anderen Containern ignoriert). Es wäre leicht zu ändern, um diese Annahme nicht zu treffen, aber das würde sie verlangsamen (derzeit ist sie nicht langsamer als handgeschrieben), wenn die Anzahl der Elemente übereinstimmt.
Cshelton

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

6

Wenn Sie eine Überladung des Bedieners mögen, haben Sie drei Möglichkeiten. Die ersten beiden werden verwendet std::pair<>und std::tuple<>sind, wie Iteratoren; Der dritte erweitert dies auf bereichsbasiert for. Beachten Sie, dass diese Definitionen der Operatoren nicht jedem gefallen werden. Bewahren Sie sie daher am besten in einem separaten Namespace auf und verwenden Sie eine using namespacein den Funktionen (keine Dateien!), In denen Sie diese verwenden möchten.

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

3

Für eine C ++ - Stream-Verarbeitungsbibliothek, die ich schreibe, suchte ich nach einer Lösung, die nicht auf Bibliotheken von Drittanbietern basiert und mit einer beliebigen Anzahl von Containern arbeitet. Am Ende hatte ich diese Lösung. Es ähnelt der akzeptierten Lösung, die Boost verwendet (und führt auch zu undefiniertem Verhalten, wenn die Containerlängen nicht gleich sind).

#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
Link defekt ... wäre nützlich, wenn der Beitrag zeigt, wie man es benutzt, zB main ()?
JavaLover

@javaLover: Sie können es genauso verwenden wie cppitertools in der Antwort von @ knedlsepp. Ein bemerkenswerter Unterschied besteht darin, dass Sie mit der obigen Lösung die zugrunde liegenden Container nicht ändern können, da das operator*for seq::iteratoreine std::tuplevon const-Referenzen zurückgibt .
Winnetou

2

Wenn Sie einen C ++ 14-kompatiblen Compiler (z. B. gcc5) haben, können Sie ihn zipin der cppitertoolsBibliothek von Ryan Haining verwenden, was sehr vielversprechend aussieht:

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
}

0

Boost.Iterators zip_iteratorkönnen Sie verwenden (Beispiele in den Dokumenten). Es funktioniert nicht mit Reichweite für, aber Sie können std::for_eachund ein Lambda verwenden.


Warum funktioniert es nicht mit Range-Based für? Kombiniere es mit Boost.Range und du solltest eingestellt sein.
Xeo

@Xeo: Ich kenne Range nicht so gut. Ich denke, Sie könnten ein Boilerplate einbeziehen und es zum Laufen bringen, aber IMO nur zu verwenden for_eachwäre weniger mühsam.
Cat Plus Plus

Du meinst so etwas ist kein Ärger : std::for_each(make_zip_iterator(make_tuple(Y1.begin(), Y2.begin())), make_zip_iterator(make_tuple(Y1.end(), Y2.end())), [](const tuple<int, int>& t){printf("%d %d\n", get<0>(t), get<1>(t)); });?
Onkel Bens

2
Ich sollte eine Kampagne starten, bei der Lambda nicht std :: for_each nützlich macht. :)
OnkelBens

2
@Xeo: Dies sollte wahrscheinlich eine separate Frage sein, aber warum oh warum?
Onkel Bens

-2

Hier ist eine einfache Version, die keinen Boost erfordert. Es ist nicht besonders effizient, da es temporäre Werte erstellt und nicht über andere Container als Listen verallgemeinert, aber keine Abhängigkeiten aufweist und den häufigsten Fall für das Zippen löst.

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

Obwohl die anderen Versionen flexibler sind, besteht der Sinn der Verwendung eines Listenoperators häufig darin, einen einfachen Einzeiler zu erstellen. Diese Version hat den Vorteil, dass der allgemeine Fall einfach ist.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.