Struktury danych

Struktury danych są bardzo istotnym elementem całego informatycznego świata. Każda rzecz jest jakąś daną zapisaną w jakiejś mniej lub bardziej skomplikowanej strukturze. Najprostszym przykładem może być liczba zapisana na zmiennej x=8. Jednak w praktyce zapisywanie wszystkiego na zmienne jest zupełnie niewygodne. Wyobraźmy sobie, że mamy do zapisania 100 kolejnych wyników jakiegoś doświadczenia. Dla przykładu możemy myśleć o bardzo życiowej (dla hodowców drożdży ;) sytuacji: mamy hodowle drożdży i badamy jak wysokie stężenie alkoholu są w stanie znieść, bez większego problemu z mnożeniem się. Możemy oczywiście napisać:

d0=10
d1=8
d2=10
d3=12
d4=6
d5=8
d6=7
d7=12
d8=10
d9=16
d10=16
d11=9
d12=14
d13=9
d14=11
d15=17
d16=18
d17=9
d18=5
d19=17
d20=11
d21=17
d22=7
d23=7
d24=12
d25=9
d26=5
d27=18
d28=6
d29=7
d30=9
d31=9
d32=6
d33=8
d34=8
d35=11
d36=13
d37=16
d38=8
d39=8
d40=12
d41=5
d42=18
d43=15
d44=17
d45=18
d46=7
d47=8
d48=13
d49=5
d50=12
d51=11
d52=11
d53=12
d54=5
d55=17
d56=7
d57=15
d58=10
d59=14
d60=18
d61=5
d62=8
d63=9
d64=10
d65=14
d66=15
d67=13
d68=16
d69=14
d70=17
d71=16
d72=10
d73=7
d74=14
d75=15
d76=17
d77=11
d78=10
d79=18
d80=18
d81=9
d82=12
d83=18
d84=12
d85=13
d86=7
d87=10
d88=16
d89=12
d90=16
d91=8
d92=11
d93=15
d94=8
d95=7
d96=7
d97=10
d98=13
d99=13

Ten zapis już na pierwszy rzut oka nie wygląda najlepiej: trzeba wymyślić bardzo dużo nazw zmiennych oraz trzeba te nazwy zapamiętać, żeby mieć jak sprawdzić jaką tolerancją cechuje się szczep hodowany w cylindrze51518155. W tym wypadku pamiętamy, że wszystkie zmienne wyglądają podobnie i możemy wpisać nazwę odpowiedniej zmiennej aby wyświetlić jej wartość. Ale co jeśli chcieli byśmy dowiedzieć się jaka jest maksymalna tolerancja? #Funkcja max(x1, x2, x3, ...) #zwraca maksymalną wartość z podanych zmiennych.

O funkcjach więcej dowiemy się w odpowiednim rozdziale, ale już w tym miejscu warto o nich wspomnieć . Na razie przyjmijmy, że funkcja to zbiór poleceń zapisany pod pewną nazwą (trochę jak zmienna), może przyjmować argumenty czyli w tym wypadku argumentami są x1, x2 , x3 i tak dalej (zmienne które zawierają liczby). Funcja może też coś zwrócić. Łatwo zrozumieć zwracanie jeśli spojrzymy na prosty  przykład:

1+2 zwróci nam 3, czli jeśli wpiszemy wynik = 1+2, to zmienna wynik będzie równa 3, tak samo możemy zrobić z funkcją max(1,2) która zwróci nam liczbę 2, więc po wpisaniu wynik = max(1,2) ustawi wartość zmiennej na 2. Nie wszystkie funkcje muszą coś zwracać i nie wszystkie muszą przyjmować jakieś argumenty. Więcej o funkcjach powiemy na następnej stronie.

Lecz wpisanie:

max(d0,d1,d2,d3,d4,d5,d6,d7,d8,d9,d10,d11,d12,d13,d14,d15,d16,d17,d18,d19,d20,d21,d22,d23,d24,d25,d26,d27,d28,d29,d30,d31,d32,d33,d34,d35,d36,d37,d38,d39,d40,d41,d42,d43,d44,d45,d46,d47,d48,d49,d50,d51,d52,d53,d54,d55,d56,d57,d58,d59,d60,d61,d62,d63,d64,d65,d66,d67,d68,d69,d70,d71,d72,d73,d74,d75,d76,d77,d78,d79,d80,d81,d82,d83,d84,d85,d86,d87,d88,d89,d90,d91,d92,d93,d94,d95,d96,d97,d98,d99)

 kosztuje nas sporo pracy, więc na pewno jest prostszy sposób, aby osiągnąć ten sam efekt, bez takiego wysiłku.

Zauważmy, że nazwy zmiennych ustaliliśmy według dość logicznego wzoru dN gdzie d jest skrótem od drożdże a N to numer hodowli. To sugeruje nam, że skoro coś się powtarza wiele razy to można to w jakiś sposób pominąć i pamiętać tylko raz. Taka chęć uproszczenia zapisu wyników prowadzi nas do dużo wygodniejszej struktury danych - listy.

 

Listy

Lista jest strukturą danych, w której możemy w ciągu zapisać dane. Dzięki niej nie musimy definiować wielu zmiennych. Wystarczy, że na liście zapiszemy nasze dane:

d=[10, 8, 10, 12, 6, 8, 7, 12, 10, 16, 16, 9, 14, 9, 11, 17, 18, 9, 5, 17, 11, 17, 7, 7, 12, 9, 5, 18, 6, 7, 9, 9, 6, 8, 8, 11, 13, 16, 8, 8, 12, 5, 18, 15, 17, 18, 7, 8, 13, 5, 12, 11, 11, 12, 5, 17, 7, 15, 10, 14, 18, 5, 8, 9, 10, 14, 15, 13, 16, 14, 17, 16, 10, 7, 14, 15, 17, 11, 10, 18, 18, 9, 12, 18, 12, 13, 7, 10, 16, 12, 16, 8, 11, 15, 8, 7, 7, 10, 13, 13]

a następnie użyjemy tej samej funkcji co poprzednio, jako argument wprowadzając listę, czyli pisząc max(d). Dzięki temu jak interpreter python'a jest skonstruowany, takie użycie daje pożądany efekt.

A co jeśli interesuje nas konkretna, np. siedemnasta wartość z listy? Możemy wpisać to:

print d[16]

dzięki temu dostaniemy wartość siedemnastego elementu z listy. Proste? Nie? Coś nie gra? Już tłumaczę: w programowaniu najczęściej numeracja nie zaczyna się od 1 tylko od zera, stąd w nawiasach kwadratowych jest liczba 16 a nie 17. Przy okazji, te liczby oznaczające pozycję w liście będziemy nazywać ich indeksami. Teraz jasne? No to super, wiemy, że w zbiornikach od 10 do 15 są specjalne, bardzo drogie gatunki drożdży i chcieli byśmy zobaczyć jak wygląda ich tolerancja. Możemy jak poprzednio wpisać

print d[9]
print d[10]
print d[11]
print d[12]
print d[13]
print d[14]

ale to aż 6 linijek kodu który jest bardzo podobny. Skoro tak jest, to chyba spodziewasz się już, że i na to jest jakaś prostsza metoda. Owszem, oto ona: w nawiasach kwadratowych możemy wpisać zakres żeby wybrać fragment naszej długiej listy

print d[9:15]

I tu znów mamy pewną dziwną sytuację związaną z indeksami w liście. Już tłumaczę: pierwszą liczbą jest numer początkowego elementu, drugą liczbą jest numer do którego chcemy wybrać fragment listy (więc musi być o jeden większy niż ostatni numer cylindra który nas interesuje). Może to być mało intuicyjne ale tak już jest.

O naszych cylidnrach wiemy jeszcze jedną rzecz: są ustawione od najstarszego do najnowszego. Więc pewnie chcemy zobaczyć czy starsze hodowle radzą sobie lepiej niż nowe (powiedzmy do 5 hodowli). Możemy więc napisać tak jak poprzednio, ale możemy też zupełnie pominąć jedną z liczb zakresu, wtedy domyślnie brany jest początek lub koniec (w zależności od tego, którą liczbę pominiemy):

print d[:5]

Nie jest to może coś bardzo użytecznego samo z siebie ale warto o tym pamiętać (bo jak zwykle zaraz się przyda).

Logiczne wydało Ci się pewnie, że numeracja jest od lewej do prawej strony listy. Ale nic nie stoi na przeszkodzie, aby to odwrócić - robimy to za pomocą znaku minus

print d[-1]

ten kod weźmie ostatni element listy. Tu znów jest pewna niespójność, bo wpisujemy minus jeden... no cóż, nie istnieje coś takiego jak ujemne zero więc tak po prostu musi być.

Skoro już umiesz wybierać konkretne wartości z listy możesz też zaktualizować wyniki badań z cylindra 12, okazuje się że tolerancja spadła o 0.1. Oto jak można to zrobić:

d[11] = d[11] - 0.1

Zajmijmy się najpierw tym, co jest po znaku równości. Wybieramy 12 element z naszej listy i odejmujemy od niego 0.1. Następnie przypisujemy tą wartość w stare miejsce tak samo jak robiliśmy to ze zwykłymi zmiennymi. Wygodne, prawda?

Dostaliśmy przed chwilą od zaprzyjaźnionego laboratorium nowe drożdże, już są w cylindrze i mamy już pierwsze wyniki badań ich tolerancji. Ale co teraz? Trzeba by je jakoś dopisać do naszej listy. Robimy to za pomocą #metody append,  jako argument podając wartość jaką chcemy dopisać na końcu listy.

UWAGA metoda jest po prostu funkcją, tyle, że "należy" do obiektu (w tym wypadku do listy). Wywołując taką metodę "mówimy" liście, że ma dopisać sobie jeszcze jedną wartość.

d.append(14)
print d

Jak widać jest to dość proste, powyższa metoda zmodyfikowała naszą listę tak aby zawierała kolejny element.

Warto od razu przedstawić po krótce wszystkie dostępne metody (jednak te powyższe na codzień są zazwyczaj wystarczające):

  • Metoda extend służy do rozszerzenia naszej listy o jakąś inną listę.
  • Metoda insert służy do wstawienia wartości w dowolne miejsce na liście. Pierwszy argument to numer, który chcemy aby nasz wstawiany element (drugi argument) miał w kolejności w liście.
  • Metoda remove usuwa wartość z listy (jeśli wartość występuje więcej niż raz, usuwana jest ta, która na liście występuje jako pierwsza).
  • Metoda pop służy również do usuwania, ale jako argument przyjmuje numer elementu listy (zwany także najczęściej jako indeks) a dodatkowo zwraca nam wartość tego elementu. Uwaga, możemy nie podawać tej metodzie żadnego argumentu, wtedy domyślnie z listy jest wyrzucany ostatni element.
  • Metoda index służy do szukania wartości na liście, zwraca listę z numerami miejsc, na których występuje poszukiwana przez nas wartość.
  • Metoda sort służy do posortowania listy, może przyjmować argumenty, ale jak na razie zostawmy ją w jej najprostszej postaci.
  • Metoda reverse odwraca kolejność listy.

Dobra wiadomość jest taka, że znajmość tych metod jest w zupełności wystarczająca do pełnego wykorzystania możliwości list.

Oprócz metod, dla list dostępnych jest też wiele funkcji pomocniczych. Np. fajną funkcją, o której warto pamiętać jest range. Funkcja ta generuje listę o danej długości z kolejnymi liczbami całkowitymi od zera.

lista = range(15)
print lista

Słowniki

Kolejną wygodną strukturą danych są słowniki. Pozwalają nam uporządkować dane według kategorii, w formie "klucz:wartość". Zobaczmy to na przykładzie, którym będzie szczep drożdży, który otrzymaliśmy na płytkach z pożywką agarową (specjalne pojemniki, w których hoduje się mikroorganizmy). Na każdej z nich jest inna ilość kolonii (skupisk). Wpiszmy je do słownika w postaci "płytka1":101...

dictionary = {'plytka1':101, 'plytka2':201, 'plytka3': 302, 'plytka4':102}

Wygląda to pięknie, wszystko jest uporządkowane, dużo danych na jednej zmiennej... ale jak wyjąć stąd jakieś dane? Jeśli spróbujemy zrobić to tak samo jak z listami, dostaniemy błąd. Wyjątkiem jest, gdy kluczem jest liczba całkowita.

Do wartości w słowniku dostajemy się używając klucza, więc aby dowiedzieć się co mamy na konketnej płytce należy wpisać:

print dictionary['plytka1']

Jeśli chcemy zobaczyć cały słownik, robimy to w taki sam sposób jak w przypadku listy lub zmiennej.

print dictionary

Nasz słownik może zawierać bardzo dużo kluczy, gdy nie jesteśmy pewni, czy jest tam akurat ten którego szukamy oraz nie możemy znaleźć go przy wywołaniu "dictionary['plytka1']", może się okazać, że po prostu zrobiliśmy literówkę we wpisywaniu nazwy klucza. Dlatego, aby przejrzyście zobaczyć nazwy wszystkich kluczy możemy użyć metody keys().

Jeśli chcemy wiedzieć, czy jakaś płytka zawierała 20 koloni, to wtedy używamy metody values() .

Zobaczmy jak to wygląda w praktyce:

print dictionary.keys()
print dictionary.values()

UWAGA: Pamiętaj, że kolejność wstawiania kluczy do słownika nie musi odpowiadać kolejności wartości wyświetlonych dzięki tym metodom.

Teraz zastanówmy się, co jeśli zdecydujemy się założyć nową hodowlę na płytce... Trzeba wtedy dodać ją do naszego słownika. Niestety nie zadziała tu funkcja append(), ale możemy to zrobić w inny (może nawet prostszy) sposób:

dictionary['plytka5']=0

Ostatni przypadek jaki może sie nam zdarzyć z naszymi grzybami, to, to że mimo naszych najlepszych starań nie wyrosną. Podobnie jak w przypadku list użyjemy del na nieudanym doświadczeniu.

Tak samo możemy postąpić jeśli znudzi nam się hodowanie drożdży na płytkach z pożywką.

print dictionary
del dictionary['plytka5']
print dictionary
del dictionary
print dictionary

Zawarte tu informację, na pewno ułatwią Ci dalszą pracę z pisaniem kodu w pythonie, szczególnie w sytuacjach, kiedy potrzebne Ci będzie katalogowanie jakichś informacji. Wykorzystaj je we własnych programach.

Pod koniec tego samouczka pokażemy też specjaną, ulepszoną wersję słowników.