Pytanie Wyznaczanie pętli w stylu Pythona w C ++ [duplikat]


Możliwe duplikaty:
Znajdź pozycję elementu w pętli opartej na zasięgu C ++ 11? 

mam vector i chciałbym go iterować, a jednocześnie mieć dostęp do indeksów dla każdego elementu (muszę przekazać zarówno element, jak i jego indeks do funkcji). Rozważył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());

Zastanawiałem się, czy może być bardziej kompaktowe rozwiązanie. Coś podobnego do Pythona wyliczać. To jest najbliższy, że mam za pomocą pętli zasięgu C ++ 11, ale konieczności zdefiniowania indeksu poza pętli w prywatnym zasięgu zdecydowanie wydaje się gorsze rozwiązanie niż 1 lub 2:

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

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


21
2017-07-04 11:31


pochodzenie


Po co upraszczać proste rzeczy? :-) - Kos
Będziesz musiał utworzyć funkcję / obiekt podobny do generatora, który zwróci 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
@Kos Jestem prawie w porządku z rozwiązaniem 2. Po prostu ciekawy, czy jest jeszcze prostszy sposób :) - betabandido


Odpowiedzi:


Jak mówi @Kos, jest to tak prosta rzecz, że nie widzę potrzeby dalszego jej upraszczania i osobiście po prostu trzymałem się tradycyjnej pętli z indeksami, z wyjątkiem tego, że wywalę std::vector<T>::size_type i po prostu użyj std::size_t:

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

Nie jestem zbyt chętny do rozwiązania 2. Wymaga to (nieco ukrytych) losowych iteratorów dostępu, które nie pozwalają na łatwą wymianę kontenera, który jest jednym z mocnych punktów iteratorów. Jeśli chcesz używać iteratorów i uczynić je ogólnymi (i prawdopodobnie spowodować trafienie wydajności podczas iteratorów nie losowy dostęp), polecam używanie std::distance:

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

11
2017-07-04 11:51



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


Oto zabawne rozwiązanie z leniwą oceną. Najpierw skonstruuj 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 wyliczenie funkcji opakowania, które wyprowadzi argumenty szablonu i zwróci generator:

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

Możesz teraz korzystać z funkcji w następujący 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ć z obydwoma const i nieconst l-referencje, jak również referencje rvalue, ale ma rzeczywisty koszt dla tych ostatnich, 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.

Od C ++ 17 deklaracje dekompozycji pozwalają nawet na zachowanie fajnej składni podobnej do Pythona, aby nazwać indeks i wartość bezpośrednio w for inicjator:

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

Nie mam pod ręką kompilatora zgodnego z C ++ 17, żeby to sprawdzić, ale mam nadzieję, że auto&& w rozkładzie jest w stanie wnioskować index tak jak std::size_t i value tak jak double&.


15
2017-07-04 12:36



Twój kod zostanie strasznie wysadzony, jeśli przejdziesz tymczasowy enumerate. - Xeo
Który kompilator używasz? Nie kompiluje się za pomocą g ++ 4.6 lub 4.7. - betabandido
@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
@betabandido Używam MinGW g ++ 4.7.0. - Morwenn
Musisz stworzyć typ _iter zmienia się w zależności od tego, czy argument enumerate jest rwartością. To jest łatwiejsze niż się wydaje, zmieniłem rozwiązanie i naprawiłem twoje begin i end Funkcje. Działa to, ponieważ Iterable w enumerate zostanie wydedukowane jako T& jeśli zdasz wartość l (czyniąc _iter członek prosty odnośnik), oraz T jeśli podasz wartość r (przeniesienie argumentu do _iter w ctor). - Xeo


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
2017-07-04 11:46



IMO to tylko komplikuje sprawy dalej ... - SingerOfTheFall
Robisz dobry punkt. Zwykle najlepsza jest zwykła pętla. Najwyraźniej OP nie chce jednak. - Karolis Juodelė
Chciałem jeszcze bardziej uprościć kod (jeśli to możliwe, oczywiście). for 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