3. Liste

Sjećate se nizova? Znate, one grupe elemenata istog tipa, fiksne dužine, koje ste koristili kad treba čuvati puno podataka. E pa, liste u Pythonu su jako slične nizovima u drugim programskim jezicim, sa jednom ili dvije "sitne" razlike koje ćemo vidjeti u nastavku.

3.1. Gdje početi?

Pa, red bi bio da prvo napravimo jednu listu. Kao što joj ime kaže, lista predstavlja listu elemenata ma nije valjda, odvojenih zarezima, a okruženu uglastim zagradama. To bi izgledalo nekako ovako:

In [17]:
sadrzajNovcanika = [10, 20, 10, 50, 5, 10] # Pfeninga, ne KM :(
print('Sadržaj novčanika:', sadrzajNovcanika)

komsijeOdKojihKrademWiFi = ['Petrović', 'Galić', 'Čajić', 'Stojaković']
print('Komšije od kojih kradem WiFi:', komsijeOdKojihKrademWiFi)

# Lista može biti i prazna
ljudiPametnijiOdMene = []
print('Ljudi pametniji od mene:', ljudiPametnijiOdMene)
Sadržaj novčanika: [10, 20, 10, 50, 5, 10]
Komšije od kojih kradem WiFi: ['Petrović', 'Galić', 'Čajić', 'Stojaković']
Ljudi pametniji od mene: []

Ako vas slučajno zanima koliko neka lista ima elemenata, funkcija len je sve što vam treba. Poslije ćemo vidjeti da ta funkcija ne radi samo sa listama, već i sa nekim drugim tipovima podataka.

In [18]:
mojaLista = [1, 2, 3, 4]
print('Lista', mojaLista, 'je dužine', len(mojaLista))
Lista [1, 2, 3, 4] je dužine 4

3.2. Prva razlika: Tipovi elemenata

Kad sam rekao da su razlike između liste i niza sitne, možda sam malo slagao...

Naime, kako tip promjenljive nije fiksan u Pythonu, nema smisla govoriti o listama cijelih brojeva ili listama stringova, kad tip tih elemenata može biti promjenjen. Što nas dovodi do prve razlike između klasičnog niza i Pythonove liste: elementi liste ne moraju biti istog tipa.

Iako ovo zvuči mnogo cool, ustvari i nije toliko - postoji jako malen broj problema koji možete riješiti brže i elegantnije tako što ćete stavljati stvari različitog tipa u listu. Za sve ostalo tu je Mastercard? ova osobina i nema nešto velik značaj.

In [29]:
mojaMjesovitaLista = [[], 1, 'dva', 3, [4, 5, 6]]
print('Lista raznih elemenata', mojaMjesovitaLista)

oblastiUpotrebe = []
print('Upotrebljava se u', oblastiUpotrebe)
Lista raznih elemenata [[], 1, 'dva', 3, [4, 5, 6]]
Upotrebljava se u []

3.3. Pristup elementima

Elementi liste se obično posmatraju nezavisno jedan od drugog, pa je osnovna operacija koju lista pruža upravo pristup pojedinačnim elementima ili kao što ćemo vidjeti, grupama elemenata. Počnimo onda sa dobrim starim, indeksiranjem.

U Pythonu, indeksi niza počinju od 0, tako da se za listu mojaLista prvi elementi nalazi na poziciji 0, a poslednji na poziciji len(mojaLista) - 1.

Kao što je to običaj u mnogim programskim jezicima, operator indeksiranja su uglaste zagrade - [ ], a radi otprilike ovako:

In [20]:
mojaLista = [14, 8, 7, 12, 11]
mojaLista[0] = 100
print('Zbir prvog i petog je', mojaLista[0] + mojaLista[4])

print('Deseti element:', mojaLista[9])
Zbir prvog i petog je 111
---------------------------------------------------------------------------
IndexError                                Traceback (most recent call last)
<ipython-input-20-ddc514baa015> in <module>()
      3 print('Zbir prvog i petog je', mojaLista[0] + mojaLista[4])
      4
----> 5 print('Deseti element:', mojaLista[9])

IndexError: list index out of range

Prvi dio ispisa je očekivan - dobili smo zbir prvog i petog elementa liste. Drugi dio nije baš najsrećniji - naša lista ima 5 članova, a mi smo htjeli ispisati deseti, što i nema mnogo smisla. Pythonu to takođe nema smisla, pa nas je obradovao sa šarenom porukom "IndexError: list index out of range".

3.3.1. Negativni indeksi

Pristupanje elementima koji se nalaze na kraju niza često zna biti pipav zadatak - potrebno je naći dužinu niza, pa od nje oduzeti 1 ili treba dodati? jer indeksi kreću od nula, pa onda oduzeti ili ovde treba dodati? redni broj elementa sa desne strane kojem hoćemo da pristupimo. Komplikovana procedura, pa se lako zbuni.

Python dozvoljava korištenje negativnih indeksa upravo da riješi ovaj problem. S obzirom da su -0 i 0 isto, poslednji element, odnosno prvi sa desne strane, se nalazi na indeksu -1, onaj prije njega na indeksu -2 i tako dalje.

In [21]:
mojaLista = [14, 8, 7, 12, 11]
print('Zbir poslednja dva je', mojaLista[-1] + mojaLista[-2])

# Naravno, kao i sa pozitivnim indeksima, i ovde možete pretjerati
print('Deseti element sa desne strane je', mojaLista[-10])
Zbir poslednja dva je 23
---------------------------------------------------------------------------
IndexError                                Traceback (most recent call last)
<ipython-input-21-c8031e6e27f0> in <module>()
      3
      4 # Naravno, kao i sa pozitivnim indeksima, i ovde možete pretjerati
----> 5 print('Deseti element sa desne strane je', mojaLista[-10])

IndexError: list index out of range

3.4. Druga razlika: Dodavanje i brisanje elemenata

U mnogim programskim jezicima, veličina niza je fiksna, te se navodi prilikom njegovog kreiranja, pa ako se preračunate, završićete sa mnogo neiskorištenog prostora ili sa elementima koje ne možete smjestiti u niz.

Za razliku od nizova, liste u Pythonu nisu fiksne veličine, već rastu i smanjuju se po potrebi, prilikom dodavanja, odnosno brisanja elemenata. Za dodavanje elemenata, koristićemo metode append, za dodavanje na kraj, odnosno insert, za umetanje elementa na određenu poziciju u nizu. Da bi se napravilo mjesta za element koji se ubacuje u niz, svi elementi koji su desno od novog elementa se "pomjere" za jedno mjesto u desno. Za brisanje bilo kog od elemenata, tu je naredba del.

In [22]:
pocetnaLista = [14, 8, 7, 12, 11]
print('Original:', pocetnaLista)

# Dodamo element na kraj
pocetnaLista.append(999)
print('Nakon dodavanja na kraj:', pocetnaLista)

# Umetnemo element na poziciju 2
pocetnaLista.insert(2, 100)
print('Nakon umetanja na poziciju 2:', pocetnaLista)

# Obrišimo ono što smo dodali:
del pocetnaLista[-1] # Element na kraju
del pocetnaLista[2]  # Element na poziciji 2

print('Nakon brisanja:', pocetnaLista)

# Ovde ne možemo pretjerati sa indeksima:
# Ako je indeks prevelik, element se dodaje na kraj
# Ako je indeks previše malen, element se dodaje na početak
pocetnaLista.insert(200, 'ja sam na kraju')
pocetnaLista.insert(-50, 'ja sam na pocetku')

print('Konačna lista', pocetnaLista)
Original: [14, 8, 7, 12, 11]
Nakon dodavanja na kraj: [14, 8, 7, 12, 11, 999]
Nakon umetanja na poziciju 2: [14, 8, 100, 7, 12, 11, 999]
Nakon brisanja: [14, 8, 7, 12, 11]
Konačna lista ['ja sam na pocetku', 14, 8, 7, 12, 11, 'ja sam na kraju']

3.5. Sjeckanje liste

Zvanični termin za ovu operaciju je slice, ali pošto sliceovanje i slajsovanje zvuče malo, nakaradno, zadržaćemo se na sjeckanju. Operacija je jednostavna - iz liste isjeći određen dio. Kad će mi to trebati? Pa recimo:

  • Zanima vas ekstenzija fajla - odsječete poslednja dva/tri karaktera
  • Zanima vas godina nečijeg rođenja, a imate njegov matični broj - odsječete sve od petog do osmog karaktera
  • Zanima vas 5 najboljih i 5 najgorih rezultata na Olimpijadi
  • Zanima vas naziv sajta na kom se trenutno nalazite
  • I još mnogo toga

Slično indeksiranju, koristi se operator [ ], ali ovaj put ne navodimo jedan broj, već dva, koje predstavljaju granice onog što želimo da isječemo, a odvojene sa dvotačkom ( : ). Mnogo bitno za napomenuti, sječenje uzima sve element počev od prvog, do ali ne uključujući poslednjeg. Za razliku od indeksiranja, rezultat sječenja je uvijek lista, koja u zavisnosti od granica sječenja može biti prazna, imati jedan ili više elemenata.

In [23]:
pocetnaLista = [14, 8, 7, 12, 11]
print('Prva tri elementa', pocetnaLista[0:3])

print('Svi elementi osim poslednjeg', pocetnaLista[0:-1])

# Iako je rezultat samo jedan element, sječenje vraća niz!
print('Samo prvi element', pocetnaLista[0:1])

# Ili nekad prazan
print('Nepostojeći isječak', pocetnaLista[100:120])
Prva tri elementa [14, 8, 7]
Svi elementi osim poslednjeg [14, 8, 7, 12]
Samo prvi element [14]
Nepostojeći isječak []

S obzirom da često želimo prvih nekoliko ili poslednjih nekoliko elemenata, postoji nekoliko skraćenica:

  • Ako se izostavi prva granica, podrazumijeva se da je nula
  • Ako se izostavi poslednja granica, podrazumijeva se da je jednaka dužini niza (ovo se radi da bi bio obuhvaćen i poslednji element)
  • Ako se izostave obe granice, vraća se kopija liste
In [24]:
pocetnaLista = [14, 8, 7, 12, 11]
print('Prva dva elementa', pocetnaLista[:2])

print('Poslednja dva elementa', pocetnaLista[-2:])

# Kopiraj listu
kopijaListe = pocetnaLista[:]

# Obriši poslednji
del kopijaListe[-1]

print('Original', pocetnaLista)
print('Izmjenjena', kopijaListe)
Prva dva elementa [14, 8]
Poslednja dva elementa [12, 11]
Original [14, 8, 7, 12, 11]
Izmjenjena [14, 8, 7, 12]

3.5.1. Zamjena isječka

Sjeckanje liste omogućava ne samo pristup određenom dijelu liste za čitanje, već taj dio možemo i mijenjati. Ono čime mijenjamo isječak jeste nova lista (ne mora biti iste dužine kao isječak), a rezultat je originalna lista, gdje je isječak zamjenjen novom listom. Mala napomena - ovo se ne odnosi na odsječak liste koji ste dodijelili nekoj promjenljivoj, pa onda tu promjenljivu mijenjali. Odnosi se isključivo na direktnu dodjelu isječku. Primjer:

In [25]:
# Zamjena je iste dužine
mojaLista = [1, 2, 100, 200, 5, 6]
mojaLista[2:4] = [3, 4]
print('Zamjena iste dužine', mojaLista)

# Zamjena je kraća
mojaLista = [1, 2, 100, 200, 5, 6]
mojaLista[2:4] = []
print('Zamjena kraćom listom', mojaLista)

# Zamjena je duža
mojaLista = [1, 2, 100, 200, 5, 6]
mojaLista[2:4] = [3, 3.25, 3.5, 3.75, 4]
print('Zamjena dužom listom', mojaLista)

# Ovo ne radi (pokušaj primjera iznad)
mojaLista = [1, 2, 100, 200, 5, 6]
promjenljiva = mojaLista[2:4]
promjenljiva = [3, 3.25, 3.5, 3.75, 4]
print('Zamjena dužom listom (pokušaj)', mojaLista)
Zamjena iste dužine [1, 2, 3, 4, 5, 6]
Zamjena kraćom listom [1, 2, 5, 6]
Zamjena dužom listom [1, 2, 3, 3.25, 3.5, 3.75, 4, 5, 6]
Zamjena dužom listom (pokušaj) [1, 2, 100, 200, 5, 6]

3.5.2. Korak

Iako sjeckanje liste omogućava da isječemo dio koji nas zanima, ograničeni smo činjenicom da možemo isjeći samo nekoliko uzastopnih elemenata. Šta ako vas zanima svaki stoti kupac, svaki treći zadatak?

Potpuna sintaksa za sjeckanje liste jeste operator indeksiranja [ ], u kojem se navode tri broja, odvojena dvotačkama ( : ). Prva dva smo do sad vidjeli - početak isječka i njegov kraj. Onaj treći nam govori da li uzimamo svaki element, svaki drugi, svaki peti i slično. Ako se ne navede on, ili se izostavi druga dvotačka, podrazumijeva se da je njegova vrijednost 1.

Kao i kod indeksa, niko nas ne primora da stavimo pozitivan broj.

Evo kako to izgleda u praksi:

In [26]:
mojaLista = [1, 2, 100, 200, 5, 6]
print('Elementi na neparnim indeksima', mojaLista[::2])
print('Elementi na parnim indeksima', mojaLista[1::2])

# Najčešća primjena negativnog indeksa - okretanje liste
print('Okrenuta lista', mojaLista[::-1])

# Dodjeljivanje takođe radi ...
mojaLista[::2] = [0, 0, 0]
print('Dodjela neparnim indeksima', mojaLista)

# ... ali samo kad nova lista ima isto elemenata koliko i isječak
mojaLista[1::2] = [0]
Elementi na neparnim indeksima [1, 100, 5]
Elementi na parnim indeksima [2, 200, 6]
Okrenuta lista [6, 5, 200, 100, 2, 1]
Dodjela neparnim indeksima [0, 2, 0, 200, 0, 6]
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-26-cbebefc084d2> in <module>()
     11
     12 # ... ali samo kad nova lista ima isto elemenata koliko i isječak
---> 13 mojaLista[1::2] = [0]

ValueError: attempt to assign sequence of size 1 to extended slice of size 3

Index