Qual è il modo migliore per scorrere simultaneamente su due o più contenitori

C ++ 11 offre diversi modi per iterare sui contenitori. Per esempio:

Ciclo basato su intervalli

for(auto c : container) fun(c) 

std :: for_each

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

Tuttavia, qual è il modo consigliato di scorrere su due (o più) contenitori della stessa dimensione per ottenere qualcosa come:

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

Per il tuo esempio specifico, basta usare

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

Per il caso più generale, puoi utilizzare il comando zip_iterator di zip_iterator , con una piccola funzione che lo rende utilizzabile in cicli basati su intervalli. Per la maggior parte dei casi, questo funzionerà:

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

Esempio dal vivo

Tuttavia, per generalità in piena regola, probabilmente vorrai qualcosa di più simile a questo , che funzionerà correttamente per array e tipi definiti dall'utente che non hanno membro begin() / end() ma che hanno funzioni di begin / end nel loro spazio dei nomi . Inoltre, questo permetterà all'utente di ottenere specificamente l'accesso const tramite le funzioni zip_c...

E se sei un sostenitore di messaggi di errore, come me, probabilmente lo vuoi, che controlla se qualche contenitore temporaneo è passato a una delle funzioni zip_... e stampa un messaggio di errore se così fosse.

Piuttosto tardi alla festa. Ma: vorrei scorrere gli indici. Ma non con il classico ciclo for ma con un ciclo basato for range sugli indici:

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

indices è una semplice funzione wrapper che restituisce un intervallo (valutato ponderatamente) per gli indici. Poiché l’implementazione, anche se semplice, è un po ‘troppo lunga per postarla qui, puoi trovare un’implementazione su GitHub .

Questo codice è efficiente quanto l’uso di un ciclo classico, manuale.

Se questo modello si verifica spesso nei tuoi dati, considera l’utilizzo di un altro modello che zip due sequenze e produce un intervallo di tuple, corrispondente agli elementi accoppiati:

 for (auto items&& : zip(containerA, containerB)) get<0>(items) = get<1>(items); 

L’implementazione di zip è lasciata come esercizio per il lettore, ma segue facilmente dall’implementazione degli indices .

mi chiedo perché nessuno ha menzionato questo:

 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: se le dimensioni del contenitore non corrispondono, dovrai inserire il codice nelle istruzioni if.

Esistono molti modi per fare cose specifiche con più contenitori, come previsto nell’intestazione algorithm . Ad esempio, nell’esempio che hai fornito, potresti usare std::copy invece di un ciclo for esplicito.

D’altra parte, non esiste alcun modo integrato per iterare genericamente più contenitori diversi da un ciclo for normale. Questo non è sorprendente perché ci sono molti modi per iterare. Pensaci: potresti scorrere attraverso un contenitore con un passo, un contenitore con un altro passo; o attraverso un contenitore fino alla fine, quindi iniziare l’inserimento mentre si passa alla fine dell’altro contenitore; o un passaggio del primo contenitore per ogni volta che passi completamente attraverso l’altro contenitore e ricomincia da capo; o qualche altro modello; o più di due contenitori alla volta; eccetera …

Tuttavia, se si desidera creare la propria funzione di stile “for_each” che itera attraverso due contenitori solo fino alla lunghezza del più breve, è ansible fare qualcosa del genere:

 template  void custom_for_each( Container1 &c1, Container2 &c2, std::function 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); } } 

Ovviamente puoi fare qualsiasi tipo di strategia di iterazioni che desideri in modo simile.

Ovviamente, si potrebbe obiettare che semplicemente eseguire direttamente il ciclo for interno è più facile che scrivere una funzione personalizzata come questa … e avresti ragione, se lo farai solo una o due volte. Ma la cosa bella è che questo è molto riutilizzabile. =)

Nel caso in cui sia necessario iterare simultaneamente solo su 2 contenitori, esiste una versione estesa dell’algoritmo for_each standard nella libreria boost, ad esempio:

 #include  #include  #include  #include  void foo(int a, int& b) { b = a + 1; } int main() { std::vector contA = boost::assign::list_of(4)(3)(5)(2); std::vector contB(contA.size(), 0); boost::for_each(contA, contB, boost::bind(&foo, _1, _2)); // contB will be now 5,4,6,3 //... return 0; } 

Quando devi gestire più di 2 contenitori con un solo algoritmo, devi giocare con zip.

un’altra soluzione potrebbe catturare un riferimento dell’iteratore dell’altro contenitore in una lambda e utilizzare un operatore di incremento post su quello. per esempio la semplice copia sarebbe:

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

all’interno di lambda puoi fare qualsiasi cosa con ita e poi incrementarlo. Questo si estende facilmente al caso di più contenitori.

Una libreria di intervalli fornisce questa e altre funzionalità molto utili. Nell’esempio seguente viene utilizzato Boost.Range . Il rangev3 di Eric Niebler dovrebbe essere una buona alternativa.

 #include  #include  #include  #include  int main(int, const char*[]) { std::vector const v{0,1,2,3,4}; std::list 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; } 

C ++ 17 lo renderà ancora migliore con i binding strutturati:

 int main(int, const char*[]) { std::vector const v{0,1,2,3,4}; std::list const l{'a', 'b', 'c', 'd', 'e'}; for(auto const& [ti, tc]: boost::combine(v, l)) { std::cout < < '(' << ti << ',' << tc << ')' << '\n'; } return 0; } 

Ecco una variante

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

Esempio di utilizzo

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

Sono un po ‘in ritardo anch’io; ma puoi usare questa (funzione variadica in stile C):

 template void foreach(std::function callback, int count...) { va_list args; va_start(args, count); for (int i = 0; i < count; i++) { std::vector v = va_arg(args, std::vector); std::for_each(v.begin(), v.end(), callback); } va_end(args); } foreach([](const int &i) { // do something here }, 6, vecA, vecB, vecC, vecD, vecE, vecF); 

o questo (usando un pacchetto di parametri di funzione):

 template void foreach(Func callback, std::vector &v) { std::for_each(v.begin(), v.end(), callback); } template void foreach(Func callback, std::vector &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); 

o questo (usando una lista di inizializzazione chiusa):

 template void foreach(Func callback, std::initializer_list> 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}); 

oppure puoi unire vettori come qui: qual è il modo migliore per concatenare due vettori? e quindi scorrere il grande vettore.