7. Järjendid ja for
-tsükkel¶
Meie senistes programmides on iga andmejupp kuskil eraldi ära mainitud (nt muutujana). Kui mõelda reaalsete programmide peale (nt firma raamatupidamissüsteem), siis üldjuhul ei ole võimalik kõiki asjassepuutuvaid objekte (nt töötajad või arved) programmis üksikult ära mainida, kuna selliste objektide hulk pole piiratud.
Selles peatükis õpid, kuidas käsitleda mitut objekti ühe kogumina ning mida taolise kogumiga Pythonis teha saab.
Järjendid¶
Järjend (ingl list) on andmetüüp loetelude esitamiseks. Järjendi loomiseks on kõige lihtsam viis kirjutada järjendisse kuuluvad väärtused (e järjendi elemendid) komadega eraldatult nurksulgude vahele:
pikad_kuud = [1, 3, 5, 7, 8, 10, 12]
linnad = ['Tartu', 'Tallinn', 'Põltsamaa']
Me salvestasime muutujasse pikad_kuud
ühe arvujärjendi (31-päevaliste kuude numbrid) ning muutujasse linnad
ühe sõnejärjendi 3 sõnega.
Operatsioonid järjenditega¶
Kui me oleme järjendi kirja pannud, siis tekib loomulikult küsimus, mida sellega teha saab? Mõnesid põhioperatsioone demonstreerib järgnev programm:
# järjendi loomine
pikad_kuud = [1, 3, 5, 7, 8, 10, 12]
# järjendit võib lihtsalt ekraanile kuvada
print(pikad_kuud)
# elementide arvu (e järjendi pikkuse) leidmine
arv = len(pikad_kuud)
print('Aastas on ' + str(arv) + ' pikka kuud')
# järjendisse kuulumise kontroll
if 2 in pikad_kuud:
print("Veebruar kuulub pikkade kuude hulka")
else:
print("Veebruar ei kuulu pikkade kuude hulka")
# ühe elemendi väärtuse küsimine järjekorranumbri järgi
# NB! järjekorranumbrid algavad 0-st!
esimene_pikk_kuu = pikad_kuud[0]
teine_pikk_kuu = pikad_kuud[1]
print("Esimene pikk kuu on " + str(esimene_pikk_kuu) \
+ ", teine pikk kuu on " + str(teine_pikk_kuu))
Ilmselt märkasid kahte operatsiooni (len
ja in
), mida oled juba kasutanud sõnede puhul. Kuna sõnet saab vaadelda kui sümbolite järjendit, siis ongi Pythonis korraldatud nii, et paljud järjendioperatsioonid toimivad ka sõnedega.
Järgnev tabel demonstreerib olulisimaid järjendioperatsioone:
Avaldis | Väärtus | Kommentaar |
---|---|---|
len([2, 1, 3, 16]) |
4 |
Elementide arv |
min([2, 1, 3]) |
1 |
Minimaalne element |
max([2, 1, 3]) |
3 |
Maksimaalne element |
sum([2, 1, 3]) |
6 |
Elementide summa |
sorted([2, 1, 3]) |
[1, 2, 3] |
Tagastab järjestatud järjendi |
3 in [2, 1, 3] |
True |
Elemendi sisaldumine järjendis |
[2, 1] + [3, 1] |
[2, 1, 3, 1] |
Järjendite liitmine |
[2, 1, 3, 1].count(1) |
2 |
Elemendi esinemiste arv |
[1, 2] == [2, 1] |
False |
Elementide järjekord loeb |
NB! Nagu viimastest ridadest selgub, võib järjendis olla korduvaid väärtusi ning elementide järjekord on oluline.
Harjutus. Järjendiavaldiste kasutamine¶
Olgu meil defineeritud järgnevad järjendimuutujad:
a = [2, 3, 1, 5]
b = [6, 4]
Koosta muutujaid a
ja b
ning järjendioperatsioone kasutades avaldis, mille väärtus oleks järjend [1, 2, 3, 4, 5, 6]
.
Järjendi elementide küsimine e indekseerimine¶
Nagu esimeses näites juba mainitud, võimaldab Python küsida järjendimuutujas mingil konkreetsel positsioonil olevat elementi, kirjutades järjendi nime taga olevatesse nurksulgudesse soovitud elemendi positsiooni e indeksi :
pikad_kuud = [1, 3, 5, 7, 8, 10, 12]
# küsi elemente indeksi järgi
esimene_pikk_kuu = pikad_kuud[0]
teine_pikk_kuu = pikad_kuud[1]
print("Esimene pikk kuu on " + str(esimene_pikk_kuu) \
+ ", teine pikk kuu on " + str(teine_pikk_kuu))
Ilmselt on pisut ootamatu aga see, et esimest positsiooni ei tähista mitte number 1
vaid 0
, st elementide nummerdamine algab 0-st. Selle omapäraga tuleb indekseerimisel (st indeksi järgi elementide küsimisel) alati arvestada.
Miks alustatakse järjendi elementide nummerdamist 0-st?
Vanemates programmeerimiskeeltes oli taoline valik tingitud järjendite esitusviisist arvuti mälus. Teine põhjus on selles, et nii saab mõnesid keerulisemaid indekseerimisavaldisi veidi lühemalt kirja panna. Kolmas ja kõige olulisem põhjus on see, et enamikus programmeerimiskeeltes on sedasi kogu aeg tehtud ning väga paljud programmeerijad on harjunud taolise nummerdamisega.
NB! Indeksina võime kasutada ka mingit täisarvulist muutujat. Seetõttu, kui kombineerime indekseerimise while
-tsükliga, siis saame iga järjendi elemendi ükshaaval ette võtta ja sellega midagi teha (nt ekraanile kuvada):
linnad = ['Tartu', 'Tallinn', 'Põltsamaa']
i = 0
while i < len(linnad):
print("Linn indeksiga " + str(i) + " on " + linnad[i])
i += 1
Sellel teemal me praegu pikemalt ei peatu, sest tuleb välja, et elementide ükshaaval läbivaatamiseks on olemas parem võimalus kui while
-tsükkel ja indekseerimine.
for
-tsükkel¶
Lisaks while
-tsüklile on Pythonis veel üks tsüklitüüp – for
-tsükkel, mis on oma olemuselt väga tihedalt seotud järjenditega.
Käivita järgnev näiteprogramm, mis koosneb ühest lihtsast for
-tsüklist:
for linn in ["Tartu", "Tallinn", "Põltsamaa"]:
print(linn)
Nagu näed, sarnaneb for
-tsükkel kuju poolest while
-tsükliga – esimesel real on päis, mis määrab korduste aluse, ning edasi tuleb taandreaga esitatud keha, mis sisaldab lauseid, mida igal kordusel käivitatakse.
for
-tsükli kordused põhinevad mingil etteantud järjendil – antud näites on selleks kolme linna nimest koosnev järjend. Igal kordusel küsitakse järjendist üks element, salvestatakse tema väärtus tsüklimuutujasse (antud näites linn
) ning seejärel käivitatakse tsükli kehas olevad laused. Elemente loetakse järjendist järjekorras, st esimesel kordusel esimene element jne. Kui kõik elemendid on sedasi läbi käidud, siis on tsükli töö tehtud – seega käivitatakse tsükli keha niipalju kordi kui on järjendis elemente.
Miks „for“?
Mõnedes programmeerimiskeeltes nimetatakse for-tsüklit hoopis for-each-tsükliks, sest tsükli keha täidetakse päises näidatud järjendi iga elemendi jaoks uuesti. Pythonis on otsustatud lühema nime „for“ kasuks.
Järjendite töötlemine¶
Paljude ülesannete puhul on vaja antud järjend elementhaaval läbi vaadata ning koguda sealjuures mingit infot. Järgnevas näites on defineeritud funktsioon, mis leiab etteantud arvujärjendi elementide hulgast suurima:
def suurim_element(arvud):
# alustuseks oletame, et esimene element on suurim
seni_suurim = arvud[0]
# hakkame järjendit läbi vaatama
# kui leiame seni leitust veel suurema, siis uuendame muutuja väärtust
for arv in arvud:
if arv > seni_suurim:
seni_suurim = arv
# kui kõik arvud on läbi vaadatud, siis ongi abimuutujasse jäänud õige vastus
return seni_suurim
# katsetame seda funktsiooni
# nagu näha, järjendit, nagu iga teist väärtust, saab anda argumendiks
s = suurim_element([8, 45, 12, 331, 123])
print("Suurim element on " + str(s))
Sellise töötlemise juures kasutatakse enamasti abimuutujat, mida nimetatakse akumulaatoriks ja millesse kogutakse samm-sammult infot läbivaadatud järjendi osa kohta. Antud näite käivitamisel on igal tsükli sammul muutuja seni_suurim
väärtuseks läbivaadatud elementide hulgast suurim.
Tegelikult on Pythonisse juba sisse ehitatud mitmeid funktsioone, mis koguvad etteantud järjendi kohta mingit infot. Näiteks funktsioon max
teeb sama, mis meie eelmise näite funktsioon. Selles peatükis aga üritame taolisi funktsioone ise leiutada, et õppida järjendeid ning for
-tsüklit paremini tundma.
Harjutus. Elementide summa¶
Kirjuta funktsioon elementide_summa
, mis võtab argumendiks arvujärjendi ning tagastab kõigi elementide summa. (Selle jaoks on küll Pythonis juba olemas funktsioon sum
, aga ära praegu seda kasuta.)
NB! Erinevalt suurima elemendi leidmise funktsioonist peaks summa funktsioon töötama ka tühja järjendiga, st elementide_summa([])
peaks andma vastuseks 0
.
Vihje
Jälgi eelmise näite skeemi – hoia akumulaatoris seni läbivaadatud summat ning igal tsükli sammul uuenda akumulaatorit. Samuti mõtle, mis on antud ülesande juures sobiv akumulaatori algväärtus.
Lõpuks kontrolli, kas sinu funktsioon annab samade järjendite puhul sama tulemuse, mis Pythoni funktsioon sum
.
Harjutus. Positiivsed vs negatiivsed¶
Kirjuta funktsioon negatiivsete_summa_suurem
, mis võtab argumendiks arvujärjendi ja tagastab True
, kui järjendis olevate negatiivsete arvude summa on suurem kui järjendis olevate positiivsete arvude summa. Vastasel juhul tuleb tagastada False
.
Vihje
Kõige lihtsam võimalik lahendus sellele ülesandele:
def negatiivsete_summa_suurem(arvud):
return False
Jah, see ülesanne oli „tillikas“ :p.
Failist lugemine¶
Tuleb välja, et for
-tsükkel on väga mugav ka failist lugemiseks:
f = open('andmed.txt')
for rida in f:
print('Lugesin järgneva rea: ' + rida)
f.close()
Seda näidet kommenteerides võiks lihtsustatult öelda, et:
- funktsioon
open
tagastab failis sisalduvad read sõnejärjendina …- … mis salvestatakse muutujasse
f
for
-tsükkel käib selle järjendi elemendid ükshaaval läbi.
Tegelikult ei ole muutujas f
siiski mitte järjend, vaid natuke keerulisem väärtus. Õnneks oskab for
-tsükkel seda väärtust käsitleda justkui järjendit, seetõttu ei pea me muretsema, kuidas need faili read tegelikult on esitatud.
Harjutus. Temperatuuride lugemine failist¶
Kirjuta programm, mis loeb tekstifailist ükshaaval Celsiuse skaalal esitatud temperatuure (iga arv on antud eraldi real) ning väljastab need ekraanile koos vastavate väärtustega Fahrenheiti skaalal.
Vihje
Meeldetuletus: nii nagu input
käsu puhul, saame ka tekstifailist lugedes sisendi alati tekstina, seetõttu tuleb antud ülesandes teisendada algandmed enne kasutamist arvudeks.
Funktsioon range
¶
Vaatame nüüd pisut teistsuguse ilmega for
-tsükli näidet:
for i in range(10):
print(i)
Selle programmi käivitamisel ilmuvad ekraanile numbrid 0..9. Selleks, et antud näitest paremini aru saada, proovi käsureal läbi järgnev näiteavaldis:
>>> list(range(5))
[0, 1, 2, 3, 4]
Avaldis range(5)
genereerib ühe järjendit meenutava väärtuse – nimelt vahemiku. Funktsioon list
teisendas selle väärtuse päris järjendiks, mis sisaldab täisarve 0..4.
Nüüd peaks olema selge, miks meie for
-tsükli näide sedasi käitus – range(10)
genereerib vahemikku 0..9 kujutava väärtuse ja kuigi tegemist pole päris järjendiga, oskab for
-tsükkel seda käsitleda justkui järjendit. Edasi toimub kõik samamoodi nagu varem kirjeldatud – pseudo-järjendist loetakse ükshaaval elemente, mis salvestatakse kordamööda tsüklimuutujasse i
ning igal kordusel käivitatakse tsükli kehas olevad laused.
Märkus
Mõnikord läheb meile korda ainult see, mitu korda tsükli keha on vaja korrata, st tsüklimuutuja konkreetsete väärtuste vastu me huvi ei tunnegi. Järgnev ruudu joonistamise näide peaks olema tuttav kolmandast peatükist, ainult et seekord kasutame while
-tsükli asemel for
-tsüklit:
from turtle import *
for i in range(4):
forward(100)
left(90)
exitonclick()
Kuigi me muutuja i
väärtust ei kasutanud, siis Pythoni süntaks nõuab ikkagi selle muutuja kirjapanekut.
Harjutus. Kilpkonn tsüklis¶
Proovi ennustada, mida joonistab järgmine programm:
from turtle import *
for i in range(30) :
forward(i * 2)
left(90)
exitonclick()
Selgitus
Nagu näed, joonistub ekraanile kandiline spiraal. Kuidas see programm aga kilpkonna abil sellise tulemuseni jõuab?
Tegelikult on antud programmi puhul üldine seletus lihtne.
for i in range(30)
ütleb, et talle järgnevat koodiblokki (taandatud ridasid) tuleb korrata 30 korda, kusjuures esimest korda on selle bloki jaoksi
väärtus 0, siis 1, siis 2 jne kuni 29-ni välja.- Esimesel kordusel, kui i=0, ei liigu kilpkonn üldse edasi, kuid pöörab 90 kraadi vasakule (nina üles suunda).
- Teisel kordusel, kui i=1, liigub kilpkonn kaks (
i*2
) sammu edasi (üles) ning siis 90 kraadi vasakule (nina nüüd vasakus suunas). - Kolmandal kordusel, kui i=2, liigub kilpkonn 4 sammu edasi (vasakule) ja siis pöörab jälle 90 kraadi vasakule (nii et nina on nüüd alla suunatud) jne kuni i=29 -ni
Et iga kord on joonistatav lõik eelmisest pikem, tekibki selle tsükli tulemusena kandiline spiraal.
Katseta erinevaid pööramise nurki ning erinevaid teepikkusi. Proovi joonistada kuuekandiline spiraal!
range
’i variandid¶
Funktsiooni range
saab kasutada ka 2 või 3 argumendiga. Järgnevas käsurea näites kasutame jälle list
funktsiooni, et näha, mida mingi range
variant tähendab:
>>> list(range(5))
[0, 1, 2, 3, 4]
>>> list(range(0, 5))
[0, 1, 2, 3, 4]
>>> list(range(2, 5))
[2, 3, 4]
>>> list(range(0, 15, 2))
[0, 2, 4, 6, 8, 10, 12, 14]
>>> list(range(5, 0, -1))
[5, 4, 3, 2, 1]
>>> list(range(0, 5, 1))
[0, 1, 2, 3, 4]
Kommentaarid:
- ühe argumendiga variandi puhul algab loetelu 0-st ning lõpeb enne näidatud argumendi väärtuseni jõudmist;
- kahe argumendi puhul algab loetelu esimese argumendi väärtusest ja lõpeb enne teise argumendini jõudmist;
- kolme argumendi puhul näitab kolmas argument väärtuste kasvamise sammu.
Harjutus. Kolmega jaguvad arvud¶
Kirjuta for
-tsükkel koos sobiva range
variandiga, mis kuvab ekraanile kõik 3-ga jaguvad arvud vahemikus 10 kuni 100.
Harjutus. range
avaldis¶
Kirjuta avaldis kujul list(range(...))
, mis tagastaks järgmise järjendi:
[100, 93, 86, 79, 72, 65, 58, 51, 44, 37, 30, 23, 16]
for
vs while
¶
Tegelikult saaks for
-tsükli asemel alati kasutada ka while
-tsüklikt, aga tulemus poleks alati nii selge. Võrdleme omavahel samaväärseid while
- ja for
-tsükleid:
i = 0
while i < 10:
print(i)
i += 1
|
for i in range(10)
print(i)
|
Kui meenutad kolmandat peatükki, siis selleks, et while
tsükliga teha mingit toimingut n korda, tuleb:
- võtta kasutusele abimuutuja (loendur) algväärtusega 0;
- tsükli kehas suurendada muutuja väärtust igal kordusel;
- tsükli päises kontrollida, et loenduri väärtus on väiksem kui n.
Nagu näha, annab for
-tsükkel koos range
’iga sama tulemuse palju lihtsamalt – tsüklimuutuja algväärtustamine, selle suurendamine ja tsükli lõpetamise kontrollimine toimuvad kõik automaatselt. Seetõttu ongi soovitatav loenduril põhinevad tsüklid kirjutada for
-tsüklina.
Samas, mõnede probleemide lahendamisel ei piisa for
-tsüklist. Näiteks kolmandas peatükis kirjeldatud arvamismängu ei saa for
-tsükliga kirja panna. Seetõttu ongi Pythonis kaks erinevat korduslauset – paindlik, aga pisut tülikas while
-lause ning mugav, aga teatud juhtudel ebasobiv for
-lause.
Veel järjendioperatsioone¶
Sõne kui järjend¶
Nagu eespool juba mainitud, saab sõnet käsitleda justkui sümbolite järjendit:
sõne = 'Tere'
print(sõne[0])
for täht in sõne:
print(täht)
Selleks, et sõnet muuta päris järjendiks, saab kasutada funktsiooni list
:
>>> list('Tere')
['T', 'e', 'r', 'e']
Meetodid split
ja join
¶
Tihti on tarvis teha mingi sõne pisut suuremateks juppideks kui üksikud tähed – näiteks võib olla vaja jagada sõnena esitatud lause eraldi sõnadeks. Selle jaoks saab kasutada sõnemeetodit split
:
>>> 'Tere hommikust'.split()
['Tere', 'hommikust']
>>> 'CY2X44;3;66;T'.split(';')
['CY2X44', '3', '66', 'T']
Kui split
-i kasutada ilma argumentideta, siis tehakse lõikamine tühikute, tabulaatorite ja reavahetuste kohalt. Kui anda ette mingi muu sümbol, siis lõigatakse sõne juppideks just selle sümboli kohalt.
Märkus
Kui meil on vaja mitmerealisest sõnest saada ridade listi, siis sobib hästi split
koos argumendiga \n
:
>>> "Esimene rida\nTeine rida\nKolmas rida".split("\n")
['Esimene rida', 'Teine rida', 'Kolmas rida']
Kui ka viimase rea lõpus on reavahetuse sümbol, siis pole tulemus võibolla päris see, mida soovid:
>>> "Esimene rida\nTeine rida\nKolmas rida\n".split("\n")
['Esimene rida', 'Teine rida', 'Kolmas rida', '']
Appi tuleb meetod splitlines
, mis käsitleb \n
-i kui rea lõpu sümbolit, mitte kui reavahetuse sümbolit:
>>> "Esimene rida\nTeine rida\nKolmas rida".splitlines()
['Esimene rida', 'Teine rida', 'Kolmas rida']
>>> "Esimene rida\nTeine rida\nKolmas rida\n".splitlines()
['Esimene rida', 'Teine rida', 'Kolmas rida']
Meetodi split
„vastand“ on meetod join
:
>>> ' '.join(['Tere', 'hommikust'])
'Tere hommikust'
>>> ';'.join(['CY2X44', '3', '66', 'T'])
'CY2X44;3;66;T'
Harjutus. Kuupäeva lahtiharutamine¶
Kirjuta funktsioon kuu
, mis võtab argumendiks sõne kujul <päev>. <kuu> <aasta> (nt '24. veebruar 1918'
) ning tagastab vastava kuu nime.
Negatiivsed indeksid¶
Järjendeid (ja sõnesid) saab indekseerida ka negatiivsete indeksitega, sel juhul hakatakse lugema järjendi lõpust:
>>> sõne = 'Tere'
>>> sõne[-1]
'e'
>>> sõne[-2]
'r'
>>> sõne[-3]
'e'
>>> sõne[-4]
'T'
Avaldis järjend[-0]
tähistab siiski esimest elementi, sest -0 = 0.
Järjendite viilutamine¶
Kirjutades nurksulgudesse indeksi asemel indeksivahemiku on järjendist (ja sõnedest) võimalik võtta alamjärjendeid (alamsõnesid):
>>> a = ['a', 'b', 'c', 'd', 'e', 'f']
>>> a[0:2]
['a', 'b']
>>> a[:2]
['a', 'b']
>>> a[2:6]
['c', 'd', 'e', 'f']
>>> a[2:]
['c', 'd', 'e', 'f']
>>> a[-2:]
['e', 'f']
>>> s = "Tere"
>>> s[0:3]
'Ter'
Koolonist vasakule tuleb kirjutada see indeks, millest alates tuleb elemente tulemusse kopeerida, ning koolonist paremale see indeks, mille juures tuleb kopeerimine lõpetada (st selle indeksiga element jääb tulemusest välja). Kui vasak indeks jätta kirjutamata, siis alustatakse esimesest elemendist, ja kui parem indeks jätta kirjutamata, siis kopeeritakse kuni järjendi lõpuni (viimane element kaasaarvatud).
Mida võiks tähendada s[:]
?
Valed indeksid¶
Proovi läbi järgnev näide, et sa tunneksid saadud veateate edaspidi ära:
a = ['a', 'b', 'c']
print(a[66])
Harjutus. Sõne viilutamine¶
Kirjuta funktsioon kaja
, mis võtab argumendiks sõne ning tagastab selle sõne 3 viimast tähte. Kui sõnes on vähem kui 3 tähte, siis tagastada terve sõne.
Ennikud¶
Ennik (ingl tuple) on Pythoni andmetüüp, mis on järjendiga väga sarnane. Enniku elemendid kirjutatakse ümarsulgude vahele, aga nende indekseerimiseks kasutatakse siiski nurksulge:
punkt = (3, 8) # kahe elemendiga ennik e paar
print("Punkti x-koordinaat on:", punkt[0])
print("Punkti y-koordinaat on:", punkt[1])
andmed = ("Peeter", "Paun", 1967) # kolme elemendiga ennik e kolmik
print("Eesnimi:", andmed[0])
print("Perenimi:", andmed[1])
print("Sünniaasta:", andmed[2])
Tegelikult saaksime siin ennikute asemel kasutada ka järjendeid, aga hea tava on kasutada järjendeid vaid neil juhtudel, kus kogumi elemendid on kõik ühte tüüpi. Kui meil on mingi kindel komplekt elemente, mida me tahame koos käsitleda, ja mis võivad olla erinevat tüüpi, siis on parem paigutada need ennikusse.
Märkus
Mingit 3-elemendilist kogumit nimetatakse tavaliselt kolmikuks, 4-elemendilist nelikuks jne. Enniku nimi on tulnud selle skeemi üldistamisest: n-elemendilist kogumit nimetatakse ennikuks.
Märkus
Kui hakkame rääkima listide muteerimisest, siis näeme veel ühte erinevust listide ja ennikute vahel: liste on võimalik muteerida, ennikuid mitte.
Mitme väärtuse tagastamine funktsioonist¶
Ennikuid kasutatakse tihti siis, kui funktsioonist on vaja tagastada mitu väärtust:
def loe_andmed(failinimi):
nimed = []
sünniaastad = []
f = open(failinimi)
for rida in f:
jupid = rida.split()
nimi = jupid[0]
sünniaasta = jupid[1]
nimed = nimed + [nimi]
sünniaastad = sünniaastad + [sünniaasta]
f.close()
# tagastan kaheelemendilise enniku
return (nimed, sünniaastad)
# salvestan enniku komponendid muutujatesse
(meeste_nimed, meeste_sünniaastad) = loe_andmed("mehed.txt")
(naiste_nimed, naiste_sünniaastad) = loe_andmed("naised.txt")
print(meeste_nimed)
print(naiste_sünniaastad)
Näite lõpp näitab, et enniku komponente saab omistamise süntaksi abil salvestada muutujatesse. (Alternatiivina võiksime kasutada ka indekseerimist.)
Märkus
Taolist ennikutega omistamise trikki kasutatakse mõnikord ka 2 muutuja väärtuste vahetamiseks:
x = 1
y = 2
x, y = y, x
print(x)
print(y)
See näide demonstreerib veel seda, et teatud juhtudel on lubatud enniku ümbert sulud ära jätta.
Vahepala: sõnede ja väljundi formaatimine¶
Seni oleme sõnede ja teiste andmetüüpide kombineerimisel kasutanud komponentide ühendamiseks operatsiooni +
ning teisendamiseks funktsiooni str
. Nüüd vaatame alternatiivset viisi selle toimingu tegemiseks.
Sõnedel on olemas meetod format
, millega saab teisendada andmeid erinevatele sõnekujudele. Selle meetodi põhiolemust demonstreerib järgnev käsurea näide:
>>> eesnimi = "Kalle"
>>> perenimi = "Kala"
>>> vanus = 25
>>> 'Klient: {0} {1}, vanus: {2}'.format(eesnimi, perenimi, vanus)
'Klient: Kalle Kala, vanus: 25'
Meetod format
konstrueerib tulemuse (uue sõne) mitmest komponendist: esimene komponent on lähtesõne, mis sisaldab muuhulgas loogeliste sulgudega tähistatud „pesasid“ (ingl placeholders); ülejäänud komponendid (st meetodi argumendid) on suvalised väärtused, mis kopeeritakse vastavatesse pesadesse.
Pesa kirjeldus on kõige lihtsamal juhul täisarv, mis näitab, kui mitmes argumentväärtus tuleb antud pesasse panna. Seejuures tuleb arvestada, et loendamist alustatakse 0-st.
Pesa kirjeldusse saab märkida ka lisatingimusi andmete formaadi kohta:
pikkused = [173.235235, 33.0, 167.333]
for i in range(len(pikkused)):
pikkus_sõnena = "{0}. pikkus on {1:>6.2f}cm".format(i, pikkused[i])
print(pikkus_sõnena)
Hakkame jupphaaval analüüsima pesa {1:>6.2f}
tähendust.
- Koolonist vasakul on pesa järjekorranumber.
>6
näitab, et sisu esitamiseks on ette nähtud 6 positsiooni ja kui tegelik sisu võtab vähem ruumi, siis tuleb sisu ette panna niipalju tühikuid, et kokku saaks 6 sümbolit..2f
ütleb, et vastavat väärtust tuleb tõlgendada ujukomaarvuna (f nagu float), mis tuleb esitada 2 komakohaga.
Märkus
format
meetodi teiste võimalustega saab tutvuda aadressil:Ülesanded¶
1. Paarisarvude loendamine¶
Kirjuta funktsioon, mis võtab argumendiks täisarvude listi ning tagastab, kui mitu elementi antud listis olid paarisarvud.
Testi oma funktsiooni erinevate listidega (sh tühja listiga).
2. Ruudud¶
Kirjuta programm, mis küsib sisendiks täisarvu ning väljastab for
-tsükli abil kõikide arvude ruudud alates 1-st kuni sisestatud arvuni (kaasaarvatud) ja lõpuks ka kõigi nende ruutude summa.
3. Teksti esitamine¶
Kirjuta programm, mis küsib kasutajalt failinime ning for
-tsüklit kasutades kuvab faili sisu ekraanile.
Seejärel täienda programmi nii, et teksti näidatakse 20 rea kaupa – st iga kord peale 20 rea näitamist jääb programm ootama kasutajapoolset ENTER-i vajutust (vihje: input()
).
Testimiseks võib alla laadida nt „Alice in Wonderland“ teksti aadressilt http://www.gutenberg.org/files/11/11.txt.
4. Kuupäevade töötlemine¶
Kirjuta funktsioon korrasta_kuupäev
, mis võtab argumendiks ühe sõnena esitatud kuupäeva (nt '24.02.1918'
) ning tagastab kuupäeva kujul <päev>. <kuu nimi> <aasta> (nt. '24. veebruar 1918'
).
Etteantud sõnes võib olla kuupäeva osade vahel kasutatud ka sidekriipse. Sel juhul tuleb tõlgendada kuupäeva kujul <aasta>-<kuu>-<päev> (nn ISO formaat, aga lubame ka 1-3 kohalisi aastaarve). NB! Tagastatav kuupäev peab olema ikkagi kujul <päev>. <kuu nimi> <aasta>.
Kui etteantud kuupäev on arusaamatul kujul või kui pole tegemist legaalse kuupäevaga, siis tagastada vastav veateade. Eeldame, et etteantud sõnes on kuu antud alati numbriga.
Soovituslik lisaülesanne
Kui osade vahel on kaldkriipsud, siis eeldame, et tegemist on USA formaadiga: <kuu>/<päev>/<kahekohaline aasta>. Uuri välja, kuidas kahekohalisi aastaid tõlgendatakse (nt. 12/25/10 vs 12/25/97).
5. Värvid¶
Aadressilt http://wiki.tcl.tk/16166 leiad loetelu värvinimedest, mida turtle
moodul tunnistab (mitmesõnalised värvinimed on pandud loogeliste sulgude vahele, neid võid lihtsuse mõttes ignoreerida). Kirjuta programm, mis kilpkonna abil näitab võimalikult suurt osa neist värvidest, näiteks värvid antud loetelu keskosast, kus pole mitmesõnalisi nimesid:
Võid proovida ka paigutada värvid hõredamalt ja kirjutada iga värvi juurde tema nime (vt http://docs.python.org/3/library/turtle.html#turtle.write).
Otsi võimalusi rakendada selle peatüki uusi teemasid (for
, split
).
NB! Ülesande saab lahendada ilma mingi rutiinse käsitööta!
Vihje
nimed_sõnena = """Mari Kalle Malle
Jüri Peeter Toomas Olev
Kaur Jaanus Jaan"""
nimed_listina = nimed_sõnena.split()
for nimi in nimed_listina:
...
6. Keskmise hinde leidmine¶
Olgu meil fail nimega hinded.txt
, kus on igal real üks hinne (vahemikus 1 kuni 5). Kirjuta programm, mis arvutab nende hinnete keskmise. (Ära unusta, et failist ridade sisselugemisel antakse read sõnedena, mitte arvudena.)
Kui oled programmi tööle saanud, siis muuda programmi põhiosa funktsiooniks, mis võtab argumendiks failinime ning tagastab failis olevate hinnete keskmise.
Lisalugemine¶
Paroolide murdmine¶
Järgnev näide demonstreerib jõumeetodil paroolide murdmise põhiideed.
Turvalisuse huvides salvestatakse infosüsteemides kasutajate paroolide asemel ühesuunalise krüpteerimismeetodiga saadud räsikoode. Kuigi räsikoodist pole otseselt võimalik parooli tuletada, tuleks seda siiski võõraste eest kaitsta, sest pahalane võib proovida krüptida sama meetodiga palju erinevaid paroole ning kui tulemuseks on sama räsikood, siis on ka parool teada.
Vali mingi inglisekeelne, väikeste tähtedega sõna parooliks ning koosta sellest MD5 räsikood, kasutades vormi aadressil: http://www.miraclesalad.com/webtools/md5.php
Lae alla inglisekeelsete paroolide nimekiri aadressilt http://www.apasscracker.com/dictionaries/ ning paki zip failis olev tekstifail lahti.
Järgnev programm küsib kasutajalt MD5 räsikoodi ning otsib paroolisõnastikust sobivat vastet. Edu korral näidatakse parool ekraanile.
import hashlib
räsi = input("Sisesta parooli MD5 räsi: ")
f = open("english.dic", encoding="latin_1")
# esialgu veel pole midagi leidnud
tulemus = "Ei leidnud parooli"
for rida in f:
# strip eemaldab rea lõpust reavahetuse
parool = rida.strip()
if hashlib.md5(parool.encode('ascii', 'ignore')).hexdigest() == räsi:
tulemus = "Vastav parool on: " + parool
break # edasi pole vaja vaadata
# faili me enam ei vaja
f.close()
print(tulemus)
Tegelikkuses ei lähe paroolide murdmine siiski nii libedalt – esiteks piirasime end praegu vaid väikeste tähtedega paroolidega ja teiseks, reaalselt kasutatavad krüptimismeetodid on palju aeglasemad kui meie kasutatud MD5.
Kilpkonna keel ja interpretaator¶
Kilpkonnaga Pythoni käsureal joonistamine on üpris lõbus, kuid muutub pikkade käskude tõttu kähku tüütuks. Arvutid on aga just mõeldud tüütute ülesannete automatiseerimiseks ja lihtsustamiseks. See motiveerib ka järgnevat ülesannet.
Antud on fail, kus igal real on kilpkonna käsk – täht ja selle järel number, näiteks:
F 100
L 90
B 100
R 120
Kirjutame programmi, mis loeb sisse vastava faili ja edastab need käsud kilpkonnale, lastes sellel joonistada siis neile vastava kujundi.
Programm ise on tegelikult üsna lihtne:
import turtle
# faili avamine
file = open("Kilpkonn.txt","r")
# faili töötlemine ja kilpkonnaga joonistamine
while True:
rida = file.readline()
# katkesta viimase rea puhul
if rida == "" :
break
# teisenda käsk kaheks komponendiks
kask = rida.split()
tyyp = kask[0]
param = int(kask[1])
if tyyp == "L" :
turtle.left(param)
elif tyyp == "R" :
turtle.right(param)
elif tyyp == "F" :
turtle.forward(param)
elif tyyp == "B" :
turtle.backward(param)
else :
print "Failis oli tundmatu käsk!"
Sisuliselt kirjutasime just interpretaatori niiöelda „kilpkonna keele“ jaoks, mis tõlkis lihtsalt loetud käsud meie kilpkonnale arusaadavasse keelde. Põhimõtteliselt sama moodi toimivad ka teiste keelte interpretaatorid. Interpretaator ei ole seega midagi keerulist ega abstraktset – tegu on lihtsalt programmiga, mis loeb käske ja täidab neid.