it-swarm.com.de

Was ist der beste Weg, um zwei oder mehr Container gleichzeitig zu durchlaufen?

C++ 11 bietet mehrere Möglichkeiten zum Durchlaufen von Containern. Zum Beispiel:

Bereichsbasierte Schleife

for(auto c : container) fun(c)

std :: for_each

for_each(container.begin(),container.end(),fun)

Was ist jedoch die empfohlene Methode, um über zwei (oder mehr) Container derselben Größe zu iterieren, um Folgendes zu erreichen:

for(unsigned i = 0; i < containerA.size(); ++i) {
  containerA[i] = containerB[i];
}
83
memecs

Ziemlich spät zur Party. Aber: Ich würde über Indizes iterieren. Nicht jedoch mit der klassischen for-Schleife, sondern mit einer bereichsbasierten for-Schleife über den Indizes:

for(unsigned i : indices(containerA)) {
    containerA[i] = containerB[i];
}

indices ist eine einfache Wrapper-Funktion, die einen (faul bewerteten) Bereich für die Indizes zurückgibt. Da die Implementierung - wenn auch einfach - ein bisschen zu lang ist, um sie hier zu posten, ist Sie finden eine Implementierung in GitHub .

Dieser Code ist so effizient wie eine manuelle, klassische for-Schleife.

Wenn dieses Muster in Ihren Daten häufig vorkommt, sollten Sie ein anderes Muster verwenden, das Zips zwei Folgen ist und einen Bereich von Tupeln erzeugt, der den paarweisen Elementen entspricht:

for (auto& [a, b] : Zip(containerA, containerB)) {
    a = b;
}

Die Implementierung von Zip bleibt dem Leser als Übung vorbehalten, lässt sich aber leicht aus der Implementierung von indices ableiten.

(Vor C++ 17 müsste stattdessen Folgendes geschrieben werden :)

for (auto items&& : Zip(containerA, containerB))
    get<0>(items) = get<1>(items);
41
Konrad Rudolph

Verwenden Sie für Ihr spezielles Beispiel einfach 

std::copy_n(contB.begin(), contA.size(), contA.begin())

Im allgemeineren Fall können Sie Zip_iterator von Boost.Iterator mit einer kleinen Funktion verwenden, um es für bereichsbasierte Schleifen zu verwenden. In den meisten Fällen wird dies funktionieren:

template<class... Conts>
auto Zip_range(Conts&... conts)
  -> decltype(boost::make_iterator_range(
  boost::make_Zip_iterator(boost::make_Tuple(conts.begin()...)),
  boost::make_Zip_iterator(boost::make_Tuple(conts.end()...))))
{
  return {boost::make_Zip_iterator(boost::make_Tuple(conts.begin()...)),
          boost::make_Zip_iterator(boost::make_Tuple(conts.end()...))};
}

// ...
for(auto&& t : Zip_range(contA, contB))
  std::cout << t.get<0>() << " : " << t.get<1>() << "\n";

Live-Beispiel.

Für eine ausgewogene Generizität möchten Sie jedoch eher etwas wie this , das für Arrays und benutzerdefinierte Typen, die kein Member begin()end() haben, aber _/dobegin/end Funktionen in ihrem Namensraum. Dies ermöglicht es dem Benutzer auch, const Zugriff durch die Zip_c...-Funktionen zu erhalten.

Und wenn Sie ein Verfechter von Nice-Fehlernachrichten sind, wie ich, dann möchten Sie wahrscheinlich this , das prüft, ob temporäre Container an eine der Zip_...-Funktionen übergeben wurden, und gibt gegebenenfalls eine Nice-Fehlermeldung aus.

37
Xeo

ich frage mich, warum niemand dies erwähnt hat:

auto ItA = VectorA.begin();
auto ItB = VectorB.begin();

while(ItA != VectorA.end() || ItB != VectorB.end())
{
    if(ItA != VectorA.end())
    {
        ++ItA;
    }
    if(ItB != VectorB.end())
    {
        ++ItB;
    }
}

PS: Wenn die Containergrößen nicht übereinstimmen, müssen Sie den Code in die if-Anweisungen einfügen.

26
Joseph

Es gibt viele Möglichkeiten, bestimmte Dinge mit mehreren Containern zu tun , wie im Header algorithm angegeben. In dem von Ihnen angegebenen Beispiel könnten Sie beispielsweise std::copy anstelle einer expliziten for-Schleife verwenden.

Auf der anderen Seite gibt es keine integrierte Möglichkeit, mehrere Container außer einer normalen for-Schleife generisch zu iterieren. Dies ist nicht überraschend, da es eine Menge Möglichkeiten zum Iterieren gibt. Denken Sie darüber nach: Sie können einen Container mit einem Schritt, einen Container mit einem anderen Schritt durchlaufen. oder durch einen Behälter bis zum Ende und dann mit dem Einfügen beginnen, während Sie bis zum Ende des anderen Behälters durchgehen; oder ein Schritt des ersten Containers für jedes Mal, wenn Sie den anderen Container vollständig durchlaufen und dann von vorne beginnen; oder ein anderes Muster; oder mehr als zwei Behälter gleichzeitig; usw ...

Wenn Sie jedoch Ihre eigene "for_each" -Stilfunktion erstellen möchten, die nur bis zur Länge des kürzesten Containers zwei Container durchläuft, können Sie dies tun etwas wie das:

template <typename Container1, typename Container2>
void custom_for_each(
  Container1 &c1,
  Container2 &c2,
  std::function<void(Container1::iterator &it1, Container2::iterator &it2)> f)
{
  Container1::iterator begin1 = c1.begin();
  Container2::iterator begin2 = c2.begin();
  Container1::iterator end1 = c1.end();
  Container2::iterator end2 = c2.end();
  Container1::iterator i1;
  Container1::iterator i2;
  for (i1 = begin1, i2 = begin2; (i1 != end1) && (i2 != end2); ++it1, ++i2) {
    f(i1, i2);
  }
}

Natürlich können Sie auf ähnliche Weise jede Art von Iterationsstrategie erstellen, die Sie möchten.

Natürlich könnten Sie argumentieren, dass es einfacher ist, die innere for-Schleife direkt auszuführen, als eine benutzerdefinierte Funktion wie diese zu schreiben ... und Sie hätten Recht, wenn Sie dies nur ein- oder zweimal tun würden. Aber das Schöne ist, dass dies sehr wiederverwendbar ist. =)

9
wjl

Falls Sie gleichzeitig nur über 2 Container iterieren müssen, gibt es eine erweiterte Version des Standard-for_each-Algorithmus in der Boost-Range-Bibliothek, z. 

#include <vector>
#include <boost/assign/list_of.hpp>
#include <boost/bind.hpp>
#include <boost/range/algorithm_ext/for_each.hpp>

void foo(int a, int& b)
{
    b = a + 1;
}

int main()
{
    std::vector<int> contA = boost::assign::list_of(4)(3)(5)(2);
    std::vector<int> contB(contA.size(), 0);

    boost::for_each(contA, contB, boost::bind(&foo, _1, _2));
    // contB will be now 5,4,6,3
    //...
    return 0;
}

Wenn Sie mehr als zwei Container in einem Algorithmus behandeln müssen, müssen Sie mit Zip spielen.

7
czarles

Eine Bereichsbibliothek bietet diese und andere sehr hilfreiche Funktionen. Im folgenden Beispiel wird Boost.Range verwendet. Eric Niebler's rangev3 sollte eine gute Alternative sein.

#include <boost/range/combine.hpp>
#include <iostream>
#include <vector>
#include <list>

int main(int, const char*[])
{
    std::vector<int> const v{0,1,2,3,4};
    std::list<char> const  l{'a', 'b', 'c', 'd', 'e'};

    for(auto const& i: boost::combine(v, l))
    {
        int ti;
        char tc;
        boost::tie(ti,tc) = i;
        std::cout << '(' << ti << ',' << tc << ')' << '\n';
    }

    return 0;
}

Mit C++ 17 wird dies mit strukturierten Bindungen noch besser:

int main(int, const char*[])
{
    std::vector<int> const v{0,1,2,3,4};
    std::list<char> const  l{'a', 'b', 'c', 'd', 'e'};

    for(auto const& [ti, tc]: boost::combine(v, l))
    {
        std::cout << '(' << ti << ',' << tc << ')' << '\n';
    }

    return 0;
}
2
Jens

eine andere Lösung könnte darin bestehen, eine Referenz des Iterators des anderen Containers in einem Lambda zu erfassen und dazu einen Nachsteigerungsoperator zu verwenden. Eine einfache Kopie wäre zum Beispiel:

vector<double> a{1, 2, 3};
vector<double> b(3);

auto ita = a.begin();
for_each(b.begin(), b.end(), [&ita](auto &itb) { itb = *ita++; })

innerhalb von Lambda können Sie mit ita machen, was dann gemacht wird. Dies erstreckt sich leicht auf den Behälter mit mehreren Behältern.

2
Vahid

Hier ist eine Variante

template<class ... Iterator>
void increment_dummy(Iterator ... i)
    {}

template<class Function,class ... Iterator>
void for_each_combined(size_t N,Function&& fun,Iterator... iter)
    {
    while(N!=0)
        {
        fun(*iter...);
        increment_dummy(++iter...);
        --N;
        }
    }

Verwendungsbeispiel

void arrays_mix(size_t N,const float* x,const float* y,float* z)
    {
    for_each_combined(N,[](float x,float y,float& z){z=x+y;},x,y,z);    
    }
0
user877329

Ich bin auch etwas spät dran. Sie können jedoch folgende Funktion verwenden (Variablische Funktion im C-Stil):

template<typename T>
void foreach(std::function<void(T)> callback, int count...) {
    va_list args;
    va_start(args, count);

    for (int i = 0; i < count; i++) {
        std::vector<T> v = va_arg(args, std::vector<T>);
        std::for_each(v.begin(), v.end(), callback);
    }

    va_end(args);
}

foreach<int>([](const int &i) {
    // do something here
}, 6, vecA, vecB, vecC, vecD, vecE, vecF);

oder dies (mit einem Funktionsparameter-Pack):

template<typename Func, typename T>
void foreach(Func callback, std::vector<T> &v) {
    std::for_each(v.begin(), v.end(), callback);
}

template<typename Func, typename T, typename... Args>
void foreach(Func callback, std::vector<T> &v, Args... args) {
    std::for_each(v.begin(), v.end(), callback);
    return foreach(callback, args...);
}

foreach([](const int &i){
    // do something here
}, vecA, vecB, vecC, vecD, vecE, vecF);

oder dies (unter Verwendung einer Klammer eingeschlossenen Initialisierungsliste):

template<typename Func, typename T>
void foreach(Func callback, std::initializer_list<std::vector<T>> list) {
    for (auto &vec : list) {
        std::for_each(vec.begin(), vec.end(), callback);
    }
}

foreach([](const int &i){
    // do something here
}, {vecA, vecB, vecC, vecD, vecE, vecF});

oder Sie können Vektoren wie hier verbinden: Wie können zwei Vektoren am besten verkettet werden? und dann über großen Vektor iterieren.

0
Szymon Marczak