PPy3/SekwencjeIIteracja

Z Brain-wiki

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

{tu będą sugerowane zadania}


poprzednie | strona główna | dalej

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