Menu

Pętla for w R: składnia, seq_along, break, next i kiedy nie używać pętli

Jak działa pętla for w R: przechodzenie po wektorach i indeksach, zbieranie wyników bez pułapki rosnącego wektora, next i break oraz kiedy wywołanie wektorowe wygrywa z pętlą.

Na tej stronie są działające edytory: edytuj, uruchamiaj i od razu zobacz wynik.

Podstawowa pętla for

Pętla for w R wykonuje blok kodu raz dla każdego elementu wektora lub listy. Zmienna pętli przyjmuje po kolei każdą wartość:

Trzy elementy, trzy przebiegi. Nie trzeba ustawiać licznika ani pilnować warunku: for (fruit in fruits) czyta się jako "dla każdego owocu w fruits" i dokładnie to się dzieje. Ten sam kształt przechodzi po elementach listy, sekwencji liczb (for (i in 1:10)) albo nazwach czegokolwiek przez names().

Wypisywanie w pętli wymaga print() lub cat()

W konsoli wpisanie x wypisuje jego wartość. W treści pętli już nie: automatyczne wypisywanie działa tylko na najwyższym poziomie, a gołe wyrażenie w pętli jest obliczane i wyrzucane:

Gdy więc pętla "nic nie robi", to pierwsza rzecz do sprawdzenia. Użyj print(), żeby szybko obejrzeć dowolną wartość, albo cat(), gdy samodzielnie składasz linię wyniku (pamiętaj o "\n", bo cat() go nie dodaje).

Pętla po indeksach z seq_along()

Czasem potrzebujesz nie tylko wartości, ale i pozycji: żeby zapisać coś w innym wektorze pod tym samym indeksem albo wypisać numerowaną listę. Przechodź po indeksach przez seq_along():

W starszym kodzie zobaczysz for (i in 1:length(prices)), a to kryje prawdziwy błąd. Gdy wektor jest pusty, length() wynosi 0, a 1:0 nie oznacza "zero iteracji", bo dwukropek liczy w dół:

Pętla po 1:length(empty) wykonuje się dwa razy i indeksuje elementy, które nie istnieją (empty[1] to NA, a przypisania tworzą widmowe wpisy). seq_along() zwraca pustą sekwencję dla pustego wektora, więc treść pętli po prostu się nie wykonuje. Niech seq_along() stanie się nawykiem; jego krewny seq_len(n) robi to samo, gdy masz liczbę, a nie wektor.

Zbieranie wyników: alokuj z góry, nie powiększaj

Naturalny pierwszy odruch przy budowaniu wyników wygląda tak:

squares <- c()
for (i in 1:10000) {
  squares <- c(squares, i^2)   # copies the ENTIRE vector every pass
}

To działa, ale każde c(squares, ...) alokuje zupełnie nowy wektor i kopiuje do niego wszystkie stare elementy. Na koniec skopiowanych zostaje około 50 milionów liczb, żeby wyprodukować 10 000. Ten antywzorzec rosnącego wektora to główny powód, dla którego ludzie mówią, że pętle w R są wolne.

Rozwiązanie: najpierw utwórz wynik w pełnym rozmiarze, a potem przypisuj po indeksie:

Dla wyników, które nie są liczbami, ten sam pomysł działa z character(n), logical(n) albo vector("list", n) dla listy dowolnych rzeczy. Pętle z wcześniej zaalokowanym wynikiem są w pełni szybkie.

next i break

Dwa słowa kluczowe sterują pętlą od środka: next porzuca bieżący przebieg i przeskakuje do następnego elementu; break całkowicie kończy pętlę.

To wypisuje 2 4 6 8: wartości nieparzyste są pomijane przez next, a gdy przychodzi 10, break kończy pętlę przed wypisaniem. Używaj next, żeby wcześnie odfiltrować przypadki (dzięki temu główna treść nie ma dodatkowego wcięcia), a break, gdy pętla znalazła to, czego szukała, i nie ma powodu kontynuować.

Pętle zagnieżdżone

Treść pętli może zawierać inną pętlę. Pętla wewnętrzna wykonuje się do końca przy każdym przebiegu zewnętrznej; klasyczna demonstracja to tabliczka mnożenia:

Dla każdego wiersza i pętla wewnętrzna przechodzi przez wszystkie kolumny j, a znak nowej linii wiersza wypisuje się po jej zakończeniu. Zagnieżdżanie na dwóch poziomach jest w porządku; przy trzech i więcej treść zwykle prosi się o zamianę w funkcję albo całe obliczenie prosi się o outer() lub operację na macierzy.

Zagnieżdżanie obejmuje też przechodzenie po liście wektorów: pętla zewnętrzna po liście, wewnętrzna (albo lepiej wywołanie wektorowe) po każdym elemencie:

Zwróć uwagę, że ostatecznie nie ma pętli wewnętrznej: mean() obsługuje cały wewnętrzny wektor jednym wywołaniem. Ta obserwacja się uogólnia, co prowadzi nas do uczciwej części.

Kiedy nie używać pętli

R to język wektorowy: jego podstawowe operacje od razu działają na całych wektorach. Pętla, która przekształca każdy element po kolei, to często dłuższy zapis jednej linijki:

Wybieraj formę wektorową zawsze, gdy istnieje: jest krótsza, trudniej w niej o błąd i jest szybsza. Przy stosowaniu funkcji do każdego elementu listy tę samą rolę pełni rodzina apply (sapply, lapply, vapply).

Nie zamieniaj tego jednak w dogmat "pętle są złe". Pętla to właściwe narzędzie, gdy iteracje zależą od poprzednich (stan bieżący, symulacje), gdy wykonujesz efekty uboczne, takie jak zapisywanie plików, albo po prostu gdy pętla to wersja, którą ty i twoi czytelnicy rozumiecie od pierwszego spojrzenia. Czytelna pętla wygrywa ze sprytną jedną linijką, której nikt nie potrafi rozczytać.

Co warto zapamiętać

  • for (x in v) { ... } wykonuje treść raz na element, bez prowadzenia licznika.
  • Wewnątrz pętli wypisuj jawnie przez print() lub cat(); gołe wyrażenia są odrzucane.
  • Po pozycjach przechodź przez seq_along(v), nigdy przez 1:length(v), bo to drugie wykonuje się dwa razy na pustym wektorze.
  • Alokuj wyniki z góry przez numeric(n) / vector("list", n) i przypisuj po indeksie; powiększanie przez c() kopiuje wszystko w każdym przebiegu.
  • next pomija przebieg, break kończy pętlę; wybieraj operacje wektorowe, gdy istnieją, ale nie bój się czytelnej pętli.

Dalej: pętle, które działają, dopóki coś się nie stanie, a nie raz na element, czyli while i repeat.

Najczęściej zadawane pytania

Jak napisać pętlę for w R?

for (x in v) { ... }: zmienna pętli x przyjmuje po kolei każdy element wektora (lub listy) v, a treść pętli wykonuje się raz na element. Żeby przechodzić po pozycjach zamiast wartości, użyj for (i in seq_along(v)) i indeksuj przez v[i].

Dlaczego seq_along zamiast 1:length(v) w R?

Gdy v jest pusty, length(v) wynosi 0, więc 1:length(v) staje się 1:0, czyli dwuelementowym wektorem c(1, 0), a treść pętli wykonuje się dwa razy na elementach, które nie istnieją. seq_along(v) zwraca pustą sekwencję dla pustego wektora, więc pętla poprawnie wykonuje się zero razy.

Dlaczego moja pętla for w R nic nie wypisuje?

Automatyczne wypisywanie w R działa tylko dla wyrażeń wpisanych na najwyższym poziomie konsoli. Wewnątrz treści pętli gołe wyrażenie, takie jak x, jest obliczane i odrzucane. Owiń je w print(x) albo użyj cat(...), gdy chcesz samodzielnie sformatować wynik.

Czy pętle for w R są wolne?

Sama pętla jest w porządku; wolne jest powiększanie w niej wektora przez result <- c(result, ...), co w każdym przebiegu kopiuje cały wektor. Zaalokuj wynik z góry przez numeric(n) lub vector("list", n) i przypisuj po indeksie, a pętla będzie działać bez zarzutu. Mimo to, gdy operacja wektorowa albo funkcja z rodziny apply wyraża ten sam pomysł, zwykle jest i szybsza, i czytelniejsza.

Ilustracja języków programowania w Coddy

Ucz się programowania z Coddy

ZACZNIJ