.. _petle: Pętle: powtarzanie bloku instrukcji -------------------------------------- Pętla umożliwia powtarzanie wybranych instrukcji (jednej lub więcej) wielokrotnie. Instrukcja warunkowa, która zostanie omówiona w kolejnym rozdziale, umożliwa *warunkowe* wykonanie pewnych instrukcji. Zarówno pętla jak i warunek dotyczą wyróżnionego **bloku programu**. W języku Python bloki wyróżniamy stosując tzw. *wcięcia*. .. code-block:: python # jakieś instrukcje # i inne instruckje # tu pętla lub warunek # te linie stanowią # jeden blok programu Wcięcia możemy robić za pomocą spacji albo tabulatora, nie możemy ich jednak stosować jednocześnie w jednym bloku. Język Python nie narzuca *głębokości wcięcia*: możemy stosować 2, 4 lub 8 spacji albo 1 tabulator. Ważne jest jednak, aby w obrębie jednego bloku wszystkie instrukcje były wcięte identycznie! Wykonanie określonej liczby powtórzeń """"""""""""""""""""""""""""""""""""""""""""" Do czego potrzebujemy pętli? Zerknijmy na prosty przykład: .. raw:: html
Na początku program drukuje liczby od 0 do 4 za pomocą czerech komend w czterech liniach kodu. Wydrukowanie 100 liczb w ten sposób wymagałoby programu na sto linii ... W przykładzie tym mamy też pętlę, która wypisze dowolnie dużo linijek na ekran, wystarczy zmienić zakres. Jest to pętla ``for``, która wykonuje określoną liczbę powtórzeń. W języku Python możemy też powtarzać, dopóki określony warunek jest spełniony (tzw. pętla ``while``). Pętla ``for`` umożliwia także powtarzanie operacji dla każdego elementu z podanego zbioru; ta możliwość zostanie omówiona później, podczas wykładu o strukturach danych. Kolejny przykład pokazuje jeszcze jedną ważną zaletę pętli: liczba powtórzeń **może być dowolna** i nie musi być określona z góry. Możemy wpisać do programu 4, 8 albo 30 linii ``print()`` ... ale nie możemy wpisać *nieznanej* liczby tych instrukcji. W poniższym przykładzie jednak pokazano trzy różnie zapisane pętle, które dają ten sam wynik: drukują one ``N`` liczb. Wartość ``N`` mogłaby być wynikiem obliczeń albo zostać wczytana z klawiatury. Zauważcie, że dla ``N = 10`` wygenerowanych liczb jest 10: zaczynamy od 0 a kończymy na 9. Liczba 10 już się nie pojawia: .. raw:: html Ogólna postać instrukcji ``range`` to ``range(od, do, co_ile)``, przy czym wartość ``do`` nie pojawia się już w wynikach. Domyślną wartością parametru ``co_ile`` jest ``1``. .. admonition:: Uwaga :class: def Przedział generowany instrukcją ``range`` jest prawostronnie otwarty; ``range(a, b)`` generuje liczby **całkowite** :math:`[a,b)`. Instrukcja ``range(b)`` jest równoważna ``range(0,b)``, czli generuje przedział :math:`[0,b)`. Nie można w ten sposób generować sekwencji liczb rzeczywistych, tzn poniższa instrukcja jest nieprawidłowa: ``range(1.0, 2.0, 0.1)`` Jak widać, wcięcie które zawiera tylko jedną instrukcję, można pominąć, pisząc całą pętlę w jednej linii (jak w linii 1). Dla czytelności programu warto jednak stosować wcięcia. Poza tym wcięcia przydają się, kiedy chcemy dopisać jakieś instrukcje do istniejącej już pętli. .. rubric:: Częste błędy W swoich pierwszych programach zdarza się Wam popełniać pewne typowe błędy, najpopularniejsze zebrałem poniżej: - pozwolę sobie powtórzyć to raz jeszcze: ta instrukcja: ``for i in range(1,10)`` nie generuje 10 liczb; a ta z kolei: ``for i in range(10)`` nie generuje liczby 10 - mieszanie tabulacji i spacji to bardzo częsty błąd; zanim wciskanie klawisza TAB (bądź spacji) wejdzie w nawyk, polecam edytor z opcją "zamień spacje na tabulatory" (lub na odwrót), np. Sublime - z pozoru drobne zmiany we wcięciach zmieniają sens (algorytm) programu, wyjmując instrukcje poza pętlę .. raw:: html Pierwsza część programu (linie 1-3) od drugiej części (linie 8-10) różni się tylko dwiema spacjami (jednym wcięciem) w linii 10. Powoduje to jednak, że linia 3 jest powtarzana, linia 10 zaś nie jest. .. rubric:: Przykład: suma szeregu Czas na konkretny przykład: obliczymy 30 wyraz następującego szeregu: .. math:: S_n = 1 + \frac{1}{2^2} + \frac{1}{3^2} + \frac{1}{4^2} + .. + \frac{1}{n^2} Problem ten tylko z pozoru jest trudny, można go rozwiązać w 4 instrukcjach: .. raw:: html Przykład ten jest dość typowy, warto go dokładniej omówić: - w linii pierwszej tworzymy zmienną ``suma`` i nadajemy jej wartość 0.0. Będzie ona *akumulować* wyniki z poszczególnych przebiegów pętli - pętla biegnie po zakresie od 1 do 31 (bez 31), co zapewnia nam sumowanie 30 wyrazów; nie możemy rozpocząć pętli od zera, bo wtedy doszło by do dzielenia przez 0 w linii trzeciej - można oczywiście napisać pętlę od ``0``, ale wtedy linię 3 trzeba zmienić na: ``suma += 1.0/((i+1)*(i+1))`` (*uwaga na nawiasy!*) - drukowanie wyniku następuje *po zakończeniu* obliczeń; wcięcie linii 5 o dwie spacje spowoduje, że drukowane będą wszystkie sumy cząstkowe od :math:`S_1` do :math:`S_{30}` - akumulowanie wyników w pętli pojawia się dość często; pamiętaj aby zmienną gromadzącą sumy zainicjować ``0`` a do zmiennej gromadzącej ilocznyny wpisać ``1`` .. admonition:: Ćwiczenie 1 :class: def Napisz pętlę, której zmienna (indeks) biegnie po wartościach: ``-3 -5 -7 -9``. Powtarzanie *dopóki* warunek jest spełniony """"""""""""""""""""""""""""""""""""""""""""" Instrukcja ``while`` sprawdza podany warunek logiczny **przed** każdym przebiegiem pętli i kończy swoje działanie wtedy, gdy tylko warunek będzie **fałszywy**. Poniższy przykład drukuje liczby od 0 dopóki są one mniejsze niż 10: .. raw:: html Działanie tego programu jest więc takie samo, jak `przykładu 2.2 <#petla_range>`__, Najczęściej pętlę ``while`` stosujemy wtedy, gdy nie umiemy z góry określić, ilu iteracji będziemy potrzebować. Poniższy program to ulepszona wersja `sumy szeregu <#szereg_range>`__, który już liczyliśmy poprzenio: .. raw:: html Tym razem jednak liczymy dotąd, aż osiągniemy zakładaną dokładność wyniku; w tym przypadku - aż dodawany wyraz jest mniejszy niż :math:`10^{-4}`. Oczywiście akurat tu wzór na wyrazy ciągu jest prosty - łatwo zgadnąć, że sumowanie zakończy się po 100 krokach. Generalnie jednak w obliczeniach numerycznych nie wiemy, ile iteracji potrzeba, aby osiągnąć zakładaną zbieżność. Czasem może to nigdy nie nastąpić, dlatego warto połączyć kryterium zadanej dokładności z maksymalną liczbą iteracji, jak w programie poniżej: .. raw:: html Zagnieżdżanie pętli """"""""""""""""""""""""""""""""""""""""""""" Powyższe przykłady były dość proste. Rzeczywiste problemy mogą być dużo bardziej skomplikowane i wymagać na przykład zagnieżdżenia pętli (konstrukcja *pętla w pętli*, *double-nested loop*). Konstrukcja *pętla w pętli* generuje kombinacje *każdy z każdym*, czyli rozpartuje wszystkie pary indeksów pętli ``(i,j)`` .. admonition:: Uwaga :class: def W przypadku pętli zagnieżdżonych zmienne będące indeksami pętli **muszą** mieć inne nazwy. Wg szeroko stosowanej konwencji nazywamy je kolejno w głąb ``i``, ``j``, ``k`` itd. .. rubric:: Kilka przykładów Generalnie pętle zagnieżdżone stosujemy wtedy, kiedy rozpatrywany problem jest *dwuwymiarowy*, np kiedy chcemy narysować prostokąt, jak w przykładzie poniżej. .. raw:: html .. admonition:: Ćwiczenie 2 :class: def Zmień powyższy program tak, aby narysować prostokąt o 6 wierszach o 8 kolumnach. W kolejnym przykładzie narysujemy trójkąt. Zauważ, że indeks pętli wewnętrznej zależy od indeksu pętli zewnętrznej! To często stosowane rozwiązanie. .. raw:: html .. admonition:: Ćwiczenie 3 :class: def Dlaczego **pierwszy** wiersz wydruku jest **pusty**? Zmień program tak, aby narysować identyczny trójkąt, ale zaczynając od pierwszej linii konsoli. Należy zwrócić uwagę na to, czym się różnią w działaniu dwie pętle "*zwykłe*" od "*zagnieżdżonych*": .. raw:: html W tym przykładzie mamy dwa fragmeny, na każdy z nich składają się 2 pętle. Różnica jest minimalna - linie 3 i 4 są wcięte o dwie spacje dzięki czemu są wewntrz pierszej pętli indeksowanej zmiennej ``i``. Linie 11 i 12 są zaś poza pętlą po ``i``. Ta drobna z pozoru różnica powoduje, że oba fragmenty działają zupełnie inaczej i drukują co innego w konsoli. Można powiedzieć, że gdy w pierwszym przypadku (czyli pętli zagnieżdżonych) liczba iteracji jest iloczynem (5 * 5 = 25 przebiegów wewnętrznej pętli), to w drugim jest sumą (5 + 5 iteracji). Czasem, kiedy ten przykład pojawia się na zajęciach, ktoś z sali zauważa, że przecież można to samo osiągnąć stosując pojedynczą pętlę: .. raw:: html To prawda, jednakże powyższe zadania służą celom dydaktycznym. Szybko można znaleźć przykład zagnieżdżonych pętli, którego nie można łatwo uprościć do pętli pojedynczej. Generalnie należy przyjąć zasadę, że gdy problem jest dwuwymiarowy, stosujemy konstrukcję *pętla w pętli*. Przykładami takich problemów są praca z tablicą dwuwymiarową albo mnożenie macierzy przez wektor lub inną macierz. .. admonition:: Praca domowa :class: def Zmień program rysujący trójkąt tak, aby wynik wyglądał jak na obrazkach poniżej (trzy obrazki dla trzech niezależnych podpunktów zadania). .. code-block:: bash # # ####### ### #### ##### ##### ####### ### ####### ########## # Podpowiedź: aby odsunąć znaki ``#`` od krawędzi konsoli, musisz narysować odpowiednią liczbę spacji.