Seznami

Recimo, da smo zbrali teže šestih študentov. Seznam tež zapišemo takole:

teze = [74, 82, 58, 66, 61, 84]

Seznam (angl. list) je zaporedje česarkoli, recimo števil, lahko pa tudi česa drugega. Števila ali kaj drugega naštejemo, vmes pišemo vejice in vse skupaj zapremo v oglate oklepaje [ in ]. Za primer sestavimo še seznam imen študentov:

imena = ["Anze", "Benjamin", "Cilka", "Dani", "Eva", "Franc"]

in seznam, ki bo povedal, ali gre za študenta ali študentko

studentka = [False, False, True, False, True, False]

Seznami lahko vsebujejo tudi še hujšo eksotiko. Imamo lahko, recimo, seznam seznamov - vanj bomo, prikladno, stlačili (pod)sezname teža-ime-spol:

podatki = [
    [74, "Anze", False],
    [82, "Benjamin", False],
    [58, "Cilka", True],
    [66, "Dani", False],
    [61, "Eva", True],
    [84, "Franc", False],
    ]

To sicer navadno delamo malo drugače (pogosto celo precej drugače), a dokler tega še ne znamo, bo dobro tudi tako. Mimogrede opazimo še dve stvari: seznam lahko, če želimo, raztegnemo v več vrst, preprosto tako, da ne zapremo oklepaja. Naredili bi lahko tudi tole

teze = [74, 82, 58,
        66, 61, 84]

Seznami lahko vsebujejo tudi še hujšo hujšo eksotiko. V resnici lahko vsebujejo karkoli, celo, recimo, funkcije:

from math import *

par_funkcij = [sin, cos, radians]

Čemu bi kdo hotel narediti seznam funkcij?! I, zato da jih bo klical, seveda. Pa kdaj kdo kliče funkcije s seznama?! Da, včasih pride prav.

Nikjer tudi ne piše, da morajo biti vsi elementi seznama istega tipa. To smo pravzaprav že izkoristili: podseznami s težo, imenom in spolom so vsebovali število, niz in logično vrednost. Navrgli bi lahko še dve funkciji in en seznam; pa ne bomo, to se ne dela. Če želimo sezname različnih tipov, raje uporabimo terke. Kaj so terke, bomo izvedeli vsak čas.)

Seznam je lahko tudi prazen

prazen = []

ali pa ima en sam element

samo_edini = [42]

Ali pa je poln praznih seznamov

polnoprazen = [[], [], [], [], [], []]

Terka

Terka (angl. tuple) je podobna seznamu. Kakšna je pomembna razlika, bomo še videli, manj pomembna pa je ta, da pri terki namesto oglatih oklepajev uporabljamo kar okrogle. (Skoraj) vse ostalo ostane enako:

teze = (74, 82, 58, 66, 61, 84)
imena = ("Anze", "Benjamin", "Cilka", "Dani", "Eva", "Franc")

Pri terkah se nam - zaradi tega, kje in kako jih uporabljamo, pogosteje zgodi, da vsebuje elemente različnih tipov. Recimo takole:

student = (74, "Anze", False)

Tudi seznam teža-ime-spol bi raje naredil takole:

podatki = [
    (74, "Anze", False),
    (82, "Benjamin", False),
    (58, "Cilka", True),
    (66, "Dani", False),
    (61, "Eva", True),
    (84, "Franc", False),
    ]

Tako kot seznami so tudi terke lahko prazne (()) ali pa imajo po en sam element. Tu imamo težavo pri terki z enim samim elementom: Če bi napisali (42) to ni seznam, temveč le 42 v oklepaju (odvečnem, seveda). Da bi povedali, da gre za terko, moramo dodati še vejico:

samo_en = (42, )

Terko smemo, zanimivo, pisati tudi brez oklepajev.

>>> t = 1, 2, 3, 4
>>> t
(1, 2, 3, 4)

Ker to ni preveč pregledno, pa to počnemo le v posebnih primerih, na katere bomo sproti opozorili.

Razpakiranje v elemente

Včasih imamo seznam ali terko z nekaj elementi, ki bi jih radi priredili več spremenljivkam. Recimo, da imamo

student = (74, "Anze", False)

Če želimo podatke prirediti spremenljivkam teza, ime in je_zenska, bi to lahko storili takole:

>>> teza, ime, je_zenska = student

Doslej smo imeli pri prirejanju na levi strani vedno eno samo spremenljivko, ki smo ji želeli prirediti vrednost na desni strani enačaja. Tu pa smo na levi našteli več imen spremenljivk (tri), na desni pa mora biti za to terka z enakim številom elementov (tremi). Terka? No, lahko je tudi seznam ali niz.

>>> a, b, c = [1, 2, 3]
>>> a
1
>>> a, b, c = "xyz"
>>> a
'x'
>>> b
'y'
>>> c
'z'

Več ko boste programirali v Pythonu, bolj boste čutili moč terk. Omenimo, recimo, da je z njimi mogoče napisati funkcijo, ki ne vrača ene, temveč več stvari. Vzemimo funkcijo splitext, ki ji damo ime datoteke, pa vrne njeno osnovno ime in njeno končnico.

>>> from os.path import *
>>> film = "Babylon 5 - 3x04 - Passing through Gethsemane.avi"
>>> zac, konc = splitext(film)
>>> zac
'Babylon 5 - 3x04 - Passing through Gethsemane'
>>> konc
'.avi'

V resnici funkcija splitext vrne terko,

>>> from os.path import *
>>> splitext(film)
('Babylon 5 - 3x04 - Passing through Gethsemane', '.avi')

ki pa smo jo kar mimogrede razpakirali v zac in konc.

Seveda bi lahko rezultat funkcije priredili tudi eni sami spremenljivki - v tem primeru bi bila ta spremenljivka terka.

>>> t = splitext(film)
>>> t
('Babylon 5 - 3x04 - Passing through Gethsemane', '.avi')

In seveda bi lahko terko nato še razpakirali.

ime, koncnica = t

Kako v Pythonu zamenjamo vrednosti dveh spremenljivk?

a = "Alenka"
b = "Tina"

Želeli bi, da bi a postal "Tina" in b "Alenka". Naivnež bi napisal tale nesmisel:

a = b
b = a

To seveda ne deluje. V prvi vrstici a-ju priredimo b, se pravi, "Tina", v drugi vrstici pa b-ju a, ki pa je medtem postal "Tina". Tako sta po tem a in b enaka "Tina".

V drugih jezikih se navadno rešimo tako, da vrednost a-ja spravimo na varno, preden ga povozimo.

tmp = a
a = b
b = tmp

V Pythonu pa to ni potrebno. Spremenljivki preprosto zapakiramo v terko, ki jo takoj razpakiramo nazaj, a v obratnem vrstnem redu.

a, b = (b, a)

V resnici navadno ne pišemo tako, temveč izpustimo oklepaje.

a, b = b, a

Oklepaje okrog terke izpuščamo preprosto zato, ker je takšno prirejanje tako pogosto, da ga programer vajen Pythona takoj prepozna. Pri tem tudi ne razmišljamo o terkah, temveč prirejanje preberemo preprosto tako, da a-ju priredimo b in b-ju a.

Zanka for

Python ima dve vrsti zank: zanki while, ki jo že poznamo, dela družbo for.

Zastavimo si preprosto nalogo (in ne čisto smiselno) nalogo: izpišimo teže in kvadrate tež vseh študentov v seznamu. Se pravi (po slovensko):

za vsako težo s seznama teze stori tole:
    izpiši težo in težo na kvadrat

V Pythonu pa prav tako, samo brez sklonov:

for teza in teze:
    print(teza, teza ** 2)

V zanki for se skriva prirejanje. Zanka najprej priredi spremenljivki teza prvi element seznama teze. Nato se izvrši vsa koda bloka znotraj bloka for - tako, kot se je dogajalo v zanki while. V naslednji rundi priredi spremenljivki teza drugi element in spet izvede kodo znotraj bloka, ... ter tako naprej do konca seznama.

Zdaj pa izpišimo vse teže, ki so večje od 70.

for teza in teze:
    if teza > 70:
        print(teza)

Napišimo program, ki pove, kako težak je najlažji študent.

najlazji = 1000
for teza in teze:
    if teza < najlazji:
        najlazji = teza
print(najlazji)

Je potrebno prevesti v slovenščino? Pa dajmo.

za začetek naj bo najlažja teža 1000 (gotovo bomo kasneje našli koga lažjega)
za vsako težo iz seznama tež:
    če je teža manjša od najmanjše doslej:
        najlažja je ta teža
izpiši najmanjšo težo

Deluje ta program samo na seznamih števil? Ali pa bi znal poiskati tudi najmanjši niz? Kako pravzaprav primerjamo nize? Nize primerja, seveda, po abecedi. In, da, program deluje tudi na nizih, vrne "najmanjši" niz - prvi niz po abecedi. Le v začetku moramo napisati najlazji = "ŽŽŽŽŽŽŽŽŽŽŽŽ" namesto najlazji = 1000. (Tole se da sprogramirati tudi tako, da deluje za nize in za števila. A ne bomo ubijali začetnikov s prezapletenimi programi.)

Mimogrede, Python ima že vdelano funkcijo min, ki vrne najmanjši element seznama.

Kako bi izračunali vsoto elementov seznama?

s = 0
for teza in teze:
    s += teza

Pa povprečno vrednost? Za to moramo poznati dolžino seznama. Pove nam jo funkcija len (okrajšava za length, če ji kot argument podamo nek seznam).

s = 0
for teza in teze:
    s += teza
s /= len(teze)

Samo... malo previdni moramo biti. Seznam bi lahko bil tudi prazen. Dogovorimo se, da bo povprečna vrednost v tem primeru 0. Pravilno delujoč program bi bil takšen.

s = 0
for teza in teze:
    s += teza
if len(teze) > 0:
    s /= len(teze)

Zanka for ne deluje le na seznamih. Tako, kot gre prek seznamov, lahko gre tudi prek terk, nizov in še prek mnogih drugih reči. V Pythonu celo za branje datotek pogosto uporabimo for, kot se bomo kmalu naučili.

>>> ime = "Cilka"
>>> for crka in ime:
...     print(crka)
'C'
'i'
'l'
'k'
'a'

Najdi takšnega, ki...

Kako bi ugotovili, ali vsebuje seznam kako sodo število?

s = [11, 13, 5, 12, 5, 16, 7]

imamo_sodo = False
for e in s:
    if e % 2 == 0:
        imamo_sodo = True
if imamo_sodo:
    print("Seznam vsebuje sodo število")
else:
    print("Seznam ne vsebuje sodega števila")

V začetku si rečemo, da nimamo nobenega sodega števila. Nato gremo prek vseh števil in čim naletimo na kakega sodega, zabeležimo, da smo, aleluja, našli sodo število.

Ne naredite klasične napake. Tole je narobe:

s = [11, 13, 5, 12, 5, 16, 7]

imamo_sodo = False
for e in s:
    if e % 2 == 0:
        imamo_sodo = True
    else:
        imamo_sodo = False
if imamo_sodo:
    print("Seznam vsebuje sodo število")
else:
    print("Seznam ne vsebuje sodega števila")

Ta program se bo ob vsakem lihem številu delal, da doslej ni bilo še nobenega sodega - zaradi imamo_sodo = False bo pozabil, da je kdajkoli videl kako sodo število. V gornjem seznamu bo, recimo, pregledal vsa števila, končal bo s 7 in si ob tem rekel imamo_sodo = False. Rezultat bo tako napačen.

Kako pa ugotovimo, ali ima seznam sama soda števila?

sama_soda = True
for e in s:
    if e % 2 != 0:
        sama_soda = False
if sama_soda:
    print("Seznam vsebuje sama soda število")
else:
    print("Seznam ne vsebuje samo sodih števil")

Spet bomo naredili podobno napako kot prej, če bomo pisali:

s = [11, 13, 5, 12, 5, 16, 72]

sama_soda = True
for e in s:
    if e % 2 != 0:
        sama_soda = False
    else:
        sama_soda = True

Program že takoj, ko vidi 1, ugotovi, da niso vsa števila v seznamu soda, in postavi sama_soda = False. Vendar melje seznam naprej in ob vsakem koraku znova nastavlja sama_soda. Ko pride do 72, postavi sama_soda na True. To pa je ravno zadnje število; sama_soda ostane True... pa smo tam.

Prekinjanje zank

Ko smo prejšnji teden pisali zanko while, smo postavili pogoj, do kdaj naj se izvaja. Zanka for bo, če se vmes ne pripeti kaj posebnega, šla vedno od začetka do konca seznama (terke, niza, datoteke...)

Včasih to ni potrebno. Pravzaprav smo pravkar videli takšen primer: rezultat gornjega programa je znan že, čim naletimo na prvo liho število. Torej bi bilo čisto vseeno, če računalnik v tistem trenutku neha pregledovanje. To mu v resnici lahko naročimo.

sama_soda = True
for e in s:
    if e % 2 != 0:
        sama_soda = False
        break
if sama_soda:
    print("Seznam vsebuje sama soda število")
else:
    print("Seznam ne vsebuje samo sodih števil")

Ukaz break pomeni, da želimo prekiniti zanko. Program skoči "ven" iz zanke in nadaljuje z izvajanjem ukazov, ki sledijo zanki.

Mimogrede, na podoben način lahko prekinemo tudi while.

Tule je bil break skoraj bolj zaradi lepšega - da računalnik ne izgublja časa brez potrebe. Poskusimo napisati program, ki takrat, ko seznam ni vseboval samo sodih števil, izpiše prvo liho število.

sama_soda = True
for e in s:
    if e % 2 != 0:
        sama_soda = False
        liho = e
if sama_soda:
    print("Seznam vsebuje samo soda števila")
else:
    print("Seznam ne vsebuje samo sodih števil: prvo liho število je", liho)

Brez break tole ne deluje: namesto prvega izpiše zadnje liho število. Rešimo se lahko z dodatnim pogojem:

sama_soda = True
for e in s:
    if e % 2 != 0 and sama_soda:
        sama_soda = False
        liho = e
if sama_soda:
    print("Seznam vsebuje samo soda števila")
else:
    print("Seznam ne vsebuje samo sodih števil: prvo liho število je", liho)

S tem, ko smo dodali and sama_soda smo poskrbeli, da se bosta sama_soda = False in liho = e izvedla le prvič. Ko bomo postavili sama_soda na False, bomo dosegli, da pogoj ne bo nikoli nikoli več resničen.

Namesto dodatnega pogoja lahko napišemo break.

sama_soda = True
for e in s:
    if e % 2 != 0 and sama_soda:
        sama_soda = False
        liho = e
        break
if sama_soda:
    print("Seznam vsebuje samo soda števila")
else:
    print("Seznam ne vsebuje samo sodih števil: prvo liho število je", liho)

S tem se program lahko pravzaprav še bolj poenostavi. Ko zanko prekinemo, e ostane, kar je bil. Torej ne potrebujemo več spremenljivke liho.

sama_soda = True
for e in s:
    if e % 2 != 0:
        sama_soda = False
        break
if sama_soda:
    print("Seznam vsebuje samo soda števila")
else:
    print("Seznam ne vsebuje samo sodih števil: prvo liho število je", e)

Else po zanki

Medtem, ko je ukaz break zelo običajna žival v vseh programskih jezikih, ima Python še eno posebnost, povezano z zankami. V večini programskih jezikov lahko else uporabimo kot "odgovor" na if. V Pythonu pa lahko else sledi tudi zanki for ali while. V pogojnem stavku (if) se koda v else izvede, če pogoj ni bil resničen. Po zanki se else izvede, če se zanka ni prekinila zaradi break. Torej, else se izvede pri zankah, ki so se iztekle "po naravni poti".

Oglejmo si še enkrat gornji program. Kar smo počeli v njem, je kar pogosto: v zanki nekaj iščemo (recimo kako liho število). Če to reč najdemo, nekaj storimo (ga izpišemo) in prekinemo zanko. Sicer (torej, če ga ne najdemo), storimo kaj drugega (izpišemo, da ga nismo našli).

Zgornji odstavek se približno, a ne čisto povsem prilega zadnjemu kosu programa. V resnici je napisan po spodnjem programu:

for e in s:
    if e % 2 != 0:
        print("Seznam ne vsebuje samo sodih števil: prvo liho število je", e)
        break
else:
    print("Seznam vsebuje samo soda števila")

Se pravi: če najdemo liho število, napišemo, da seznam vsebuje liho število in prekinemo zanko. Ta del je jasen. Ne spreglejte pa, kje je else: poravnan je s for ne z if! Ta else se torej nanaša na for. Kar napišemo v else-u za for, se zgodi, če se zanka ni prekinila z breakom.

Preden naredimo še kakšen primer, si oglejmo še nekaj drugega.

Štetje z zanko for

Tisti, ki poznajo kak drug, C-ju podoben jezik, so najbrž pričakovali nekoliko drugačno zanko.

Poglejmo resnici v oči: v C, C++, C#, Javi sta zanka for in while le dva načina, na katera povemo eno in isto.

for(zacetek; pogoj; korak) {
    koda;
}

je isto kot

zacetek;
while(pogoj) {
    koda;
    korak;
}

Če je ideja zanke for v C-ju podobnih jezikih ta, da se z njo šteje, pa imamo v Pythonu za to nekaj priročnejšega in splošnejšega.

V programih pogosto potrebujemo še dva tipa zank. Pogosto bomo želeli nekaj ponoviti vnaprej predpisano oziroma pred zanko izračunano število ponovitev, recimo petkrat ali desetkrat. Prav tako bomo pogosto želeli, da program "šteje" od 10 do 20, od 0 do 100 ali kaj takega.

Za oboje bo poskrbela kar zanka for, ob pomoči priročne funkcije range.

Funkcija range je na prvi pogled povsem neimpresivna. Vrača sezname zaporednih števil.

V Pythonu 3.0 je ta funkcija narejena boljše kot v različicah pred njim, vendar je nova oblika za začetnika manj očitna, zato bomo za trenutek skočili v stari Python 2.7. Kar sledi, v novem Pythonu namreč ne bi dalo enakih izpisov.

>>> range(5, 10)
[5, 6, 7, 8, 9]
>>> range(10, 20)
[10, 11, 12, 13, 14, 15, 16, 17, 18, 19]
>>> range(7)
[0, 1, 2, 3, 4, 5, 6]
>>> range(16)
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]

Funkcija range je vrnila seznam celih števil, ki se začne pri prvi vrednosti in konča eno pred zadnjo. Koliko elementov ima seznam, ki ga vrne range(7)? Sedem. Zato ker vsebuje prvi element in ne zadnjega, in ker se začne z 0.

Funkcija range je torej narejena praktično, ker se težko zmotimo glede tega, koliko elementov bo vrnila.

Lepo se tudi sešteva. range(10, 15) + range(15, 20) so ravno vsa števila od 10 do 20. Če bi bil range(10, 15) enak [10, 11, 12, 13, 14, 15], bi se v range(10, 15) + range(15, 20) število 15 ponovilo dvakrat.

Funkcija ima lahko še en, tretji argument. Ta predstavlja korak.

>>> range(5, 15, 2)
[5, 7, 9, 11, 13]

Korak je lahko tudi negativen.

>>> range(10, 0, -1)
[10, 9, 8, 7, 6, 5, 4, 3, 2, 1]
>>> range(10, 0, -3)
[10, 7, 4, 1]
>>> range(10, 0, -5)
[10, 5]

Funkcijo range uporabljamo skoraj izključno v zankah for.

V prvi zanki while, ki smo jo napisali, smo izpisali števila od 1 do 10, takole nekako:

i = 1
while i < 11:
    print(i)
    i += 1

Z zanko for je preprostejši.

for i in range(1, 11):
    print(i)

Seveda ostaja odprto vprašanje, zakaj bi si kdo tako želel izpisati števila od 1 do 10. Štetje je kljub temu uporabno tudi za kaj drugega, ne le za izpisovanje.

Praštevila

Napisati želimo program, ki pove, ali je dano število praštevilo. Za to bomo preverili vsa števila od 2 do n-1: če katerokoli od njih deli n, potem n ni praštevilo.

n = int(input("Vpiši število"))
je_prastevilo = True
for i in range(2, n):
    if n % i == 0:
        je_prastevilo = False

V začetku predpostavimo, da je število praštevilo (je_prastevilo = True), če med števili med 2 in n (for i in range(2, n)) naletimo na njegov delitelj (if n % i == 0), pa presodimo, da število pač ne more biti praštevilo (je_prastevilo = False). Mimogrede naj opozorimo na napako, na katero stalno opozarjamo:

n = int(input("Vpiši število"))
je_prastevilo = True
for i in range(2, n):
    if n % i == 0:
        je_prastevilo = False
    else:
        je_prastevilo = True # TO JE NAROBE!

Ko je število enkrat obsojeno kot sestavljeno, ga ne moremo več "pomilostiti" nazaj v praštevilo. Če je tako, pa tudi ni potrebe, da bi po tem, ko enkrat odkrijemo delitelj, sploh še preverjali naslednje potencialne delitelje. Zdaj pač že vemo: break in else po zanki.

n = int(input("Vpiši število"))

for i in range(2, n):
    if n % i == 0:
        print(n, "ni praštevilo, saj je deljivo z", i)
        break
else:
    print(n, "je praštevilo")

Razpakiranje v zanki forcode> in še malo telovadbe

Nekoč na začetku predavanja smo imeli seznam študentov in njihovih tež:

podatki = [
    (74, "Anze", False),
    (82, "Benjamin", False),
    (58, "Cilka", True),
    (66, "Dani", False),
    (61, "Eva", True),
    (84, "Franc", False),
    ]

Recimo, da bi radi izpisali imean študentov in njihove teže.

Če gremo prek seznama z zanko for, bomo dobivali terke. Te lahko, vemo, razpakiramo.

for student in podatki:
    teza, ime, spol = student
    print(ime, ": ", teza)

Gre pa še dosti elegantneje: razpakiranje lahko opravimo kar znotraj glave zanke, brez (nepotrebne) terke t:

for teza, ime, je_zenska in podatki:
    print(ime, ": ", teza)

Program torej pravi

za vsako trojko (teza, ime, je_zenska) iz seznama podatki:
    izpisi ime in tezo

Lahko pa se tudi malo igramo in izrišemo graf:

for teza, ime, je_zenska in podatki:
    print(ime + " " + "*"*teza)

Skoraj, ampak ne čisto. Vse skupaj bi radi še poravnali. O tem, kako se v resnici oblikuje izpis, se bomo pogovarjali drugič, danes pa bomo s tem opravili nekoliko po domače. Predpostavimo, da so imena dolga največ 15 znakov. Pred vsako ime bomo dopisali toliko presledkov, da bo skupna dolžina enaka 15. (Mimogrede bom izdal, da dolžino niza dobimo s funkcijo len.)

for teza, ime, je_zenska in podatki:
    print(" "*(15 - len(ime)) + ime + " " + "*"*teza)

Zanka po dveh seznamih

Zdaj pa izpišimo imena in teže, pri tem, da se le-ta nahajajo v ločenih seznamih.

teze = [74, 82, 58, 66, 61, 84]
imena = ["Anze", "Benjamin", "Cilka", "Dani", "Eva", "Franc"]
studentka = [False, False, True, False, True, False]

Naivno bi se lotili takole

for ime in imena:
    print(ime, ":")

in potem obstali, ker na tem mestu ne moremo priti do teže študenta s tem imenom. Do običajne in zelo zelo zelo napačne rešitve nas pripelje ta zgrešeni premislek: zanko moramo speljati prek imen in prek tež, torej

for ime in imena:
    for teza in teze:
        print(ime, ": ", teza)

Rezultat je nepričakovan za vse tiste, ki ga niso pričakovali. Program namreč izpiše

Anze :  74
Anze :  82
Anze :  58
Anze :  66
Anze :  61
Anze :  84
Benjamin :  74
Benjamin :  82
Benjamin :  58
Benjamin :  66
Benjamin :  61
Benjamin :  84
Cilka :  74
Cilka :  82
Cilka :  58
Cilka :  66
Cilka :  61
Cilka :  84
Daniel :  74
Daniel :  82
Daniel :  58
Daniel :  66
Daniel :  61
Daniel :  84
Eva :  74
Eva :  82
Eva :  58
Eva :  66
Eva :  61
Eva :  84
Franc :  74
Franc :  82
Franc :  58
Franc :  66
Franc :  61
Franc :  84

Računalniki imajo to nadležno navado, da vedno naredijo natanko tisto, kar jim naročimo. In v tem primeru smo mu naročili tole:

za vsako ime na seznamu ime stori tole:
    za vsako tezo na seznamu tez stori tole:
        izpisi ime in tezo

Kdor ne razume, kaj se je zgodilo in zakaj, naj to nujno premisli, da se ne bo kasneje učil na lastnih napakah.

Ko bo to opravljeno, bo razumel: potrebujemo samo eno zanko, ki gre istočasno po obeh seznamih. Aha, takole?

for ime in imena:
for teza in teze:
    print(ime, ": ", teza)

Ummm, ne. Ena zanka je ena zanka. To sta dve. Običajni rešitvi tega problema sta dve. To, ki bi bila večini bližje, bom danes zamolčal, iz vzgojnih razlogov (kdor jo bo odkril sam, jo pač bo; žal). Funkcija zip združi dva seznama (ali več seznamov) v seznam terk.

>>> zip(teze, imena)
[(74, 'Anze'), (82, 'Benjamin'), (58, 'Cilka'), (66, 'Daniel'), (61, 'Eva'), (84, 'Franc')]
>>> zip(teze, imena, studentka)
[(74, 'Anze', False), (82, 'Benjamin', False), (58, 'Cilka', True), (66, 'Daniel', False), (61, 'Eva', True), (84, 'Franc', False)]
>>> zip(teze, imena, studentka, teze)
[(74, 'Anze', False, 74), (82, 'Benjamin', False, 82), (58, 'Cilka', True, 58), (66, 'Daniel', False, 66), (61, 'Eva', True, 61), (84, 'Franc', False, 84)]

Opomba: tudi tale izpis je iz starega Pythona. Od verzije 3.0 si lahko le predstavljamo, da zip dela takole. V resnici naredi nekaj majčkeno drugačnega (kaj, je za nas ta mesec še prezapleteno), rezultat pa je isti.

Zipamo lahko vse, prek česar lahko naženemo zanko for. Torej ne le seznamov, temveč tudi nize, terke in še kaj.

>>> zip("abcd", "ABCD")
[('a', 'A'), ('b', 'B'), ('c', 'C'), ('d', 'D')]
>>> zip("abcd", range(4))
[('a', 0), ('b', 1), ('c', 2), ('d', 3)]

Rešimo torej nalogo: izpišimo imena in teže iz ločenih seznamov.

for ime, teza in zip(imena, teze):
    print(ime, ": ", teza)

Za konec pa izpišimo še povprečno težo študentk.

skupna_teza = 0
studentk = 0
for teza, je_zenska in zip(teze, studentka):
    if je_zenska:
        skupna_teza += teza
        studentk += 1
print(skupna_teza / (studentk or 1))

Dolžina seznama, terke, niza

Seznami, terke in naši stari znanci nizi imajo nekaj skupnega. Pravzaprav veliko skupnega. Razlikujejo se le v podrobnostih. Skupno jim je, recimo, da imajo vsi trije dolžino. Gornji seznami šestih tež, šestih imen in šestih indikatorjev spola so dolžine 6. Terka 1, 2, 3, 4 je dolžine 4 in niz "Benjamin" je dolžine 8. Dolžine reči, ki imajo dolžino, nam pove funkcija len. To sem napisal tako imenitno, da bom moral še enkrat, da bo sploh kdo razumel: funkcija lensprejme en argument, recimo seznam, terko ali niz in kot rezultat vrne dolžino tega seznama, terke ali niza.

>>> b = 'Benjamin'
>>> len(b)
8
>>> len(podatki)
6
>>> len((1, 2, 3))
3
>>> len(12)
Traceback (most recent call last):
  File "", line 1, in 
TypeError: object of type 'int' has no len()

Morda je koga presenetilo zadnje: številka 12 bi lahko bila dolga 12, ne? Ali pa 2, ker ima dve števki? Ne. Funkcija len v bistvu pove število elementov, ki jih vsebuje podani argument. Niz "Benjamin" vsebuje 8 znakov, seznam podatki vsebuje 6 podseznamov in terka (1, 2, 3) ima tri elemente. Število 12 pa nima elementov.

Še ena prikladna značilnost seznamov (nizov, terk in še česa): prazni seznami so, tako kot prazni nizi, neresnični. Seznam lahko uporabimo v pogoju.

if s:
    print("Seznam s ni prazen")
else:
    print("Seznam s je prazen")

Indeksiranje

Do elementov seznamov, terk in nizov ne pridemo le z zanko for. Dobimo jih lahko tudi tako, da preprosto zahtevamo element na tem in tem mestu. Vzemimo za primer seznam imen. Spomnimo se, kako je videti.

>>> imena
['Anze', 'Benjamin', 'Cilka', 'Dani', 'Eva', 'Franc']

Spremenljivka imena vsebuje šest elementov. Če hočemo dostopati do posameznega elementa - recimo izpisati njegovo vrednost - povemo njegov indeks, "zaporedno številko". Zapišemo jo v oglate oklepaje za imenom spremenljivke, takole:

>>> imena[2]
'Cilka'

Se pravi, imena[2] vrne drugi element seznama.

Emmm, drugi?! Mar ni drugi element Benjamin, ne Cilka? Drži, ampak kakor šteje python, je drugi element Cilka. Anže je pa ničti. Ne le v pythonu, skoraj v vseh jezikih štejemo od 0. Prvi element ima indeks 0, drugi element 1 in tretji 2. Zakaj? Razlogi so tehnični in praktični. Tehničnega boste razumeli, ko boste (če boste) poslušali predavanja iz Cja. Tradicionalno indeks pomeni "odmik od začetka": ko je odmik 0, dobimo prvi element in ko je odmik 2, se odmaknemo dva elementa, torej pristanemo na tretjem. Medtem ko je v Cju (in še nižjih jezikih) ta, tehnični, argument morda smiseln, upravičimo štetje od ničle v Pythonu in drugih višjih jezikih s praktičnostjo: reči se tako lepše izidejo. Boste videli.

Če je koga zmedlo, povejmo na glas: oglati oklepaji imajo dve vlogi. Prej smo jih uporabljali, da smo vanje zaprli seznam, zdaj vanje zapiramo indekse. Naj vas to ne vznemirja, python bo že pravilno razumel, kaj mislite, celo v tako hecnih situacijah, kot je tale:

>>> [3, 1, 4, 1, 5, 9][2]
4

Prvi oklepaji definirajo seznam, drugi zaprejo indeks, 2, ki pove, kateri element tega seznama nas zanima.
[
Zveni bedasto in neuporabno? Potem mi povejte, ali je bedasto in neuporabno tole:

["moski", "zenska"][spol[0]]
in tole
"MZ"[spol[0]]
]

Na enak način kot sezname indeksiramo tudi terke in nize.

>>> 'Benjamin'[0]
'B'
>>> 'Benjamin'[2]
'n'

Kadar seznam vsebuje sezname, bomo včasih uporabljali dvojne indekse. Spomnimo se seznama podatki: vsebuje šest elementov, in ti elementi so spet seznami s po tremi elementi, ki predstavljajo težo, ime in spol. Vzemimo, recimo, drugi element:

>>> en_student = podatki[2]
>>> en_student
[58, "Cilka", True]

Zdaj lahko o Cilki nekaj povemo.

>>> print(en_student[1], "tehta", en_student[0], "kilogramov.")
Cilka tehta 58 kilogramov.

Lahko pa opravimo oboje v enem zamahu.

>>> print(podatki[2][1], "tehta", podatki[2][0], "kilogramov.")

podatki[2][0] pomeni (če preberemo z desne) ničti element drugega elementa seznama podatki. Če se vam zdi to hudo, vas lahko potolažim, da ni: v resnici je še bolj grozno. Ker je podatki[2][1] niz, se lahko vprašamo po, recimo, tretji črki tega niza.

>>> print("Tretja crka niza", podatki[2][1], "je", podatki[2][1][3])

(V kost za glodanje C-jašem pa naj vprašam, ali podatki[2][1][3][0][0][0][0] kaj izpiše ali se pritoži, da je tu nekaj narobe. Ampak to je pa res neuporabno.)

Kaj se zgodi, če je indeks prevelik? Nič lepega.

>>> "Benjamin"[100]
Traceback (most recent call last):
  File "", line 1, in 
IndexError: string index out of range

Kako velik pa je lahko indeks? Če ima niz osem črk in je prva ničta, je zadnja sedma. Indeks mora biti torej manjši od dolžine - največji dovoljeni indeks je tisto, kar vrne funkcija len, minus 1.

Python (in še marsikateri današnji jezik) ima še en trik: indeksiranje s konca: -1 je zadnji element, -2 predzadnji in tako naprej.

>>> 'Benjamin'[-1]
'n'
>>> 'Benjamin'[-2]
'i'
>>> 'Benjamin'[-3]
'm'

Rezanje

Poleg indeksiranja, ki vrača elemente nizov, seznamov, terk (in še česa), pozna Python še rezanje (slice), ki vrača dele nizov, seznamov, terk (in še česa). Rezino opišemo z indeksom prvega elementa in indeksom prvega elementa, ki ga ne želimo več vključiti v rezino. Med indeksa postavimo dvopičje. Se pravi, rezina 2:5 pomeni vse elemente od onega z indeksom 2 do tistega z indeksom 4 (ne 5!).

Smo to že kje videli? Smo, seveda. Funckija range uporablja natančno isto logiko. Tako kot pri range, je tudi pri rezanju to odlična ideja.

Oglejmo si, kaj na to pravi Benjamin.

>>> b[2:5]
'nja'
>>> b[5:8]
'min'
>>> b[2:5]+b[5:8]
'njamin'

Spodnjo ali zgornjo mejo smemo tudi izpustiti. V tem primeru dobimo vse elemente od začetka oz. do konca. Tu bosta izkazali svojo moč prav obe navidez neintuitivni pravili - štetje od 0 in to, da rezina ne vključuje zadnjega elementa.

>>> b[:5]
>>> b[:5]
'Benja'
>>> b[5:]
'min'

b[:5] vrne prvih pet elementov. b[5:] vrne vse od petega naprej; ker štejemo od 0, to pomeni, da izpustimo prvih pet. Se pravi, če želimo nizu s odbiti prvih pet znakov, bomo rekli

s = s[5:]

Da peti študent vstane le enkrat, nam pride prav, če želimo v niz kaj vriniti. Če bi radi za petim znakom niza vrinili X, to storimo takole:

>>> b[:5]+"X"+b[5:]
'BenjaXmin'

Ste opazili, da nam sploh ni bilo treba pomisliti na to, da štejemo od 0? Vidite, kako naravno je to. Če bi šteli od 1, bi bilo tole precej bolj zapleteno.

Še dodatne možnosti prinese indeksiranje od zadaj. Iz niza lahko poberemo, recimo, elemente od predpredpredzadnjega (-5) do predzadnjega (-2). Koliko jih bo? Trije, seveda.

>>> b[-5:-2]
'jam'

No, tegale najbrž ne uporabimo velikokrat. Pač pa nas pogosto zanimajo, recimo, zadnji štirje znaki. Ali pa vsi razen zadnjih štirih.

>>> film = "Babylon 5 - 3x04 - Passing through Gethsemane.avi"
>>> film[-4:]
'.avi'
>>> film[:-4]
'Babylon 5 - 3x04 - Passing through Gethsemane'

Mimogrede, tole sicer varneje delamo s funkcijo, ki smo jo že omenili, namreč splitext, ki prejme kot argument ime datoteke in vrne terko z dvema elementoma, imenom datoteke brez končnice in končnico.

Kako pa bi od niza odbili prve tri in zadnja dva znaka? Takole:

>>> b[3:-2]
'jam'

Že tale primer pokaže, zakaj je štetje od 0 in čudno pravilo, po katerem je prvi element rezine vključen, zadnji pa ne, tako smiselno in uporabno. Tisti, ki bi v vsej stvari vendarle videli še kako logiko, pa bo morda prepričala spodnja tabelica, ob kateri lahko še enkrat premislijo vse primere.

012345678
Benjamin
-8-7-6-5-4-3-2-1

Vsi znaki med drugim in petim so 'nja', znaki od 3 do -2 so 'jam'... In tako naprej.

Vendar še nismo končali. Izpustimo lahko tudi zgornjo in spodnjo mejo. Kaj dobimo v tem primeru? Cel niz. Je to uporabno? Na nizih pravzaprav ne. Pri čem drugem pa nam bo prišlo še prav.

Poleg meja rezine lahko podamo tudi korak. Namesto vsakega znaka lahko zahtevamo, recimo, vsak drugi znak, tako da dodamo še eno dvopičje, ki mu sledi velikost koraka.

>>> '0123456789'[2:9]
'2345678'
>>> '0123456789'[2:9:2]
'2468'
>>> '0123456789'[2:9:3]
'258'
>>> '0123456789'[2:9:4]
'26'

Korak je lahko, tako kot pri range tudi negativen! V tem primeru je potrebno zamenjati meji - prva mora biti višja od druge.

>>> '0123456789'[9:2:-1]
'9876543'
>>> '0123456789'[9:2:-2]
'9753'

Meje smemo seveda spet tudi izpuščati:

>>> '0123456789'[9::-1]
'9876543210'

Takole obrnemo predavatelja.

>>> 'demšar janez'[::-1]
'zenaj rašmed'

Vse tole je videti nekoliko zapleteno in tuje. In morda je: povaditi bo treba, pa se bo udomačilo. Pa se splača? Za odbijanje znakov od nizov? Se! Lepota je v tem, da se natanko enako kot indeksiranje in rezanje nizov obnaša tudi indeksiranje seznamov in vsega drugega. Pa še kaj - zanke for, recimo, ki jih bomo vsak čas spoznali.

Poglejmo si torej še rezanje seznamov.

>>> imena = ["Anze", "Benjamin", "Cilka", "Dani", "Eva", "Franc"]
>>> imena[2:5]
['Cilka', 'Dani', 'Eva']
>>> imena[2:]
['Cilka', 'Dani', 'Eva', 'Franc']
>>> imena[:2]
['Anze', 'Benjamin']
>>> imena[:-2]
['Anze', 'Benjamin', 'Cilka', 'Dani']
>>> imena[-2:]
['Eva', 'Franc']
>>> imena[::-1]
['Franc', 'Eva', 'Dani', 'Cilka', 'Benjamin', 'Anze']

Že videno. Rezanje seznamov se vede enako kot rezanje nizov. Rezanje terk pa prav tako.

Spreminjanje seznamov z indeksiranjem in rezanjem

Tole je preprosto. Vsak element seznama se vede na nek način kot spremenljivka: lahko mu priredimo vrednost in s tem "povozimo" prejšnjo vrednost.

>>> imena
['Anze', 'Benjamin', 'Cilka', 'Dani', 'Eva', 'Franc']
>>> imena[3] = "Daniel"
>>> imena
['Anze', 'Benjamin', 'Cilka', 'Daniel', 'Eva', 'Franc']

Tako kot prej lahko tudi zdaj indeksiramo od spredaj ali od zadaj.

(Za tiste, ki znate kak drug jezik, predvsem Php: elementov ni mogoče dodajati s prirejanjem! Pythonovi seznami niso ekvivalentni Phpjevim: temu, česar se v phpju reče array, je bližji pythonov slovar (dictionary, dict), ki ga bomo spoznali v prihodnosti.)

Pa rezine? Glede na to, da je rezina podseznam, moramo tudi pri prirejanju rezin prirejati podsezname.

>>> imena
['Anze', 'Benjamin', 'Cilka', 'Daniel', 'Eva', 'Franc']
>>> imena[1:4]=["Ben", "Cecilija", "Dani"]
>>> imena
['Anze', 'Ben', 'Cecilija', 'Dani', 'Eva', 'Franc']

Mora biti seznam, ki ga prirejamo, enako dolg kot rezina, ki jo bomo povozili? Ne, čemu? Takole zamenjamo tri z dvema:

>>> imena[1:4]=["Ben-Cecil", "Daniel"]
>>> imena
['Anze', 'Ben-Cecil', 'Daniel', 'Eva', 'Franc']

Ali pa nobenega s tremi:

>>> imena[2:2] = ["D2", "D3", "D4"]
>>> imena
['Anze', 'Ben-Cecil', 'D2', 'D3', 'D4', 'Daniel', 'Eva', 'Franc']

Z rezinami lahko tudi pobrišemo del seznama.

>>> imena
['Anze', 'Ben-Cecil', 'D2', 'D3', 'D4', 'Daniel', 'Eva', 'Franc']
>>> imena[2:5]
['D2', 'D3', 'D4']
>>> imena[2:5]=[]
>>> imena
['Anze', 'Ben-Cecil', 'Daniel', 'Eva', 'Franc']

Za brisanje obstaja še veliko drugih načinov, recimo tale

>>> imena
['Anze', 'Ben-Cecil', 'Daniel', 'Eva', 'Franc']
>>> del imena[2]
>>> imena
['Anze', 'Ben-Cecil', 'Eva', 'Franc']
>>> del imena[1:3]
>>> imena
['Anze', 'Franc']

Če kdo pričakuje, da bomo zdaj povedali še, da enako delamo tudi z nizi in terkami ... se moti. Nizov in terk ne moremo spreminjati!

>>> b = 'Benjamin'
>>> b[2]='a'
Traceback (most recent call last):
  File "", line 1, in 
TypeError: 'str' object does not support item assignment

Tu je torej osnovna razlika med seznamom in terko: seznam je spremenljiv, terka ne. Tudi niza ne moremo spreminjati, kakor smo pravkar videli. Kako pa bi potem zamenjali tretji znak niza b s črko 'a'?

>>> b = b[:2]+'a'+b[3:]
>>> b
'Beajamin'

To je seveda nerodno. Čemu je torej tako? Čemu ne moremo spreminjati nizov tako, kot sezname? (In čemu sploh ta trapasta terka?) Videli bomo, da nam pride včasih zelo zelo prav, da so nekateri objekti nespremenljivi. (Nekateri - pogosto tudi jaz - pravijo celo, da jeziki sploh ne bi smeli dopuščati spreminjanja spremenljivk.) Nize pa si v resnici redko želimo spreminjati - da, pogosto jih bomo sestavljali ali obtesovali, zelo redko pa si želimo spreminjati posamezne črke. Zato nas to, da so konstantni, ne bo preveč motilo, velikokrat pa nam bo koristilo.

Računske operacije na seznamih (in drugod)

V svojem prvem soočenju s programiranjem smo spoznali aritmetične izraze: seštevali in množili smo števila, jih kvadrirali in računali sinuse. Zadnjič smo naleteli na logične izraze, kjer smo računali z logičnimi vrednostmi, True in False. Danes je čas, da se nehamo čuditi ob vsakem izrazu posebej in jim dajati pridevke "aritmetični" "logični" in tako naprej. Računati se da pač z različnimi rečmi in ena od teh reči so tudi seznami.

Lahko sezname seštevamo? Kaj dobimo, če seštejemo [2, 5, -1] in [3, 7, 4]? Dobimo [5, 12, 3] ali [2, 5, -1, 3, 7, 4]? Oboje bi bilo smiselno, odgovor pa je takšen: če so se seznami doslej vedli tako podobno nizom, naj se še glede seštevanja.Ker je 'abc' + 'def' enako 'abcdef', naj bo tudi [2, 5, -1] + [3, 7, 4] enako [2, 5, -1, 3, 7, 4].

Podobno je z množenjem. [2, 5, -1] * 2 bi moralo biti po vsej logiki enako [2, 5, -1] + [2, 5, -1], se pravi [2, 5, -1, 2, 5, -1]. In tudi je.

K seznamu lahko prištejemo le seznam, k nizu niz, k terki terko. Vse tri pa lahko pomnožimo s celim številom - in z ničemer drugim.

Poleg + in * pa poznajo vsi trije še dva operatorja, in in not in. S prvim vprašamo, ali seznam oz. terka vsebujeta določen element in ali niz vsebuje določen podniz.

>>> 1 in [1, 2, 3]
True
>>> 4 in [1, 2, 3]
False
>>> 'in' in 'Benjamin'
True
>>> 'an' in 'Benjamin'

Drugi je seveda ravno nasproten, z njim se vprašamo ali seznam (niz) ne vsebuje elementa (podniza).

>>> 1 not in [1, 2, 3]
False
>>> 4 not in [1, 2, 3]
True
>>> 'in' not in 'Benjamin'
False
>>> 'an' not in 'Benjamin'
True

V resnici not in ni prav potreben, saj je x not in l isto kot not x in l.