¿Cuál es la mejor manera de iterar sobre dos o más contenedores simultáneamente?

C ++ 11 proporciona múltiples formas de iterar sobre contenedores. Por ejemplo:

Bucle de rango

for(auto c : container) fun(c) 

std :: for_each

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

Sin embargo, ¿cuál es la forma recomendada de iterar sobre dos (o más) contenedores del mismo tamaño para lograr algo como:

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

Para su ejemplo específico, solo use

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

Para el caso más general, puede usar el zip_iterator de zip_iterator , con una función pequeña para que pueda usarse en bucles basados ​​en rangos. Para la mayoría de los casos, esto funcionará:

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

Ejemplo en vivo

Sin embargo, para la genérica en toda regla, probablemente desee algo más como esto , que funcionará correctamente para matrices y tipos definidos por el usuario que no tienen miembro begin() / end() pero tienen funciones de begin / end en su espacio de nombres . Además, esto permitirá al usuario obtener acceso directo a través de las funciones zip_c...

Y si usted es partidario de buenos mensajes de error, como yo, entonces probablemente quiera esto , que verifica si se pasaron contenedores temporales a cualquiera de las funciones de zip_... e imprime un bonito mensaje de error si es así.

Bastante tarde a la fiesta. Pero: iteraría sobre los índices. Pero no con el bucle for clásico, sino con un bucle for the-based sobre los índices:

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

indices es una función de envoltura simple que devuelve un rango (evaluado de forma diferida) para los índices. Dado que la implementación, aunque simple, es demasiado larga para publicarla aquí, puede encontrar una implementación en GitHub .

Este código es tan eficiente como usar un bucle for manual y clásico.

Si este patrón ocurre a menudo en sus datos, considere usar otro patrón que zip dos secuencias y produzca un rango de tuplas, correspondiente a los elementos emparejados:

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

La implementación de zip se deja como un ejercicio para el lector, pero se sigue fácilmente desde la implementación de los indices .

Me pregunto por qué nadie mencionó esto:

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

PD: si los tamaños de los contenedores no coinciden, entonces tendrá que poner el código dentro de las declaraciones if.

Hay muchas maneras de hacer cosas específicas con varios contenedores como se proporciona en el encabezado del algorithm . Por ejemplo, en el ejemplo que ha proporcionado, puede usar std::copy lugar de un ciclo for explícito.

Por otro lado, no existe una forma incorporada de iterar genéricamente varios contenedores distintos de un bucle for normal. Esto no es sorprendente porque hay muchas formas de iterar. Piénselo: podría iterar a través de un contenedor con un paso, un contenedor con otro paso; o a través de un contenedor hasta que llegue al final, luego comience a insertar mientras avanza hasta el final del otro contenedor; o un paso del primer contenedor por cada vez que pasa completamente por el otro contenedor y luego comienza de nuevo; o algún otro patrón; o más de dos contenedores a la vez; etc …

Sin embargo, si desea crear su propia función de estilo “for_each” que recorre dos contenedores solo hasta la longitud de la más corta, podría hacer algo como esto:

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

Obviamente, puede hacer cualquier tipo de estrategia de iteraciones que desee de forma similar.

Por supuesto, podría argumentar que simplemente hacer el ciclo interno for directamente es más fácil que escribir una función personalizada como esta … y estaría en lo cierto, si solo va a hacerlo una o dos veces. Pero lo bueno es que esto es muy reutilizable. =)

En caso de que necesite iterar simultáneamente sobre 2 contenedores solamente, hay una versión extendida del algoritmo for_each estándar en la biblioteca de rangos de impulso, por ejemplo:

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

Cuando necesitas manejar más de 2 contenedores en un algoritmo, entonces necesitas jugar con zip.

Otra solución podría ser capturar una referencia del iterador del otro contenedor en una lambda y usar un operador de incremento de posición en eso. por ejemplo, copia simple sería:

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

dentro de lambda puedes hacer lo que sea con ita y luego incrementarlo. Esto se extiende fácilmente a la caja de contenedores múltiples.

Una biblioteca de rango proporciona esta y otras funcionalidades muy útiles. El siguiente ejemplo usa Boost.Range . El rangev3 de Eric Niebler debería ser una buena 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 hará aún mejor con enlaces estructurados:

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

Aquí hay 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; } } 

Ejemplo de uso

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

Estoy un poco tarde también; pero puede usar esto (función variadica al estilo 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 esto (usando un paquete de parámetros de función):

 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 esto (usando una lista de inicializadores incluidos)

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

o puede unir vectores como aquí: ¿Cuál es la mejor forma de concatenar dos vectores? y luego iterar sobre un gran vector.