Poglejmo, kako reševati čisto prvo domačo nalogo z generatorskimi izrazi. Spomnimo se: naloga je videti tako.

Imamo datoteko takšne oblike.

11
17
24
30
-1
13
27
33
-1
12
27
34
40
-1
9
-1
8
20
30
31
-1

Tule se je prodajalo pet predmetov.

Vidimo torej:

  1. Skupno so prodali 5 predmetov.
  2. Najvišja dosežena cena je 40.
  3. Skupna cena prodanih predmetov je 143 (to je, 30 + 33 + 34 + 9 + 31).
  4. Ana je kupila 2 predmeta, Berta pa 3.
  5. Ana je porabila 42, Berta 101.

Napisati nam je program, ki prebere in izračuna vse te stvari.

1. Število predmetov

Prešteti moramo, koliko je enic. Najočitnejše je: pokličemo list, kot argument damo odprto datoteko. Preštejemo, koliko vrstic je enakih "-1\n".

list(open("drazba.txt")).count("-1\n")
5

Marsikaj lahko očitamo tej rešitvi. Od tega, da moramo dodajati \n, do tega, da nas bo zmedel vsak dodaten presledek. Pa še celotno datoteko preberemo in shranimo v seznam.

Za začetek: preberimo številke iz datoteke.

print([int(v) for v in open("drazba.txt")])
[11, 17, 24, 30, -1, 13, 27, 33, -1, 12, 27, 34, 40, -1, 9, -1, 8, 20, 30, 31, -1]

Še vedno beremo v seznam - tega se bomo znebili kasneje. Že to je boljše, saj nas prazni prostor ne moti več. Rešitev, podobna prejšnji vendar varnejša, je torej

[int(v) for v in open("drazba.txt")].count(-1)
5

V resnici nas zanima samo, ali je številka enaka -1. Pa pripravimo seznam True-jev in False-ov.

print([int(v) == -1 for v in open("drazba.txt")])
[False, False, False, False, True, False, False, False, True, False, False, False, False, True, False, True, False, False, False, False, True]

Zanima nas, koliko je True-jev. To bi lahko (spet) dobili s count, lahko pa kar seštejemo elemente tega seznama, saj je True toliko kot 1 in False toliko kot 0.

sum([int(v) == -1 for v in open("drazba.txt")])
5

Končno, namesto da sestavljamo seznam, lahko funkciji sum podamo kar generator.

sum(int(v) == -1 for v in open("drazba.txt"))
5

Tak način programiranja nas bo spremljal naslednjih nekaj tednov.

2. Najdražji predmet

Naloga pač sprašuje po največji številki. To je še preprostje kot število predmetov:

max(int(v) for v in open("drazba.txt"))
40

Za vajo (in prihodnjo rabo) se lotimo še malo drugače: izračunajmo maksimum vseh zadnjih cen predmetov.

Ko smo reševali naloge z datotekami smo (namerno!) odlašali s tem, da bi spoznali in uporabljali sezname. Naučili smo se, da je koristno poznati trenutno in prejšnjo vrstico datoteke. Zdaj vemo za pairwise, ki vrne zaporedne pare.

from itertools import pairwise

pairwise(open("drazba.txt"))
<itertools.pairwise at 0x1084e9ea0>

No, ja, pairwise je generator, ki generira par za parom. Če hočemo, da jih "izgenerira" in pokaže, jih zložimo v seznam.

print(list(pairwise(open("drazba.txt"))))
[('11\n', '17\n'), ('17\n', '24\n'), ('24\n', '30\n'), ('30\n', '-1\n'), ('-1\n', '13\n'), ('13\n', '27\n'), ('27\n', '33\n'), ('33\n', '-1\n'), ('-1\n', '12\n'), ('12\n', '27\n'), ('27\n', '34\n'), ('34\n', '40\n'), ('40\n', '-1\n'), ('-1\n', '9\n'), ('9\n', '-1\n'), ('-1\n', '8\n'), ('8\n', '20\n'), ('20\n', '30\n'), ('30\n', '31\n'), ('31\n', '-1\n')]

Zanimajo nas cene v vrsticah, ki jim sledi vrstica -1.

[int(prej) for prej, potem in pairwise(open("drazba.txt")) if int(potem) == -1]
[30, 33, 40, 9, 31]

Ker pairwise generira pare zaporednih elementov, gremo čeznje z zanko, for prej, potem in pairwise(open("drazba.txt")). Zanimajo nas le tisti pari, pri katerih je drugi element enak -1, saj to pomeni, da je bil izdelek v tem trenutku prodan. Zato dodamo pogoj if int(potem) == -1. V seznam pa zlagamo končne cene, torej int(potem).

Najdražji prodani izdelek je potemtakem:

max(int(prej) for prej, vrstica in pairwise(open("drazba.txt")) if int(vrstica) == -1)
40

3. Skupna cena prodanih predmetov

Ker smo se malo bolj potrudili pri prejšnji točki, nam je zdaj preprosto dobiti skupno ceno prodanih izdelkov. Dobimo jo iz natančno takšnega seznama kot prej, le namesto max pokličemo sum.

sum(int(prej) for prej, vrstica in pairwise(open("drazba.txt")) if int(vrstica) == -1)
143

4. Koliko predmetov je kupil kdo

Za vsak izdelek moramo vedeti, koliko ponudb je bil deležen. Zato moramo dobiti številke vrstic, v katerih je prišlo do nakupa - se pravi številke vrstic, ki vsebujejo -1.

nakupi = [i for i, v in enumerate(open("drazba.txt")) if int(v) == -1]

nakupi
[4, 8, 13, 15, 20]

Z enumerate(open("drazba.txt")) smo dobili seznam parov (številka vrstice, vrstica). Razpakiramo jih v i in v. Naredimo seznam tistih i-jev (številk vrstic), v katerih je vsebina vrstice enaka -1. Zdaj pogledamo te številke.

nakupi
[4, 8, 13, 15, 20]

Za prvi izdelek so bile ponudbe štiri (v vrsticah 0, 1, 2, 3; v četrti je bil prodan). Za drugega so bile ponudbe tri (5, 6, 7), za tretjega štiri (9, 10, 11, 12), za četrtega 1 (14) in za petega spet štiri (16, 17, 18, 19). Število ponudb torej dobimo tako, da odštejemo dva zaporedna elementa nakupi in od razlike odštejemo še 1. Prvi predmet je poseben, z njim se bomo ukvarjali potem.

Razlike med zaporednima elementoma dobimo preprosto.

ponudb = [x - y - 1 for y, x in pairwise(nakupi)]

ponudb
[3, 4, 1, 4]

Zdaj pa rešimo še prvi element. Preprosto bo: delali se bomo, ko da je pred ničto vrstico - torej v minus prvi vrstici - še ena -1. Torej, kot da bi bile -1-ke v vrsticah

[-1] + nakupi
[-1, 4, 8, 13, 15, 20]

Pa zdaj preštejmo število ponudb:

ponudb = [x - y - 1 for y, x in pairwise([-1] + nakupi)]

ponudb
[4, 3, 4, 1, 4]

Berta je kupila vse predmete s sodim številom ponudb, torej jih je kupila

sum(x % 2 == 0 for x in ponudb)
3

Ana pa, seveda, tiste z lihim:

sum(x % 2 == 1 for x in ponudb)
2

5. Poraba

Cene vseh izdelkov znamo dobiti, to smo počeli že v drugi točki.

cene = [int(prej) for prej, vrstica in pairwise(open("drazba.txt")) if int(vrstica) == -1]

Zanima nas vsota vseh cen, za katere je bilo število ponudb, recimo, sodo, če nas zanima Bertina poraba.

sum(cena for cena, ponudb_zanj in zip(cene, ponudb) if ponudb_zanj % 2  == 0)
101

Z zip smo sestavili cene in števila ponudb.

list(zip(cene, ponudb))
[(30, 4), (33, 3), (40, 4), (9, 1), (31, 4)]

Potem smo preprosto pogledali vsoto prvih elementov parov (cena) za vse tiste pare, pri katerih je drugi element (število ponudb) sod.

Toliko je porabila Berta, Ana pa toliko, kolikor je vsota cen izdelkov z lihim številom ponudb.

sum(cena for cena, ponudb_zanj in zip(cene, ponudb) if ponudb_zanj % 2  == 0)
101