Izpit 31. januar 2024

1. Knjigovodstvo

Napišite funkcijo knjigovodstvo(ime_datoteke), ki prejme ime datoteke s prihodki (vrstice s pozitivnimi števili) in odhodki (negativna števila) v obliki, kot jo kaže okvirček na desni. Funkcija mora vrniti trojko: seznam s pari imen dohodkov in pripadajočih zneskov, seznam s pari imen odhodkov in pripadajočih cen (kot pozitivna števila) ter skupno vsoto. Za datoteko

slika: 50
slika: 100
tempera: -3
stol: -20
kip: 20
zrak: 0
torba: 12

vrne

([('slika', 50), ('slika', 100), ('kip', 20), ('torba', 12)],
 [('tempera', 3), ('stol', 20)],
 159)

pri čemer je 159 preprosto vsota vseh številk.

Rešitev

Najprej pripravimo, kar bomo vrnili - seznama prihodki in odhodki ter bilanca.

Nato gremo po datoteki: vsako vrstico razdelimo glede na : in pretvorimo znesek v število. Če je šteivlo negativno, dodamo par s stvarjo in zneskom v odhodki (pri čemer mora biti število -znesek), sicer v prihodki. Pa bilanco povečamo za znesek.

def knjigovodstvo(ime_datoteke):
    prihodki = []
    odhodki = []
    bilanca = 0
    for vrstica in open(ime_datoteke):
        stvar, znesek = vrstica.split(":")
        znesek = int(znesek)
        if znesek < 0:
            odhodki.append((stvar, -znesek))
        elif znesek > 0:
            prihodki.append((stvar, znesek))
        bilanca += znesek
    return prihodki, odhodki, bilanca

Nekateri radi pišejo

    if znesek > 0:
        bilanca += znesek
    else:
        bilanca -= abs(znesek)

To seveda ni potrebno. Python zna prištevati negativne vrednosti. :)

Prav tako je lepo, da bilanco spreminjamo izven if in ne znotraj, ločeno za odhodke in za prihodke.

Eden od študentov je poskusil nalogo rešiti z numpy-jem. Sicer se mu ni čisto posrečilo, vendar gre in je kar lepo. Dobra ideja.

def knjigovodstvo(ime_datoteke):
    podatki = np.genfromtxt(ime_datoteke, delimiter=":", dtype=str)
    stvari = podatki[:, 0]
    cene = podatki[:, 1].astype(int)
    prihodki = cene > 0
    odhodki = cene < 0
    return (list(zip(stvari[prihodki], cene[prihodki])),
            list(zip(stvari[odhodki], -cene[odhodki])),
            np.sum(cene))

Zoprna stvar je tisto na koncu: z zip poberemo skupaj stvari in cene, da nam sestavi pare, potem pa pokličemo še list, da jih zložimo v seznam.

2. Draginja

Napiši funkcijo draginja(odhodki), ki vrne seznam parov imen stvari in cen. Ista stvar se lahko pojavi večkrat, a morda z različnimi cenami. Funkcija mora vrniti ime stvari, katere povprečna cena je bila največja. Če je takšnih stvari več, lahko vrne poljubno izmed njih.

Klic draginja([('stol', 20), ('torba', 12), ('tempera', 3), ('miza', 50), ('stol', 30), ('stol', 60), ('miza', 40), ('torba', 5)]) vrne 'miza', saj je povprečna cena miz enaka (50 + 40) / 2 = 45, medtem ko je povprečna cena, recimo, stolov (20 + 30 + 60) / 3 = 36,66, torbe in tempere pa so še cenejše.

Rešitev

Tole je naloga iz slovarjev. Da bomo lahko računali povprečne cene, bomo potrebovali dva: v enem beležimo vsote cen vsake stvari, v drugem število takšnih stvari.

Ko se odločimo glede tega, je funkcija preprosta.

def draginja(odhodki):
    skupna_cena = defaultdict(float)
    skupno_kosov = defaultdict(int)
    for stvar, cena in odhodki:
        skupna_cena[stvar] += cena
        skupno_kosov[stvar] += 1

    naj_stvar, naj_povp = None, -1
    for stvar in skupna_cena:
        povp = skupna_cena[stvar] / skupno_kosov[stvar]
        if povp > naj_povp:
            naj_stvar, naj_povp = stvar, povp
    return naj_stvar

Zoprni drugi del se da skrajšati, če znamo bolj spretno uporabljati max in poznamo lambde.

def draginja(odhodki):
    skupna_cena = defaultdict(float)
    skupno_kosov = defaultdict(int)
    for stvar, cena in odhodki:
        skupna_cena[stvar] += cena
        skupno_kosov[stvar] += 1
    return max(skupna_cena, key=lambda stvar: skupna_cena[stvar] / skupno_kosov[stvar])

3. Dragocenosti

Napišite funkcijo dragocenosti(stvari, cene, meja), ki prejme numpy-jevo tabelo z imeni stvari in dvodimenzionalno tabelo, katere vrstice ustrezajo stvarem (v enakem vrstnem redu kot so te naštete v prvi tabeli), stolpci pa dnevom. Vrednosti v tabeli povedo, koliko je stvar stala na posamezni dan.

Funkcija mora vrniti tabelo z imeni vseh stvari, katerih cene so bile vsaj en dan višje od podane meje meja. Vrstni red mora biti enak vrstnemu redu v tabeli stvari.

Pri tej nalogi pričakujemo rešitev v čistem numpy-ju, brez zank v Pythonu.

Rešitev

Takole.

def dragocenosti(stvari, cene_po_dnevih, meja):
    return stvari[np.any(cene_po_dnevih > meja, axis=1)]

Ali pa takole.

def dragocenosti(stvari, cene_po_dnevih, meja):
    return stvari[np.max(cene_po_dnevih, axis=1) > meja]

4. Spletno knjigovodstvo

Ko poženete teste, se bo v direktoriju s programom pojavila datoteka knjigovodstvo.html. Lahko jo odprete v brskalniku ali urejevalniku.

Napišite funkcijo spletno_knjigovodstvo(ime_datoteke), ki zna prebrati podatke iz takšne datoteke. Predpostaviti smete, da opis nakupa vedno vsebuje le eno število ter da je to število pozitivno in celo. Funkcija mora vrniti seznam s pari stvari in cen. Za podano datoteko naj vrne [('slika', 50), ('slika', 100), ('tempera', 3), ('polomljen stol', 20), ('kip', 20), ('zrak', 0), ('torba', 12)].

Rešitev

Seveda bomo uporabili BeautifulSoup in regularne izraze.

Podatki so v delu HTML-ja, ki je videti tako:

        <dt>slika</dt>
        <dd>Stala je 50, lahko bi tudi malo manj.</dd>
        <dt>slika</dt>
        <dd>Ta je bila malo dražje, zanjo smo dali 100.</dd>

Poiščemo vse dt in potem dd, ki jim sledijo. Znotraj tega, kar je bilo v dd poiščemo zaporedja števk \d+. Kar najdemo, pretvorimo v število.

def spletno_knjigovodstvo(ime_datoteke):
    soup = BeautifulSoup(open(ime_datoteke), "html.parser")
    postavke = []
    for vrstica in soup.find_all("dt"):
        stvar = vrstica.text
        opis = vrstica.find_next_sibling("dd").text
        znesek = re.search(r"\d+", opis).group()
        postavke.append((stvar, int(znesek)))
    return postavke

5. Evidenca

Napišite funkcijo evidenca(postavke, ime_datoteke), ki prejme postavke v obliki trojk z imenom stvarmi, ceno za kos in številom kosov, na primer [("slika", 50, 2), ("tempera", 3, 1), ("stol", 20, 1), ("kip", 20, 12), ("zrak", 0, 141), ("torba", 12, 1)]. Funkcija mora v datoteko ime_datoteke zapisati tabelo v obliki, ki jo kaže slika na desni. Točno obliko - število presledkov - razberite iz testov.

Stvar        Cena x Kosov     Skupaj
------------------------------------
slika          50 x 2            100
tempera         3 x 1              3
stol           20 x 1             20
kip            20 x 12           240
zrak            0 x 141            0
torba          12 x 1             12

Rešitev

Tule gre zgolj za osnovno oblikovanje nizo: prešteti moramo, na koliko znakov poravnamo kakšno stvar, pa z > in < potiskati stvari na desno in na levo.

def evidenca(postavke, ime_datoteke):
    f = open(ime_datoteke, "w")
    f.write(f"{'Stvar':10} {'Cena':>6} x {'Kosov':<5} {'Skupaj':>10}\n")
    f.write("-" * 36 + "\n")
    for stvar, cena, kosov in postavke:
        f.write(f"{stvar:10} {cena:>6} x {kosov:<5} {cena * kosov:10}\n")