2012-07-04 15 views
21

Powiel możliwe:
Find position of element in C++11 range-based for loop?Pythona jak wyliczenie pętli w C++

mam vector i chciałbym iteracyjne go, a jednocześnie mają dostęp do indeksy dla każdego elementu (muszę przekazać zarówno element, jak i jego indeks do funkcji). Rozważałem następujące dwa rozwiązania:

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

Zastanawiam się, czy może być bardziej kompaktowe rozwiązanie. Coś podobnego do Pythona enumerate. To jest najbliższe, co mam za pomocą pętli zasięgu C++ 11, ale określenie indeksu poza pętlą w prywatnym zakresie zdecydowanie wydaje się gorszym rozwiązaniem niż 1 lub 2:

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

Czy jest jakiś sposób (być może za pomocą funkcji Boost), aby uprościć najnowszy przykład w taki sposób, aby indeks znalazł się w pętli?

+5

Po co upraszczać proste rzeczy? :-) – Kos

+0

Musiałbyś stworzyć funkcję/obiekt podobny do generatora, który zwraca std :: pair i użyje pierwszego i drugiego pola pary. Prawdopodobnie możesz użyć makr, aby zrobić lewę, ale nie ma przydatnego i eleganckiego sposobu użycia składni podobnej do Pythona w C++. Twoje drugie rozwiązanie to prawdopodobnie najlepsza rzecz do zrobienia. – Morwenn

+0

@Kos Jestem prawie w porządku z rozwiązaniem 2. Po prostu ciekawy, czy jest jeszcze prostszy sposób :) – betabandido

Odpowiedz

10

Jako @Kos mówi, jest to taka prosta sprawa, że ​​tak naprawdę nie widzi potrzeby dalszego uproszczenia go i osobiście po prostu trzymać się tradycyjnych dla pętli z indeksów, z wyjątkiem, że będę rowu std::vector<T>::size_type i po prostu użyć std::size_t:

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

nie jestem zbyt chętni do roztworu 2. wymaga (kinda ukryte) random access iteratory które nie pozwalają łatwo zamienić pojemnik, który jest jednym z silnych punkty iteratorów. Jeśli chcesz użyć iteratorów i uczynić go generic (i ewentualnie ponieść spadek wydajności, gdy iteratory są nie dostępie swobodnym), polecam korzystania std::distance:

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

Biorąc pod uwagę, że jakakolwiek próba znalezienia się w pobliżu wyliczenia Pythona wydaje się skutkować ogromnym nadejściem kodu, myślę, że lepiej jest użyć dowolnego z tych dwóch rozwiązań. – betabandido

1

Jednym ze sposobów jest zawarcie pętli w funkcji własnej.

#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 to tylko komplikuje rzeczy dalej ... – SingerOfTheFall

+2

Robisz dobry punkt. Zwykle najlepsza jest zwykła pętla. Najwyraźniej OP nie chce jednak. –

+1

Naprawdę chciałem jeszcze bardziej uprościć kod (jeśli to możliwe, oczywiście). 'dla idx, elem in enumerate (v): foo (idx, elem)' wydaje mi się prostsze niż jakiekolwiek inne rozwiązanie zamieszczone w moim pytaniu lub w odpowiedziach. Ale, oczywiście, jest to rozwiązanie Python i prosiłem o C++. – betabandido

13

Oto niektóre zabawne rozwiązanie używając leniwą ocenę. Po pierwsze, zbudować obiekt generatora 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 }; 
     } 
}; 

Następnie utwórz enumerate funkcji wrapper który będzie wydedukować argumenty szablon i powrotu generatora:

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

Teraz można korzystać z funkcji w ten sposób:

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

Powyższa implementacja jest zwykłą zabawką: powinna działać zarówno z wartością l. const, jak i non-const s jak i rvalue-references, ale ma rzeczywisty koszt dla tego drugiego, biorąc pod uwagę, że kopiuje cały iterowalny obiekt kilka razy. Ten problem z pewnością można rozwiązać za pomocą dodatkowych poprawek.

ponieważ C++ 17, deklaracje rozkładu nawet pozwalają mieć chłodną python-jak składni do nazywania indeks i wartość bezpośrednio w for inicjatora:

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

nie mam do C++ Kompilator zgodny z 17 pod ręką, aby to sprawdzić, ale mam nadzieję, że auto&& w rozkładzie jest w stanie wywnioskować index jako std::size_t i value jako double&.

+0

Twój kod zostanie wysadzony w powietrze strasznie, jeśli przekażesz tymczasowy kod do 'wyliczenia'. – Xeo

+0

Którego kompilatora używasz? Nie kompiluje się za pomocą g ++ 4.6 lub 4.7. – betabandido

+0

@Xeo Czy to nie jest śmieszne? Prawdopodobnie masz rację i naprawdę nie wiem, jak zrobić bezpieczniejszą wersję. W każdym razie korzystanie z tej funkcji nie jest tak wygodne jak zwykłe stare rozwiązanie. – Morwenn