No, z jednodniowym poślizgiem ale jedziemy dalej ;).
Dzisiaj opowiemy o tym, jak nasz program może pracować z wieloma, wieloma zmiennymi. Jak to zrobić dobrze?
Tablice, listy i inne tatałajstwo:
Nasze programy potrafią już robić całkiem sporo rzeczy. Niemniej jednak, są bardzo ograniczone przez liczbę szuflad (zmiennych), do których możemy wsadzać nasze ciekawe wyniki wyrażeń. Jak np. zrobić program, który ma 1000 zmiennych od z1 do z1000 i wypisuje je w odwrotnej kolejności? Ktoś sprytny potrafi to zrobić bez tych rzeczy, które poznamy teraz (
jak? To całkiem ciekawe pytanie!). Załóżmy, że chcielibyśmy zrobićwypisywanie 10 liczb. Zapewne kod musiałby wyglądać tak:
Kod:
z1 = 10
z2 = 11
z3 = 12
...
z10 = 92
print z10
...
print z1
Ale widzimy, że to jest kiepskie. Nikomu nie chciałoby się wstawiać 100 zmiennych, albo 1000. Można by napisać program, który... napisałby nam taki program. Nie możemy też skorzystać z pętli, a nawet jeśli, to musielibyśmy wstawić tam 10 ifów - co wcale nam nie pomaga.
Chcielibyśmy mieć jakieś sensowne rozwiązanie. W C i w Pythonie rozwiązujemy taki problem za pomocą
tablic (C), oraz
list (Python). Tak naprawdę to bardzo podobne rzeczy, tylko tablice są o wiele bliżej naszego modelu pamięci. Kolejną część oprę o listy, aczkolwiek końcówkę dla wymiataczy C poświęcę tablicom.
Lista? Cóż to takiego?
Na pewno widzieliście już listy w życiu. Lista długów Plutona mogłaby wyglądać tak:
Kod:
LISTA PLUTONA:
1. Czwartek, 5000 zł
2. Piątek, 1823 zł
3. Haracz dla Torg Usera, 500 zł
Taka lista ma swoją nazwę i
pozycje. Zamieniając taką listę na taką:
Kod:
LISTA PLUTONA:
0. Czwartek, 5000 zł
1. Piątek, 1823 zł
2. Haracz dla Torg Usera, 500 zł
Jesteśmy już bardzo blisko tego, czym są listy w językach programowania. Mianowicie, w dużym uproszczeniu są to zmienne, które przechowują wiele wartości jednocześnie.
Lista Plutona w pythonie wyglądałaby następująco:
Kod:
listaPlutona = ["Czwartek, 5000 zł", "Piątek, 1823 zł", "Haracz dla Torg Usera, 500 zł"]
Ważne są tutaj nawiasy [, oraz ]. Wartości w liście są podawane po przecinku.
Aby dostać się do pierwszej wartości, musimy podać programowi do której wartości chcemy się odwołać. Tutaj przydadzą się znów nasze kwadratowe nawiasy:
Kod:
listaPlutona[0] # pierwszy element
listaPlutona[1] # drugi element
i tak dalej...
Wartości w liście idą od 0 do x-1, gdzie x to liczba elementów w liście. Mając listę 8-elementową, możliwe indeksy to 0, 1, 2, 3, 4, 5, 6, 7.
Indeksami nazywamy te 'pozycje', które wartości zajmują w liście.
No ok, mamy zmienną, która przechowuje wiele wartości. Taka lista nie pomogłaby nam jednak zbytnio, gdybyśmy nie mogli dodawać do niej elementów, usuwać elementy, oraz wiedzieć, ile ich mamy.
Aby dowiedzieć się, ile trzymamy elementów w liście, możemy skorzystać z magicznego zaklęcia len:
Kod:
len([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]) # 10
len([[1,2], [3,4]]) # 2 (tak, możemy zagnieżdżać listy)
len([[1,2,3], 4, 6, "Cześć", [7]][0]) # 3. Pierwszy element w liście też jest listą, a za pomocą [0] się do niego dostaliśmy. Stąd też liczymy długość tej listy.
Aby dodawać elementy do listy (na koniec), należy skorzystać z magicznego zaklęcia append lub insert. Tutaj uwaga, w przeciwieństwie do len, append jest pisane po kropce przy liście. Dlaczego i czym są te zaklęcia? Opowiemy w następnej części.
Kod:
l = [1,2,3,4]
l.append(5) # l = [1,2,3,4,5]
l.append(8) # l = [1,2,3,4,5,8]
l.insert(2, -1) # l = [1,2,-1,3,4,5,8] Wstawiamy element -1 na przed drugą pozycją (czyli będzie 0, 1, <nowy element>, 2...)
l.insert(0, -100) # l = [-100, 1, 2, -1, 3, 4, 5, 8]. Tak wsadza się element na początek listy.
l.insert(len(l), 101) # l = [-100, 1, 2, -1, 3, 4, 5, 8, 101] Jak widać, jest to odpowiednik zaklęcia append.
Możemy również usuwać elementy z list.
Kod:
l = [1,2,3,4]
l.pop() # l = [1,2,3]
l.pop(0) # l = [2,3] możemy podać który konkretnie element usunąć. Jeżeli go nie podamy, zostanie usunięty ostatni element na liście.
l.pop(1) # l = [2]
l.pop() # l = []
W Pythonie też mamy kilka bardzo fajnych rzeczy dotyczących list, dosyć unikalnych dla tego języka.
Zauważmy, że lista jest odpowiednikiem matematycznego ciągu. Podciągiem spójnym nazywamy kawałek takiego ciągu. Np. niech będzie ciąg:
Kod:
(a_n) = (1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
Lista która by była identyczna z tym ciągiem, to:
Kod:
a_n = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
Możemy wziąć kawałek tej listy. Przykład:
Kod:
a_n[0:3] # Weźmy elementy 0, 1, 2. Wynik: [1, 2, 3]
a_n[3:0] # Jeżeli element końcowy jest mniejszy niż początkowy, Python zwróci pustą listę - [].
a_n[-1] # W pythonie jeżeli podamy -1 jako indeks, dostaniemy ostatni element w liście.
a_n[0:-3] # Bierzemy od indeksu 0 do indeksu trzeciego od końca. Czyli indeksy 0, 1, 2, 3, 4, 5, 6. Wynik: [1, 2, 3, 4, 5, 6, 7]
Jest to czasami przydatne. Przydatną rzeczą jest to, że indeksy działają także dla napisów. Zatem możemy np. zrobić coś takiego:
Kod:
"Pluton"[2:6] # "uton"
Jednym z rozpoznawalnych i bardzo ciekawych uprzyjemniaczy w Pythonie są tzw. wyrażenia listowe. Pozwalają one na tworzenie list na podstawie innych list, korzystając z warunku:
Kod:
losoweLiczby = [-8, 1, -23, -82, 22, 11, 7, -5, 3, 0, 2, -8, 19, -29, 10]
losoweLiczbyUjemne = [ ujemnaLiczba for ujemnaLiczba in losoweLiczby if ujemnaLiczba < 0 ] # Definiujemy sobie listę losoweLiczbyUjemne, korzystając z listy losoweLiczby. Do tej listy zostaną dodane te wartości z listy losoweLiczby, które spełniają warunek ujemnaLiczba < 0
Wyrażenie listowe ma taki schemat:
Kod:
[<wyrażenie tworzące element> for <zmienna> in <lista> if <warunek>]
[<wyrażenie tworzące element> for <zmienna> in <lista>]
Pod <zmienna> zostanie podstawiony każdy element z listy <lista>. Możemy tej zmiennej używać w <wyrażenie tworzące element>. Zostanie on dodany tylko wtedy, gdy <warunek> jest prawdziwy. Jeżeli warunku nie ma (druga wersja), stworzymy listę, która po prostu przekształca elementy listy <lista> za pomocą jakiegoś wyrażenia. Na przykład:
Kod:
lista = [1,2,3,4]
y = 4
[(x + 1 + y) for x in lista if x >= 2]
Wynik:
Polecam poeksperymentować z tym w interaktywnym shellu.
W językach programowania listy są tak rozpowszechnione, że powstały specjalne konstrukcje ułatwiające prace z nimi. Jedną z nich jest specjalna pętla, która przechodzi przez wszystkie elementy w liście:
Kod:
for <zmienna> in <lista>:
# kod
W bloku # kod możemy korzystać ze zmiennej, do której zostanie przypisany element listy. Pętla for najpierw wykona kod ze zmienną przypisaną do pierwszego elementu, potem do drugiego i tak dalej.
Przykładowo wypisanie listy możemy zrobić w ten sposób:
Kod:
l = [1,2,3,4]
for element in l:
print element
To bardzo dużo materiału, może być nowy.
Zachęcam, naprawdę zachęcam do zabawy w shellu i ZADAWANIA PYTAŃ. Nie jest żadnym wstydem nie ogarnąć tego po tylko przeczytaniu. Najlepiej pobawcie się z tym, to najlepszy sposób na naukę. Zobaczcie na przykład, co się stanie, gdy w trzyelementowej liście spróbujemy dostać się do 10 elementu.
O C słów kilka:
Wspominałem w pierwszej części, że C jest językiem zdecydowanie bliżej maszyny. Listy (tablice) są tutaj kanonicznym przykładem na to, że poruszamy się jednak w niższej warstwie niż Python.
Tablice w C to bardzo biedny kuzyn Pythonowych list. Nie możemy do nich dodawać, ani usuwać elementów (mają stały rozmiar). Nie możemy się spytać "hej, ile masz elementów?". Niemniej jednak, takie różnice sprawiają m.in., że C jest zdecydowanie szybsze od Pythona. Postaram się wytłumaczyć czym są tablice i jak sobie radzić z problemami.
Pamiętacie nasze szufladki? Zmienne były etykietkami, które pozwalały nam na szybkie dostanie się w naszej szafie do danej szufladki. Oczywiście, to są tylko nasze nazwy. Tak naprawdę, nasze szufladki mają swoje 'wbudowane' nazwy. Są to tzw.
adresy. Mówią nam one, gdzie w całej komodzie znajduje się nasza szuflada. Jest to numer szuflady. Później dowiemy się o tym, że tak naprawdę to ma bardzo potężne konsekwencje - na razie opowiem o tym, do czego może się to nam przydać w kontekście tablic.
Wyobraźmy sobie, co się dzieje w naszej szafie (pamięci), gdy w C napiszemy coś takiego:
Wtedy, Szafarz (nasz system operacyjny) robi kilka rzeczy. Przede wszystkim, szuka nowej szuflady. Musi być ona nieużywana, czyli nie mieć naklejonej etykietki. Jeżeli znajdzie on taką szufladę, sprawdza, czy ma ona odpowiedni rozmiar. Pisząc int, dostarczamy szafarzowi informacji - ta zmienna jest takiego typu, więc potrzebujemy do niej szuflady o określonej wielkości. Jeżeli ma odpowiedni rozmiar, szafarz nakleja etykietę na szufladę, którą znalazł.
Potem następuje przypisanie - twoje 12 wpada do szafy, a to co było wcześniej w środku wyparowuje. Zauważmy, że
szafarz nie opróżnia szafy - jeżeli zadeklarujemy zmienną, a jej nie zdefiniujemy, wśrodku znajdująsię śmieci. To częsty błąd początkujących programistów.
Ok. Mamy zatem zmienną. A co, jeżeli chcielibyśmy mieć zmienną przechowującą 5 wartości?
Kod:
int x[5] = { 1, 2, 4, 8, 9 };
Teraz Szafarz działa trochę inaczej. Daje on nam gwarancje, że
dane w tablicy będą umieszczone razem w pamięci. W takim wypadku bierze on liczbę elementów tablicy i szuka pięciu szuflad o określonej wielkości. Jeżeli je znajdzie,
nakleja etykietkę x na pierwszą szufladę w rzędzie. Później będziemy mogli mówić, że x jest wskaźnikiem na pierwszy element tablicy (cokolwiek to znaczy). Z racji tego, że szafarz nie jest głupi i wie jaką wielkość ma int, odpytując go o:
Szafarz bierze x, patrzy na adres - numer szuflady i przechodzi o 2 szuflady do przodu, wyciąga co jest w środku i daje Ci.
W szczególności, nikt nie wie, ile elementów ma ta tablica. To wie tylko i wyłącznie programista. Jeżeli spróbujesz dostać się do 6. elementu, Szafarz zabije Twój program. Nie wolno tykać szuflad, których szafarz Ci nie przydzielił. Szafarz czuwa niczym Torg User.
Nie ma pętli przechodzącej przez wszystkie elementy. Niemniej jednak, istnieje specjalna pętla, która pomaga bardzo przy przechodzeniu elementów w tablicy. Przykładowo:
Kod:
int n = 100;
int t[n];
// wypełniamy wartości tablicy tutaj, jakiś kod...
// ...
// wypisujemy wartości:
int i;
for(i = 0; i < n; ++i) {
printf("%d ", t[i]);
}
To taki wariant pętli while, który ma postać:
Kod:
for(<co ma się zdarzyć, nim pętla ruszy>; <warunek pętli>; <co ma się stać po każdym obrocie pętli>) {
<kod>
}
Przykładowo, ta pętla ustawi na początek wartość 0. Potem sprawdzi warunek. 0 < 100, więc uruchomi ciało pętli. Wypisze t[0], czyli
pierwszy element tablicy t. Po tym stanie się to, co staje się w <co ma się stać po każdym obrocie pętli>. W tym wypadku - wartość i powiększy się o 1 (i++ to ładny zapis na i = i + 1, jeszcze ładniejszy niż i += 1. ++i podobnie, jest niewielka różnica pomiędzy tymi zapisami). Potem znowu zostanie sprawdzony warunek, 1 < 100... i tak dalej.
I to tyle. Jesteśmy ograniczeni przez rozmiar tablicy - nie musimy oczywiście używać wszystkich elementów (i tak zazwyczaj się robi usuwanie/dodawanie elementów w tablicach - tworzy się zmienną rozmiar i powiększając / pomniejszając ją udajemy, że dodajemy/usuwamy elementy w tablicy). Nie ma tutaj tak ładnych konstrukcji jak w Pythonie.
Nie robię listy zadań dopóki nie pobawicie się przykładami. Chciałbym, żebyście zadawali pytania ;)
Powodzenia, koderzy!
Pozdrawiam
Killavus
Zakładki