Domača naloga se navezuje na prejšnjo domačo nalogo, vodni top. Pri reševanju smete uporabiti dele programa, ki ste ga pisali prejšnji teden, ali pa celo dele rešitve, ki je objavljena na spletu. Bistvo naloge je namreč v tem, da se naučimo zapakirati tisto, kar smo delali prejšnji teden, v lepe in pregledne funkcije.

Testi

testi-vodni-top-s-funkcijami.py

Ogrevalne funkcije

  • koordinate(ime, kraji) prejme ime kraja in seznam krajev (kraji), kot smo ga vajeni iz prejšnje domače naloge. Funkcija vrne terko s koordinatama podanega kraja. Če tega kraja ni, naj ne vrne ničesar (torej None).

  • razdalja_koordinat(x1, y1, x2, y2) dobi koordinate dveh točk in vrne razdaljo med njima.

  • razdalja(ime1, ime2, kraji) prejme imeni dveh krajev in vrne razdaljo med njima.

Funkcije se morajo lepo in zgledno klicati med sabo. Funkcija razdalja mora pridobiti koordinate in izračunati razdaljo tako, da pametno uporablja prvi dve funkciji.

Rešitev

Naloga, modro, najprej zahteva funkcijo koordinate, s katero dobimo koordinate podanega kraja. Takšno funkcijo je modro napisati, ker nam bo pomagala pri vseh ostalih.

Preprosta je: z zanko gremo čez kraje, dokler ne naletimo na tistega s pravim imenom in tedaj vrnemo njegove koordinate.

def koordinate(ime, kraji):
    for kraj, x, y in kraji:
        if kraj == ime:
            return x, y

En način, kako to narediti neposrečeno, je zaplesti zanko takole:

    for podatki in kraji:
        kraj, x, y = podatki

Prepričan sem, da je marsikateri na ušesih sedeči študent (ali takšen, ki že vse zna :) napisal

    for podatki in kraji:
        kraj = podatki[0]
        x = podatki[1]
        y = podatki[2]

Druga nepotrebna komplikacija je tole

def koordinate(ime, kraji):
    for kraj, x, y in kraji:
        if kraj == ime:
            break
    return x, y

Preprosteje je, če return napišemo kar v zanki, saj s tem tako ali tako prekinemo zanko in vrnemo rezultat. Še bolj nepotrebna komplikacija je

def koordinate(ime, kraji):
    for kraj, x, y in kraji:
        if kraj == ime:
            iskani_x, iskani_y = x, y
    return iskani_x, iskani_y

Ko enkrat najdemo, vrnemo.

Naslednja pomožna funkcija je razdalja_koordinat. Tu le vrnemo, kar nam naračuna stari Grk.

from math import *

def razdalja_koordinat(x1, y1, x2, y2):
    return sqrt((x1 - x2) ** 2 + (y1 - y2) ** 2)

Funkcija razdalja zdaj dvakrat pokliče funkcijo koordinate, da izve koordinate prvega in drugega kraja, nato pa funkcijo razdalja_koordinat, da ta izračuna razdaljo.

def razdalja(ime1, ime2, kraji):
    x1, y1 = koordinate(ime1, kraji)
    x2, y2 = koordinate(ime2, kraji)
    return razdalja_koordinat(x1, y1, x2, y2)

Testi vam ne bodo pustili, da bi funkcijo napisali takole:

def razdalja(ime1, ime2, kraji):
    for kraj, x1, y1 in kraji:
        if kraj == ime1:
            break
    for kraj, x2, y2 in kraji:
        if kraj == ime2:
            break
    return sqrt((x1 - x2) ** 2 + (y1 - y2) ** 2)

Ideja domače naloge je, da se naučimo delati s funkcijami. Ne le sestavljati, temveč tudi uporabljati lastne funkcije. V tem pogledu ta rešitev ni pravilna, pa čeprav bi sicer dala pravilni rezultat.

Obvezni del

  • v_dometu(ime, domet, kraji) vrne seznam krajev, ki jih lahko zalije kraj ime, če ima top z dometom domet. Kraj ne zaliva sebe.

  • najbolj_oddaljeni(ime, imena, kraji) prejme ime nekega kraja, seznam imen nekih krajev (imena) in že običajni seznam terk z imeni in koordinatami krajev. Med kraji v seznamu imena (ne med vsemi kraji, temveč samo med temi!) mora vrniti ime tistega, ki je najbolj oddaljen od kraja ime. Če recimo, pokličemo najbolj_oddaljeni("Ljubljana", ["Domžale", "Kranj", "Maribor", "Vrhnika"], kraji), kjer so kraji vsi kraji iz prejšnje naloge, vrne "Maribor", saj je Maribor med temi štirimi kraji najdalj od Ljubljane

  • zalijemo(ime, domet, kraji) vrne ime najbolj oddaljenega kraja, ki ga lahko zalije kraj ime, če ima top z dometom domet.

Rešitev

Tole je malo podobno kot prej: dve funkciji, tretja pa le pokliče tidve.

Funkcija v_dometu pripravi prazen seznam. Nato gre čez vse kraje; preveri ali je razdalja večja od 0 (in torej ne gre za isti kraj) in manjša od dometa; če to drži, ga doda na seznam. Ta seznam potem na koncu vrne.

def v_dometu(ime, domet, kraji):
    zaliti = []
    for kraj, x, y in kraji:
        if 0 < razdalja(ime, kraj, kraji) <= domet:
            zaliti.append(kraj)
    return zaliti

Funkcija najbolj_oddaljeni počne nekaj, kar smo že velikokrat počeli -- recimo takrat, ko smo iskali ime najtežje osebe v seznamu. Iščemo največjo reč po nekem kriteriju; tokrat torej najbolj oddaljeni kraj iz seznama nekih krajev.

def najbolj_oddaljeni(ime, imena, kraji):
    naj_razdalja = 0
    for kraj in imena:
        r = razdalja(ime, kraj, kraji)
        if r > naj_razdalja:
            naj_razdalja = r
            naj_kraj = kraj
    return naj_kraj

Zadnja funkcija je potem trivialna: vrniti moramo najbolj oddaljeni kraj izmed krajev, ki so v dometu. Napišemo lahko

def zalijemo(ime, domet, kraji):
    kandidati = v_dometu(ime, domet, kraji)
    return najbolj_oddaljeni(ime, kandidati, kraji)

ali pa kar

def zalijemo(ime, domet, kraji):
    return najbolj_oddaljeni(ime, v_dometu(ime, domet, kraji), kraji)

Dodatni del

  • presek(s1, s2) prejme dva seznama in vrne seznam elementov, ki se pojavijo v obeh. Vrstni red elementov v vrnjenem seznamu je lahko poljuben.

  • skupno_zalivanje(ime1, ime2, domet, kraji) prejme dve imeni krajev, domet vodnih topov, ki ju imajo v teh dveh krajih, in seznam vseh krajev. Vrniti mora seznam vseh krajev, ki jih lahko zalivamo iz obeh krajev.

Rešitev

Dodatna naloga je tokrat izjemoma preprostejša od obveznih. :)

Da izračunamo presek, najprej pripravimo prazen seznam. Nato gremo prek enega seznama, za vsak njegov element pogledamo, ali ga najdemo tudi v drugem seznamu in ga, če je tako, dodamo v oni seznam, v katerem zbiramo presek.

def presek(s1, s2):
    p = []
    for e in s1:
        if e in s2:
            p.append(e)
    return p

Enkrat kmalu se bomo učili, da se da v Pythonu to narediti tudi veliko preprosteje in hitrejše.

Skupno zaliti kraji so potem tisti kraji, ki so v preseku krajev, ki so v dometu.

def skupno_zalivanje(ime1, ime2, domet, kraji):
    return presek(v_dometu(ime1, domet, kraji),
                  v_dometu(ime2, domet, kraji))
Zadnja sprememba: torek, 23. marec 2021, 20.37