Zemljevidi

Za domačo nalogo boste napisali tri razrede. Prvi bo Zemljevid, iz njega bosta izpeljana Drzava in Mesto. Razred Zemljevid bo predpostavljal, da imajo izpeljani razredi

  • atribut koordinate, ki je slovar s koordinatami. Predpostavljal bo, da so ključi slovarja imena mest (ali trgovin, muzejev, restavracij); kakšne so vrednosti, ga ne zanima;
  • metodo razdalja, ki sprejme (poleg self) dva argumenta, to je, imeni mest ali trgovin ali muzejev ali česarkoli že in vrne razdaljo med njima.

Razred Zemljevid mora imeti metode, ki vrnejo dolžino poti, najbližje mesto, n najbližjih mest in najkrajšo pot prek mest na seznamu. Točneje, razred je videti takole

class Zemljevid: def najblizje(self, mesto): pass def dolzina_poti(self, pot): pass def najblizja(self, mesto, n): pass def najkrajsa(self, mesta): pass Le pass morate nadomestiti s pravimi funkcijami. V razred ne smete dodajati nobenih novih metod, pa tudi argumentov obstoječih ne smete spreminjati.

Izpeljana razreda Drzava in Mesto bosta dodala konstruktor in metodo razdalja. Konstruktor sprejme en argument, ime datoteke. Datoteki nista enake oblike: datoteka za razred Drzava je enaka kot v zadnji domači nalogi. Datoteka za razred Mesto ima najprej ime mesta, nato tabulator in potem koordinato oblike, na primer, C12. Prvi znak bo vedno črka, preostanek bo neka poljubno dolga številka. Kako razred Mesto shrani koordinato, je vaša odločitev, pomembno je le, da njegova funkcija razdalja zna delati s takšnimi koordinatami. (Namig: ord(c) vrne "zaporedno številko" znaka c).

Poleg tega morata izpeljana razreda definirati funkcijo razdalja. Razreda se razlikujeta v tem, kako računata razdaljo. Drzava računa razdaljo tako, kot ste jo računali v prejšnji domači nalogi. Mesto računa Manhattansko razdaljo: razdalja med C5 in E1 je 6, ker se premaknemo s C na E (to je 2) in iz 5 na 1 (to je 4), in 2 + 4 = 6.

class Drzava(Zemljevid): def __init__(self, ime_dat): pass def razdalja(self, mesto1, mesto2): pass class Mesto(Zemljevid): def __init__(self, ime_dat): pass def razdalja(self, mesto1, mesto2): pass

Naloge se loti takole: v razred Zemljevid skopiraj vsebino funkcij iz prejšnje domače naloge. Uporabiš lahko svojo rešitev ali rešitev, ki bo (je) objavljena na Učilnici. Podobno lahko tudi za razred Drzava uporabiš že napisane funkcije. Na novo bo potrebno sprogramirati le obe metodi razreda Mesto.

V prejšnji domači nalogi ste pisali funkcijo beri_mesta, ki je prebrala datoteko in vrnila slovar. V tej nalogi to preselite v konstruktor; ta bo prebral datoteko in shranil rezultat (slovar) v self.koordinate. Konstruktorji ne vračajo rezultata (vsaj ne v takšnem smislu, kot "običajne" funkcije in metode.

Metode razreda Zemljevid bodo uporabljale (predvsem) metodo razdalja, ki jo bodo priskrbeli izpeljani razredi, na podoben način, kot je na predavanjih metoda pozdravi uporabljala metodo naziv, ki so jo priskrbeli izpeljani razredi.

Poleg tega bosta Zemljevidovi metodi najblizje in najblizja uporabljali self.koordinate. Vendar bosta iz tega slovarja jemali le ključe - imena mest - ne pa tudi koordinat, saj jih "ne razumeta". Točneje, razreda Drzava in Mesto najbrž ne bosta shranjevali koordinat na enak način - ali pač, to razreda Zemljevid ne briga.

Rešitev

Rešitev je tokrat kar dolga, vendar ni bilo veliko programiranja, temveč samo prirejanje kode iz prejšnje naloge - z izjemo metod v razredu Mesta, ki pa tudi nista višek programerske znanosti.

Narediti je bilo potrebno tole: razred Zemljevid ste morali napolniti z istoimenskimi funkcijami iz prejšnje naloge. Metode razreda Zemljevid predpostavljajo, da objekt (self) vsebuje atribut koordinate. Zaradi tega metode, za razliko od onih funkcij izpred enega tedna, nimajo argumenta koordinate, temveč le - tako kot vedno vse metode - argument self. Do koordinat pridejo prek njega.

V vseh skopiranih metodah ste torej morali zamenjati koordinate s self.koordinate. Obenem je bilo potrebno klice funkcij, kot so razdalja in dolzina_poti zamenjati s klici metod, torej self.razdalja in self.dolzina_poti. Po novem te funkcije namreč niso več nekaj, kar "kar tako plava naokrog", temveč so metode objekta in jih je treba tudi klicati kot metode objekta. Navsezadnje tudi append vedno kličemo z nek_seznam.append, ne samo z append(42) (dodaj 42 ... kam?! komu?!)

Metode razreda Zemljevid predpostavljajo, da je konstruktor poskrbel, da ima objekt atribut koordinate in predpostavljajo, da obstaja metoda razdalja. To ste morali priskrbeti v izpeljanih razredih Drzava in Mesto. Lepota objektnega programiranja je v tem, da konkstruktorja bereta podatke v različnih formatih in jih tudi shranjujeta v različno sestavljenih slovarjih koordinate. Kako je sestavljen ta slovar, mora vedeti le funkcija razdalja.

import math import itertools class Zemljevid: def najblizje(self, mesto): naj_mesto, naj_razdalja = "", 10000 for mesto2 in self.koordinate: if mesto == mesto2: continue d = self.razdalja(mesto, mesto2) if d < naj_razdalja: naj_mesto, naj_razdalja = mesto2, d return naj_mesto def dolzina_poti(self, pot): dolzina = 0 for i in range(len(pot) - 1): dolzina += self.razdalja(pot[i], pot[i + 1]) return dolzina def najblizja(self, mesto, n): razdalje = [] for mesto2 in self.koordinate: razdalje.append((self.razdalja(mesto, mesto2), mesto2)) razdalje.sort() naj = [] for _, mesto in razdalje[1:n + 1]: naj.append(mesto) return naj def najkrajsa(self, mesta): naj_pot, naj_razdalja = mesta, self.dolzina_poti(mesta) for red in itertools.permutations(mesta[1:-1]): pot = [mesta[0]] + list(red) + [mesta[-1]] razdalja = self.dolzina_poti(pot) if razdalja < naj_razdalja: naj_pot, naj_razdalja = pot, razdalja return naj_pot class Drzava(Zemljevid): def __init__(self, ime_dat): self.koordinate = {} for v in open(ime_dat): mesto, x, y = v.split("\t") x = int(x[:3]) + int(x[4:6]) / 60 + int(x[7:9]) / 3600 y = int(y[:3]) + int(y[4:6]) / 60 + int(y[7:9]) / 3600 self.koordinate[mesto] = (x, y) # <a href="http://www.platoscave.net/blog/2009/oct/5/calculate-distance-latitude-longitude-python/" class="_blanktarget">http://www.platoscave.net/blog/2009/oct/5/calculate-distance-latitude-longitude-python/</a> def razdalja(self, mesto1, mesto2): lat1, lon1 = self.koordinate[mesto1] lat2, lon2 = self.koordinate[mesto2] radius = 6371 # km dlat = math.radians(lat2-lat1) dlon = math.radians(lon2-lon1) a = math.sin(dlat/2) * math.sin(dlat/2) + math.cos(math.radians(lat1)) \ * math.cos(math.radians(lat2)) * math.sin(dlon/2) * math.sin(dlon/2) c = 2 * math.atan2(math.sqrt(a), math.sqrt(1-a)) d = radius * c return d class Mesto(Zemljevid): def __init__(self, ime_dat): self.koordinate = {} for v in open(ime_dat): mesto, koord = v.split("\t") self.koordinate[mesto] = ord(koord[0]) - ord("A"), int(koord[1:]) def razdalja(self, mesto1, mesto2): x1, y1 = self.koordinate[mesto1] x2, y2 = self.koordinate[mesto2] return abs(x1 - x2) + abs(y1 - y2)
Last modified: Saturday, April 26, 2014, 11:51 PM