W przypadku tablicy o wielu wymiarach zwykle musimy napisać for
pętlę dla każdego z jej wymiarów. Na przykład:
vector< vector< vector<int> > > A;
for (int k=0; k<A.size(); k++)
{
for (int i=0; i<A[k].size(); i++)
{
for (int j=0; j<A[k][i].size(); j++)
{
do_something_on_A(A[k][i][j]);
}
}
}
double B[10][8][5];
for (int k=0; k<10; k++)
{
for (int i=0; i<8; i++)
{
for (int j=0; j<5; j++)
{
do_something_on_B(B[k][i][j]);
}
}
}
for-for-for
Często widzisz tego rodzaju pętle w naszym kodzie. Jak używać makr do definiowania for-for-for
pętli, aby nie musieć za każdym razem ponownie pisać tego rodzaju kodu? Czy jest lepszy sposób na zrobienie tego?
O(n) = n^3
kodu ...Odpowiedzi:
Po pierwsze, nie używasz takiej struktury danych. Jeśli potrzebujesz trójwymiarowej macierzy, możesz ją zdefiniować:
Lub jeśli chcesz indeksować używając
[][][]
, potrzebujesz,operator[]
który zwraca proxy.Gdy już to zrobisz, jeśli okaże się, że ciągle musisz iterować tak, jak przedstawiłeś, udostępniasz iterator, który będzie to obsługiwał:
Następnie po prostu piszesz:
(Lub tylko:
jeśli masz C ++ 11.)
A jeśli potrzebujesz trzech indeksów podczas takich iteracji, możesz stworzyć iterator, który je ujawni:
źródło
vector<vector<vector<double> > >
do reprezentowania trójwymiarowego pola. Przepisanie kodu na odpowiednik powyższego rozwiązania spowodowało przyspieszenie o 10.Matrix3D
prawdopodobnie powinien to być szablon, ale jest to bardzo prosty szablon). I musisz tylko debugowaćMatrix3D
, nie za każdym razem, gdy potrzebujesz macierzy 3D, więc oszczędzasz ogromną ilość czasu na debugowaniu. Jeśli chodzi o jasność: jak jeststd::vector<std::vector<std::vector<int>>>
jaśniejsza niżMatrix3D
? Nie wspominając o tym, żeMatrix3D
wymusza to fakt, że masz macierz, podczas gdy zagnieżdżone wektory mogą być postrzępione, a powyższe jest prawdopodobnie znacznie szybsze.Używanie makra do ukrycia
for
pętli może być bardzo mylące, wystarczy zaoszczędzić kilka znaków. Użyłbym piecyk do pętli zamiast:Oczywiście można zastąpić
auto&
zeconst auto&
jeśli jesteś w rzeczywistości, nie modyfikując dane.źródło
int
zmiennych.do_something_on_A(*j)
?auto
fork
ii
może być uzasadnione. Tyle że nadal rozwiązuje problem na niewłaściwym poziomie; prawdziwy problem polega na tym, że używa on wektorów zagnieżdżonych.)k
to cały wektor wektorów (cóż, odniesienie do niego), a nie indeks.Coś takiego może pomóc:
Aby było N-ary, potrzebujemy trochę magii szablonów. Przede wszystkim powinniśmy stworzyć strukturę SFINAE, aby rozróżnić, czy ta wartość czy kontener. Domyślna implementacja wartości i specjalizacji dla tablic i każdego z typów kontenerów. Jak zauważa @Zeta, możemy określić standardowe kontenery po zagnieżdżonym
iterator
typie (najlepiej powinniśmy sprawdzić, czy typ może być używany z podstawą zakresu,for
czy nie).Wdrożenie
for_each
jest proste. Domyślna funkcja wywołafunction
:A specjalizacja będzie się nazywać rekurencyjnie:
I voila:
Również to nie zadziała dla wskaźników (tablice przydzielone w stercie).
źródło
Container
i dla innych.is_container : has_iterator<T>::value
mojej odpowiedzi i nie musisz pisać specjalizacji dla każdego typu, ponieważ każdy kontener powinien miećiterator
typedef. Zapraszam do całkowitego wykorzystania czegokolwiek z mojej odpowiedzi, twoja jest już lepsza.Container
Pomoże też, jak wspomniałem, koncepcja.::iterator
nie tworzy iterowalnego zakresu.int x[2][3][4]
jest doskonale iterowalny, ponieważstruct foo { int x[3]; int* begin() { return x; } int* end() { return x+3; } };
nie jestem pewien, jakąT[]
specjalizację ma robić?Większość odpowiedzi po prostu pokazuje, jak C ++ można przekształcić w niezrozumiałe rozszerzenia składniowe, IMHO.
Definiując jakiekolwiek szablony lub makra, po prostu zmusisz innych programistów do zrozumienia fragmentów zaciemnionego kodu zaprojektowanego w celu ukrycia innych fragmentów zaciemnionego kodu.
Zmusisz każdego gościa, który czyta Twój kod, do posiadania wiedzy na temat szablonów, aby uniknąć wykonywania swojej pracy polegającej na definiowaniu obiektów z jasną semantyką.
Jeśli zdecydowałeś się użyć surowych danych, takich jak trójwymiarowe tablice, po prostu żyj z nimi lub zdefiniuj klasę, która nadaje zrozumiałe znaczenie Twoim danym.
jest po prostu zgodny z tajemniczą definicją wektora wektora wektora int bez wyraźnej semantyki.
źródło
AKTUALIZACJA: Wiem, że o to prosiłeś, ale lepiej tego nie używaj :)
źródło
TRIPLE_FOR
zostały zdefiniowane w jakimś nagłówku, co mam myśleć, gdy widzę tutaj `TRIPLE_FOR.Jednym z pomysłów jest napisanie iterowalnej klasy pseudokontenerowej, która „zawiera” zestaw wszystkich krotek z wieloma indeksami, które będą indeksowane. Brak implementacji, ponieważ zajmie to zbyt dużo czasu, ale pomysł jest taki, że powinieneś być w stanie napisać ...
źródło
Widzę tutaj wiele odpowiedzi, które działają rekurencyjnie, wykrywając, czy dane wejściowe są kontenerem, czy nie. Zamiast tego, dlaczego nie wykryć, czy bieżąca warstwa jest tego samego typu, co funkcja? Jest znacznie prostszy i pozwala na bardziej zaawansowane funkcje:
Jednak to (oczywiście) daje nam niejednoznaczne błędy. Więc używamy SFINAE do wykrywania, czy bieżące wejście pasuje do funkcji, czy nie
To teraz poprawnie obsługuje kontenery, ale kompilator nadal uważa to za niejednoznaczne dla input_types, które można przekazać do funkcji. Więc używamy standardowej sztuczki C ++ 03, aby preferować pierwszą funkcję od drugiej, również przekazując zero i sprawiając, że ta, którą wolimy, accept i int, a druga zajmie ...
Otóż to. Sześć stosunkowo prostych wierszy kodu i możesz iterować po wartościach, wierszach lub dowolnej innej podjednostce, w przeciwieństwie do wszystkich innych odpowiedzi.
Dowód kompilacji i wykonania tu i tutaj
Jeśli chcesz wygodniejszej składni w C ++ 11, możesz dodać makro. (Obserwowanie nie zostało przetestowane)
źródło
Zastrzegam tę odpowiedź następującym stwierdzeniem: zadziałałoby to tylko wtedy, gdybyś działał na rzeczywistej tablicy - nie zadziała w twoim przykładzie
std::vector
.Jeśli wykonujesz tę samą operację na każdym elemencie wielowymiarowej tablicy, nie dbając o położenie każdego elementu, możesz skorzystać z faktu, że tablice są umieszczane w ciągłych lokalizacjach pamięci i traktować całość jako jedną duża jednowymiarowa tablica. Na przykład, jeśli w drugim przykładzie chcielibyśmy pomnożyć każdy element przez 2,0:
Zwróć uwagę, że powyższe podejście pozwala również na użycie pewnych „właściwych” technik C ++:
I zazwyczaj nie radzę tego podejścia (preferując coś podobnego odpowiedź Jefffrey'S), gdyż opiera się na zdefiniowaniu rozmiary dla tablic, ale w niektórych przypadkach może to być przydatne.
źródło
B[0][0][i]
doi >= 3
; nie jest to dozwolone, ponieważ uzyskuje dostęp poza (wewnętrzną) tablicą.Byłem trochę zszokowany, że nikt nie zaproponował jakiejś pętli opartej na magii arytmetycznej do wykonania tej pracy.
Ponieważ C. Wang szuka rozwiązania bez zagnieżdżonych pętli, zaproponuję jedno:Cóż, to podejście nie jest eleganckie i elastyczne, więc moglibyśmy spakować cały proces do funkcji szablonu:
Tę funkcję szablonu można również wyrazić w postaci zagnieżdżonych pętli:
I można go użyć, dostarczając tablicę 3D o dowolnym rozmiarze oraz nazwę funkcji, pozwalając dedukcji parametrów wykonać ciężką pracę polegającą na zliczaniu rozmiaru każdego wymiaru:
W kierunku bardziej ogólnych
Ale po raz kolejny brakuje mu elastyczności, ponieważ działa tylko dla tablic 3D, ale używając SFINAE możemy wykonać pracę dla tablic o dowolnym wymiarze, najpierw potrzebujemy funkcji szablonu, która iteruje tablice rangi 1:
I jeszcze jeden, który iteruje tablice dowolnej rangi, wykonując rekursję:
To pozwala nam na iterację wszystkich elementów we wszystkich wymiarach tablicy o dowolnych wymiarach.
Praca z
std::vector
W przypadku wielu zagnieżdżonych wektorów rozwiązanie przypomina tablicę o dowolnych wymiarach, ale bez SFINAE: Najpierw będziemy potrzebować funkcji szablonu, która iteruje
std::vector
si i wywołuje żądaną funkcję:I kolejna funkcja szablonu, która iteruje dowolny rodzaj wektorów i nazywa siebie:
Niezależnie od poziomu zagnieżdżenia,
iterate_all
wywoła wersję wektora wektorów, chyba że wersja wektora wartości jest lepiej dopasowana, kończąc tym samym rekurencję.Myślę, że treść funkcji jest dość prosta i nieskomplikowana ... Zastanawiam się, czy kompilator mógłby rozwinąć te pętle (jestem prawie pewien, że większość kompilatorów mogłaby rozwinąć pierwszy przykład).
Zobacz demo na żywo tutaj .
Mam nadzieję, że to pomoże.
źródło
Użyj czegoś podobnego do tych linii (pseudokodu, ale idea pozostaje ta sama). Wyodrębniasz wzorzec do zapętlenia raz i za każdym razem stosujesz inną funkcję.
źródło
Trzymaj się zagnieżdżonych pętli for!
Wszystkie proponowane tutaj metody mają wady związane z czytelnością lub elastycznością.
Co się stanie, jeśli będziesz musiał wykorzystać wyniki wewnętrznej pętli do przetwarzania w zewnętrznej pętli? Co się stanie, jeśli potrzebujesz wartości z zewnętrznej pętli w wewnętrznej pętli? Większość metod „hermetyzacji” zawodzi tutaj.
Zaufaj mi, widziałem kilka prób „wyczyszczenia” zagnieżdżonych pętli for i ostatecznie okazuje się, że zagnieżdżona pętla jest w rzeczywistości najczystszym i najbardziej elastycznym rozwiązaniem.
źródło
Jedną z technik, których użyłem, są szablony. Na przykład:
Następnie po prostu wywołujesz
do_something_on_A(A)
swój główny kod. Funkcja szablonu jest tworzona raz dla każdego wymiaru, pierwszy raz zT = std::vector<std::vector<int>>
, drugi raz zT = std::vector<int>
.Możesz uczynić to bardziej ogólnym, używając
std::function
(lub obiektów podobnych do funkcji w C ++ 03) jako drugiego argumentu, jeśli chcesz:Następnie nazwij to tak:
Działa to nawet wtedy, gdy funkcje mają tę samą sygnaturę, ponieważ pierwsza funkcja lepiej pasuje do wszystkiego, co zawiera
std::vector
typ.źródło
Możesz generować indeksy w jednej pętli w ten sposób (A, B, C to wymiary):
źródło
Jedną rzeczą, którą możesz chcieć wypróbować, jeśli masz instrukcje tylko w najbardziej wewnętrznej pętli - a twoim zmartwieniem jest bardziej rozwlekły charakter kodu - jest użycie innego schematu białych znaków. To zadziała tylko wtedy, gdy możesz określić pętle for na tyle zwięźle, aby wszystkie pasowały do jednej linii.
Dla twojego pierwszego przykładu przepisałbym go jako:
Jest to trochę popychanie, ponieważ wywołujesz funkcje w zewnętrznych pętlach, co jest równoważne umieszczaniu w nich instrukcji. Usunąłem wszystkie niepotrzebne spacje i może być przejezdne.
Drugi przykład jest znacznie lepszy:
Może to być inna konwencja białych znaków, niż lubisz używać, ale daje kompaktowy wynik, który mimo to nie wymaga żadnej wiedzy poza C / C ++ (takiej jak konwencje makr) i nie wymaga żadnych sztuczek, takich jak makra.
Jeśli naprawdę potrzebujesz makra, możesz pójść o krok dalej, wykonując na przykład:
co zmieniłoby drugi przykład na:
i pierwszy przykład też wypada lepiej:
Miejmy nadzieję, że dość łatwo można powiedzieć, które stwierdzenia pasują do których ze stwierdzeniami. Uważaj też na przecinki, teraz nie możesz ich używać w pojedynczej klauzuli żadnego z
for
s.źródło
for
pętli w wierszu nie czyni go bardziej czytelnym, ale zmniejsza .Oto implementacja C ++ 11, która obsługuje wszystko iterowalne. Inne rozwiązania ograniczają się do kontenerów z
::iterator
typedef lub tablicami: ale afor_each
dotyczy iteracji, a nie kontenera.Izoluję również SFINAE w jednym miejscu
is_iterable
cechy. Wysyłanie (między elementami i iterowalnymi) odbywa się za pośrednictwem wysyłania tagów, co moim zdaniem jest jaśniejszym rozwiązaniem.Kontenery i funkcje zastosowane do elementów są doskonale przekazane, umożliwiając zarówno dostęp do zakresów i funktorów , jak
const
i brakconst
dostępu do nich.Funkcja szablonu, którą implementuję. Wszystko inne może trafić do przestrzeni nazw szczegółów:
Wysyłanie tagów jest znacznie czystsze niż SFINAE. Te dwa są używane odpowiednio dla obiektów iterowalnych i obiektów nieterowalnych. W ostatniej iteracji pierwszej przydałoby się idealne przekazywanie, ale jestem leniwy:
To jest szablon wymagany do pisania
is_iterable
. I zrobić wyszukiwanie argumentów na utrzymaniubegin
iend
w obszarze nazw szczegółów. To naśladuje to, cofor( auto x : y )
robi pętla dość dobrze:TypeSink
Jest przydatna do badania, jeśli kod jest poprawny. RobiszTypeSink< decltype(
kod) >
i jeślicode
jest poprawne, wyrażenie jestvoid
. Jeśli kod jest nieprawidłowy, włącza się SFINAE, a specjalizacja jest blokowana:Testuję tylko dla
begin
.adl_end
Badanie może być również wykonywana.Ostateczna realizacja
for_each_flat
okazuje się niezwykle prosta:Przykład na żywo
To jest na samym dole: nie krępuj się, aby znaleźć najlepsze odpowiedzi, które są solidne. Chciałem tylko użyć kilku lepszych technik!
źródło
Po pierwsze, nie powinieneś używać wektorów wektorów. Każdy wektor ma zapewnioną ciągłą pamięć, ale „globalna” pamięć wektora wektorów nie jest (i prawdopodobnie nie będzie). Powinieneś także użyć standardowej tablicy typów bibliotek zamiast tablic w stylu C.
Jeszcze lepiej, możesz zdefiniować prostą klasę macierzy 3D:
Możesz pójść dalej i uczynić to w pełni poprawnym, dodać mnożenie macierzy (właściwe i elementowe), mnożenie przez wektory itp. Możesz nawet uogólnić to na różne typy (zrobiłbym to szablonem, gdybyś używał głównie podwójnych) .
Możesz także dodać obiekty proxy, aby wykonać B [i] lub B [i] [j]. Mogliby zwrócić wektory (w sensie matematycznym) i macierze pełne podwójnych &, potencjalnie?
źródło