Funktio parametrina
Olemme jo aikaisemmin käyttäneet metodia sort
ja funktiota sorted
järjestämään listoja luonnolliseen järjestykseen. Metodit toimivat sellaisenaan hyvin luvuista ja merkkijonoista koostuvien listojen kanssa, mutta jos lista sisältää monimutkaisempia alkioita, Python ei välttämättä järjestä listaa niin kuin ohjelmoija toivoisi.
Esimerkiksi lista tupleja järjestetään oletuksena jokaisen tuplen ensimmäisen alkion perusteella:
tuotteet = [("banaani", 5.95), ("omena", 3.95), ("appelsiini", 4.50), ("vesimeloni", 4.95)]
tuotteet.sort()
for tuote in tuotteet:
print(tuote)
('appelsiini', 4.5) ('banaani', 5.95) ('omena', 3.95) ('vesimeloni', 4.95)
Mitä jos haluaisimme järjestää tuotelistan hinnan perusteella?
Funktiot parametrina
Järjestysmetodille tai -funktiolle voidaan antaa toisena parametrina järjestyksen määräävä avain. Avaimeksi annetaan funktio, joka kertoo, miten yksittäisen alkion arvo määritetään. Python kutsuu tätä funktiota järjestämisen aikana alkioiden vertailemiseen.
Esimerkiksi:
def hintajarjestys(alkio: tuple):
# Palautetaan tuplen toinen alkio eli hinta
return alkio[1]
if __name__ == "__main__":
tuotteet = [("banaani", 5.95), ("omena", 3.95), ("appelsiini", 4.50), ("vesimeloni", 4.95)]
# Hyödynnetään funktiota hintajarjestys
tuotteet.sort(key=hintajarjestys)
for tuote in tuotteet:
print(tuote)
('omena', 3.95) ('appelsiini', 4.5) ('vesimeloni', 4.95) ('banaani', 5.95)
Nyt ohjelma järjestää listan hinnan mukaiseen järjestykseen. Mutta mitä ohjelmassa oikeastaan tapahtuu?
Funktion hintajarjestys
määrittely on melko yksinkertainen: se saa parametrikseen yhden alkion ja palauttaa alkiolle arvon - tässä tapauksessa tuplen toisen alkion (joka esimerkissämme esittää tuotteen hintaa). Tarkastellaan kuitenkin lähemmin järjestysmetodia kutsuvaa riviä:
tuotteet.sort(key=hintajarjestys)
Rivillä annetaan metodille sort
parametriksi funktio. Ei siis funktion paluuarvoa, vaan viittaus funktioon. Järjestysmetodi kutsuu tätä funktiota jokaiselle alkiolle.
Kutsut nähdään selkeästi lisäämällä vertailufunktioomme ylimääräinen tulostuslause:
def hintajarjestys(alkio: tuple):
# Tulostetaan alkio
print(f"Kutsuttiin hintajarjestys({alkio})")
# Palautetaan tuplen toinen alkio eli hinta
return alkio[1]
tuotteet = [("banaani", 5.95), ("omena", 3.95), ("appelsiini", 4.50), ("vesimeloni", 4.95)]
# Hyödynnetään funktiota hintajarjestys
tuotteet.sort(key=hintajarjestys)
for tuote in tuotteet:
print(tuote)
Kutsuttiin hintajarjestys(('banaani', 5.95)) Kutsuttiin hintajarjestys(('omena', 3.95)) Kutsuttiin hintajarjestys(('appelsiini', 4.5)) Kutsuttiin hintajarjestys(('vesimeloni', 4.95)) ('omena', 3.95) ('appelsiini', 4.5) ('vesimeloni', 4.95) ('banaani', 5.95)
Järjestys saadaan käännettyä päinvastaiseksi hyödyntämällä sekä metodista sort
että funktiosta sorted
löytyvää toista parametria reverse
:
tuotteet.sort(key=hintajarjestys, reverse=True)
t2 = sorted(tuotteet, key=hintajarjestys, reverse=True)
Funktion sisällä määritelty funktio
Jos haluaisimme siirtää edellisessä esimerkissä tehdyn järjestämisen omaan funktioonsa jarjesta_hinnan_mukaan
, voisimme toteuttaa sen seuraavasti:
def hintajarjestys(alkio: tuple):
return alkio[1]
def jarjesta_hinnan_mukaan(alkiot: list):
# käytetään täällä funktiota hintajarjestys
return sorted(alkiot, key=hintajarjestys)
tuotteet = [("banaani", 5.95), ("omena", 3.95), ("appelsiini", 4.50), ("vesimeloni", 4.95)]
for tuote in jarjesta_hinnan_mukaan(tuotteet):
print(tuote)
Jos järjestämisen käyttämää apufunktiota hintajarjestys
ei käytetä missään muussa kohtaa ohjelmaa kuin funktiossa jarjesta_hinnan_mukaan
, sen määrittely voitaisiin siirtää funktion sisälle:
def jarjesta_hinnan_mukaan(alkiot: list):
# määritellään apufunktio tällä kertaa funktion sisällä
def hintajarjestys(alkio: tuple):
return alkio[1]
return sorted(alkiot, key=hintajarjestys)
Järjestys varastosaldon mukaan
/
Tee funktio jarjesta_varastosaldon_mukaan(alkiot: list)
. Funktio saa parametrina listan tupleja, joissa kolmantena alkiona on tuotteiden varastosaldo. Funktio järjestää parametrinaan saamat tuotteet varastosaldojen mukaiseen kasvavaan järjestykseen. Funktio ei muuta parametrina olevaa listaa, vaan palauttaa uuden listan.
Funktio toimii seuraavasti:
tuotteet = [("banaani", 5.95, 12), ("omena", 3.95, 3), ("appelsiini", 4.50, 2), ("vesimeloni", 4.95, 22)]
for tuote in jarjesta_varastosaldon_mukaan(tuotteet):
print(f"{tuote[0]} {tuote[2]} kpl")
Järjestys tuotantokausien mukaan
/
Tee funktio jarjesta_tuotantokausien_mukaan(alkiot: list)
. Funktio saa parametrina listan sanakirjoja, jotka edustavat yksittäisiä TV-sarjoja, ja järjestää ne tuotantokausien lukumäärän mukaiseen kasvavaan järjestykseen. Funktio ei muuta parametrina olevaa listaa, vaan palauttaa uuden listan.
Funktio toimii seuraavasti:
sarjat = [{ "nimi": "Dexter", "pisteet" : 8.6, "kausia":9 }, { "nimi": "Friends", "pisteet" : 8.9, "kausia":10 }, { "nimi": "Simpsons", "pisteet" : 8.7, "kausia":32 } ]
for sarja in jarjesta_tuotantokausien_mukaan(sarjat):
print(f"{sarja['nimi']} {sarja['kausia']} tuotantokautta")
Järjestys pisteiden mukaan
/
Tee funktio jarjesta_pisteiden_mukaan(alkiot: list)
. Funktio saa parametrina listan sanakirjoja, jotka edustavat yksittäisiä TV-sarjoja, ja järjestää ne pisteiden mukaiseen laskevaan järjestykseen. Funktio ei muuta parametrina olevaa listaa, vaan palauttaa uuden listan.
sarjat = [{ "nimi": "Dexter", "pisteet" : 8.6, "kausia":9 }, { "nimi": "Friends", "pisteet" : 8.9, "kausia":10 }, { "nimi": "Simpsons", "pisteet" : 8.7, "kausia":32 } ]
print("IMDB:n mukainen pistemäärä")
for sarja in jarjesta_pisteiden_mukaan(sarjat):
print(f"{sarja['nimi']} {sarja['pisteet']}")
Omien olioiden alkioiden järjestäminen
Kirjoitetaan samaa periaatetta hyödyntäen ohjelma, joka järjestää listan omasta Opiskelija
-luokasta luotuja olioita kahden eri kriteerin avulla:
class Opiskelija:
""" Luokka mallintaa yhtä opiskelijaa """
def __init__(self, nimi: str, tunnus: str, pisteet: int):
self.nimi = nimi
self.tunnus = tunnus
self.pisteet = pisteet
def __str__(self):
return f"{self.nimi} ({self.tunnus}), {self.pisteet} op."
def tunnuksen_mukaan(alkio: Opiskelija):
return alkio.tunnus
def pisteiden_mukaan(alkio: Opiskelija):
return alkio.pisteet
if __name__ == "__main__":
o1 = Opiskelija("Aapeli", "a123", 220)
o2 = Opiskelija("Maija", "m321", 210)
o3 = Opiskelija("Anna", "a999", 131)
opiskelijat = [o1, o2, o3]
print("Tunnuksen mukaan:")
for opiskelija in sorted(opiskelijat, key=tunnuksen_mukaan):
print(opiskelija)
print()
print("Pisteiden mukaan:")
for opiskelija in sorted(opiskelijat, key=pisteiden_mukaan):
print(opiskelija)
Aapeli (a123), 220 op. Anna (a999), 131 op. Maija (m321), 210 op.
Pisteiden mukaan: Anna (a999), 131 op. Maija (m321), 210 op. Aapeli (a123), 220 op.
Järjestäminen toimii niinkuin pitää. Jos olioille arvon antavia funktioita tunnuksen_mukaan
ja pisteiden_mukaan
ei tarvita muuten, voimme kuitenkin vielä yksinkertaistaa ohjelmaa.
Kiipeilyreitti
/
Tehtäväpohjan mukana tulee valmis luokka Kiipeilyreitti
, jota käytetään seuraavasti:
reitti1 = Kiipeilyreitti("Kantti", 38, "6A+")
reitti2 = Kiipeilyreitti("Smooth operator", 11, "7A")
reitti3 = Kiipeilyreitti("Syncro", 14, "8C+")
print(reitti1)
print(reitti2)
print(reitti3.nimi, reitti3.pituus, reitti3.grade)
Kantti, pituus 38 metriä, grade 6A+ Smooth operator, pituus 11 metriä, grade 7A Syncro 14 8C+
Pituuden mukainen järjestys
Tee funktio pituuden_mukaan(reitit: list)
joka palauttaa kiipeilyreitit pituuden mukaan käänteisessä järjestyksessä.
Funktio toimii seuraavasti:
r1 = Kiipeilyreitti("Kantti", 38, "6A+")
r2 = Kiipeilyreitti("Smooth operator", 11, "7A")
r3 = Kiipeilyreitti("Syncro", 14, "8C+")
r4 = Kiipeilyreitti("Pieniä askelia", 12, "6A+")
reitit = [r1, r2, r3, r4]
for reitti in pituuden_mukaan(reitit):
print(reitti)
Kantti, pituus 38 metriä, grade 6A+ Syncro, pituus 14 metriä, grade 8C+ Pieniä askelia, pituus 12 metriä, grade 6A+ Smooth operator, pituus 11 metriä, grade 7A
Vaikeuden mukainen järjestys
Tee funktio vaikeuden_mukaan(reitit: list)
joka palauttaa kiipeilyreitit vaikeuden (eli graden) mukaan laskevassa järjestyksessä. Jos reittien vaikeus on sama, ratkaisee pituus vaikeuden. Pidempi on vaikeampi. Kiipeilyreittien vaikeusasteikko on 4, 4+, 5, 5+, 6A, 6A+, ... eli käytännössä se seuraa aakkosjärjestystä.
Funktio toimii seuraavasti:
r1 = Kiipeilyreitti("Kantti", 38, "6A+")
r2 = Kiipeilyreitti("Smooth operator", 11, "7A")
r3 = Kiipeilyreitti("Syncro", 14, "8C+")
r4 = Kiipeilyreitti("Pieniä askelia", 12, "6A+")
reitit = [r1, r2, r3, r4]
for reitti in vaikeuden_mukaan(reitit):
print(reitti)
Syncro, pituus 14 metriä, grade 8C+ Smooth operator, pituus 11 metriä, grade 7A Kantti, pituus 38 metriä, grade 6A+ Pieniä askelia, pituus 12 metriä, grade 6A+
Vihje jos järjestysperusteena on lista tai tuple, järjestetään ensisijaiseti ensimmäisen alkion mukaan, toissijaisesti toisen:
lista = [("a", 4),("a", 2),("b", 30), ("b", 0) ]
print(sorted(lista))
[('a', 2), ('a', 4), ('b', 0), ('b', 30)]
Kiipeilykalliot
/
Tehtäväpohjasta löytyy luokan Kiipeilyreitti
lisäksi luokka Kiipeilykallio
.
k1 = Kiipeilykallio("Olhava")
k1.lisaa_reitti(Kiipeilyreitti("Kantti", 38, "6A+"))
k1.lisaa_reitti(Kiipeilyreitti("Suuri leikkaus", 36, "6B"))
k1.lisaa_reitti(Kiipeilyreitti("Ruotsalaisten reitti", 42, "5+"))
k2 = Kiipeilykallio("Nummi")
k2.lisaa_reitti(Kiipeilyreitti("Syncro", 14, "8C+"))
k3 = Kiipeilykallio("Nalkkilan släbi")
k3.lisaa_reitti(Kiipeilyreitti("Pieniä askelia", 12, "6A+"))
k3.lisaa_reitti(Kiipeilyreitti("Smooth operator", 11, "7A"))
k3.lisaa_reitti(Kiipeilyreitti("Possu ei pidä", 12 , "6B+"))
k3.lisaa_reitti(Kiipeilyreitti("Hedelmätarha", 8, "6A"))
print(k1)
print(k3.nimi, k3.reitteja())
print(k3.vaikein_reitti())
Olhava, 3 reittiä, vaikein 6B Nalkkilan slabi 4 Smooth operator, pituus 9 metriä, grade 7A
Reittien määrän mukaan
Tee funktio reittien_maaran_mukaan
, joka järjestää kiipeilykalliot reittien määrän mukaiseen kasvavaan suuruusjärjestykseen.
# k1, k2 ja k3 määritelty kuten edellä
kalliot = [k1, k2, k3]
for kallio in reittien_maaran_mukaan(kalliot):
print(kallio)
Nummi, 1 reittiä, vaikein 8C+ Olhava, 3 reittiä, vaikein 6B Nalkkilan slabi, 4 reittiä, vaikein 7A
Vaikeimman reitin mukaan
Tee funktio vaikeimman_reitin_mukaan
, joka järjestää kiipeilykalliot kalliolta löytyvän vaikeimman reitin mukaiseen laskevaan suuruusjärjestykseen.
# k1, k2 ja k3 määritelty kuten edellä
kalliot = [k1, k2, k3]
for kallio in vaikeimman_reitin_mukaan(kalliot):
print(kallio)
Nummi, 1 reittiä, vaikein 8C+ Nalkkilan slabi, 4 reittiä, vaikein 7A Olhava, 3 reittiä, vaikein 6B
Lambda-lauseke
Lambda-lausekkeen avulla voidaan luoda anonyymi funktio eli funktio, joka muodostetaan sillä hetkellä, kun sitä tarvitaan. Lausekkeen yleinen syntaksi on seuraava:
lambda <parametrit> : <lauseke>
Esimerkiksi tuplelistan järjestys onnistuisi näin käyttämällä lambda-lauseketta:
tuotteet = [("banaani", 5.95), ("omena", 3.95), ("appelsiini", 4.50), ("vesimeloni", 4.95)]
# Funktio luodaan "lennosta" lambda-lausekkeella:
tuotteet.sort(key=lambda alkio: alkio[1])
for tuote in tuotteet:
print(tuote)
('omena', 3.95) ('appelsiini', 4.5) ('vesimeloni', 4.95) ('banaani', 5.95)
Lauseke
lambda alkio: alkio[1]
vastaa funktiomäärittelyä
def hinta(alkio):
return alkio[1]
paitsi että lambda-lauseketta käytettäessä funktiolle ei anneta nimeä. Tämän takia muodostettavaa funktiota kutsutaan anonyymiksi funktioksi.
Muuten lambdan avulla muodostettava funktio on kuin mikä tahansa muukin funktio. Esimerkiksi seuraava esimerkki järjestää merkkijonot niiden viimeisten merkkien mukaiseen aakkosjärjestykseen:
mjonot = ["Mikko", "Makke", "Maija", "Markku", "Mikki"]
for jono in sorted(mjonot, key=lambda jono: jono[-1]):
print(jono)
Maija Makke Mikki Mikko Markku
Mennään vielä pidemmälle: yhdistämällä listakooste ja join
-metodi lambda-lausekkeeseen voidaan esimerkiksi järjestää merkkijonot niistä löytyvien vokaalien mukaiseen järjestykseen välittämättä muista merkeistä:
mjonot = ["Mikko", "Makke", "Maija", "Markku", "Mikki"]
for jono in sorted(mjonot, key=lambda jono: "".join([m for m in jono if m in "aeiouyäö"])):
print(jono)
Makke Maija Markku Mikki Mikko
Anonyymejä funktioita voi hyödyntää Pythonissa monien muidenkin valmiiden funktioiden yhteydessä. Esimerkiksi funktioille min
ja max
voidaan määritellä samalla tavalla parametri key
, jonka perusteella minimi- tai maksimiarvo valitaan.
Esimerkissä poimitaan levyistä aluksi vanhin ja sitten pisin:
class Levy:
"""Luokka mallintaa yhtä äänilevyä"""
def __init__(self, nimi: str, esittaja: str, vuosi: int, kesto: int):
self.nimi = nimi
self.esittaja = esittaja
self.vuosi = vuosi
self.kesto = kesto
def __str__(self):
return f"{self.nimi} ({self.esittaja}), {self.vuosi}. {self.kesto} min."
if __name__ == "__main__":
l1 = Levy("Nevermind", "Nirvana", 1991, 43)
l2 = Levy("Let It Be", "Beatles", 1969, 35)
l3 = Levy("Joshua Tree", "U2", 1986, 50)
levyt = [l1, l2, l3]
print("Vanhin levy:")
print(min(levyt, key=lambda levy: levy.vuosi))
print("Pisin levy: ")
print(max(levyt, key=lambda levy: levy.kesto))
Vanhin levy: Let It Be (Beatles), 1969. 35 min. Pisin levy: U2 (Joshua Tree), 1986. 50 min.
Palloilijat
/
Tehtäväpohjasta löytyy luokka Palloilija
, jolla on seuraavat julkiset piirteet:
- nimi
- pelinumero
- tehtyjen maalien määrä
maalit
- annettujen syöttöjen määrä
syotot
- peliminuuttien määrä
minuutit
Kirjoita seuraavien tehtävänantojen mukaiset funktiot. Huomaa, että jokaisessa funktiossa palautetaan erityyppiset tiedot.
Eniten maaleja
Kirjoita funktio eniten_maaleja
, joka saa parametrikseen listan palloilijoita.
Funktio palauttaa merkkijonona sen pelaajan nimen, joka on tehnyt eniten maaleja.
Eniten pisteitä
Kirjoita funktio eniten_pisteita
, joka saa parametrikseen listan palloilijoita.
Funktio palauttaa tuplena sen pelaajan nimen ja pelinumeron, joka on tehnyt yhteensä eniten pisteitä. Pisteisiin lasketaan siis sekä maalit että syötöt.
Vähiten peliminuutteja
Kirjoita funktio vahiten_minuutteja
, joka saa parametrikseen listan palloilijoita.
Funktio palauttaa sen Palloilija
-olion, jolla on vähiten peliminuutteja kaikista pelaajista.
Testiohjelma
Voit testata koodisi toimintaa seuraavalla ohjelmalla:
if __name__ == "__main__":
pelaaja1 = Palloilija("Kelju Kojootti", 13, 5, 12, 46)
pelaaja2 = Palloilija("Maantiekiitäjä", 7, 2, 26, 55)
pelaaja3 = Palloilija("Uka Naakka", 9, 1, 32, 26)
pelaaja4 = Palloilija("Pelle Peloton", 12, 1, 11, 41)
pelaaja5 = Palloilija("Hessu Hopo", 4, 3, 9, 12)
joukkue = [pelaaja1, pelaaja2, pelaaja3, pelaaja4, pelaaja5]
print(eniten_maaleja(joukkue))
print(eniten_pisteita(joukkue))
print(vahiten_minuutteja(joukkue))
Tulostuksen tulisi olla:
Kelju Kojootti ('Uka Naakka', 9) Palloilija(nimi=Hessu Hopo, pelinumero=4, maalit=3, syotot=9, minuutit=12)
Funktiot parametreina omissa funktioissa
Pythonissa on siis mahdollista välittää viittaus johonkin funktioon toiselle funktiolle. Tarkastellaan vielä esimerkkinä omaa funktiota, joka saa parametrikseen toisen funktion:
# tyyppivihje callable viittaa funktioon
def suorita_operaatio(operaatio: callable):
# Kutsutaan välitettyä funktiota
return operaatio(10, 5)
def summa(a: int, b: int):
return a + b
def tulo(a: int, b: int):
return a * b
if __name__ == "__main__":
print(suorita_operaatio(summa))
print(suorita_operaatio(tulo))
print(suorita_operaatio(lambda x,y: x - y))
15 50 5
Funktion suorita_operaatio
lopputulos siis riippuu siitä, mikä funktio sille on välitetty parametrina. Funktioksi kelpaa mikä tahansa funktio (niin def
-lauseella määritelty kuin anonyymikin) jolla on kaksi parametria.
Vaikkei funktioiden välittäminen parametrina olekaan kaikkein yleisimmin tarvittava operaatio, on se joka tapauksessa hyödyllinen mekanismi. Esimerkiksi seuraava ohjelma kirjoittaa tiedostosta 1 halutut rivit tiedostoon 2. Rivien valintakriteeri annetaan funktiona, joka palauttaa True
, jos rivi tulee kirjoittaa toiseen tiedostoon:
def kopioi_rivit(lahde_nimi: str, kohde_nimi: str, kriteeri= lambda x: True):
with open(lahde_nimi) as lahde, open(kohde_nimi, "w") as kohde:
for rivi in lahde:
# Poistetaan ensin tyhjät merkit alusta ja lopusta
rivi = rivi.strip()
if kriteeri(rivi):
kohde.write(rivi + "\n")
# Esimerkkejä
if __name__ == "__main__":
# Jos kolmatta parametria ei ole määritelty, kopioidaan kaikki
kopioi_rivit("eka.txt", "toka.txt")
# Kopioidaan kaikki ei-tyhjät rivit
kopioi_rivit("eka.txt", "toka.txt", lambda rivi: len(rivi) > 0)
# Kopioidaan kaikki rivit, joilla on sana "Python"
kopioi_rivit("eka.txt", "toka.txt", lambda rivi: "Python" in rivi)
# Kopioidaan kaikki rivit, jotka eivät pääty pisteeseen
kopioi_rivit("eka.txt", "toka.txt", lambda rivi: rivi[-1] != ".")
Funktiossa parametrille kriteeri
on määritelty oletusarvoksi lambda-lauseke lambda x: True
, jonka tuottama anonyymi funktio palauttaa arvon True
kaikille syötteille. Niinpä oletuksena kopioidaan kaikki rivit tiedostosta toiseen. Jos käyttäjä antaa kolmannelle parametrille arvon, tämä korvaa oletusarvon.
Tuotteiden haku
/
Tässä tehtävässä käsitellään tupleina esitettäviä tuotteita, jotka on esimerkeissä alustettu muuttujaan tuotteet
seuraavasti:
tuotteet = [("banaani", 5.95, 12), ("omena", 3.95, 3), ("appelsiini", 4.50, 2), ("vesimeloni", 4.95, 22), ("Kaali", 0.99, 1)]
Jokaisessa tuplessa ensimmäinen alkio siis edustaa nimeä, seuraava hintaa ja kolmas määrää.
Toteuta funktio hae(tuotteet: list, kriteeri: callable)
, missä toisena parametrina on funktio, joka saa parametriksi yhden tuotetta edustavan tuplen ja palauttaa totuusarvon. Funktio palauttaa listassa parametrina annetuista tuotteista ne, jotka toteuttavat kriteerin.
Sopiva kriteeri voisi olla esimerkiksi seuraavanlainen
def hinta_alle_4_euroa(tuote):
return tuote[1] < 4
Funktio siis palauttaa True jos tuotteen hinta on alle 4 euroa.
Funktio haku
toimii seuraavasti:
for tuote in hae(tuotteet, hinta_alle_4_euroa):
print(tuote)
('omena', 3.95, 3) ('kaali', 0.99, 1)
Kriteerifunktion voi myös määritellä lambda-funktiona. Seuraava käyttää funktiota haku
etsimään tuotteet, joita on vähintään 11 kappaletta:
for tuote in hae(tuotteet, lambda t: t[2]>10):
print(tuote)
('banaani', 5.95, 12) ('vesimeloni', 4.95, 22)