vez una recorrer listas lista c++ c++11 iterator containers

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



recorrer dos listas a la vez python (9)

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


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

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


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 .


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

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


Estoy un poco tarde también; pero puede usar esto (función variadica al estilo C):

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

o esto (usando un paquete de parámetros de función):

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

o esto (usando una lista de inicializadores incluidos)

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

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


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

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. =)


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.


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<double> a{1, 2, 3}; vector<double> 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.


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

Ejemplo en vivo

Sin embargo, para la genérica en toda regla, probablemente desee algo más como this , 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 this , que verifica si se pasaron contenedores temporales a cualquiera de las funciones de zip_... e imprime un bonito mensaje de error si es así.


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

C ++ 17 lo hará aún mejor con enlaces estructurados:

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