Luku 5.6: Silmukoita, merkkijonoja ja vaalit
Johdanto
Tähänastisissa esimerkeissä for
-silmukoilla on käyty läpi vain vektoreita ja puskureita.
Mutta kuten luvussa 5.5 mainittiin, voi for
-silmukalla käydä läpi hyvinkin erilaisia
alkiokokoelmia. Vaikkapa merkkijonoja:
Merkkijonon läpikäynti
Sanotaan esimerkin vuoksi, että halutaan tulostaa raportti kustakin tietyn merkkijonon sisältämästä merkistä. Merkkijonolle "kissa" tulostettaisiin näin:
Merkki: k
Merkki: i
Merkki: s
Merkki: s
Merkki: a
Tämä onnistuu seuraavalla Scala-koodilla.
for merkki <- "kissa" do
println("Merkki: " + merkki)
Muuttuja merkki
on siis tyyppiä Char
.
Silmukan runko toistuu yhtä monta kertaa kuin merkkijonossa on merkkejä. Merkit käydään läpi järjestyksessä alusta loppuun.
Entä jos haluttaisiinkin numeroitu tuloste ja lisäksi merkkien Unicode-arvot?
Indeksillä 0 on k, Unicodessa merkki #107.
Indeksillä 1 on i, Unicodessa merkki #105.
Indeksillä 2 on s, Unicodessa merkki #115.
Indeksillä 3 on s, Unicodessa merkki #115.
Indeksillä 4 on a, Unicodessa merkki #97.
Yksi toteutustapa on tämä:
var indeksi = 0
for merkki <- "kissa" do
println("Indeksillä " + indeksi + " on " + merkki + ", Unicodessa merkki #" + merkki.toInt + ".")
indeksi += 1
Käytetään askeltajamuuttujaa pitämään kirjaa indeksistä, jossa ollaan menossa. Sen arvoa kasvatetaan yhdellä joka kierroksella aina tulostamisen jälkeen.
Char
-luokassa on määritelty toInt
-metodi, joka palauttaa
kyseisen merkin Unicode-arvon kokonaislukuna.
Äskeinen ohjelma löytyy myös ForLoops-oheismoduulista pakkauksesta o1.looptest
, samoin
kuin usea muu tämän luvun esimerkki ja tehtävä.
Tutki debuggerilla
Debuggeri on ohjelman suoritusvaiheiden tutkimiseen sopiva aputyökalu. Esimerkiksi IntelliJ’hin sisältyy debuggeri.
Tässä kurssimateriaalissa on erillinen sivu, joka esittelee debuggerin käyttöä.
Suosittelemme, että tutustut debuggerimateriaaliin ennen kuin jatkat tässä luvussa. Tämän luvun ohjelmia voi käydä läpi debuggerilla rivi riviltä, mikä voi auttaa hahmottamaan niiden toimintaa ja toimii myös harjoituksena debuggerin käytöstä.
Debuggeri voi auttaa virheiden etsinnässä monissa tulevissa harjoitustehtävissä ja kurssin ulkopuolella ohjelmoidessasi.
Pieni ohjelmointitehtävä: vain isot kirjaimet
Moduulin ForLoops pakkauksesta o1.looptest
löytyy task2.scala
. Olion toivottu toiminta
on kuvattu sen ohjelmakoodin kommentissa. Täydennä olio toiveiden mukaiseksi.
A+ esittää tässä kohdassa tehtävän palautuslomakkeen.
Pieni vaikka monisanaisempi tehtävä: DNA:n ominaisuuksia
Solujemme DNA:n keskeisiä rakennusaineita ovat neljänlaiset emäkset (base): guaniini (G), adeniini (A), sytosiini (C) ja tymiini (T). DNA-säiettä voidaan kuvata luettelemalla sen emäkset järjestyksessä, esimerkiksi GATCACAGGT ja niin edelleen. Eliön DNA on yksilöllinen, mutta saman lajin yksilöiden väliset erot ovat pieniä ja lajien välilläkin on merkittäviä yhtäläisyyksiä.
DNA:n eräitä ominaisuuksia kuvaa sen GC-pitoisuus (GC content) eli prosenttiluku, joka kertoo, kuinka suuri osuus emäksistä on guaniinia tai sytosiiniä. Esimerkiksi DNA-pätkän ATGGACAT GC-pitoisuus on 37,5 %, koska kahdeksasta emäksestä kolme on guaniini tai sytosiini. GC-pitoisuudella on merkitystä esimerkiksi geeniteknologiassa (ks. esim. Wikipedia).
Ota esiin task3.scala
. Voit ajaa tämän pikkuohjelman, joka toimii tekstikonsolissa näin:
Enter a species file name (without .mtdna extension): human The GC content is 80.13914555929992%.
Ohjelma näyttää siis laskevan käyttäjän mainitseman lajin DNA:n GC-pitoisuuden. Syötteeksi sille voi antaa jonkin merkkijonoista human, chicken, chimpanzee, mouse, opossum, pufferfish, yeast tai test; ohjelma käyttää apunaan vastaavannimisiä moduuliin liitettyjä tiedostoja, joihin on kirjattu lajien DNA-sekvenssejä (tarkemmin sanoen mitokondrio-DNA-tietoja; mtDNA).
Ohjelman raportoima tulos on kuitenkin pielessä, koska tiedostossa task3.scala
määritelty funktio gcContent
on virheellisesti toteutettu. Tuloksen kuuluisi
syötteellä human
olla reilut 44 % eikä 80 %.
Tehtäväsi on tutustua funktion toteutukseen (löytyy mainitusta tiedostosta sekä alta) ja tehdä siihen tarvittava pikkuinen muutos, joka saa sen toimimaan.
Funktion tulisi laskea parametriksi annettua merkkijonoa vastaava GC-pitoisuus, eli
esimerkiksi parametriarvolla "ATGGACAT"
palautettaisiin Double
-arvo 37.5. Tässä
leluesimerkissämme kaikki merkityksettömät merkit, kuten välilyönnit ja emästä
kuvaamattomat kirjaimet, yksinkertaisesti ohitetaan, joten esimerkiksi merkkijono
"ATG G ACATXXYZYZ"
tuottaisi myös tuloksen 37.5.
def gcContent(dna: String) =
var gcCount = 0
var totalCount = 0
for base <- dna do
if base == 'G' || base == 'C' then
gcCount += 1
else if base == 'A' || base == 'T' then
totalCount += 1
end for
100.0 * gcCount / totalCount
Char
-olioillekin on literaalimerkintä vastaavasti kuin
luvuille, totuusarvoille ja merkkijonoille. Toisin kuin
merkkijonoliteraali, Char
-arvo eli yksittäinen merkki
kirjoitetaan yksinkertaisiin hipsukoihin.
Jos haluat kokeilla ohjelmaa itse valitsemillasi kirjainsekvensseillä,
voit muokata kansiosta mkDNA_examples
löytyvää tiedostoa test.mtdna
ja antaa ohjelmalle syötteen test
.
A+ esittää tässä kohdassa tehtävän palautuslomakkeen.
Lukujen läpikäyminen: to
, until
ja indices
Luvussa 5.2 mainittiin, että Int
-olioilla on metodi nimeltä to
, jota on yleensä
mukavinta käyttää operaattorinotaatiolla eikä pistenotaatiolla.
Se palauttaa viittauksen Range
-olioon:
5 to 15res0: Range.Inclusive = Range 5 to 15
Range
-olio edustaa kokonaislukuluetteloa. Sekin on alkiokokoelma, jonka voi käydä läpi
for
-silmukalla:
val tuhanteen = 1 to 1000
for luku <- tuhanteen do
println(luku)
Silmukkamuuttuja luku
toimii askeltajana, joka saa vuorollaan kunkin positiivisen
kokonaisluvun 1, 2, 3, ..., 1000. Ohjelma tulostaa nämä luvut järjestyksessä omille
riveilleen.
Äskeisen ohjelman voi kirjoittaa yksinkertaisemminkin:
for luku <- 1 to 1000 do
println(luku)
Vastaavaa käskyä voi käyttää myös indeksejä käyttävän kokoelman — esimerkiksi merkkijonon — indeksien läpikäymiseen. Tämä ohjelma tuottaa tutun tulosteen:
val merkkijono = "kissa"
for indeksi <- 0 to merkkijono.length - 1 do
val merkki = merkkijono(indeksi)
println("Indeksillä " + indeksi + " on " + merkki + ", Unicodessa merkki #" + merkki.toInt + ".")
Jos käytämme to
-metodia, on parametriarvoksi muistettava antaa
vähennyslaskun merkkijono.length - 1
tulos, jotta nollasta
alkava indeksi pysyy rajoissa.
Pieniä ohjelmointitehtäviä: to
, until
yms.
Moduulin ForLoops pakkauksesta o1.looptest
löytyy lisää pikkuohjelmia, jotka on
tarkoitus muokata: katso nyt tiedostot task4.scala
, task5.scala
ja task6.scala
.
Olioiden toivottu toiminta on myös kuvattu noissa tiedostoissa. Täydennä ne toiveiden
mukaisiksi.
Nämä pienet tehtävät ovat toisistaan riippumattomia. Voit tehdä ja palauttaa ne yksitellen, järjestyksessä.
Muista testata ohjelmia ensin itse. Käytä debuggeria virheiden paikallistamisessa apuna, kun on tarvetta.
A+ esittää tässä kohdassa tehtävän palautuslomakkeen.
A+ esittää tässä kohdassa tehtävän palautuslomakkeen.
A+ esittää tässä kohdassa tehtävän palautuslomakkeen.
indices
-metodi
Kun halutaan käydä läpi kaikki merkkijonon tai muun kokoelman indeksit, on kokoelman
indices
-metodi usein kätevä vaihtoehto until
- ja to
-metodeille.
val merkkijono = "kissa"merkkijono: String = kissa for indeksi <- merkkijono.indices do println("Indeksillä " + indeksi + " on " + merkkijono(indeksi))Indeksillä 0 on k Indeksillä 1 on i Indeksillä 2 on s Indeksillä 3 on s Indeksillä 4 on a
Tämä perustuu siihen, että indices
palauttaa juuri sellaisen Range
-olion, jonka
olisimme voineet until
-metodillakin luoda:
merkkijono.indicesres1: Range = Range 0 until 5
Kunkin alkion muokkausta
Edellisessä luvussa 5.5 meillä oli toimimaton versio funktiosta, jonka piti summata
puskurin kuhunkin alkioon ykkösen. Tässä toimiva versio, joka hyödyntää indices
ia:
def kasvataAlkioita(luvut: Buffer[Int]) =
for indeksi <- luvut.indices do
luvut(indeksi) = luvut(indeksi) + 1
Käydään läpi muuttuvatilaisen puskurimme indeksit.
Sijoitetaan kullekin indeksille aiempaa isompi luku.
Lyhyempikin merkintä luvut(indeksi) += 1
toimii.
Ja toki indeksikokoelman voi muodostaa jollain muullakin
tavalla. Esim. until
toimii.
Election-tehtävä
Tehtävänanto
Tehtäväsi on
noutaa Election-moduuli,
tutustua moduulin Scaladoc-dokumentaatioon ja valmiina annettuun
Candidate
-luokan ohjelmakoodiin, joka kuvaa ehdokkaita vaaleissa,kirjoittaa Scala-ohjelmakoodi, joka toteuttaa puuttuvan luokan
District
, joka kuvaa vaalipiirejä, ja(tietysti) testata, että luokka toimii ja korjata tarvittaessa.
Yleisiä ohjeita ja vinkkejä
District
-luokka muistuttaa monella tavalla luvun 5.5AuctionHouse
-luokkaa. Kertaa tuota lukua tarvittaessa.Useassa
District
-luokan metodissa voi käyttääfor
-silmukkaa toteutuksen osana.Luvussa 5.5 nähty ohjelmakoodi voi toimia inspiraationa
District
-luokan toteutukselle.
Käytä annettua käynnistysfunktiota
testElection
, joka kutsuuDistrict
-luokan metodeita. Voit muokata sitä testataksesi erilaisia tapauksia.Ellei sinulla ole jonkinlaista toteutusta kullekin
District
-luokan metodille, eitestElection
-ohjelmaa voi annetussa muodossa ajaa.Voit testata testiohjelmalla osittaistakin
District
-toteutusta, jos "kommentoit ulos" puuttuvien metodien kutsut testiohjelmasta tai laadit toistaiseksi toteuttamattomilleDistrict
-luokan metodeille aluksi toteutuksenraakileet (vaikkapa???
-merkintää käyttäen; luku 4.1).
Kannattanee edetä seuraavaksi kuvatuissa vaiheissa.
Suositellut työvaiheet
Tutustu ensin dokumentaatioon. Huomaa, että usea
District
-luokan metodi on merkitty toteutettavaksi vasta huomattavasti myöhemmässä tehtävässä (luku 10.1). Nyt tässä luvussa kuuluu toteuttaa kaikki muut puuttuvat metodit paitsi nämä erikseen merkityt.Tutustu moduulin sisältämään ohjelmakoodiin.
Ryhdy toteuttamaan
District
-luokkaa seuraavissa vaiheissa. KäytätestElection
-ohjelmaa sitä mukaa kuin luotDistrict
-luokkaan uusia metodeita.Määrittele luokkaan
District
luontiparametrit ja ilmentymämuuttujat.Luo
toString
-metodi.Luo
printCandidates
-metodi.Voit ottaa mallia vaikkapa
AuctionHouse
-luokannextDay
-metodista.
Luo
candidatesFrom
-metodi.Voit ottaa mallia eräästä
AuctionHouse
-luokan metodista, jossa on samankaltainen perusajatus.Huomaa, että paluuarvon tyypiksi on määrätty
Vector[Candidate]
.
Luo
topCandidate
-metodi.Tässäkin voi ottaa mallia eräästä
AuctionHouse
n metodista.Osaatko käyttää vektorin
head
- jatail
-metodeita niin, ettei metodissa turhaan vertailla ensimmäistä alkiota itseensä? (Ei välttämätöntä.)
Luo
totalVotes
-nimiset metodit.
Vapaaehtoinen mutta hyvin suositeltava lisätehtävä
Toimivatko kaksi totalVotes
-metodiasi jo? Hyvä! Mutta tuliko
niistä keskenään kovin samankaltaiset: useita samoja koodirivejä
molemmissa?
Toisteisuus heikentää koodin muokattavuutta, kasvattaa virheiden mahdollisuutta eikä ole kaunista.
Yksi hyvä tapa tehdä koodista vähemmän toisteista on kirjata
apumetodiin se osa algoritmista, joka on noille metodeille
yhteistä. Tässä yhteinen osa on äänten laskeminen ehdokkaita
sisältävästä vektorista: totalVotes
-metodit eroavat toisistaan
vain siinä, mitkä ehdokkaat otetaan mukaan laskentaan.
Apumetodin voi määritellä vaikkapa tähän tapaan:
private def countVotes(candidates: Vector[Candidate]) =
// Laske yhteisäänet saadusta vektorista ja palauta summa.
Apumetodista on perusteltua tehdä yksityinen, koska metodi on
tarkoitettu vain District
-tyypin sisäiseen käyttöön.
Osaatko toteuttaa apumetodin countVotes
ja toteuttaa totalVotes
-metodit niin, että ne kutsuvat countVotes
-metodia antaen sille
asianmukaisen parametriarvon?
A+ esittää tässä kohdassa tehtävän palautuslomakkeen.
Ja vielä...
AuctionHouse
-luokan metodeissa ja luultavasti myös useassa
District
-luokkaan laatimassasi metodissa toistuu sama kaava:
Määritellään tulosmuuttuja ja asetetaan sille alkuarvo.
Sitten käytetään silmukkaa, joka käy läpi kokoelman alkiot ja suorittaa niille jonkin operaation:
for muuttuja <- kokoelma do
Lopuksi palautetaan tulosmuuttujan arvo, joka määritettiin silmukan aikana.
Toistuvan kaavan vuoksi metodien koodissa esiintyy toisiaan muistuttavia rivejä.
Eikös tämäkin ole eräänlaista toisteisuutta? Voisiko jopa tämän välttää?
Kyllä voi ja tyylikkäällä tavalla voikin. Mutta se vaatii lisää esitietoja, joten palataan aiheeseen luvuissa 6.3 ja 7.1.
Pikkutehtävä: kuvan muodostaminen silmukalla
FlappyBug-tehtävä (osa 16/17: enemmän esteitä)
Tähän asti FlappyBug-pelissämme on ollut vain yksi este. Muokkaa peliä niin, että esteitä
on useita. Kutakin estettä kuvaa oma Obstacle
-olionsa, jotka tallennetaan vektoriin.
Esteet käyttäytyvät keskenään samalla tavalla, mutta ne ovat erikokoisia ja kullakin on
oma sijaintinsa. Toteuta muutokset for
-silmukoita käyttäen.
Tarvittavat muutokset ovat:
Esteet osaksi peliä: korvaa luokan
Game
obstacle
-ilmentymämuuttujan osoittama 70-pikselinen este vektorilla, jossa on kolme estettä. Ensimmäisen koko on 70, toisen 30 ja viimeisen 20. Vaihda samallaobstacle
-muuttujan nimeksiobstacles
.Kukin este liikkeelle: muuta
Game
-luokantimePasses
-metodia niin, että se liikuttaa kutakin vektoriin tallennetuista esteistä.Varsin yksinkertainen
for
-silmukka riittää.
Kuolinehdon päivitys: muuta
Game
-luokanisLost
-metodia niin, että se palauttaatrue
, jos ötökkä ei ole pelialueella tai jos mikä tahansa esteistä koskettaa ötökkää.Tässä kohdassa on vähän enemmän tekemistä (toistaiseksi opituilla konsteilla) kuin edellisessä. Jos et keksi ratkaisutapaa, katso vinkki alta.
Kukin esteistä näkyviin: muuta käyttöliittymän
makePic
-metodia niin, että se muodostaa kuvan kustakin pelin estevektoriin tallennetusta esteestä ja asettaa nuo kuvat taustaa vasten. Viimeisenä päälle asetetaan edelleen ötökän kuva.Vinkki
Käytä
for
-silmukan tukenaPic
-tyyppistä kokoojamuuttujaa: samalla kun käyt läpi vektoria, päivität kokoojaa asemoimalla uuden estekuvan kokoojan vanhan arvon päälle. (Vrt. edellinen pikkutehtävä.)
A+ esittää tässä kohdassa tehtävän palautuslomakkeen.
Uusi muuttujarooli: (yksisuuntainen) lippu
Äskeisessä isLost
-vinkissä oli esimerkki eräästä muuttujan roolista
(luku 2.6) eli yleisestä muuttujan käyttötavasta, josta ei ole tässä
materiaalissa aiemmin mainittu.
Ohjelmoijat puhuvat melko usein lipuista (flag). Lipuksi kutsutaan tallennettua tietoa, jota käytetään ilmoittamaan jostakin asiaintilasta: "lippu nostetaan salkoon".
Yllä ehdotettiin, että käytät isLost
-metodissa Boolean
-tyyppistä
muuttujaa tietyllä tavalla. Tämä muuttuja on lippu, joka "nostetaan"
(eli saa arvon true
) merkkinä siitä, että ötökkää koskettava este on
löydetty. Tarkemmin sanoen kyseessä on yksisuuntainen lippu
(one-way flag): kun sen arvo on kerran vaihdettu, se ei enää muutu.
Lipulla on vain kaksi mahdollista arvoa, minkä vuoksi lippumuuttujat
ovat usein Boolean
-tyyppisiä.
Tehokkuudesta huolestuneille
Äskeisessä isLost
-metodissa homma on periaatteessa selvä heti,
kun yksikin ötökkää koskettava este löytyy. Muita esteitä ei
silloin edes tarvitsisi käydä läpi. Ehdotetulla tavalla toteutettu
metodi kuitenkin käy aina läpi koko estevektorin.
Nythän noita esteitä oli vektorissa vain kolme, joten koneelta ei mene kauan käydä vektori läpi loppuun asti. Mutta mitä jos kävisimme samalla idealla läpi jotakin valtavaa kokoelmaa selvittääksemme, löytyykö sieltä vähintään yksi tietyn ehdon toteuttava alkio? Voisi toivoa, että koneen saisi lopettamaan heti, kun tulos on selvillä.
Asia järjestyy kyllä; konstit on monet. Palaamme aiheeseen mm. luvuissa 7.1 ja 7.2 työkalupakkimme painavoituessa.
Sisäkkäiset silmukat
Silmukat voivat olla sisäkkäin. Tällöin sisempi silmukka suoritetaan jokaisella ulomman silmukan "kierroksella".
Sisempi kaksikierroksinen silmukka tulee siis suoritetuksi useita kertoja, ulompi
kolmikierroksinen silmukka vain kerran. Sisin println
-käsky suorittuu yhteensä kuusi
kertaa.
Silmukanlukemistehtävä
Pieni ohjelmointitehtävä
Tee viimeinenkin o1.looptest
-pakkauksen pikkutehtävä: task7.scala
.
Muista taas testata ja käyttää debuggeria tarvittaessa.
A+ esittää tässä kohdassa tehtävän palautuslomakkeen.
Käyttöalue eli skooppi
Tässä vaiheessa kurssia on tullut vastaan jo esimerkkiä sisäkkäisistä koodirakenteista. Tarkastellaan siis nyt, miten sisäkkäisyys vaikuttaa esimerkiksi muuttujien määrittelyihin.
Kullakin ohjelman pääosalla — muuttujalla, metodilla, luokalla, yksittäisoliolla —
on käyttöalue (eli näkyvyysalue eli skooppi; scope). Käyttöalue on se osa
ohjelmakoodia, josta kyseiseen osaan pääsee käsiksi. Käyttöalue riippuu yhteydestä,
jossa kyseinen piirre on määritelty, minkä lisäksi sitä voi säädellä näkyvyysmääreillä
kuten private
.
Yritys käyttää muuttujaa tai metodia sen käyttöalueen ulkopuolelta aiheuttaa käännösaikaisen virheilmoituksen.
Tarkastellaan ensin kertauksenomaisesti luokan välittömien osien käyttöaluetta ja sitten paikallisten muuttujien käyttöaluetta.
Luokan osien käyttöalue
Sinun ei tarvitse ymmärtää, mitä seuraava luokka tekee (joskin se on opetetun perusteella ymmärrettävissä). Luokka on tässä vain esimerkkinä sisäkkäisyydestä ja käyttöalueista, ja tutkimme sitä pintapuolisesti:
class Joulukuusi(puunKorkeus: Int):
val korkeus = puunKorkeus.min(1000)
private val leveinKohta = this.leveys(this.korkeus - 1)
override def toString =
var kuva = this.rivi('*', 1)
for riviNro <- 1 until this.korkeus do
kuva += this.rivi('^', this.leveys(riviNro))
kuva += this.rivi('|', (this.leveinKohta - 4).min(3).max(1))
kuva
private def leveys(rivi: Int) = rivi / 2 * 2 + 1
private def rivi(merkki: Char, montako: Int) =
val tyhjaaVasemmalle = (this.leveinKohta - montako) / 2
val kokoRivi = " " * tyhjaaVasemmalle + merkki.toString * montako + "\n"
kokoRivi
end Joulukuusi
Julkisen ilmentymämuuttujan kuten korkeus
käyttöalueeseen sisältyy
koko luokka. Lisäksi siihen voi viitata luokan ulkopuoleltakin, kunhan
käytössä on kyseisen luokan ilmentymä: olio.korkeus
. Samoin julkista
metodia kuten toString
voi käyttää mistä tahansa päin luokkaa tai sen
ulkopuolelta. Ilmentymämuuttuja tai metodi on julkinen ellei toisin
määritetä.
Yksityisen ilmentymämuuttujan kuten leveinKohta
ja yksityisen
metodin kuten leveys
tai rivi
käyttöalue on koko kyseinen luokka
(mahdollisine kumppaniolioineen; luku 5.3).
Luokka itse on julkinen, joten sitä voi käyttää muualta ohjelmasta vapaasti. (Yksityisiäkin luokkia on mahdollista määritellä, mutta sitä emme tee tällä kurssilla.)
Paikallisten muuttujien käyttöalue
class Joulukuusi(puunKorkeus: Int):
val korkeus = puunKorkeus.min(1000)
private val leveinKohta = this.leveys(this.korkeus - 1)
override def toString =
var kuva = this.rivi('*', 1)
for riviNro <- 1 until this.korkeus do
kuva += this.rivi('^', this.leveys(riviNro))
kuva += this.rivi('|', (this.leveinKohta - 4).min(3).max(1))
kuva
private def leveys(rivi: Int) = rivi / 2 * 2 + 1
private def rivi(merkki: Char, montako: Int) =
val tyhjaaVasemmalle = (this.leveinKohta - montako) / 2
val kokoRivi = " " * tyhjaaVasemmalle + merkki.toString * montako + "\n"
kokoRivi
end Joulukuusi
Metodien sisäisiin algoritmitoteutuksiin ei pääse käsiksi mistään kyseisen metodin ulkopuolelta.
Kun siirrät hiiren kursorin seuraavien laatikoiden päälle, korostuvat mainittujen muuttujien käyttöalueet.
Parametrimuuttuja kuten merkki
tai montako
on määritelty koko
kyseisen metodin ohjelmakoodissa. Sitä voi käyttää sieltä mistä vain.
Metodin koodissa uloimmalla tasolla määritelty paikallinen muuttuja,
kuten tyhjaaVasemmalle
, on käytettävissä määrittelykohdasta alkaen
metodin koodin loppuun.
Sama pätee muuttujalle kokoRivi
.
Kun ulompi käsky sisältää muuttujamäärittelyn, niin määritelty
muuttuja on käytettävissä vain kyseisen ulomman käskyn sisällä.
Esimerkiksi tässä muuttuja riviNro
on määritelty vain for
-silmukan sisällä.
Toinen esimerkki
Tässä vielä yksi (sinänsä täysin hyödytön) esimerkkikoodi, joka korostaa paikallisten muuttujien käyttöalueita:
def metodi(parametri: Int) =
println("Terve")
var ulko = parametri * 2
val joku = 1
for keski <- 1 to 3 do
println("Moi")
val joku = 10
ulko += keski * 2 + joku
if keski < ulko + parametri then
val sisa = readLine("Anna luku: ").toInt
ulko += sisa + joku
end if
end for
ulko + joku
parametri
n käyttöalue on koko metodi.
ulko
n käyttöalue on koko metodi sen määrittelystä alkaen.
keski
toimii vain silmukan sisällä.
sisa
-muuttuja on käytettävissä vain
if
-käskyn sisällä.
Entä joku
? Niitä on kaksi, mikä on sallittua sisäkkäisissä rakenteissa:
Sisempi joku
-muuttuja on käytettävissä
silmukan sisällä sen määrittelykäskyn jälkeen.
Ulompi joku
on periaatteessa käytettävissä
koko metodissa sen määrittelystä alkaen. Mutta:
sisempi joku
peittää (shadows) ulomman
samannimisen muuttujan ja käytännössä poistaa
for
-silmukan sen käyttöalueesta. Niinpä...
... osa joku
-nimistä viittaa sisempään muuttujaan...
... ja yksi ulompaan.
Esimerkistä on pääteltävissä yleinen periaate: käyttöalue rajoittuu sisimpään lohkoon
(block), jossa muuttuja on määritelty. Epävirallisesti voimme sanoa, että lohko
tarkoittaa ohjelman osaa, jossa on sisällä käskyjä, esimerkiksi for
-silmukan runkoa
tai if
-käskyn haaraa. Lohkot voivat olla sisäkkäisiä; sisennykset auttavat hahmottamaan
lohkorakenteen.
Käyttöalueen valitseminen muuttujalle
Nyrkkisääntö: Valitse mahdollisimman pieni toimiva käyttöalue.
Jos et keksi hyvää perustelua tehdä muuttujasta ilmentymämuuttujaa, tee siitä paikallinen muuttuja. Sijoita paikalliset muuttujat sisimpään mahdolliseen lohkoon.
Käyttöalueen rajaaminen usein parantaa ohjelman luettavuutta ja muokattavuutta. Kun käyttöalue on kapea, on selvää, missä osissa koodia muuttuja on merkityksellinen eikä synny turhia riippuvuuksia koodin eri osien välillä. Pieni käyttöalue joskus myös vähentää turhaa muistinkäyttöä ja voi kasvattaa suoritusnopeuttakin.
Liiallinen ilmentymämuuttujien käyttö on tyypillinen aloittelevan olio-ohjelmoijan virhe.
Tutkitaan nyt siksi käyttöalueiden näkökulmasta vielä yhtä esimerkkiä, luvusta 3.5
tuttua VendingMachine
-limuautomaattiluokkaa. Siinä valinnat ilmentymä- ja paikallisten
muuttujien välillä on tehty järkevästi:
class VendingMachine(var bottlePrice: Int, private var bottleCount: Int):
private var earnedCash = 0
private var insertedCash = 0
def sellBottle() =
if this.isSoldOut || !this.enoughMoneyInserted then
None
else
this.earnedCash = this.earnedCash + this.bottlePrice
this.bottleCount = this.bottleCount - 1
val changeGiven = this.insertedCash - this.bottlePrice
this.insertedCash = 0
Some(changeGiven)
def addBottles(newBottles: Int) =
this.bottleCount = this.bottleCount + newBottles
Limuautomaattiin päätyneen rahan määrä on automaatin tilaa kuvaava arvo, automaattiolion ominaisuus. Tästä arvosta tulee pitää kirjaa silloinkin, kun ei olla suorittamassa mitään automaattiluokan metodia. Näinpä se on syytä määritellä ilmentymämuuttujiksi. Sama pätee muihinkin tämän luokan ilmentymämuuttujiin.
Myyntimetodissa tarvitaan väliaikaisesti muuttujaa vaihtorahasumman tallentamiseen ennen sen palauttamista. Kyseessä on yksittäistä metodia suoritettaessa käytetty tilapäissäilö, välitulos, joka ei kuvaa mitään pysyvämpää limuautomaatin tilasta eikä ole muiden metodien kannalta kiinnostava. Niinpä on järkevää käyttää paikallista muuttujaa.
Metodien parametriarvot ovat usein (kuten tässäkin) kiinnostavia vain paikallisesti.
Määrittele siis ilmentymämuuttujiksi vain muuttujat, joille pätee ainakin jokin seuraavista.
Muuttuja selvästi kuvaa oliolle kuuluvaa tietoa (henkilön nimi, opiskelijan kurssit).
Muuttuja tallentaa arvon, jonka täytyy pysyä tallessa silloinkin, kun ei olla suorittamassa mitään luokassa määriteltyä metodia.
Muuttuja liittyy johonkin resurssioptimointiin (jollaisia emme tavoittele tällä kurssilla).
Yhteenvetoa
for
-silmukalla voi käydä vektorien ja puskurien alkioiden lisäksi läpi myös esimerkiksi lukuja ja merkkijonojen merkkejä.Silmukat voivat olla sisäkkäin, jolloin sisempi silmukka tulee suoritetuksi jokaisella ulomman silmukan "kierroksella".
Mm. muuttujilla ja metodeilla on käyttöalue eli rajattu osa ohjelmasta, josta käsin niitä voi käyttää.
Käyttöalue kannattaa yleensä valita mahdollisimman pieneksi.
Paikallisten muuttujien käyttöalueeseen vaikuttaa metodin lohkorakenne.
Lukuun liittyviä termejä sanastosivulla: silmukka,
for
-silmukka, iteraatio; kokoelma, merkkijono; käyttöalue, lohko; yksisuuntainen lippu; debuggeri.
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!
Materiaalin luvut tehtävineen ja viikkokoosteineen on laatinut Juha Sorva.
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, Kaisa Ek, Joonatan Honkamaa, Antti Immonen, Jaakko Kantojärvi, Onni Komulainen, Niklas Kröger, Kalle Laitinen, Teemu Lehtinen, Mikael Lenander, Ilona Ma, Jaakko Nakaza, Strasdosky Otewa, Timi Seppälä, Teemu Sirkiä, Joel Toppinen, Anna Valldeoriola Cardó ja Aleksi Vartiainen.
Lukujen alkuja koristavat kuvat ja muut vastaavat kuvituskuvat on piirtänyt Christina Lassheikki.
Yksityiskohtaiset animaatiot Scala-ohjelmien suorituksen vaiheista suunnittelivat Juha Sorva ja Teemu Sirkiä. Teemu Sirkiä ja Riku Autio toteuttivat ne apunaan Teemun aiemmin rakentamat työkalut Jsvee ja Kelmu.
Muut diagrammit ja materiaaliin upotetut vuorovaikutteiset esitykset laati Juha Sorva.
O1Library-ohjelmakirjaston ovat kehittäneet Aleksi Lukkarinen, Juha Sorva ja Jaakko Nakaza. Useat sen keskeisistä osista tukeutuvat Aleksin SMCL-kirjastoon.
Tapa, jolla 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+ luotiin alun perin Aallon LeTech-tutkimusryhmässä pitkälti opiskelijavoimin. Nykyään tätä avoimen lähdekoodin projektia kehittää Tietotekniikan laitoksen opetusteknologiatiimi ja tarjoaa palveluna laitoksen IT-tuki; sitä ovat kehittäneet kymmenet Aallon opiskelijat ja muut.
A+ Courses -lisäosa, joka tukee A+:aa ja O1-kurssia IntelliJ-ohjelmointiympäristössä, on toinen avoin projekti. Sen suunnitteluun ja toteutukseen on osallistunut useita opiskelijoita yhteistyössä O1-kurssin opettajien kanssa.
Kurssin tämänhetkinen henkilökunta löytyy luvusta 1.1.
Joidenkin lukujen lopuissa on lukukohtaisia lisäyksiä tähän tekijäluetteloon.
Merkkijonokin on kokoelma merkkejä — siis
Char
-tyyppisiä arvoja (luku 5.2) — ja sitäkin voi käyttää tässä nuolen oikealla puolella.