# 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. Ni mu popolnoma uspelo. Vendar se da in je kar lepo.

    def knjigovodstvo(ime_datoteke):
        podatki = np.genfromtxt(ime_datoteke, delimiter=":", dtype=str)
        stvari = podatki[:, 0]
        cene = podatki[:, 1].astype(int)
        dohodki = cene > 0
        odhodki = cene < 0
        return (list(zip(stvari[dohodki], cene[dohodki])),
                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. Dobavitelji

Imamo drevo dobaviteljev. Če se odločimo nek izdelek kupiti prek Elizabete, ga lahko dobimo od nje, lahko pa od kogarkoli, ki je v "drevesu" pod njo, recimo od Barbare, France, Alenke. Kadar gre izdelek prek več rok, vmesni členi seveda poberejo provizije: če nekaj kupimo od Elizabete, dejansko pa pride izdelek od Franca, bo Jurij pribil 10 % na Frančevo ceno, Elizabeta pa 10 % na Jurijevo.

"Drevo" je v slovarju pogodbeniki. Cene so v slovarju cene: njegovi ključi so imena pogodbenikov, vrednosti pa slovarji, katerega ključi so imena izdelkov, vrednosti pa cene. Elizabetina cena za metlo je v `cene["Elizabeta"]["metla"]`. Če Elizabeta nima na zalogi metel, v njenem slovarju tega ključa ni. Če nekdo v resnici nima nobenih izdelkov, temveč samo posreduje, ga ni niti v slovarju cene.

Predpostaviti smeš, da za vsak izdelek obstaja vsaj en ponudnik.

Napiši funkcijo `najcenejsa_ponudba(dobavitelj, izdelek)`, ki vrne najnižjo ceno, po kateri lahko od pogodbenika dobimo podani izdelek. Ta cena je torej lahko cena, po kateri izdelek dejansko pride od te osebe, lahko pa je cena, po kateri izdelek ponuja kdo od podpogodbenikov, seveda z dodanimi provizijami.

## Rešitev

Za začetek pogledamo, za koliko se reč dobi pri `dobavitelj`; če je ne ponuja, je njegova cena `inf`. Nato gremo po vseh, ki so pod njim; cene, ki jih ponudijo, pomnožimo z 1.1 in če so nižje od najnižje doslej, si zapomnimo to.

    def najcenejsa_ponudba(dobavitelj, stvar):
        najcena = cene.get(dobavitelji, {}).get(stvar, float("inf"))
        for pogodbenik in dobavitelji[dobavitelj]:
            cena = najcenejsa_ponudba(pogodbenik, stvar) * 1.1
            if cena < najcena:
                najcena = cena
        return najcena

# 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")