2012-07-04 6 views
21

Duplicar posibles:
Find position of element in C++11 range-based for loop?Python como en C++

tengo un vector y me gustaría repetir y, al mismo tiempo, tener acceso a la índices para cada elemento individual (necesito pasar tanto el elemento como su índice a una función). He considerado las dos soluciones siguientes:

std::vector<int> v = { 10, 20, 30 }; 

// Solution 1 
for (std::vector<int>::size_type idx = 0; idx < v.size(); ++idx) 
    foo(v[idx], idx); 

// Solution 2 
for (auto it = v.begin(); it != v.end(); ++it) 
    foo(*it, it - v.begin()); 

Me preguntaba si podría haber una solución más compacta. Algo similar a Python's enumerate. Esto es lo más cercano que obtuve usando un bucle de rango C++ 11, pero tener que definir el índice fuera del bucle en un ámbito privado definitivamente parece ser una solución peor que 1 o 2:

{ 
    int idx = 0; 
    for (auto& elem : v) 
     foo(elem, idx++); 
} 

¿Hay alguna forma (tal vez usando Boost) para simplificar el último ejemplo de tal manera que el índice se autocontenga en el ciclo?

+5

¿Por qué simplificar las cosas simples? :-) – Kos

+0

Debería crear una función/objeto similar a un generador que devuelva std :: pair y use el primer y segundo campo del par. Probablemente pueda usar macros para hacer el truco, pero no hay una manera práctica y elegante de usar la sintaxis similar a Python en C++. Su segunda solución es probablemente lo mejor que puede hacer. – Morwenn

+0

@Kos Estoy bastante bien con la solución 2. Solo curiosidad si hay una manera más simple :) – betabandido

Respuesta

10

Como @Kos dice, esto es una cosa tan simple que yo no veo la necesidad de simplificar aún más y, personalmente, sólo se adhieren a la tradicional bucle for con índices, excepto que me zanja std::vector<T>::size_type y simplemente usar std::size_t:

for(std::size_t i = 0; i < v.size(); ++i) 
    foo(v[i], i); 

no estoy demasiado interesado en la solución 2. se requiere (un poco escondidos) iteradores de acceso aleatorio que no le permitiría cambiar fácilmente el contenedor, que es uno de los fuertes puntos de iteradores. Si desea utilizar iteradores y que sea genérica (y posiblemente incurrir en una pérdida de rendimiento cuando los iteradores son no de acceso aleatorio), me gustaría recomendar el uso std::distance:

for(auto it(v.begin()); it != v.end(); ++it) 
    foo(*it, std::distance(it, v.begin()); 
+3

Dado que cualquier intento de acercarse a la enumeración de Python parece terminar en una gran cantidad de código, creo que es mejor usar cualquiera de estas dos soluciones. – betabandido

1

Una forma es envolver el lazo en una función propia.

#include <iostream> 
#include <vector> 
#include <string> 

template<typename T, typename F> 
void mapWithIndex(std::vector<T> vec, F fun) { 
    for(int i = 0; i < vec.size(); i++) 
     fun(vec[i], i); 
} 

int main() { 
    std::vector<std::string> vec = {"hello", "cup", "of", "tea"}; 
    mapWithIndex(vec, [](std::string s, int i){ 
     std::cout << i << " " << s << '\n'; 
    }); 
} 
+1

IMO esto solo complica las cosas aún más ... – SingerOfTheFall

+2

Haces un buen punto. Normalmente, un ciclo simple es el mejor. Aparentemente, el OP no quiere uno. –

+1

En realidad, quería simplificar aún más el código (si es posible, por supuesto). 'para idx, elem en enumerate (v): foo (idx, elem)' me parece más simple que cualquier otra solución publicada en mi pregunta o en las respuestas. Pero, por supuesto, esa es una solución de Python, y estaba pidiendo una de C++. – betabandido

13

Aquí es algún tipo de solución divertida usando evaluación perezosa En primer lugar, construir el objeto generador de enumerate_object:

template<typename Iterable> 
class enumerate_object 
{ 
    private: 
     Iterable _iter; 
     std::size_t _size; 
     decltype(std::begin(_iter)) _begin; 
     const decltype(std::end(_iter)) _end; 

    public: 
     enumerate_object(Iterable iter): 
      _iter(iter), 
      _size(0), 
      _begin(std::begin(iter)), 
      _end(std::end(iter)) 
     {} 

     const enumerate_object& begin() const { return *this; } 
     const enumerate_object& end() const { return *this; } 

     bool operator!=(const enumerate_object&) const 
     { 
      return _begin != _end; 
     } 

     void operator++() 
     { 
      ++_begin; 
      ++_size; 
     } 

     auto operator*() const 
      -> std::pair<std::size_t, decltype(*_begin)> 
     { 
      return { _size, *_begin }; 
     } 
}; 

A continuación, crear una enumeración función de contenedor que deducir los argumentos de plantilla y volver al generador:

template<typename Iterable> 
auto enumerate(Iterable&& iter) 
    -> enumerate_object<Iterable> 
{ 
    return { std::forward<Iterable>(iter) }; 
} 

Ahora puede utilizar la función de esa manera:

int main() 
{ 
    std::vector<double> vec = { 1., 2., 3., 4., 5. }; 
    for (auto&& a: enumerate(vec)) { 
     size_t index = std::get<0>(a); 
     double& value = std::get<1>(a); 

     value += index; 
    } 
} 

La aplicación anterior es un mero juguete: deberá trabajar tanto con const y no const lvalue referencia s, así como referencias de valores-r, pero tiene un costo real para este último, considerando que copia todo el objeto repetible varias veces. Este problema seguramente podría resolverse con ajustes adicionales.

Como C++ 17, declaraciones de descomposición, incluso le permiten tener la fresca sintaxis de Python como para nombrar el índice y el valor directamente en el inicializador for:

int main() 
{ 
    std::vector<double> vec = { 1., 2., 3., 4., 5. }; 
    for (auto&& [index, value] a: enumerate(vec)) { 
     value += index; 
    } 
} 

que no tienen un C++ compilador compatible 17 a mano para comprobarlo, pero espero que el auto&& en la descomposición es capaz de inferir index como std::size_t y value como double&.

+0

Tu código explotará horriblemente si pasas un término temporal a 'enumerar'. – Xeo

+0

¿Qué compilador estás usando? No compila con g ++ 4.6 o 4.7. – betabandido

+0

@Xeo ¿No es gracioso? Probablemente tengas razón y realmente no veo cómo hacer una versión más segura. De todos modos, usar esa función ni siquiera es tan útil como la antigua solución simple. – Morwenn

Cuestiones relacionadas