PPy3/SekwencjeIIteracja: Różnice pomiędzy wersjami

Z Brain-wiki
Linia 324: Linia 324:
 
*z wykorzystaniem pętli <tt>while</tt>
 
*z wykorzystaniem pętli <tt>while</tt>
  
2. Wykorzystując pętlę <tt>while</tt> oraz tzw. metodę sita Eratostenesa napisać program, który sprawdza czy zadana liczba naturalna jest liczbą pierwszą.
+
2. Wykorzystując pętlę <tt>while</tt> napisać program, który sprawdza czy zadana liczba naturalna jest liczbą pierwszą.
  
 
3. Napisać program, który sprawdza jaka jest najmniejsza liczba wyrazów szeregu harmonicznego, jaką należy posumować, aby wynik przekroczył daną liczbę dodatnią (,,próg").
 
3. Napisać program, który sprawdza jaka jest najmniejsza liczba wyrazów szeregu harmonicznego, jaką należy posumować, aby wynik przekroczył daną liczbę dodatnią (,,próg").

Wersja z 12:04, 8 lip 2016

Sekwencje i iteracja

Sekwencje to typ danych charakteryzujący się tym, że składają się z elementów zgromadzonych w pewnej kolejności. Elementem sekwencji może być cokolwiek (w tym - inna sekwencja), ale w najprostszym przypadku elementami tymi są liczby.

Sekwencje to szczególny przypadek kolekcji - takich typów danych, które składają się z elementów, ale niekoniecznie są one uporządkowane. Innym przykładem kolekcji jest zbiór (set).

Najczęściej używanym typem sekwencji jest lista (list). Listę można stworzyć (i zapamiętać) np. nadając jej pewien początkowy skład:

numerki = [0, 1, 2, 3, 5, 8]

To co stoi po prawej stronie znaku równości to literalny zapis listy - ciąg elementów, oddzielonych przecinkami, wewnątrz pary nawiasów kwadratowych.

Z listy - podobnie jak z każdej sekwencji - można pobrać element stojący w dowolnej pozycji:

print(numerki[4])
 5

Uwaga: pozycje numerowane są kolejnymi liczbami naturalnymi, począwszy od zera - podobnie, jak w większości języków programowania.

Można też podmienić istniejący element na inny:

numerki[0] = -1
print(numerki)
 [-1, 1, 2, 3, 5, 8]

a nawet przedłużyć ją o kolejny element:

numerki.append(13)
print(numerki)
 [-1, 1, 2, 3, 5, 8, 13]

oraz usunąć element (skracając listę):

del numerki[0]
print numerki
 [1, 2, 3, 5, 8, 13]

Łatwo też ustalić, jaka jest ,,długość" listy, czyli liczba jej elementów:

print(len(numerki))
 6

Jest jeszcze sporo innych ,,gotowych do użytku" operacji na listach. Ale najważniejsza cecha listy jest taka: można zmieniać jej skład, a nawet długość, a pozostaje ona tą samą listą. Nawet jeśli występuje w programie pod więcej niż jedną nazwą.

Pętla for

Naczelne zastosowanie listy (czy ogólniej -sekwencji), to iteracja - czyli wykonanie jakiejś operacji dla każdego elementu. I tak wygląda najprostszy rodzaj iteracji - Pętla for:

for x in numerki:
    print(x)

Zamiast print(x) pętlę for może tworzyć dowolny blok instrukcji, zgodnie z zasadami jakie już poznaliśmy przy omawianiu instrukcji warunkowej. Cały blok tworzący wnętrze pętli będzie wykonany wielokrotnie, tyle razy, ile elementów ma lista, a za każdym razem pod nazwę stojącą bezpośrednio po słowie for podstawiony będzie kolejny element listy (przywołanej po słowie in) - kolejny, tzn. zgodnie z kolejnością, w jakiej umieszczone są one w liście.

Inny, znany nam już rodzaj sekwencji to napis (string), zwany czasami łańcuchem znakowym. Napis składa się ze znaków, i jest w szczególności sekwencją, której elementami są znaki. Analogicznie jak w przypadku listy, można pobierać znaki stojące na poszczególnych pozycjach w napisie. Nie można natomiast podmieniać znaków w napisie, ani ich usuwać bądź dopisywać: w odróżnieniu od listy, napis raz utworzony jest niezmienny. Można go zastąpić innym napisem, i ewentualnie opatrzyć ten nowy napis tą samą nazwą, ale nie da się go zmodyfikować. I nie jest to dzielenie włosa na czworo, własność ta ma namacalne konsekwencje:

napis1 = 'abc'
napis2 = napis1
napis1 = 'ABC'
print(napis1, napis2)
 ABC abc
# dla porównania, listy:
lista1 = ['a', 'b', 'c']
lista2 = lista1
lista1[0] = 'A'
print(lista1, lista2)
 ['A', 'b', 'c'] ['A', 'b', 'c']

Powraca jeszcze raz pytanie: czym są poszczególne znaki? Tzn. jaki typ danych reprezentują? Otóż w Pythonie pojedyncze znaki też są po prostu napisami, tyle że o długości jeden. Poza długością nic ich szczególnie nie wyróżnia. Ta uwaga jest na użytek tych z Was, którzy znają jakiś inny język programowania, gdyż w wielu z nich znak jest oddzielnym, innym niż napis typem danych.

Pętlę for można zatem zastosować do napisu - zamiast do listy, wówczas w kolejnych iteracjach będą uwzględniane kolejne znaki, a których składa się napis.

Ogólna postać pętli for

for element in sekwencja:
    <blok instrukcji>
else:
    <blok instrukcji>
  • Pierwszy blok instrukcji będzie wykonany tyle razy, ile elementów ma sekwencja
  • Za każdym razem nazwa element oznaczać będzie kolejny element sekwencji, zgodnie z ich porządkiem
  • sekwencja może być pusta; nie jest to błąd, ale wtedy pierwszy blok nie będzie wykonany ani razu
  • Gdy elementy sekwencji się wyczerpią, wykonany zostanie blok po else
  • Blok else (wraz z linijką go otwierającą) jest opcjonalny (niekonieczny)
  • W pierwszym bloku mogą wystąpić specjalne polecenia: break i continue
    • break znaczy: przerwij pętlę natychmiast, pomiń wszystkie pozostałe elementy sekwekcji, pomiń również ewentualny blok else
    • continue oznacza: przerwij działania na aktualnym elemencie, i wróć do początku bloku biorąc kolejny element. Jeśli już zabrakło elementów, przejdź do bloku else, albo (gdy go nie ma) do polecenia następnego po pętli

Blok else w pętli for jest stosunkowo rzadko widywany. Nawet wielu dość biegłych w Pythonie nie wie o tej opcji.

A co, jeśli chcielibyśmy za pomocą pętli for (lub inaczej) zmienić w jakiś sposób skład samej sekwencji? Na przykład:

for x in lista:
    x = x + 1

TO NIE ZADZIAŁA - w tym sensie, że lista będzie dalej miała te same elementy co poprzednio, a nie - powiększone o 1. W istocie operacja taka nic nie osiąga - oprócz tego, że po jej zakończeniu nazwa x ma wartość ostatniego elementu listy, powiększonego o 1. To samo można dostać bez pisania pętli.

Jak więc osiągnąć zmianę - przeliczenie - wszystkich elementów sekwencji wg. jakiejś reguły (np. powiększ każdy z nich o 1)?

Pożytecznym sposobem tworzenia sekwencji jest funkcja range:

for x in range(3):
    print(x)
 0
 1
 2

Argument (tu: 3) oznacza liczbę elementów w wyprodukowanej sekwencji. Domyślnie zaczyna się ona od zera, tak jak numeracja pozycji w sekwencjach - ale można użyć wywołania range(a, b) aby uzyskać elementy dowolnego ciągu arytmetycznego - o elemencie początkowym a i przyroście b.

Tu jest pewne oszustwo: wynikiem wywołania range nie jest tak naprawdę lista ani sekwencja, ale jeszcze pewnego innego rodzaju obiekt mogący służyć za źródło elementów w iteracji. Na razie to jest jednak drobny szczegół. Jeżeli potrzebujemy dosłownej listy o takich elementach, wystarczy wykonać tzw. rzutowanie, czyli przyłożyć funkcję list: lista = list(range(3))

Wracając do problemu przeliczenia wszystkich elementów listy:

lista = [3, 5, 8]
for k in range(3):
    lista[k] += 1
print lista
 [4, 6, 9]

I to działa. Nie jest to jednak optymalne rozwiązanie.

  • Pisząc to co powyżej wiedzieliśmy, że mamy do czynienia z listą o długości 3. W ogólności musielibyśmy użyć funkcji len;
  • Można to jednak zrobić bardziej elegancko:
for k, x in enumerate(lista):
    lista[k] = x + 1

Funkcja enumerate produkuje sekwencję par: nr pozycji, element - i jest bardzo przydatna w takich sytuacjach.

Z dokonywaniem wewnątrz pętli zmian na sekwencji, po której iterujemy, należy jednak uważać. To, co robimy w tym przykładzie jest akurat niegroźne, natomiast umieszczenie wewnątrz pętli operacji zmieniających długość sekwencji, po której iterujemy mogłoby dać nieoczekiwane wyniki.

Więcej o listach

Elementy listy (innych rodzajów sekwencji również) można numerować od końca, używając ujemnych wartości wskaźników pozycji:

numerki = [0, 1, 2, 3, 5, 8]
numerki[-1]
 8
numerki[-3]
 3

Z list (i innych sekwencji) można wyjmować nie tylko poszczególne elementy, ale również podsekwencje, zwane też wycinkami:

numerki = [0, 1, 2, 3, 5, 8]
nn = numerki[1:4]
 [1, 2, 3]
nn = numerki[2:]
 [2, 3, 5, 8]
nn = numerki[:3]
 [0, 1, 2]
nn = numerki[1:4:2]
 [1, 3]
nn = numerki[4:1:-1]
 [5, 3, 2]
nn = numerki[:]
 [0, 1, 2, 3, 5, 8]

Jak widać, pominięcie pozycji początkowej lub końcowej oznacza: ,,do oporu". Trzecia liczba charakteryzująca wycinek, o ile występuje - to ,,skok" wskaźnika. Przykładowo, a[::2] oznaczałoby listę zawierającą co drugi element listy a, natomiast a[::-1]: listę składającą się z tych samych elementów, co a - lecz w odwrotnej kolejności.

Wyrażenie numerki[:], tzw. pełny wycinek, tworzy pełną kopię listy numerki; tzn. nie jest to ta sama lista, chociaż składa się z tych samych elementów.

Wycinek jest zawsze nową listą, również w ostatnim przypadku - gdy jej skład jest taki sam, jak oryginalnej. Wtedy stanowi kopię pierwotnej listy. Można jednak też podstawiać do wycinka:

numerki[4:5] = [4]
 [0, 1, 2, 3, 4, 8]
numerki[5:5] = [5, 6, 7]
 [0, 1, 2, 3, 4, 5, 6, 7, 8]

i jest to jeszcze jeden sposób, by zmienić zawartość listy - w tym być może jej długość - zachowując jej tożsamość.

Listy można do siebie dodawać:

l = ['a', 'b', 'c'] + ['y', 'z']
 ['a', 'b', 'c', 'y', 'z']

w ten sposób powstaje nowa lista, która ze składników bierze elementy ale poza tym nie ma z nimi nic wspólnego; późniejsze zmiany w listach-składnikach nie mają wpływu na sumę.

Listy można też zwielokratniać; np. aby uzyskać listę siedmiu dwójek:

l = [2] * 7
 [2, 2, 2, 2, 2, 2, 2]

ta sama co poprzednio uwaga stosuje się i tu.

Elementy listy można posumować:

x = sum(range(101))
 5050

(wprawdzie range(101) nie jest dosłownie listą, ale z ,,prawdziwą" listą można zrobić to samo).

Podobnie jak inne obiekty, listy posiadają szereg metod, za pomocą których można na nich wykonywać różne działania. Przykładowo:

numerki.append(9)
 [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
numerki.extend([10, 11])
 [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]

Zwróćcie uwagę na notację: kropka sygnalizuje ,,wyciągnięcie" metody z danego obiektu (tu: listy), w nawiasach wpisujemy dane dla tej metody (argumenty - tu pojedyncze, ale może być więcej).

Więcej o napisach

Napisy dopuszczają tylko część operacji dostępnych dla list, ze względu na własność niemodyfikowalności. Posiadają za to szereg swoistych metod. Trzeba pamiętać, że żadna z tych metod nie zmienia napisu, którego dotyczy - wynikiem może być jedynie nowy napis, stworzony na podstawie oryginalnego.

Przykład: metoda upper (zamienia małe litery na wielkie):

'abcde'.upper()
 'ABCDE'

Napis zawsze można łatwo zamienić na listę znaków:

l = list('abcde')
 ['a', 'b', 'c', 'd', 'e']

Do operacji odwrotnej można użyć metody join:

''.join(l)
 'abcde'
', '.join(l)
 'a, b, c, d, e'

Pętla while

To jeszcze jeden sposób iteracji, tak naprawdę prostszy od pętli for - gdyż nie oparty na sekwencjach; zwykle mniej wygodny w użyciu, ale w niektórych przypadkach nieodzowny.

# postać najprostsza

while WARUNEK:
    BLOK INSTRUKCJI

# ogólna postać:

while WARUNEK:
    BLOK1
else:
    BLOK2

Polega na powtarzaniu bloku instrukcji nieokreśloną liczbę razy; przed każdym kolejnym wykonaniem na nowo sprawdzany jest warunek, i jeśli okaże się fałszywy - powtarzanie się kończy.

Innym sposobem przerwania pętli while jest umieszczenie gdzieś w bloku instrukcji break - zwykle pod pewnym warunkiem (tzn. wewnątrz instrukcji warunkowej if). Wykonanie break polega na ,,wyskoczeniu" z pętli - i przejściu do dalszego ciągu programu, po bloku wewnętrznym pętli.

Oczywiście aby WARUNEK mógł okazać się za którymś razem fałszywy, i spowodować przerwanie pętli, musi on być zbudowany z wykorzystaniem zmiennych, których wartości ulegają zmianom wewnątrz pętli. Zdarza się jednak widzieć pętlę zaczynającą się od while True: - formalnie jest to pętla nieskończona, którą może zakończyć jedynie wywołanie instrukcji break występującej gdzieś w wewnętrznym bloku.

Wewnątrz bloku może wystąpić również instrukcja continue - działa ona analogicznie, jak w pętli for, a więc powoduje przerwanie wykonywania aktualnej iteracji i przeskok do początku pętli, czyli sprawdzenia warunku jej kontynuowania.

W ogólnej postaci, blok występujący po linijce else: jest wykonywany po zakończeniu pętli wskutek niespełnienia warunku początkowego, natomiast jest pomijany, jeśli przerwanie pętli było spowodowane instrukcją break.

Zasadniczo każdą pętlę for dałoby się przepisać jako równoważną pętlę while - przykładowo:

for x in lista:
    działaj_na(x)

# równoważnie:

k = 0
while k < len(lista):
    działaj_na(lista[k])
    k += 1

nie należy jednak zazwyczaj tego robić, ponieważ (najczęściej) pętla for jest bardziej czytelna.

Ćwiczenia

1. Napisać program, który sumuje liczby naturalne od 1 do 100:

  • z wykorzystaniem pętli for
  • z wykorzystaniem pętli while

2. Wykorzystując pętlę while napisać program, który sprawdza czy zadana liczba naturalna jest liczbą pierwszą.

3. Napisać program, który sprawdza jaka jest najmniejsza liczba wyrazów szeregu harmonicznego, jaką należy posumować, aby wynik przekroczył daną liczbę dodatnią (,,próg").



poprzednie | strona główna | dalej

RobertJB (dyskusja) 12:22, 29 cze 2016 (CEST)