Czy są jakieś zalety używania tej dodatkowej zmiennej w adnotacji pętli for?

11

Znalazłem następującą adnotację w pętli w dużym projekcie, nad którym pracuję (pseudokod):

var someOtherArray = [];
for (var i = 0, n = array.length; i < n; i++) {
    someOtherArray[i] = modifyObjetFromArray(array[i]);
}

To, co zwróciło moją uwagę, to ta dodatkowa zmienna „n”. Nigdy wcześniej nie widziałem napisanego w ten sposób for for lop.

Oczywiście w tym scenariuszu nie ma powodu, dla którego ten kod nie mógłby zostać napisany w następujący sposób (do czego jestem bardzo przyzwyczajony):

var someOtherArray = [];
for (var i = 0; i < array.length; i++) {
    someOtherArray[i] = modifyObjetFromArray(array[i]);
}

Ale przyszło mi do głowy.

Czy istnieje scenariusz, w którym napisanie takiej pętli for miałoby sens? Przychodzi mi na myśl, że długość „tablicy” może się zmieniać podczas wykonywania pętli for, ale nie chcemy zapętlać dalej niż pierwotny rozmiar, ale nie wyobrażam sobie takiego scenariusza.

Zmniejszenie tablicy wewnątrz pętli również nie ma większego sensu, ponieważ prawdopodobnie otrzymamy OutOfBoundsException.

Czy istnieje znany wzorzec projektowy, w którym ta adnotacja jest przydatna?

Edytuj Jak zauważył @ Jerry101 przyczyną jest wydajność. Oto link do testu wydajności, który utworzyłem: http://jsperf.com/ninforloop . Moim zdaniem różnica nie jest wystarczająco duża, chyba że iterujesz przez bardzo dużą tablicę. Kod, z którego go skopiowałem, miał tylko do 20 elementów, więc myślę, że czytelność w tym przypadku przeważa nad wydajnością.

Vlad Spreys
źródło
W reakcji na Twoją edycję: jest to programowanie obiektowe. Standardowa długość tablicy jest szybka i tania. Ale z jakiegokolwiek powodu implementacja .length może zmienić się w coś kosztownego. Jeśli wartość pozostaje taka sama, nie otrzymuj jej wiele razy.
Pieter B
Zgadzam się. Dobrze jest wiedzieć o tej opcji, ale prawdopodobnie nie użyłbym jej zbyt często, chyba że otrzymam podobny przykład do zapytania SQL. Dziękuję☺
Vlad Spreys
Chodzi o to, że jeśli twoja tablica zawiera milion elementów, będziesz wywoływać tablicę. Długość milion razy zamiast 1 raz, podczas gdy wartość pozostanie taka sama. Pytanie o milion razy i świadomość, że każda kolejna odpowiedź będzie taka sama jak pierwsza odpowiedź ...... brzmi dla mnie trochę nonsensownie.
Pieter B
1
Nie uważam, że to znacznie utrudnia odczytanie kodu. (Poważnie, jeśli ktoś ma problemy z tak prostą pętlą, powinien się martwić.) Ale: 1) Byłbym strasznie zaskoczony, gdyby po 4 dekadach języków C i C każdy poważny kompilator jeszcze tego nie zrobił dla Was; oraz 2) nie może to być gorący punkt. Nie wydaje się, żeby ktoś spędził dodatkowe 5 sekund na analizie, chyba że jest w bibliotece (np. Implementacja struktury danych).
Doval
1
W języku C # / .NET użycie wariantu nmoże być wolniejsze niż użycie wariantu, array.Lengthponieważ JITter może nie zauważyć, że może wyeliminować sprawdzanie granic tablicy.
CodesInChaos

Odpowiedzi:

27

Zmienna nzapewnia, że ​​wygenerowany kod nie pobiera długości tablicy dla każdej iteracji.

Jest to optymalizacja, która może mieć wpływ na czas działania w zależności od używanego języka, niezależnie od tego, czy tablica jest w rzeczywistości obiektem kolekcji, czy „tablicą” JavaScript, i innymi szczegółami optymalizacji.

Jerry101
źródło
3
Nie, nie ma. To, czy pobierana jest długość tablicy, każda iteracja zależy nie tylko od języka, ale także od opcji kompilatora i kompilatora oraz tego, co dzieje się w pętli (mówię o C i C ++)
Bovo
.. Mimo to osobiście umieściłbym n przypisanie tuż nad pętlą, gdybym naprawdę tego chciał, ponieważ - jak zauważył OP - jest to rzadki konstrukt (YMMV) do użycia i łatwo go pominąć / nie zrozumieć przez innych programistów kod.
cwap,
20
Jak napisano, nobejmuje pętlę, co zwykle jest dobrą rzeczą. Zdefiniowałbym to przed pętlą, tylko gdybym chciał go później użyć ponownie.
Jerry101
4
@ Jerry101: Najwyraźniej fragmentem kodu jest JavaScript, w którym nie ma czegoś takiego jak zakres pętli…
Bergi
1
@ BЈовић: w praktyce kompilatorowi rzadko udaje się udowodnić, że rozmiar tablicy się nie zmienia (zazwyczaj jest to banalnie możliwe do udowodnienia tylko wtedy, gdy wektor, nad którym zapętlasz, jest lokalny dla funkcji i wywołujesz tylko funkcje wbudowane w pętli).
Matteo Italia,
2

Pobieranie długości tablicy może być z łatwością „droższe” niż faktyczna akcja, nad którą iterujesz.

Więc jeśli zestaw się nie zmienia, zapytaj o długość tylko raz.

Nie z tablicami, ale z zestawami rekordów pochodzącymi z serwera sql. Widziałem radykalną poprawę, nie sprawdzając liczby rekordów przy każdej iteracji. (oczywiście rób to tylko wtedy, gdy możesz zagwarantować, że tablica lub zestaw rekordów nie ulegną zmianie podczas tego procesu).

Pieter B.
źródło
Jeśli liczba rekordów się zmieni, to co? Powiedzmy, że było 100 elementów, a podczas przetwarzania elementu 50 wstawiany jest element po # 25. Kiedy więc przetwarzasz element nr 51, jest on taki sam jak numer 50, który już przetworzyłeś, i wszystko idzie nie tak. Problem nie polega na tym, że długość się zmienia, jest o wiele bardziej wszechobecna.
gnasher729 17.04.17
@ gnasher729 po przejściu do zachowania asymetrycznego wiele rzeczy może się nie udać. Ale są rzeczy, które możesz zrobić przeciwko temu, na przykład transakcja początkowa i SQL.
Pieter B
2

Przychodzi mi na myśl, że długość „tablicy” może się zmieniać podczas wykonywania pętli for, ale nie chcemy zapętlać dalej niż pierwotny rozmiar, ale nie wyobrażam sobie takiego scenariusza.

Ludzie rozmawiający o wydajności prawdopodobnie mają rację, dlatego właśnie tak zostało napisane (osoba, która to napisała w ten sposób, może nie mieć racji, że znacząco wpływa na wydajność, ale to inna sprawa).

Jednak, aby odpowiedzieć na tę część pytania, myślę, że najprawdopodobniej stanie się to, gdy głównym celem pętli jest modyfikacja tablicy, w której zapętla się ona. Rozważ coś takiego w pseudokodzie:

guests = [];
for (i = 0; i < invitations.length; ++i) {
    if (! invitations[i].is_declined()) {
        guests.append(invitations[i].recipient())
    }
}
// maybe some other stuff to modify the guestlist
for (i = 0, n = guests.length; i < n; ++i) {
    if (guests[i].has_plus_one()) {
        guests.append(guests[i].get_plus_one());
    }
}

Oczywiście są inne sposoby na napisanie tego. W tym przypadku mogłem sprawdzić, czy są plusy w tym samym czasie, co sprawdzenie, czy adresaci zaakceptowali zaproszenia i utworzyli pełną listę gości po jednym zaproszeniu. Niekoniecznie chcę połączyć te dwie operacje, ale nie mogę od razu wymyślić powodu, dla którego jest to zdecydowanie błędne, i dlatego ten projekt jest wymagany . Czasami jest to opcja do rozważenia.

Właściwie myślę, że najbardziej błędne w tym kodzie jest to, że członkostwo w gueststablicy oznacza różne rzeczy w różnych punktach kodu. Dlatego z pewnością nie nazwałbym tego „wzorem”, ale nie wiem, czy to wystarczająca wada, aby wykluczyć robienie czegoś takiego :-)

Steve Jessop
źródło
Tablica może być również modyfikowana przez inny wątek. Uniemożliwia to kompilatorowi optymalizację pobierania długości. W związku z tym programista wyraźnie mówi, że tablica nie zmienia się nawet w innych wątkach.
Basilevs,
0

Jeśli to w ogóle możliwe, powinieneś użyć iteratora, który przemierza wszystkie elementy tablicy. Używasz tablicy jako sekwencji, a nie jako pamięci o dostępie swobodnym, więc byłoby oczywiste, że iterator, który po prostu wykonuje dostęp sekwencyjny, byłby szybszy. Wyobraź sobie, że tablica jest w rzeczywistości drzewem binarnym, ale ktoś napisał kod, który określa „długość” poprzez zliczenie elementów i kod, który uzyskuje dostęp do i-tego elementu, również przez przemierzanie elementu, każdy w O (n ). Iterator może być bardzo szybki, twoja pętla będzie wolna.

gnasher729
źródło