Tokratna naloga se je nadaljevala iz nalog z vaj. Zato so tule najprej rešitve teh, nato še rešitev domače naloge.
V eni od nalog smo se ukvarjali z resničnimi podatki z vremenskih postaj v Sloveniji. Podatki so s strani https://www.ncei.noaa.gov/data/daily-summaries/access/. Ta vsebuje datoteke za vse(?) postaje na svetu; arhiv z vsemi datotekami je na https://www.ncei.noaa.gov/data/daily-summaries/archive/ (in je velik 7 GB). Da ne bi pobrali vsega, so v priloženi datoteki .zip samo datoteke, ki se nanašajo na Slovenijo.
Kot boste videli, podatki niso v tako prijazni obliki, kot so bili tisti, s katerimi ste delali vi. Za to vajo boste delali s podatki v obliki, kakršno bi dobili na spletu. Kot boste videli, to ne bo zgolj vaja iz programiranja, temveč tudi iz brskanja po dokumentaciji in splošne iznajdljivosti. Narobe lahko gre marsikaj, sitnosti lahko povzroča karkoli. Vaje iz resničnega sveta, torej.
Datoteko s podatki lahko odzipaš v direktorij, v katerem bo program, lahko drugam (če jih znaš kljub temu brati).
Vsaka postaja ima svojo kodo. Kredarica, recimo, je SIE00105938.
Napiši funkcijo kodirnik_postaj
, ki ne prejme nobenih
argumentov, vrne pa takšen slovar:
{'Bilje': 'SIE00115106',
'Celje Medlog': 'SIE00115176',
'Crnomelj Doblice': 'SIE00114856',
'Kocevje': 'SIE00114956',
'Kredarica': 'SIE00105938',
'Lesce': 'SIE00114966',
'Letalisce Edvarda Rusjana Mari': 'SIE00115156',
'Letalisce Jozeta Pucnika Ljubl': 'SIE00115146',
'Lisca': 'SIE00115186',
'Ljubljana Bezigrad': 'SIM00014015',
'Murska Sobota Rakican': 'SIE00115196',
'Nova Vas Na Blokah': 'SIE00115066',
'Novo Mesto': 'SIE00115126',
'Portoroz Letalisce': 'SIE00115166',
'Postojna': 'SIE00115076',
'Ratece Planica': 'SIE00115206',
'Smartno Pri Slovenj Gradcu': 'SIE00115136',
'Topol Pri Medvodah': 'SIE00115006',
'Veliki Dolenci': 'SIE00115096',
'Vojsko': 'SIE00115016'}
Bodi pozoren na velike in male črke. Slovar mora seveda razbrati iz datotek in/ali njihovih imen. (Teste bi prestala tudi, če bi ta slovar preprosto prekopirali v funkcijo. :)
Neka zelo kratka različica bi bila
import os
import csv
def kodirnik_postaj():
= {}
kodirnik for fn in os.listdir("podatki"):
for vrstica in csv.DictReader(open("podatki/" + fn)):
"NAME"][:-4].title()] = vrstica["STATION"]
kodirnik[vrstica[return kodirnik
Funkcija predpostavlja, da so datoteke v poddirektoriju "podatki" in da v tem direktoriju ni ničesar drugega kot te datoteke. Poleg tega prebere vse vrstice datoteke, čeprav je ime postaje znano že po prvi vrstici in bi lahko branje prekinili.
Primerna rešitev je
def kodirnik_postaj():
= {}
kodirnik for fn in os.listdir():
if not fn.endswith(".csv"):
continue
for vrstica in csv.DictReader(open(fn)):
"NAME"][:-4].title()] = vrstica["STATION"]
kodirnik[vrstica[break
return kodirnik
Zdaj imamo pogoj, ki preverja, ali se datoteka konča s ".csv". Če ni
tako, s continue
preskočimo ostanek zanke in nadaljujemo z
naslednjo datoteko. Poleg tega pa prekinemo drugo zanko takoj po branju
prve vrstice (break
).
Imena krajev niso lepa. Nekatera se končajo sredi besede, druga so brez šumnikov, tretja vsebujejo preveč podroben opis krajev (namesto Celje Medlog smo zadovoljni tudi s Celje). Napiši funkcijo popravi(kodirnik), ki prejme slovar, kakršnega vrača prva funkcija in ga spremeni - zamenja imena krajev (ključe) z lepšimi.
Zahtevane pretvorbe so zapisane v tem slovarju.
popravki = {'Murska Sobota Rakican': 'Murska Sobota',
'Crnomelj Doblice': 'Črnomelj',
'Letalisce Edvarda Rusjana Mari': 'Maribor',
'Letalisce Jozeta Pucnika Ljubl': 'Brnik',
'Ljubljana Bezigrad': 'Ljubljana',
'Kocevje': 'Kočevje',
'Smartno Pri Slovenj Gradcu': 'Smartno pri Slovenj Gradcu',
'Kredarica': 'Kredarica',
'Veliki Dolenci': 'Veliki Dolenci',
'Novo Mesto': 'Novo mesto',
'Nova Vas Na Blokah': 'Bloke',
'Celje Medlog': 'Celje',
'Portoroz Letalisce': 'Portorož',
'Topol Pri Medvodah': 'Topol pri Medvodah',
'Ratece Planica': 'Rateče'
}
Torej: 'Crnomelj Doblice' se mora spremeniti v 'Črnomelj'.
Ta slovar skopiraj v funkcijo in ga uporabi.
Pomoč: ključa v slovarju se ne da spremeniti. Pač pa odstraniš stari
ključ (uporabiš lahko del d[k]
ali, mogoče še boljše
d.pop(k)
, kjer je d
slovar, k
pa
ključ, ki ga želiš odstraniti) in dodaš nov, popravljen ključ, z
vrednostjo, kakršna je bila pripisana staremu ključu.
Smiselna rešitev je
def popravi(kodirnik):
for ime, menjava in popravki.items():
= kodirnik.pop(ime) kodirnik[menjava]
Predpostavili smo, da se vsako ime, ki ga je potrebno popraviti (in
je torej v popravki
), dejansko pojavi v slovarju
kodirnik
. Če ni tako, bo kodirnik.pop(ime)
javil napako.
Nekateri študenti so naredili obratno: z zanko so šli čez
kodirnik
in poskušali zamenjevati ključe, ki jih je
potrebno zamenjati. To je, kot prvo, potratno: predstavljajte si, da bi
imeli kodirnik z vsemi postajami na svetu, preimenovati pa bi jih bilo
potrebno 100. Gornji program gre pač čez 100 postaj, ki potrebujejo
preimenovanje; različica, ki gre čez vse postaje in preverja, katere
preimenovati, pa gre čez, uh, vse postaje na svetu.
Še pomembneje: to ne deluje. Če greš z zanko čez slovar (ali množico), v tej zanki tega slovarja (ali množice) ne smeš spreminjati. Zanka bo to opazila in javila napako. Zakaj? Zato ker se v slovarju (predvsem pa v množici - slovar je od Pythona 3.6 bolj "stabilen") vrstni red elementov spreminja in zanka ne more vrniti vseh elementov, če se ji ti sproti mešajo.
Napiši funkcijo preberi_meritve(ime_postaje, kodirnik)
,
ki prejme ime postaje in kodirnik (slovar, kakršnega pridela prejšnja
funkcija). Vrniti mora slovar, katerega ključi so datumi, vrednosti pa
najvišje temperature na ta dan.
Ključi morajo biti terke v obliki (leto, mesec, dan). Po klicu
temperature = preberi_meritve("Kredarica", kodirnik)
, je
temperature[(2023, 8, 13)]
temperatura 13. avgusta
2023.
Temperatura je v stolpcu TMAX, podana je v desetinkah Celzijev. Deliti jo morate torej z 10. Na omenjeni datum je za temperaturo na Kredarici zapisano 144; to pomeni, da je bila temperatura 14.4 Celzijev. Shraniti morate seveda to, popravljeno temperaturo.
Na nekatere dneve temperatura ni bila izmerjena. Vrednosti za ta dan
ne shranjuj v slovar. Tako, recimo, slovar za Kredarico ne bo imel
ključa (2023, 10, 4)
, ker ta dan termometer, izgleda, ni
obratoval. :)
Uporabili bomo csv.DictReader
. Pri sestavljanju naloge
sem mislil, da bo tako lažje kot uporabiti csv.reader
in
ročno šteti stolpce. Potem se je izkazalo, da vsaj ena postaja (Bilje)
ne meri globine snežne ideje (zraven Nove Gorice to res ni preveč
smiselno), zato ima datoteka tam manj stolpcev in pride TMAX na vrsto že
prej. Brez DictReader
-ja bi tu torej imeli hujše težave,
kot je bilo videti na prvi pogled.
def preberi_meritve(kraj, kodirnik):
= {}
meritve for vrstica in csv.DictReader(open(kodirnik[kraj] + ".csv")):
= datetime.strptime(vrstica["DATE"], "%Y-%m-%d")
datum = vrstica["TMAX"].strip()
temp if temp:
= int(temp) / 10
temp = temp
meritve[(datum.year, datum.month, datum.day)] return meritve
Datum smo razkopali z datetime.strptime
. To se mi zdi
lažje kot strip("-")
in potem pretvarjanje vsakega kosa
posebej v int
. Komur je bolj všeč po starem, pa naj dela po
starem.ž
Napiši funkcijo mrzli_silvester(podatki)
, ki prejme
slovar, kakršnega vrne funkcija preberi_podatke
. Vrniti
mora leto z najhladnejšim silvestrskim dnem. Torej: med vsemi leti, za
katere je znana temperatura 31. decembra, mora vrniti tisto leto, ko je
bila le-ta najnižja. Če je takih let več, naj vrne najzgodnejše.
Nasvet: najstarejši podatki so iz leta 1900, torej lahko iščete podatke za 31. 12. po letih od 1900 do 2022. Ne delajte zanke čez celoten slovar (to deluje, vendar ni zgledno), temveč le čez leta.
Tole niti ni bila naloga iz tekoče snovi, temveč zgolj iz slovarjev, ki jih poznamo že več kot mesec. Morda je bila ideja bolj v tem, da zagotovo rešite naloge z vaj. :)
import math
def mrzli_silvester(podatki):
= naj_mraz = math.inf
naj_leto for leto in range(1900, 2023):
= (leto, 12, 31)
datum if podatki.get(datum, math.inf) < naj_mraz:
= podatki[datum]
naj_mraz = leto
naj_leto return naj_leto
No, da bo vsaj nekaj novega, smo uporabili math.inf
za
začetno "najnižjo" temperaturo.
Za celovitejši občutek obsežnosti celotnega projekta :), je to vsa koda na kupu.
import os
import csv
import math
from datetime import datetime
## Naloge z vaj
def kodirnik_postaj():
= {}
kodirnik for fn in os.listdir():
if not fn.endswith(".csv"):
continue
for vrstica in csv.DictReader(open(fn)):
"NAME"][:-4].title()] = vrstica["STATION"]
kodirnik[vrstica[break
return kodirnik
def popravi(kodirnik):
for ime, menjava in popravki.items():
= kodirnik.pop(ime)
kodirnik[menjava]
def preberi_meritve(kraj, kodirnik):
= {}
meritve for vrstica in csv.DictReader(open(kodirnik[kraj] + ".csv")):
= datetime.strptime(vrstica["DATE"], "%Y-%m-%d")
datum = vrstica["TMAX"].strip()
temp if temp:
= int(temp) / 10
temp = temp
meritve[(datum.year, datum.month, datum.day)] return meritve
# Domača naloga
def mrzli_silvester(podatki):
= naj_mraz = math.inf
naj_leto for leto in range(1900, 2023):
= (leto, 12, 31)
datum if podatki.get(datum, math.inf) < naj_mraz:
= podatki[datum]
naj_mraz = leto
naj_leto return naj_leto