Po tem, ko smo se poučili o tem, kako so shranjeni nizi in kaj vse se še vsebujejo poleg vidnih znakov, se vrnimo k vremenu v Radovljici, podatkom z dražbe in izboru koles.
for vrstica in open("temperature.txt"):
print(vrstica)
24
18
15
16
18
Vidimo prazne vrstice? Ki jih ne bi bilo, če bi izpisovali številke, ne nizov?
for vrstica in open("temperature.txt"):
= int(vrstica)
temp_c print(temp_c)
24
18
15
16
18
To se zgodi, ker vsaka vrstica, prebrana iz datoteke, vsebuje tudi znak za konec vrstice. Tako je, recimo, videti zadnja.
vrstica
'18\n'
To nas potem pesti pri nalogi z vaj, kjer je potrebno prešteti, kolikokrat se je nek lastnik štirih koles peljal s katerim izmed njih, recimo Cubom.
= 0
cube for vrstica in open("../vaje/kolesa.txt"):
if vrstica == "Cube":
+= 1
cube
cube
0
Že če pogledamo zadnjo vrstico, vidimo, da se je vozil tudi s Cubom,
vendar ima vrstica na koncu, seveda, \n
, zato primerjava
vrstica == "Cube"
vrne False
.
vrstica
'Cube\n'
== "Cube\n" vrstica
True
Seveda lahko pišemo
= 0
cube for vrstica in open("../vaje/kolesa.txt"):
if vrstica == "Cube\n":
+= 1
cube
cube
46
in dobimo pravi rezultat, vendar nam gre (no, vsaj meni) to na živce.
Menda ne obstaja kak način, da se znebimo tega trapastega
\n
?
Še hujše: kaj, če je pri zapisovanju nekonsistenten in včasih piše "Cube", včasih "cube" in včasih "CUBE"? Obstaja kakšna oblika primerjanja, ki ignorira razliko med velikimi in malimi črkami?
Aja, pa, zdi se mi, da še nisem omenil: njegova tipkovnica je nekaj pokvarjena in včasih dela dvojne n-je, tako da se v njegovi datoteki včasih pojavi tudi Stevenns, NNakamura in Cannyon (in Canyonn). Je mogoče na kak preprost način zamenjati vse dvojne n-je v nizu z enojnimi?
Kam gremo? V funkcije za delo z nizi. Pravzaprav v nekaj splošnejšega.
Doslej smo spoznali le prgišče Pythonovih funkcij - le toliko, da smo se jih naučili klicati in da smo lahko kaj drobnega sprogramirali.
print
in input
. Tidve sta splošno
uporabni funkciji, predvsem prva. (Druga bo hitro potonila iz spomina,
ker v resnici ni tako zelo uporabna.) Podobno open
.sqrt
,
sin
in log
. Te so v resnici pospravljene v
škatlici z imenom math
in da smo jih lahko uporabili, smo
jih morali pričarati s from math import *
.int
in float
. To v resnici nista
funkciji, temveč tipa. Vendar so vsi tipi v Pythonu tudi na nek način
funkcije. Kakorkoli, int
in float
ste očitno
zelo splošni zadevi.Python 3.12 ima v resnici samo 71 funkcij (med katerimi je kakšnih 20 tipov). Še več, izmed teh 71 funkcij sem tako, na hitro, naštel 20 funkcij, ki jih dejansko kolikor toliko redno uporabljam. Ostale so bolj kot ne eksotika. Ostale, uporabne funkcije - in Python jih ima na tisoče - so pospravljene po škatlicah.
Kakšnih škatlicah? Takole. Vzemimo nek niz, recimo
= "NNakamura\n" vrstica
Tako kot številke smo tudi nize na tablo narisali kot škatlice, ki
vsebujejo nek podatek. Poleg tega vsebujejo te škatlice tudi funkcije za
delo s tem podatkom. Kot smo omenili v vaji "Kolesa" se lahko odvečnega
\n
na koncu niza znebimo tako, da napišemo
vrstica = vrstica.strip()
. Ta strip
je
funkcija, ki se nahaja "znotraj" niza vrstica
. In do
tistega, kar je "znotraj", pridemo tako, da napišemo
vrstica
, nato piko (ki v vseh kontekstih v Pythonu - še
nekaj jih bo - pomeni "znotraj") in potem ime tistega, kar je "znotraj",
torej strip
. Ker je to funkcija, jo pokličemo. In ker zanjo
(zdajle) nimamo posebnih argumentov, pustimo oklepaja prazna.
vrstica
'NNakamura\n'
vrstica.strip()
'NNakamura'
Takšnim funkcijam znotraj škatlic rečemo metode, po angleško method. (Ime je, priznajmo, malo čudno. Zgodovinsko. Vseeno boljše od tega, kako so jih klicali v začetku: sporočilo, message. V kontekstu takratnega razmišljanja, opisovanja tega, kar se dogaja ob klicanju metod, je bilo to ime smiselno, danes pa niti med programerji ni veliko takšnih, ki bi vedeli za ta izraz.)
strip
strip
je torej metoda nizov. Vsi nizi imajo
strip
(vsak svojega, drugega? kakor se vzame.) in njeno
delo je, da vrne niz brez belega prostora na začetku in na koncu. Beli
prostor so presledki, znak za novo vrstico in tabulatorji.
= " niz s preveč presledki. \n Res. \n\n "
s
s.strip()
'niz s preveč presledki. \n Res.'
Vidimo: odstranil je vse presledke in nove vrstice na začetku in na koncu, tiste vmes pa je pustil pri miru.
Obstajata različici lstrip
in rstrip
, ki
odstranita le presledke na začetku in na koncu. Kateri je kateri, si
boste že zapomnili, saj znate angleško.
Metodi strip
lahko podamo tudi argument, če želimo
odstranjevati kaj drugega kot presledke. To bomo počeli redko, a vseeno
pokažimo:
".....čemu služijo te pike?!...".strip(".")
'čemu služijo te pike?!'
Mimogrede smo videli, da lahko metode kličemo tudi na
literalih za nize ("dobesedno" navedenem nizu) ne le na nizih,
ki so že poimenovani, kot je bil gornji s
.
Tako se bomo torej znebili \n
na koncu vrstice s
kolesom.
vrstica.strip()
'NNakamura'
Nobena metoda niza ne spreminja niza, temveč kvečjemu vrne
nov niz. Nizi so nespremenljivi. Niz, na katerega se nanaša
vrstica
, je še vedno tak, kot je bil.
vrstica
'NNakamura\n'
Metoda strip
je zgolj vrnila nov niz in če hočemo, da
niz, na katerega se nanaša vrstica, ne bo več vseboval \n
,
moramo imenu vrstica
prirediti vrednost, ki jo vrne
vrstica.strip()
.
= vrstica.strip()
vrstica
vrstica
'NNakamura'
Naš naslednji kamen spotike je bilo primerjanje, pri katerem
zanemarjamo razliko med malimi in velikimi črkami. Takšno torej, po
katerem so nizi "canyon"
, "Canyon"
in
"CANYON"
enaki.
Priznati moram, da funkcije, namenjene takšnemu primerjanju, nisem še
nikoli uporabil, sem pa slutil, kje jo najti in jo tam ob zapisovanju
teh zapiskov tudi hitro našel. Za tiste, ki bi jo utegnili potrebovati:
imenuje se locale.strcoll
. Kako, da tak postaran programer,
kot sem jaz, ne ve zanjo? Preprosto: ta problem rešim po preprostejši
bližnjici. locale.strcoll
bi moral uporabiti le za urejanje
nizov po abecedi, za ugotavljanje enakosti pa oba niza preprosto
pretvorim v male (ali velike) črke.
vrstica.lower()
'nnakamura'
vrstica.upper()
'NNAKAMURA'
Ali celo
vrstica.capitalize()
'Nnakamura'
Spet: nobena od teh funkcij ne spreminja niza, vse zgolj vrnejo nov niz. Če hočem, da bo vrstica vsebovala niz, zapisan s samimi malimi črkami, potrebujem
= vrstica.lower() vrstica
vrstica
'nnakamura'
Zavoljo popolnosti povejmo še za eno metodo: casefold
.
Ta je podobna lower
, vendar naredi še nekateri druge
pretvorbe, recimo zamenja ß z ss, saj je "Straße" za
tiste, ki znajo nemško, isto kot "strasse". Poleg tega, recimo, poskrbi,
da so vsi šumniki zapisani na enak način (zapisati jih je mogoče z enim
znakom ali kot kombinacijo c/s/z s strešico).
"Straße".casefold()
'strasse'
Tudi metoda replace
ima nerodno ime, saj ne spreminja
niza, temveč vrne niz, v katerem je nek znak ali podniz zamenjan z
drugim.
"a", "e") vrstica.replace(
'nnekemure'
"mu", "mjav") vrstica.replace(
'nnakamjavra'
"am", "") vrstica.replace(
'nnakura'
Kar potrebujemo mi, je zamenjava dveh nn-jev z enim:
"nn", "n") vrstica.replace(
'nakamura'
To dela tudi za Canyona:
"cannyon".replace("nn", "n")
'canyon'
"canyonn".replace("nn", "n")
'canyon'
"cannyonn".replace("nn", "n")
'canyon'
Ker vsaka od teh metod vrne nov niz, jih lahko "verižimo".
= "NNakamura\n" vrstica
"nn", "n") vrstica.strip().lower().replace(
'nakamura'
In zdaj lahko končno preštejemo te bicikle:
= stevens = nakamura = canyon = 0
cube for vrstica in open("../vaje/kolesa.txt"):
= vrstica.strip().lower().replace("nn", "n")
vrstica if vrstica == "cube":
+= 1
cube if vrstica == "stevens":
+= 1
stevens if vrstica == "nakamura":
+= 1
nakamura if vrstica == "canyon":
+= 1
canyon
print("Cube:", cube)
print("Stevens:", stevens)
print("Nakamura:", nakamura)
print("Canyon:", canyon)
Cube: 46
Stevens: 1
Nakamura: 23
Canyon: 30
Nizi imajo še par ducatov metod, vendar večine bodisi še ne moremo
razumeti, bodisi niso prav splošno uporabne. Tu bomo navrgli le še dve,
potem pa posvetili daljši razdelek zvezdi današnjega predavanja, metodi
split
.
Preprosti, a pogosto uporabni metodi sta startswith
in
endswith
. Podamo jima niz in vrneta True
ali
False
.
= "Benjamin" ime
"Ben") ime.startswith(
True
"B") ime.startswith(
True
"") ime.startswith(
True
"tisto zgoraj je malo hecno") ime.startswith(
False
"min") ime.endswith(
True
"max") ime.endswith(
False
Obe metodi bomo - kot vse, ki vračajo False
ali
True
- seveda najpogosteje uporabljali v pogojih v
if
(in while
, ko ga bomo poznali).
Hja. V resnici je v direktoriju vaje samo precej poenostavljena različica datoteke s kolesarsko statistiko. V resnici je videti tako:
Cube,51,1216
Canyon,104,444
Cube,74,1508
Cube,79,936
Nakamura,17,95
(in tako naprej)
V vsaki vrstici so trije podatki, ločeni z vejicami: najprej ime kolesa, ki ga je imel neznani kolesar nek dan za izlet, nato prevožena razdalja in potem višinski metri. Kako prebrati to?
Za začetek vzemimo le eno vrstico.
= "Cube,51,1216" vrstica
Doslej zamolčana metoda nizov je split
. Ta razbije nize
na več nizov; kot argument ji podamo ločilo - v našem primeru
vejico.
",") vrstica.split(
['Cube', '51', '1216']
Metoda je vrnila tri nize. Kaj pomenijo tisti oglati oklepaji, ni današnja tema. Motili nas ne bodo. Ker imamo tri nize, jih bomo priredili trem spremenljivkam.
= vrstica.split(",") kolo, razdalja, visina
Razumemo? Desno od enačaja imamo tri stvari, torej levo od njega potrebujemo tri spremenljivke.
Njihove vrednosti so, kot morajo biti.
kolo
'Cube'
razdalja
'51'
visina
'1216'
Pa ugotovimo, kako daleč se je možakar peljal in kakšna je skupna višina njegovih izletov.
= 0
skupna_razdalja = 0
skupna_visina for vrstica in open("kolesa.txt"):
= vrstica.split(",")
kolo, razdalja, visina += int(razdalja)
skupna_razdalja += int(visina)
skupna_visina
print("Skupna razdalja:", skupna_razdalja)
print("Skupna višina:", skupna_visina)
Skupna razdalja: 6359
Skupna višina: 98076
Meni se zdi to kar imenitno. Kar zapletene datoteke znamo že brati.
V zvezi s split
je potrebno povedati še nekaj. Datoteke
s podatki, ločene z vejicami, pogosto dobimo iz kakega Excela. Včasih pa
so podatki ločeni s tabulatorji ali celo le s presledki - se pravi,
belim prostorom. V tem primeru pokličemo split
brez
argumentov.
"Cube 45 1024".split()
['Cube', '45', '1024']
To je podobno, vendar ne popolnoma isto, kot če kot argument podamo presledek.
"Cube 45 1024".split(" ")
['Cube', '45', '1024']
Razlika je v tem, kako split
obravnava zaporedne
presledke v nizu.
"Cube 45 1024".split()
['Cube', '45', '1024']
"Cube 45 1024".split(" ")
['Cube', '', '', '45', '', '', '', '', '1024']
V prvem primeru upošteva zaporedne presledke kot en sam presledek, v
drugem primeru, ko smo podali argument, pa je pet zaporednih presledkov
obravnaval kot pet ločil - med njimi pa so bili prazni nizi. Tega
navadno nočemo. Kadar vas torej zasrbi, da bi poklicali
split(" ")
, se obrzdajte in pokličite
split()
.