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.
# 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:
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:
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.
Uwaga
Przedział generowany instrukcją range jest prawostronnie otwarty; range(a, b) generuje liczby całkowite \([a,b)\). Instrukcja range(b) jest równoważna range(0,b), czli generuje przedział \([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.
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 10mieszanie 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ę
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.
Przykład: suma szeregu
Czas na konkretny przykład: obliczymy 30 wyraz następującego szeregu:
Problem ten tylko z pozoru jest trudny, można go rozwiązać w 4 instrukcjach:
Przykład ten jest dość typowy, warto go dokładniej omówić:
w linii pierwszej tworzymy zmienną
sumai nadajemy jej wartość 0.0. Będzie ona akumulować wyniki z poszczególnych przebiegów pętlipę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 \(S_1\) do \(S_{30}\)
akumulowanie wyników w pętli pojawia się dość często; pamiętaj aby zmienną gromadzącą sumy zainicjować
0a do zmiennej gromadzącej ilocznyny wpisać1
Ćwiczenie 1
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:
Działanie tego programu jest więc takie samo, jak przykładu 2.2,
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, który już liczyliśmy poprzenio:
Tym razem jednak liczymy dotąd, aż osiągniemy zakładaną dokładność wyniku; w tym przypadku - aż dodawany wyraz jest mniejszy niż \(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:
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)
Uwaga
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.
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.
Ćwiczenie 2
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.
Ćwiczenie 3
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”:
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ę:
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.
Praca domowa
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).
# # ####### ### #### ##### ##### ####### ### ####### ########## #
Podpowiedź: aby odsunąć znaki # od krawędzi konsoli, musisz narysować odpowiednią liczbę spacji.