Napišite funkcijo cena(zasedenost, cene)
, ki prejme
numpyjevo tabelo zasedenost
tipa bool
, ki ima
šest stolpcev in toliko vrstic, kolikor vrst ima letalo ter vsebuje
True
na mestih, ki ustrezajo zasedenim sedežem in
False
na mestih, ki ustrezajo prostim. Poleg tega dobi
Pythonov seznam cene
, ki vsebuje cene vozovnic za sedeže v
prvih treh stolpcih, na primer [200, 175, 193]
, če sedeži
ob oknu (stolpec 0) stanejo 200, sedeži na sredini (stolpec 1) 175 in
sedeži ob hodniku (stolpec 2) 193 evrov. Cene sedežev v drugih treh
stolpcih so zrcalno enake tem (na primer 193, 175 in 200).
Funkcija mora vrniti vsoto cen vseh prodanih vozovnic. Funkcija mora biti napisana v numpyju. Točkovanje bo upoštevalo eleganco rešitve.
Tule je najprej rešitev. Njena razlaga je spodaj.
def cena(zasedenost, cene):
return np.sum(zasedenost * (cene + cene[::-1]))
Zdaj pa razložimo počasi, po korakih.
Najprej smo razširili cene na vseh šest stolpcev. Če imamo
Pythonov seznam cene
, na primer
= [200, 175, 193] cene
lahko k njemu pripnemo še prezrcaljen seznam,
+ cene[::-1] cene
[200, 175, 193, 193, 175, 200]
pa dobimo cene za vse stolpce. (Nekateri študenti so prezrli, da so cene na desni strani letala zrcalne tem na levi. To rešitev sem štel za "skoraj pravilno", saj bi njihovi programi z malo spremembo delovali pravilno.)
Zdaj pa imamo dve poti. Očitnejša (a, kot se bo izkazalo, malenkost daljša, vendar vam pri ocenjevanju tega nisem štel v slabo), je, da najprej preštejemo zasedenost vseh stolpcev.
import numpy as np
= np.array([
zasedenost True, False, False, False, False, False],
[False, True, True, False, False, False],
[False, False, False, True, True, True],
[False, False, False, True, True, True],
[False, True, False, True, True, True],
[False, False, False, True, True, False],
[False, False, False, False, True, False]
[ ])
Zasedenost po stolpcih dobimo tako, da tabelo seštejemo po osi 0,
sum(zasedenost, axis=0) np.
array([1, 2, 1, 4, 5, 3])
To tabelo pomnožimo s cenami.
sum(zasedenost, axis=0) * (cene + cene[::-1]) np.
array([200, 350, 193, 772, 875, 600])
pa dobimo vsote cen po stolpcih. To, končno, seštejemo v skupno ceno.
sum(np.sum(zasedenost, axis=0) * (cene + cene[::-1])) np.
2990
Druga rešitev je, da celotno tabelo pomnožimo s cenami.
* (cene + cene[::-1]) zasedenost
array([[200, 0, 0, 0, 0, 0],
[ 0, 175, 193, 0, 0, 0],
[ 0, 0, 0, 193, 175, 200],
[ 0, 0, 0, 193, 175, 200],
[ 0, 175, 0, 193, 175, 200],
[ 0, 0, 0, 193, 175, 0],
[ 0, 0, 0, 0, 175, 0]])
In to seštejemo.
sum(zasedenost * (cene + cene[::-1])) np.
2990
Če bi nas bolelo za učinkovitost, bi bila prva rešitev boljša, saj ima manj množenj. Vendar letala niso tako velika, da bi se to res poznalo, zato je morda boljša druga, krajša. V resnici sta obe dovolj hitri, za potrebe tega predmeta pa sploh.
Potovalne agencije pošiljajo potnikom sporočila v slogu
Spoštovani,
potrjujemo nakup vozovnice. Detajli so zapisani spodaj.
Posebne želje: nima posebnih želja.
Ime potnika: Janez Tone Novak
Starost: 34
Cena: 184E
Dodelili smo vam sedež 12A, kot ste želeli.
Vsebina je lahko zelo različna, vedno pa velja naslednje:
Napišite funkcijo preberi_sporocilo(ime_datoteke)
, ki
kot argument dobi ime datoteke s takšnim sporočilom ter vrne par z
imenom potnika in oznako sedeža. Če sporočilo ne vsebuje katerega od teh
dveh podatkov, namesto tega podatka vrne None
. Za primer na
sliki funkcija vrne ("Janez Tone Novak", "12A")
.
V nalogi moramo pokazati, da znamo prebrati datoteko in uporabljati regularne izraze. Gre tudi brez, vendar je precej bolj zoprno; bomo videli.
Regularni izraz za besedo, ki se začne z veliko črko in je dolgo vsaj
dve črki [A-Z]\w+
-- [A-Z]
je velika črka
(angleške abecede) in \w+
pomeni eno ali več črk. Zapleteni
del je, da ima potnik dve ali več imen, ločenih s presledki. To dobimo z
([A-Z]\w+)( [A-Z]\w+)+
. V prvi skupini je prvo ime, v drugi
skupini so nadaljnja. Vse to zapremo še v ene oklepaje, da dobimo
"skupino", ki vsebuje vsa potnikova imena.
Pred vse skupaj postavimo .+:
- dvopičje, ki mu sledi
presledek. Celoten regularni izraz je torej
.+: (([A-Z]\w+)( [A-Z]\w+)+)
. Kar zoprno. Na srečo bo prav
ta del možno kolikor toliko spodobno narediti brez regularnih
izrazov.
Oznako sedeža poiščemo z \d\d?[A-F]
- števka, nato morda
še ena števka in potem črka med A in F. Manjša težava je, da bo tole
našlo tudi 184E
(reklo bo, da gre za sedež 84E, saj najde
dve števki, ki ji sledi črka). Da bo naloga rešena popolnoma pravilno
(in preživela vse teste), moramo zato povedati še, da pred prvo števko
ne sme biti drugi števk, \D\d\d?[A-F]
. Del, ki vsebuje
številko sedeža, zapremo v skupino, da jo bomo lažje izvlekli iz
aajdenega podniza, \D(\d\d?[A-F])
.
Funkcija je potem preprosta: v začetku postavimo potnik
in sedez
na None
, potem v vsaki vrstici
poskusimo najti enega od gornjih izrazov. Če uspemo, postavimo ustrazno
spremenljivko, kot je treba.
import re
def preberi_sporocilo(ime_datoteke):
= None
potnik = None
sedez for vrstica in open(ime_datoteke, encoding="utf-8"):
= re.match(r".+: (([A-Z]\w+)( [A-Z]\w+)+)", vrstica)
mo if mo:
= mo.group(1)
potnik = re.search(r"\D(\d\d?[A-F])", vrstica)
mo if mo:
= mo.group(1)
sedez return potnik, sedez
Pa če nočemo uporabiti regularnih izrazov? (Morda zato, ker jih ne znamo?) V tem primeru postanejo stvari veliko nadležnejše.
V funkciji bomo uporabljali metode s.isupper()
,
s.isdigit()
in s.isalpha()
, ki povedo, ali niz
vsebuje same velike črke, same števke ali same črke. Očitno bi šlo tudi
brez njih, vendar obstajajo meje, do katerih sem si pripravljen v teh
zapiskih zaplesti življenje. :)
def preberi_sporocilo(ime_datoteke):
= None
potnik = None
sedez for vrstica in open(ime_datoteke, encoding="utf-8"):
# Imena potnika
if ":" in vrstica:
= vrstica.split(":")[-1]
ime for beseda in ime.split():
if not (beseda[0].isupper() and beseda.isalpha()):
break
else:
= ime.strip()
potnik
# Oznaka sedeža
for i, c in enumerate(vrstica):
if c.isdigit() \
and (i == 0 or not vrstica[i - 1].isdigit()):
if i + 1 < len(vrstica)\
and vrstica[i + 1] in "ABCDEF":
= vrstica[i:i + 2]
sedez if i + 2 < len(vrstica) \
and vrstica[i + 1].isdigit()\
and vrstica[i + 2] in "ABCDEF":
= vrstica[i:i + 3]
sedez
return potnik, sedez
Da dobimo ime potnika, preverimo, ali vsebuje vrstica dvopičje in v
tem primeru predpostavimo, da bi lahko bilo ime tisto, kar sledi
(zadnjemu) dvopičju. (Mimogrede: zgoraj smo napisali
ime = vrstica.split(":")[-1]
; bolj Pythonovski način bi bil
*_, ime = vrstica.split(":")
, vendar se s takšnim
leporečjem pri tem predmetu ne obremenjujemo.)
Vse, kar je sledilo dvopičju, razbijemo na besede in preverimo, ali je prva črka velika in je niz sestavljen iz samih črk. Če naletimo na kakšno besedo, ki ne ustreza temu pogoju, prekinemo zanko. Če se zanka izteče brez prekinitve, smo našli ime.
Na nekem predavanju smo omenili generatorje. Tu bi nam prišli nekoliko prav: namesto
for beseda in ime.split():
if not (beseda[0].isupper() and beseda.isalpha()):
break
else:
= ime.strip() potnik
bi lahko napisali tudi
if all(beseda[0].isupper() and beseda.isalpha() for beseda in ime.split()):
potnik = ime.strip()
Drugi del, iskanje oznake sedeža, je še bolj zoprn. Gremo čez
vrstico. Zanimajo nas tista mesta, ki vsebujejo števko in pred njimi ni
števke (ali pa smo na začetku; v tem primeu ne gledamo prejšnjega znaka,
čeprav bi to tu slučajno delovalo -- pogledali bi pač znak z indeksom
-1
, torej zadnji znak, ta pa je \n
, kar ni
števka).
Če neko mesto ustreza temu pogoju, gre lahko za oznako sedeža. Da bi
bilo tako, mu mora slediti črka, ali pa števka in nato črka. Pač ...
veliko if
-ov.
Pri pregledovanju izdelkov se je - po pričakovnjih - pokazalo, da so mnogi pravilno napisali regularni izraz za številke sedežev, izraz za imena potnikov pa je bil seveda trši oreh. V tem primeru bi lahko dobili imena potnikov brez regularnega izraza (prvi, preprostejši del spodnje različice programa), številko sedeža pa z regularnim izrazom.
Z regularnimi izrazi pa je življenje seveda preprostejše.
Neka letalska družba pa sedežev ne dodeljuje vnaprej, temveč lahko potniki zgolj izrazijo željo. Potnike nato vkrcavajo po seznamu. Vsak potnik se usede na želeni sedež, če je ta že zaseden, pa se pomika nazaj po vrstah, dokler ne naleti na vrsto, v kateri je sedež v želenem stolpcu prazen, ter ga zasede. Če so, na primer, že zasedeni sedeži 12C, 13C in 14C, se bo potnik, ki bi želel sedeti na 12C, usedel na 15C. Letalska družba uporablja dolga letala, ki imajo 130 vrst, obenem pa ji ne gre prav dobro (čudno, čudno), zato smete predpostaviti, da se bo po tem postopku našel sedež za vsakogar.
Napišite funkcijo razpored(seznam)
, ki dobi seznam s
pari (ime_potnika, zeleni_sedez)
in vrne nov seznam, ki je
enak temu, le da so sedeži zamenjani z sedeži, ki jih bodo potniki
dejansko dobili.
Pomoč: če je c
črka A, B, C, D, E ali F, je
ord(c) - 65
enak 0, 1, 2, 3, 4 oz. 5. Mogoče vam pride
prav.
Tole je naloga iz množic in iz zanke while
. Lahko pa
tudi ni iz množic. Vsekakor pa je iz zanke while
. :)
Nekam bo potrebno shranjevati zasedena mesta. Lahko, torej,
pripravimo množico zasedeni
z zasedenimi mesti, poleg tega
pa seznam dejanski
, v katerega bomo postavljali potnike in
ga na koncu vrnili.
def razpored(zelje):
= set()
zasedeni = []
dejanski for potnik, sedez in zelje:
= int(sedez[:-1]), sedez[-1]
vrsta, stolpec while (vrsta, stolpec) in zasedeni:
+= 1
vrsta
zasedeni.add((vrsta, stolpec))f"{vrsta}{stolpec}"))
dejanski.append((potnik, return dejanski
Ker smo toliko delali z numpy
-jem -- pa tudi zaradi
tega, kako so bili shranjeni podatki v prvi nalogi -- je gotovo prišlo
komu na misel namesto množice uporabiti numpy-jevo tabelo. (Priznanje:
tudi sam sem nalogo najprej rešil tako. Vendar je to predvsem posledica
tega, kaj sem razmišljal, ko sem jo sestavljal.)
def razpored(zelje):
= np.zeros((130, 6), dtype=bool)
zaseden = []
razpored for potnik, sedez in zelje:
= int(sedez[:-1]), ord(sedez[-1]) - 65
vrsta, stolpec while zaseden[vrsta, stolpec]:
+= 1
vrsta = True
zaseden[vrsta, stolpec] f"{vrsta}{sedez[-1]}"))
razpored.append((potnik, return razpored
Takšna rešitev je malenkost bolj zapletena, ker potrebujemo še indeks
stolpca. Tega dobimo z ord
, kot je svetoval opis
naloge.
Morda bi koga zmotilo, dvojno shranjevanje podatkov. To, kaj je
zasedeno, je razvidno že iz razpored
, torej je
zaseden
v bistvu nepotreben. To je res, vendar je nerodno,
da je razpored
seznam, v katerem je zoprno preverjati, ali
je določen sedež zaseden. Če že hočemo, pa lahko uporabimo slovar, ki za
vsak sedež pove, kdo sedi na njem.
def razpored(zelje):
= {}
zasedeni for potnik, sedez in zelje:
= int(sedez[:-1]), sedez[-1]
vrsta, stolpec while (vrsta, stolpec) in zasedeni:
+= 1
vrsta = potnik
zasedeni[vrsta, stolpec]
= []
dejanski for (vrsta, stolpec), potnik in zasedeni.items():
f"{vrsta}{stolpec}"))
dejanski.append((potnik, return dejanski
Prvi del se je malenkost poenostavil. V return
pa moramo
iz slovarja sestaviti pare potnikov in sedežev.
Drugi del lahko poenostavimo, če znamo sestavljati izpeljane sezname:
return [(potnik, f"{vrsta}{stolpec}")
for (vrsta, stolpec), potnik in zasedeni.items()]
Napišite funkcijo ravnotezje(ime_datoteke)
, ki prejme
ime datoteke s podatki, ločenimi z vejico. V prvi vrstici so zapisana
imena stolpcev. Eden od njih se imenuje sedez; sedeži so zapisani v
običajni obliki, npr. 12F ali 1C ali 128E. Funkcija mora vrniti razliko
med številom potnikov, ki sedijo na desni (stolpci D, E, F) in levi
(stolpci A, B, C) strani letala. Če, recimo, na desni sedi 7 potnikov
več, vrne 7; če je 7 potnikov več na levi, vrne -7.!
Ta, ki je normalno in pridno delal domače naloge, se ob tej nalogi
lahko zahvali za poceni točke. Potrebno je le uporabiti
DictReader
in brati vrednosti v ustreznem stolpcu - to, v
čemer so nas vadile naloge od vremena do medvedov. Glede na zadnji znak
je bilo potrebno spreminjati ravnotežje letala. Lahko ločeno štejemo
levo- in desnosedeče ter na koncu vrnemo razliko ...
import csv
def ravnotezje(ime_datoteke):
= desno = 0
levo for vrstica in csv.DictReader(open(ime_datoteke, encoding="utf-8"), delimiter=","):
if vrstica["sedez"][-1] <= "C":
+= 1
levo else:
+= 1
desno return desno - levo
... lahko pa razliko računamo že kar sproti.
def ravnotezje(ime_datoteke):
= 0
ravno for vrstica in csv.DictReader(open(ime_datoteke, encoding="utf-8"), delimiter=","):
if vrstica["sedez"][-1] <= "C":
-= 1
ravno else:
+= 1
ravno return ravno
Kakor vam drago, je rekel Vili.
Napišite funkcijo vozni_redi(potniki, ime_datoteke)
.
Argument potniki je seznam z imeni potnikov, številkami letov in pari
(ura, minuta) za odhod in prihod. Primer podatkov je, recimo
[("Ana Argon", "LH2832", (12, 10), (13, 20)), ("Berta Bor", "UO391", (15, 5), (20, 30)), ("Cilka Cankar", "LH192", (7, 0), (12, 30))]
.
Funkcija mora v datoteko s podanim imenom zapisati tabelico z imeni,
leti in časi, v naslednji obliki.
Ana Argon LH2832 12:10-13:20
Berta Bor UO391 15:05-20:30
Cilka Cankar LH192 7:00-12:30
Točno obliko - število presledkov - razberite iz testov.
Tudi tale je bila kar poceni.
def vozni_redi(potniki, ime_datoteke):
= open(ime_datoteke, "w", encoding="utf-8")
f for potnik, let, (od_h, od_m), (pri_h, pri_m) in potniki:
f"{potnik:20} {let:>6} {od_h:2}:{od_m:02}-{pri_h:2}:{pri_m:02}\n") f.write(
Ta naloga se je izkazala za - do neke mere - najlažjo. Preglavice so
vam delale le vodilne ničle - večina je pisala, na primer
{od_m:2}
namesto {od_m:02}
. Nekateri so se
znašli tako, da so pred izpisom preverili, ali je število manjše od 10
in v tem primeru dodalo manjkajočo ničlo. No, tudi tako se da.
Ker smo tule napisali veliko različnih rešitev, združimo vse skupaj še v "pričakovano" rešitev izpita. Tudi za vtis o dolžini celotnega izpita.
import re
import csv
import numpy as np
def cena(zasedenost, cene):
return np.sum(zasedenost * (cene + cene[::-1]))
def preberi_sporocilo(ime_datoteke):
= None
potnik = None
sedez for vrstica in open(ime_datoteke, encoding="utf-8"):
= re.match(r".+: (([A-Z]\w+)( [A-Z]\w+)+)", vrstica)
mo if mo:
= mo.group(1)
potnik = re.search(r"\D(\d\d?[A-F])", vrstica)
mo if mo:
= mo.group(1)
sedez return potnik, sedez
def razpored(zelje):
= set()
zasedeni = []
dejanski for potnik, sedez in zelje:
= int(sedez[:-1]), sedez[-1]
vrsta, stolpec while (vrsta, stolpec) in zasedeni:
+= 1
vrsta
zasedeni.add((vrsta, stolpec))f"{vrsta}{stolpec}"))
dejanski.append((potnik, return dejanski
def ravnotezje(ime_datoteke):
= 0
ravno for vrstica in csv.DictReader(open(ime_datoteke, encoding="utf-8"), delimiter=","):
if vrstica["sedez"][-1] <= "C":
-= 1
ravno else:
+= 1
ravno return ravno
def vozni_redi(potniki, ime_datoteke):
= open(ime_datoteke, "w", encoding="utf-8")
f for potnik, let, (od_h, od_m), (pri_h, pri_m) in potniki:
f"{potnik:20} {let:>6} {od_h:2}:{od_m:02}-{pri_h:2}:{pri_m:02}\n") f.write(