Kurssin viimeisimmän version löydät täältä: O1: 2024
Luku 6.4: Lisää kokoelmia, lisää ohjelmia
Tästä sivusta:
Pääkysymyksiä: Olisiko lisää käteviä kokoelmametodeita? Miten yhdistelen kokoelmien metodeita?
Mitä käsitellään? Lisää kokoelmien metodeita ja uusia käyttötapauksia edellisen luvun esittelemille. Luvun lopussa puhutaan paikallisista funktioista sekä sulkeumista.
Mitä tehdään? Enimmäkseen ohjelmoidaan.
Suuntaa antava työläysarvio:? Kolme, neljä tuntia.
Pistearvo: A95 + B30 + C30.
Oheisprojektit: Election, FlappyBug, HigherOrder.
Tulosarvon tuottaminen kokoelman sisällön perusteella
foldLeft
-metodi
Luvun 6.1 päättäneessä turnElementsIntoResult
-tehtävässä työstimme ajatusta siitä,
että kokoelman perusteella voidaan muodostaa yksi tulos (esim. kokoelman alkioiden summa
tai tulo) soveltamalla alkioihin tiettyä funktiota toistuvasti. Tarkemmin sanoen:
sovelletaan tiettyä kaksiparametrista funktiota (esim. yhteenlaskua) ensin johonkin
alkuarvoon ja kokoelman ensimmäiseen alkioon, sitten näin saatuun välitulokseen ja
kokoelman toiseen alkioon, jne.
Tällainen hyvinkin yleiskäyttöinen metodi on määritelty myös Scala-kokoelmille. Sen nimi
on foldLeft
. Seuraava käsky on eräs tapa laskea kokoelman sisältämien lukujen summa:
val luvut = Vector(10, 5, 4, 5, -20)luvut: Vector[Int] = Vector(10, 5, 4, 5, -20) luvut.foldLeft(0)( (sumSoFar, next) => sumSoFar + next )res0: Int = 4
Tässä luvusta 6.1 toisintona (päivitetyllä termistöllä) graafinen esitys, joka kuvaa
foldLeft
-metodin toiminnan:
Summaavan foldLeft
-kutsun voi kirjoittaa yksinkertaisemminkin. Alla on myös esimerkki
alkioiden tulon laskemisesta:
luvut.foldLeft(0)( _ + _ )res1: Int = 4 luvut.foldLeft(1)( _ * _ )res2: Int = -20000
Ja tässä parametrifunktio summaa ensimmäiseen parametriinsa (välitulokseen) toisen parametrin (seuraavan alkion) toisen potenssin. Lopputuloksena saadaan alkioiden neliöiden summa:
luvut.foldLeft(0)( (sum, next) => sum + next * next )res3: Int = 566
Sivumaininta: helpompi tapa laskea summa tai tulo
Koska tässä luvussa on puhe korkeamman asteen funktioista laatimisesta, niin edellä "unohdettiin" eräs seikka.
Kokoelman alkioiden summan laskeminen on kovin yleinen toimenpide, ja Scala API tarjoaa
valmiin metodin. Saat alkioiden summan kutsumalla kokoelmaolion sum
-metodia: luvut.sum
.
Tämän metodin toimiminen edellyttää, että alkiot ovat sopivan tyyppisiä (lukuja).
Vastaavasti product
-metodilla voi laskea alkioiden tulon.
Kaikkiin ongelmiin ei kuitenkaan ole yhtä helppoa valmista ratkaisua tarjolla kuin
yhteen- ja kertolaskuun. Kuten jo näit, foldLeft
-metodilla voi paitsi laskea summan
myös muodostaa muunlaisia tuloksia alkioiden perusteella.
Lisää fold
-esimerkkejä: alkioiden tutkiminen
Yllä tulos oli aina samaa tyyppiä kuin kokoelman alkiotkin (tässä Int
). Tämä ei ole
välttämätöntä. Voidaan esimerkiksi muodostaa lukujen perusteella Boolean
-tyyppinen
tulos.
Seuraavat foldLeft
-kutsut etsivät kokoelmasta tietynlaisia lukuja yllä käsitellyn
exists
-metodin tapaan.
Tämä käsky vastaa käskyä luvut.exists( _ < 0 )
:
luvut.foldLeft(false)( (foundYet, next) => foundYet || next < 0 )res4: Boolean: true
false
. Tämä on se tulos, joka saataisiin, jos
vektorissa ei olisi alkioita lainkaan. (Tällöinhän ei ole myöskään
negatiivista alkiota.)Boolean
-tyyppinen. Tämä välitulos kertoo, onko negatiivista
lukua jo löydetty.true
, jos ja vain jos negatiivinen luku oli
löydetty jo aiemmin tai nyt tarkasteltava luku on negatiivinen.Tässä lyhyempi versio edellisestä sekä esimerkki, jossa saadaan false
-tulos:
luvut.foldLeft(false)( _ || _ < 0 )res5: Boolean = true luvut.foldLeft(false)( _ || _ < -100 )res6: Boolean = false
Lisää fold
-esimerkkejä: kuvien yhdisteleminen
Seuraavassa esimerkissä muodostetaan kuvia yhdistämällä pienempiä kuvia. Mitä koodi tekee ja millaisia yhdistelmiä syntyy? Päättele, ennusta ja kokeile.
val base = triangle(50, 50, Yellow)base: Pic = triangle-shape val circles = (1 to 255).map( n => circle(n, Color(n, 0, 255)) )circles: IndexedSeq[Pic] = Vector(circle-shape, circle-shape, circle-shape, circle-shape, circle-shape, [...]) val artwork1 = circles.foldLeft(base)( _.onto(_) )artwork1: Pic = combined pic
val artwork2 = (1 to 50).foldLeft(circle(50, Red))( (result, n) => result.leftOf(circle(n, Black)) )artwork2: Pic = combined pic
Ja ehkä muistat vielä placeStar
-funktion, jonka toteutit Stars-projektiin luvussa 4.4
ja joka asettelee yhden tähden taivasta vasten? Asiasta ei silloin ollut puhetta, mutta
Stars-projektiin valmiiksi kirjoitettu koodi muodostaa koko tähtitaivaan kuvan nimenomaan
foldLeft
-käskyllä, joka toistuvasti kutsuu laatimaasi funktiota näin:
def create(skyData: StarMap, bgSize: Int) = {
val darkSky = rectangle(bgSize, bgSize, Black)
skyData.stars.foldLeft(darkSky)(placeStar)
}
Vielä abstraktiotasoista
Pikkutehtäviä: foldLeft
reduceLeft
-metodi
Metodi reduceLeft
muistuttaa foldLeft
iä kovasti. Eroina ovat:
reduceLeft
ille ei erikseen tarvitse antaa alkuarvoa, vaan ensimmäisenä tuloksena käytetään kokoelman ensimmäistä alkiota.reduceLeft
ei toimi tyhjälle kokoelmalle lainkaan, vaan aiheuttaa ajonaikaisen virhetilanteen, jos sitä kutsutaan tyhjälle kokoelmalle.reduceLeft
in palauttama lopputulos on (ja välituloksetkin ovat) aina samaa tyyppiä kuin kokoelman alkiotkin.
Käytetään kokeeksi reduceLeft
-metodia pienimmän alkion selvittämiseen:
val luvut = Vector(10, 5, 4, 5, -20)luvut: Vector[Int] = Vector(10, 5, 4, 5, -20) luvut.reduceLeft( min(_, _) )res7: Int: -20
Tai vielä lyhyemmin:
luvut.reduceLeft(min)res8: Int: -20
reduceLeft
on kätevä silloin, jos 1) voidaan olettaa, että kokoelmassa on ainakin yksi
alkio, ja 2) yksialkioisen kokoelman tapauksessa tulokseksi kelpaa kokoelman ainoa alkio
sellaisenaan.
Tässä lisäesimerkki kuvilla:
val kuvat = Vector(Pic("face.png"), Pic("horse.png"), Pic("obstacle.png"))kuvat: Vector[Pic] = Vector(face.png, glider.png, obstacle.png) val allekkain = kuvat.reduceLeft( _.above(_) )allekkain: Pic = combined pic
Termeistä
Verbin fold käyttö juontuu siitä, että kyse on ikään kuin
kokoelman sisältämien arvojen rimpsun "taittelemisesta" kasaan
yhdeksi lopputulokseksi. Sana left liittyy siihen, missä
järjestyksessä metodi käy alkiot läpi: foldLeft
aloittaa
läpikäynnin "vasemmalta" eli kokoelman alusta ja etenee
vaiheittain "oikealle".
Tehtävä: selvitä, mitä tekevät Scalan metodit foldRight
ja
reduceRight
.
reduceLeft
plus Option
— ja favorite
taas
Yllä todettiin reduceLeft
-metodin rajoitukseksi se, että
kokoelmassa tulisi olla ainakin yksi alkio, koska muuten
tulosta ei ole määritelty.
Toisaalta olet jo oppinut käyttämään Option
-luokkaa kuvaamaan
tilanteita, joissa jokin tulos joko on tai ei ole olemassa.
Nämä ajatukset voi yhdistää: olkoon tulos None
, jos alkioita ei
ole ja muussa tapauksessa sovelletaan reduceLeft
iä ja kääritään
sen palautusarvo Some
-kääreeseen.
Kokeillaan tätä palaamalla vielä GoodStuff-projektin
Category
-luokkaan ja sen suosikkikirjanpitoon, josta on esitetty
versioita luvuissa 4.3 ja 5.5. Tässä taas yksi versio:
def favorite =
if (this.experiences.isEmpty) None else Some(this.experiences.reduceLeft( _.chooseBetter(_) ))
Ilman if
-käskyllä toteutettua tarkistusta reduceLeft
aiheuttaisi ajonaikaisen virhetilanteen (vrt. luvun 4.2
NullPointerException
).
Tarve yhdistää reduceLeft
-kutsu ja Option
-kääre on sen
verran yleinen, että tarkoitukseen on tehty oma metodinsa.
Varsin näppärä toteutus favorite
-metodille syntyy tätä
reduceLeftOption
-metodia käyttämällä:
def favorite = this.experiences.reduceLeftOption( _.chooseBetter(_) )
Tämä on käytännössä lyhennysmerkintä edelliselle versiolle,
jossa käytettiin if
iä, Option
ia ja reduceLeft
iä
erikseen.
Vapaaehtoinen lisätehtävä: MapReduce
Tässä ja edellisessä luvussa ovat esiintyneet map
- ja reduce
-
nimiset metodit, jotka muiden esiteltyjen ohella ovat tyypillisiä
funktionaaliselle ohjelmointitavalle (mistä lisää luvussa 10.2).
Selvitä ainakin pääpiirteissään, mikä on MapReduce-ohjelmointitapa, jota mm. Google-yhtiö on käyttänyt valtavien datamäärien käsittelyyn, ja mikä on sen yhteys kohtaamiimme kokoelmankäsittelymetodeihin.
Tehtävä: Election-ratkaisua uusiksi (osa 2/2)
Edellisessä luvussa 6.3 uudistit pari District
-luokan metodia. Uudista nyt kaksi
lisää: totalVotes
-nimiset metodit. Toteuta ne silmukan sijaan käyttämällä joko
foldLeft
iä tai map
- ja sum
-metodeita. Tai mieluiten mieti vaikka molemmat
toteutustavat.
Jos teit taannoin kuten luvussa 5.6 ehdotettiin ja laadit totalVotes
eille avuksi
countVotes
-metodin, niin kohdista muutos siihen.
District
-luokasta edelleen puuttumaan jäävät metodit toteutetaan aikanaan luvussa 9.2.
A+ esittää tässä kohdassa tehtävän palautuslomakkeen.
FlappyBug-tehtävä (osa 17/17: koodia siistimmäksi)
Tutkikaamme myös FlappyBug-projektiin luvussa 5.6 toteutettuja metodeja sillä silmällä, miten niitä voisi uudistaa korkeamman asteen metodeilla.
Tehtävänanto
Uudista seuraavat metodit. Metodien tulee tehdä sama kuin ennen, mutta yksinkertaista
toteutuksia korvaamalla for
-silmukat korkeamman asteen metodikutsuilla. Voit ottaa
pohjaksi oman aiemman ratkaisusi tai taannoisen esimerkkiratkaisun.
timePasses
luokastaGame
. Käytäforeach
-metodia (luku 6.3).isLost
samasta luokasta. Toteutus yksinkertaistuu huomattavasti luvun 6.3 esittelemällä metodilla, jolla voit nimensä mukaisesti suoraan tutkia, onko ehdon toteuttavaa estettä olemassa.- Käyttöliittymän
makePic
-metodi. Korvaa silmukkafoldLeft
-metodikutsulla, joka sijoittaa kunkin esteen paikoilleen.
Palauttaminen
A+ esittää tässä kohdassa tehtävän palautuslomakkeen.
Jälkihuomio 1: abstraktiotasoista
Edellisen luvun 6.3 Snake-tehtävässä pärjäsit placeCopies
-metodilla,
koska kaikki madon segmentit piirrettiin samannäköisinä. Käytössäsi
oli siis korkean abstraktiotason työkalu, joka hoiti homman.
Äskeisessä tehtävässä taas toivottavasti käytit makePic
iin
foldLeft
iä, kuten pyydettiin. placeCopies
ei olisikaan
kelvannut, koska esteiden kuvat eivät olleet keskenään identtisiä;
valmiin työkalun puuttuessa turvauduit matalamman abstraktiotason
yleiskäyttöisempään työkalustoon.
Jos o1
-kirjasto ei olisi sattunut tarjoamaan placeCopies
-metodia,
niin myös matotehtävän olisi toki voinut ratkaista myös
foldLeft
illä. (Kokeile, jos haluat.)
Itse asiassa kirjaston ohjelmakoodissa placeCopies
on toteutettu
nimenomaan foldLeft
illä, joten epäsuorasti käytit sitä joka
tapauksessa.
Jälkihuomio 2: jatkoa tehokkuuspohdinnoille
Käytit toivottavasti isLost
-metodissa exists
-metodia (tai
kenties forall
ia).
Luvun 5.6 vapaaehtoisessa osiossa pohdittiin tehokkuutta: tekikö vanha versio periaatteessa turhaa työtä käymällä koko vektorin läpi vaikka olikin jo löytynyt ehdon täyttävä este?
exists
ja forall
keskeyttävät läpikäynnin, kun palautusarvo
on selvillä, joten niiden käyttäminen on yksi tapa välttää tuo
(tässä mitätön ja vain periaatteellinen) tehokkuusongelma.
Emme palaa FlappyBugin pariin enää kurssin pisteytettyjen tehtävien puitteissa, mutta voit itse kehitellä peliä pidemmälle, jos haluat.
Kokoelmametodien yhdistelyä
Ohjelmia kirjoittaessa tulee usein vastaan tilanteita, joissa on hyödyksi yhdistellä kokoelmien metodeita. Tulet näkemään tästä monia esimerkkejä jo tälläkin kurssilla.
Tässä yksi pikkuesimerkki lukuvektorilla. Seuraava käsky laskee sellaisten positiivisten alkioiden lukumäärän, joiden toinen potenssi on parillinen:
val luvut = Vector(10, 5, 4, 5, -20)luvut: Vector[Int] = Vector(10, 5, 4, 5, -20) luvut.filter( _ > 0 ).map( n => n * n ).count( _ % 2 == 0 )res9: Int = 2
Seuraava pikkutehtävä tarkastelee metodien yhdistelemistä yleisemmällä tasolla:
Sadetehtävä
Johdanto
Kuvitellaan, että käytössä on mittari, joka tuottaa Vector[Int]
-muotoisia raportteja
sademääristä seuraavasti:
- Vektorissa esiintyvät nollat ja positiiviset kokonaisluvut edustavat sademäärämittausten tuloksia.
- Vektoriin voi olla tallennettuna yksi tai useampia mittausjaksoja. Kunkin jakson lopuksi siellä on erikoisarvo 999999, joka ei ole mittausdataa vaan ainoastaan ilmaisee jakson päättyvän.
- Lisäksi vektorissa voi olla negatiivisia lukuja, jotka ovat muuta dataa eivätkä kerro sademääristä.
Tehtävänanto
Laadi HigherOrder-projektin tiedostoon rainfall.scala
funktio averageRainfall
, joka
vastaanottaa ainoaksi parametrikseen kuvatunlaisen lukuvektorin. Funktio palauttaa
keskimääräisen sademäärän laskettuna ensimmäisestä parametrivektorissa esiintyvästä
mittausjaksosta. Funktion on toimittava seuraavien esimerkkien mukaisesti.
import o1.rainfall._import o1.rainfall._ averageRainfall(Vector(40, 0, 50, 999999))res10: Option[Int] = Some(30) averageRainfall(Vector(0, 0, 0, 0, 999999))res11: Option[Int] = Some(0) averageRainfall(Vector(999999))res12: Option[Int] = None
Option
iin käärittynä, Int
-tyyppisenä.Jos vektorissa on useita mittausjaksoja, vain ensimmäinen huomioidaan:
averageRainfall(Vector(50, 150, 100, 100, 999999, 20, 30, 90, 999999, 0, 0, 999999))res13: Option[Int] = Some(100) averageRainfall(Vector(999999, 50, 100, 999999))res14: Option[Int] = None
Negatiiviset arvot eivät saa vaikuttaa tulokseen lainkaan:
averageRainfall(Vector(-10, 50, -100, 150, -1, -5, 100, 100, 999999, 20, 30, 90, 999999, 0, 0, 999999))res15: Option[Int] = Some(100) averageRainfall(Vector(-100, -5, -10, 999999, 50, 100, 999999))res16: Option[Int] = None
Ohjeita ja vinkkejä
- Tavallinen kokonaislukujen jakolasku (luku 1.3) riittää tässä. Älä ryhdy pyöristämään "oikein".
- Käytä korkeamman asteen metodeita. Vaihtoehtoja on monia. Mitä
erilaisia ratkaisutapoja keksit?
- Voit vertailun vuoksi tehdä myös silmukoihin
ja
if
-käskyihin perustuvan ratkaisun. Kummanlainen ratkaisu ilmaisee ohjelman tarkoituksen paremmin?
- Voit vertailun vuoksi tehdä myös silmukoihin
ja
- Voit olettaa, että syötevektori päättyy aina lukuun 999999.
Palauttaminen
A+ esittää tässä kohdassa tehtävän palautuslomakkeen.
Kuivuustehtävä
Tehtävänanto
Toteuta samaan tiedostoon myös funktio drySpell
, joka:
- ottaa ensimmäisenä parametrinaan samanlaisen sadetietovektorin
kuin
averageRainfall
; - ottaa toisena parametrinaan positiivisen kokonaisluvun
length
, joka kertoo etsittävän "kuivan kauden" pituuden; - etsii vektorista ensimmäisen sellaisen pätkän, jossa vähintään
length
peräkkäistä alkiota ovat suuruudeltaan välillä 0–5; - palauttaa löydetyn kuivan kauden ensimmäisen indeksin (jos löytyi), tai negatiivisen luvun (jos ei löytynyt); ja
- huomioi kaikki mittausjaksot ja kaikki vektorin sisältämät luvut. Kuitenkaan kuiva kausi ei voi ulottua mittausjakson rajana toimivan luvun 999999 molemmille puolille. Myös negatiivinen luku katkaisee kuivan kauden.
Esimerkki:
val exampleData = Vector(10, 0, 3, 999999, 2, -10, 0, 0, 2, 1, 20, 1, 0, 1, 999999)exampleData: Vector[Int] = Vector(10, 0, 3, 999999, 2, -10, 0, 0, 2, 1, 20, 1, 0, 1, 999999) drySpell(exampleData, 3)res17: Int = 6
Ohjeita ja vinkkejä
Käytä jo esiteltyjä kokoelmametodeita. Opettele lisäksi nämä ja hyödynnä:
indexWhere
etsii ensimmäisen alkion, joka täyttää annetun ehdon. Esimerkkejä:val kokeilu = Vector(10, 5, 0, 3, 0, 100, 50)kokeilu: Vector[Int] = Vector(10, 5, 0, 3, 0, 100, 50) kokeilu.indexWhere( _ <= 0 )res18: Int = 2 kokeilu.indexWhere( _ < 0 )res19: Int = -1
sliding
tuottaa tietyn mittaisia pätkiä alkuperäisestä kokoelmasta. Esimerkkejä:kokeilu.sliding(4).foreach(println)Vector(10, 5, 0, 3) Vector(5, 0, 3, 0) Vector(0, 3, 0, 100) Vector(3, 0, 100, 50) kokeilu.sliding(5).foreach(println)Vector(10, 5, 0, 3, 0) Vector(5, 0, 3, 0, 100) Vector(0, 3, 0, 100, 50) kokeilu.sliding(5).toVectorres20: Vector[Vector[Int]] = Vector(Vector(10, 5, 0, 3, 0), Vector(5, 0, 3, 0, 100), Vector(0, 3, 0, 100, 50))
Tarkemmin sanoen
sliding
in palauttama "luettelo pätkiä" on ns. iteraattori, jolla voi käydä tietyt kokoelman pätkät kerran(!) läpi. Iteraattorille voi kutsua monia tuttuja kokoelmametodeita kutenforeach
,toVector
jaindexWhere
. Jos haluat käsitellä kokoelman pätkiä usealla eri metodilla, voit kopioida pätkät esim. vektoriin. (Tässä tehtävässä se ei tosin ole välttämätöntä.)
Palauttaminen
A+ esittää tässä kohdassa tehtävän palautuslomakkeen.
Pohdintatehtäviä
Päätämme luvun parilla pulmalla, joissa tutkit annettuja Scala-koodeja ja arvioit miten ne toimivat, jos toimivat. Tehtävien kautta nousee esiin ohjelmointitekniikoita, joista on kasvavassa määrin hyötyä, kun etenemme.
Seuraavat tehtävät ovat tarkoituksella sellaisia, ettei kaikkia vastauksia voi aukottomasti päätellä aiemmin esitetystä materiaalista. Toisaalta vastaukset ovat varsin järkeenkäyviä. Muista lukea palaute vääristä ja oikeista vastauksista!
Pohdinta-aihe 1: funktio funktion sisällä
Pohdinta-aihe 2: paikalliset muuttujat
Muutama sana sulkeumista
def etsiIsot(vektori: Vector[Int], raja: Int) = {
def lasketaanIsoksi(luku: Int) = luku > raja
vektori.filter(lasketaanIsoksi)
}
Esimerkkikoodimme näyttää hyvin ymmärrettävältä: etsitään vektorista sellaisia lukuja, joiden arvo ylittää tietyn muuttujan arvon. On "luonnollista", että koodi toimii.
Asia ei kuitenkaan ole itsestäänselvä. filter
hän suoritetaan
omassa kutsupinon kehyksessään etsiIsot
-funktiota vastaavan
kehyksen päällä, eikä funktioilla ole pääsyä alempien kehysten
paikallisiin muuttujiin (luku 1.8).
Vastaava yritys ei toimisikaan kaikissa ohjelmointikielissä, mutta monissa (kuten Scalassa) toimii. Miksi?
Koskapa koodi toimii, on mitä ilmeisimmin niin, että filter
-funktiolle
jotenkin välittyy ei vain lasketaanIsoksi
-funktio sellaisenaan
vaan myös pääsy raja
-muuttujaan, jota tuo funktio käyttää.
Tällaista funktion ja yhden tai useamman siihen kytketyn
ulkopuolisen muuttujan yhdistelmää sanotaan sulkeumaksi eli
klosuuriksi (closure). (Funktio "sulkee sisälleen" itsensä
ulkopuolella määriteltyjä muuttujia.)
Scala-kääntäjä huolehtii sulkeuman luomisesta automaattisesti, kun käytät funktiossa muuttujia, jotka ovat määriteltyjä siinä ympäristössä, mihin funktion määrittelykin kirjoitetaan. Sulkeuman käyttö ohjelmassa onkin usein huomaamatonta ja intuitiivista.
Sulkeumat ovat eräs syy määritellä funktio toisen sisään.
Esimerkiksi lasketaanIsoksi
voi käyttää raja
-muuttujaa juuri
siksi, että se on määritelty etsiIsot
-funktion sisällä, ja
samasta syystä pääsy tuohon muuttujaan voidaan välittää sulkeumassa
filter
-metodilla.
Esimerkissämme määrittelimme nimellisen funktion lasketaanIsoksi
,
mutta sulkeumia syntyy myös funktioliteraaleja käyttäessä. Tässä
toinen versio äskeisestä esimerkistä:
def etsiIsot(vektori: Vector[Int], raja: Int) =
vektori.filter( _ > raja )
filter
-metodille parametriksi
literaalin määrittelemän vertailufunktion.filter
-metodille
sulkeuman, jonka koodi on _ > raja
ja jolla
on pääsy tarvitsemaansa raja
-muuttujaan.Tällainen ympäröivässä kontekstissa määritellyn muuttujan käyttö funktioliteraalissa (ja sulkeuman automaattinen muodostuminen literaalin perusteella) onkin aivan tavallista Scala-ohjelmissa.
Sulkeumista opit lisää jatkokurssilla tai omaehtoisella tiedonhaulla. Tämän kurssin puitteissa riittää tietää se, että äskeisen esimerkin kaltaista koodia voi mainiosti Scala-ohjelmaan kirjoittaa. Voit hyvin luonnolliseen tapaan käyttää kutsuvan koodin määrittelemää muuttujaa osana parametriksi välitettävän funktion ohjelmakoodia. Sulkeumille löytyy hyötykäyttöä tulevissa tehtävissä.
Lisää sisäkkäisyyttä
Paitsi että Scalassa voi määritellä funktioita sisäkkäin, niin luokkia, yksittäisolioita ja funktioita voi muutenkin laittaa joustavasti eri tavoin sisäkkäin. On mahdollista esimerkiksi määritellä luokka toisen luokan sisälle tai yksittäisolio metodin sisälle. Kokeile itse, jos huvittaa.
Yhteenvetoa
- Käteviin kokoelmia läpi käyviin metodeihin lukeutuvat edellisessä
luvussa opittujen lisäksi myös erittäin yleiskäyttöiset
foldLeft
jareduceLeft
. Niillä voi muodostaa tulosarvon kokoelman alkioita vuoron perään työstämällä. - Yhteen kiedottujen silmukka- ja
if
-rakenteiden sijaan on usein mielekkäämpää muotoilla algoritmi ketjuksi korkeamman asteen metodikutsuja, jossa yhden metodin palautusarvoon kohdistetaan seuraava operaatio vaiheittain. - Funktion voi määritellä toisen sisään paikalliseksi funktioksi samaan tapaan kuin muuttujankin voi.
- Funktiota määritellessä voi sen ohjelmakoodissa käyttää funktion itsensä ulkopuolisia muuttujia ympäröivästä kontekstista; näin syntyvää rakennetta kutsutaan sulkeumaksi eli klosuuriksi.
- Lukuun liittyviä termejä sanastosivulla: korkeamman asteen funktio; kokoelma; abstraktiotaso; paikallinen funktio, käyttöalue; sulkeuma.
Palaute
Huomaathan, että tämä on henkilökohtainen osio! Vaikka olisit tehnyt lukuun liittyvät tehtävät parin kanssa, täytä palautelomake itse.
Tekijät
Tämän oppimateriaalin kehitystyössä on käytetty apuna tuhansilta opiskelijoilta kerättyä palautetta. Kiitos!
Kierrokset 1–13 ja niihin liittyvät tehtävät ja viikkokoosteet on laatinut Juha Sorva.
Kierrokset 14–20 on laatinut Otto Seppälä. Ne eivät ole julki syksyllä, mutta julkaistaan ennen kuin määräajat lähestyvät.
Liitesivut (sanasto, Scala-kooste, usein kysytyt kysymykset jne.) on kirjoittanut Juha Sorva sikäli kuin sivulla ei ole toisin mainittu.
Tehtävien automaattisen arvioinnin ovat toteuttaneet: (aakkosjärjestyksessä) Riku Autio, Nikolas Drosdek, Joonatan Honkamaa, Jaakko Kantojärvi, Niklas Kröger, Teemu Lehtinen, Strasdosky Otewa, Timi Seppälä, Teemu Sirkiä ja Aleksi Vartiainen.
Lukujen alkuja koristavat kuvat ja muut vastaavat kuvituskuvat on piirtänyt Christina Lassheikki.
Yksityiskohtaiset animaatiot Scala-ohjelmien suorituksen vaiheista ovat suunnitelleet Juha Sorva ja Teemu Sirkiä. Niiden teknisen toteutuksen ovat tehneet Teemu Sirkiä ja Riku Autio käyttäen Teemun toteuttamia Jsvee- ja Kelmu-työkaluja.
Muut diagrammit ja materiaaliin upotetut vuorovaikutteiset esitykset on laatinut Juha Sorva.
O1Library-ohjelmakirjaston ovat kehittäneet Aleksi Lukkarinen ja Juha Sorva. Useat sen keskeisistä osista tukeutuvat Aleksin SMCL-kirjastoon.
Opetustapa, jossa käytämme O1Libraryn työkaluja (kuten Pic
) yksinkertaiseen graafiseen
ohjelmointiin on saanut vaikutteita tekijöiden Flatt, Felleisen, Findler ja Krishnamurthi
oppikirjasta How to Design Programs sekä Stephen Blochin oppikirjasta Picturing Programs.
Oppimisalusta A+ on luotu Aallon LeTech-tutkimusryhmässä pitkälti opiskelijavoimin. Pääkehittäjänä toimii tällä hetkellä Jaakko Kantojärvi, jonka lisäksi järjestelmää kehittävät useat tietotekniikan ja informaatioverkostojen opiskelijat.
Kurssin tämänhetkinen henkilökunta on kerrottu luvussa 1.1.
foldLeft
-metodilla on kaksi parametriluetteloa (kutentabulate
-metodilla; luku 6.1). Ensimmäiseen kirjoitetaan alkuarvo, joka on samalla lopputulos siinä tapauksessa, ettei kokoelmassa olisi alkioita lainkaan, ja...