Potniki
Testi
Testi: testi-potniki-2.py
Naloga
Podan je razred Potnik
.
Potniki začenjajo svojo pot na koordinatah (0, 0) in imajo določeno količino
energije. Nato hodijo naokrog tako, da kličemo metodo pojdi
, ki ji
kot argument podamo smer in razdaljo; na kakšen način ju podajamo, boste videli
spodaj. Vsaka prehojena enota razdalje potniku vzame eno enoto energije; vsakič
ko prečka os x ali os y, dobi 5 enot energije. (Koti in koordinate so obrnjene tako kot v matematiki - pi/2 kaže navzgor, to je, v smeri osi y).
Metoda pojdi
deluje tako, da pokliče metodo
premik(smer, razdalja)
. Ta še ni napisana - napisati jo boste
morali sami. Poleg tega pojdi
preveri, ali je potnik ob tem
premiku prečkal os x ali y in poveča energijo.
Metoda porabi
preveri, ali ima potnik še dovolj energije, da
prehodi določeno razdaljo. Če je nima, vrne False
; če jo ima,
vrne True
in zmanjša potnikovo energijo. Potnik mora imeti pred
odhodom na pot dovolj energije za celo pot; če bi na poti slučajno dobil energijo,
ker bo prestopil os, to ne šteje.
Razreda Potnik
ne smete spreminjati.
Obvezna naloga
Iz razreda Potnik
boste izpeljali tri razrede. Vsi trije bodo
imeli (poleg podedovanih) le eno metodo, namreč premik
. Metoda
premik
mora primerno klicati Potnik
ovo
metodo porabi
.
Vaša metoda premik
mora sprejeti argumente v primerni obliki,
izračunati razdaljo, ki naj bi jo potnik prehodil, s klicem metod
porabi
odšteti energijo in, če porabi
ne vrne
False
(kar bi pomenilo, da potnik nima dovolj energije), potnika
dejansko prestaviti na nove koordinate, tako da spremeni self.x
in self.y
.
Vaša metoda premik
naj ne uporablja (direktno) atributa
__energija
(dva podčrtaja na začetku sta dogovorjeni znak za
"pusti pri miru!"), temveč naj z energijo dela le prek metode
porabi
.
Orto
se premika le orgotonalno, se pravi, na jug, sever, vzhod
in zahod. Njegova metoda premik(smer, razdalja)
naj pričakuje, da
bo smer
eden izmed nizov "S"
, "J"
,
"V"
ali "Z"
, razdalja
pa pač razdalja,
ki naj jo prehodi v tej smeri.
OrtoPlus
pozna poleg tega še smeri "SV"
,
"SZ"
, "JV"
in "JZ"
. Pomen argumenta
razdalja
je takšen: če pokličemo pojdi("SV", 1)
,
bo šel potnik za eno enoto na sever in za eno na vzhod - potuje naravnost, torej
po diagonali. Skupaj bo torej prepotoval (približno) 1.41 enote, torej ga bo to
stalo tudi toliko energije.
Liberalec
dobi smer podano kot kot (v radianih), razdalja pa
je razdalja, ki jo bo naredil v tej smeri.
Rešitev
Vsi trije izpeljani razredi bodo torej imeli metodo premik
,
ki pokliče podedovano porabi
in če ta vrne True
,
spremeni self.x
in self.y
. Metode se razlikujejo le
po tem, za koliko premaknejo in s kakšnim argumentom kličejo
porabi
.
Pri prvih dveh razredih se je pokazalo, da niste preveč zvesti bralci mojih rešitev domačih nalog. V rešitvi naloge Pike bi med drugim lahko videli tale trik:
To je praktično enako temu, kar zgoraj počneta Orto
in
OrtoPlus
. Podobne reči smo počeli v domačih nalogah tudi v
preteklih letih, torej bi lahko isti trik videli tudi tam.
Žal je bila večina rešitev v slogu spodnje rešitve.
Gre za izdelek enega od študentov, vendar bi si lahko izbral tudi katerega drugega. Tule je slučajno zbrano skupaj veliko reči, ki so vredne komentarja.
Najprej: vidim, da sem vas zmedel s super()
. Žal sem ga moral
pokazati, ko smo sestavljali nekoliko bolj zapletene konstruktorje. Zapomnite
si tole: super
uporabimo samo, ko kličemo podedovano metodo, ki
smo jo povozili. S super
povemo, da ne bi radi poklicali svoje
metode, temveč podedovano. Razredi, izpeljani iz razreda Potnik
,
nimajo svoje metode porabi
, zato je self.porabi
isto (a običajnejše in jasnejše) kot super().porabi
.
Še bolj ne delajte tega:
Kaj naredi tole, se sploh nismo učili. S tem pokličemo metodo
porabi
, ki pripada razredu, ne objektu, zato moramo v klicu
"ročno" dodati self
. To v Pythonu 3 zelo redko počnemo (prej je
bilo včasih v določenih kontekstih praktično, zdaj pa tak način klicanja
potrebujemo le redko.
Vrstica
je napačna iz več razlogov. Kot prvo, napačna je formula: če sem na točki
(102, 196) in se premaknem za 5 korakov na SZ, bo razdalja, ki jo bom naredil,
natančno enaka, kot če bi bil na točki (-21, 42) in se premaknil za pet korakov
proti SZ. Torej self.x
in self.y
sploh nista
pomembna.
Program je slučajno prestal teste, ker pač ni bilo testa, ki bi poskušal, kaj se dogaja na kakšnih specifičnih koordinatah. Če bi bilo med testi, recimo, tole, pa bi pokazali napako:
S prvim korakom pride potnik na koordinate (0, 8) in ima še 2 enoti energije.
En korak proti SZ bi mu vzel 1.41 energije; ker je ima dovolj, bi se premaknil
na (1, 9). Vendar gornja formula pravi, da je razdalja enaka korenu iz
(self.x + 1) ** 2 + (self.y + 1) ** 2
; ker je self.y
enak 8, je to koren iz 82.
Za začetek moramo torej pobrisati self.x
in self.y
.
Za float
ni prav nobene potrebe: rezultat sqrt
-ja
je že float
, torej ni potrebe, da bi ga spreminjali v
float
.
Veliko vas je napisalo nekaj takšnega. Tule pa pride prav malenkost
matematike. To je isto kot sqrt(2 * razdalja ** 2)
, kar je isto
kot razdalja * sqrt(2)
. Se pravi
Še bolj nerodna je naslednja vrstica, ki pravi
namesto
Tu je float
celo zelo napačen: rezultat klica
porabi
je True
ali False
in nekaj
vrstic kasneje program celo preverja if poraba != False
- torej
očitno predpostavlja, da je še vedno tipa bool
.
Večina je v svojih funkcijah napisala nekaj v slogu
Nekdo je to genialno rešil takole:
Če je smer
samo "S", "J", "V" ali "Z" bo
sqrt(len(smer))
enak 1 in kar piše je isto kot
self.porabi(razdalja)
. Če ima smer dve črki, pa množimo s korenom
2. To bi delovalo celo v treh dimenzijah, kjer bi lahko šli še gor in dol ter
zato občasno množili s korenom iz 3. Super!
Sledi
Če poraba
ni False
je True
. In
nekateri so v resnici pisali (manj ponesrečeni)
if poraba == True:
ali, kot je prišlo izgleda v modo ob tej
nalogi, if poraba is True:
. Še vedno ne razumem, kaj je narobe
iz if poraba:
. :)
Ista pesem, le v drugem molu - iz drugega izdelka.
x != True
je isto kot not x
. :)
Izogibajte se tega
Lepše je
Ideja, da pripravimo spremenljivko z resnično razdaljo, je čisto lepa. Ni pa
dobro, da za to žrtvujemo ime razdalja
, saj vsebuje pravo razdaljo,
ki jo bomo kasneje še potrebovali. V splošnem je to slaba ideja zaradi
nepreglednosti, kadar delamo z necelimi števili, pa je ideja še toliko slabša.
Zavedati se moramo, da necela števila niso povsem natančna in z vsako operacijo
bomo izgubili nekaj natančnosti. Tule se to še ne pozna, v kakšnih resnejših
izračunih pa bi nas lahko začelo tepsti.
Naloga je večkrat pokazala na vaše pomanjkljivo znanje matematike. Tule je še en zanimiv primer.
Nekateri ste smeri najprej pretvorili v kote (kar je slaba ideja, ker potem stvari niso več nujno natančne. Nato je iz tega izračunal premike.
Kote je bilo potrebno pretvoriti v radiane. Ne bi bilo za inženirja spodobno, da bi kote že zapisal v radianih? Torej, da bi vedel, da je pi/2 90 stopinj in tako naprej?
Še bolj nerodno je, da so ti koti odšteti od 90 stopinj in se vrtijo v
napačno smer. To smo imeli pri želvi, ker smo kote zapisovali tako, kot bi bili
bližji otrokom (0 kaže gor in potem v smeri urinega kazalca). Tu pa tole le
vse pomeša ... kar se vidi po tem, da v programu piše
prilezna = sin(phi) * razdalja
. Dolžino priležne stranice navadno
dobimo s kosinusom, ne sinus. In, ja, "vodoravni" je kosinus. Do te zmede pride
zaradi tistega 90 - smer. Programer mora znati matematiko.
V tem pogledu je še bolj simpatično tole: pretvorimo kot iz radianov v stopinje, da jih bomo potem pretvarjali nazaj v radiane.
Mimogrede, zakaj kot 0 stopinj obravnavati ločeno?
Še bolj žalostne reči so se dogajale s kvadranti. Funkciji sinus in kosinus že povesta, kar je treba - kvadrantov ni potrebno obravnavati ločeno. Nekaj od spodnjega je prav, nekaj pa tudi ne (vendar testi niso preverjali ravno vseh možnih smeri).
Primerjajte to z rešitvijo, ki jo napišejo takšni, ki poznajo kotne funkcije:
Nekoga bi bilo treba tepsti. :) Predpostavljam, da to, da desetkrat prišteješ 1, namesto da bi enkrat prištel deset, ne odraža neznanja matematike. ;)
Še en primer predolge rešitve:
Ko programirate, vedno razmišljajte, kaj storiti, da vam ne bi bilo potrebno ponavljati kode. Tule se štirikrat ponovi
in še štirikrat
Oboje se da narediti enkrat za štirikrat, kot v prejšnjih rešitvah.
Dodatna naloga
Razrede v obvezni nalogi smo zastavili nekoliko nerodno. Kot ste najbrž
opazili, ste v vseh metodah premik
pisali podobne stvari. Tule
je boljši osnovni razred; poimenovali smo ga Potnik2
. Napiši
razrede Orto2
, OrtoPlus2
, Liberalec2
,
ki so izpeljani iz razreda Potnik2
, vedejo pa se enako kot
njihovi soimenjaki brez dvojk.
Kako deluje lepše zasnovani Potnik2
in kaj mora početi njihova
metoda premik
, pa razberi sam(a) - tudi to je del naloge.
Rešitev
Kot je lepo zapisal nek študent:
Z drugimi besedami: vse tisto, kar je skupno vsem izpeljanim razredom, naj
bo raje v osnovnem razredu, Potnik
. Ko si izmišljamo hierarhijo
razredov in njihove metode, jih postavimo tako, da bo vsa skupna koda zbrana
na enem mestu, v predniku, ne pa v vsaki metodi posebej. Če moramo v vseh
izpeljanih razredih preverjati, ali imamo dovolj energije, je to naloga za
osnovni razred.
Metoda premik
v dodatni nalogi mora vrniti premik v smeri
x in y. To, kako iz podane razdalje in smeri določiti premik, je namreč edino,
po čemer se razredi razlikujejo, torej naj bo to edino, kar je prepuščeno
specifičnim metodam.
Sprogramirati je bilo potrebno le tole: