Kurssin viimeisimmän version löydät täältä: O1: 2024
Luku 5.6: Silmukoita, merkkijonoja ja vaalit
Tästä sivusta:
Pääkysymyksiä: Miten käytän itse silmukoita kirjoittaessani
luokkaa? Saisiko lisäesimerkkejä for
-silmukoista? Mitä jos
laittaa silmukoita sisäkkäin? Jospa FlappyBugissa olisi useita
esteitä?
Mitä käsitellään? Käytännön harjoitusta edellisen luvun
aiheesta. Lisäksi: Merkkijonojen ja Range
-olioiden läpikäyminen.
Sisäkkäiset for
-silmukat.
Mitä tehdään? Lähinnä ohjelmoidaan.
Suuntaa antava työläysarvio:? Neljä tai viisi tuntia.
Pistearvo: A225.
Johdanto
Tähänastisissa esimerkeissä for
-silmukoilla on käyty läpi vain vektoreita ja puskureita.
Kuitenkin kuten luvussa 5.5 mainittiin, voi for
-silmukalla käydä läpi hyvinkin
erilaisia alkiokokoelmia. Vaikkapa merkkijonoja:
Merkkijonon läpikäyminen
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") {
println("Merkki: " + merkki)
}
merkki
on siis tyyppiä Char
.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") {
println("Indeksillä " + indeksi + " on " + merkki + ", Unicodessa merkki #" + merkki.toInt + ".")
indeksi += 1
}
Char
-luokassa on määritelty toInt
-metodi, joka palauttaa
kyseisen merkin Unicode-arvon kokonaislukuna.Äskeinen ohjelma löytyy myös ForLoops-oheisprojektista 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 Eclipseen sisältyy debuggeri.
Tässä kurssimateriaalissa on erillinen sivu, joka esittelee debuggerin käyttöä.
Suosittelemme, että tutustut debuggerimateriaaliin ennen kuin jatkat tässä luvussa. Voit sitten käydä tämän luvun ohjelmia läpi debuggerilla rivi riviltä. Se voi auttaa hahmottamaan ohjelmien 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
Projektin ForLoops pakkauksesta o1.looptest
löytyy yksittäisolio Task2
. Olion
toivottu toiminta on kuvattu sen ohjelmakoodin kommentissa. Täydennä olio toiveiden
mukaiseksi ja palauta oheisella lomakkeella.
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 siinä olevat emäkset järjestyksessä, esimerkiksi GATCACAGGT jne. 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).
Nouda Task3
. Voit kokeilla 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ä projektiin 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) {
if (base == 'G' || base == 'C') {
gcCount += 1
} else if (base == 'A' || base == 'T') {
totalCount += 1
}
}
100.0 * gcCount / totalCount
}
Char
-olioillekin on literaalimerkintä vastaavasti kuin
luvuille, totuusarvoille ja merkkijonoille. Huomaa, että 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) {
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) {
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) {
val merkki = merkkijono(indeksi)
println("Indeksillä " + indeksi + " on " + merkki + ", Unicodessa merkki #" + merkki.toInt + ".")
}
to
-metodia, on parametriarvoksi muistettava antaa
vähennyslaskun merkkijono.length - 1
tulos, jotta nollasta
alkava indeksi pysyy rajoissa.Pieniä ohjelmointitehtäviä: to
, until
yms.
Projektin ForLoops pakkauksesta o1.looptest
löytyy lisää yksittäisolioita, jotka on
tarkoitus muokata: katso nyt tiedostot Task4.scala
, Task5.scala
, Task6.scala
ja
Task7.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.
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) { 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
Election-tehtävä
Tehtävänanto
Tehtäväsi on:
- noutaa projekti Election,
- tutustua projektin 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.
- Useassa
- Käytä annettua käynnistysoliota
ElectionTest
, joka kutsuuDistrict
-luokan metodeita. Voit muokata sitä testataksesi erilaisia tapauksia.- Ellei sinulla ole jonkinlaista toteutusta
kullekin
District
-luokan metodille, eiElectionTest
-ohjelmaa voi annetussa muodossa ajaa. - Voit kuitenkin 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).
- Ellei sinulla ole jonkinlaista toteutusta
kullekin
- 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 9.2). Nyt tässä luvussa kuuluu toteuttaa kaikki muut puuttuvat metodit paitsi nämä erikseen merkityt. - Tutustu projektin sisältämään ohjelmakoodiin.
- Ryhdy toteuttamaan
District
-luokkaa seuraavissa vaiheissa. KäytäElectionTest
-käynnistysoliota sitä mukaa kuin luotDistrict
-luokkaan uusia metodeita. - Määrittele luokkaan
District
konstruktoriparametrit ja ilmentymämuuttujat. - Luo
toString
-metodi. - Luo
printCandidates
-metodi.- Voit ottaa mallia vaikkapa
AuctionHouse
-luokannextDay
-metodista.
- Voit ottaa mallia vaikkapa
- Luo
candidatesFrom
-metodi.- Voit ottaa mallia
AuctionHouse
-luokanpurchasesOf
-metodista. - Huomaa, että palautusarvon tyypiksi
on määrätty
Vector[Candidate]
. Voit käyttää puskuria palautettavan kokoelman koostamiseen (kuten AuctionHouse1-projektissa), mutta siirrä puskurin sisältö lopuksi vektoriintoVector
-metodilla.
- Voit ottaa mallia
- Luo
topCandidate
-metodi.- Voit ottaa mallia
AuctionHouse
-luokanpriciest
-metodista. - Osaatko käyttää vektorin
head
- jatail
-metodeita niin, ettei metodissa turhaan vertailla ensimmäistä alkiota itseensä? (Ei välttämätöntä.)
- Voit ottaa mallia
- Luo
totalVotes
-nimiset metodit.- Voit ottaa mallia
AuctionHouse
-luokantotalPrice
- janumberOfOpenItems
-metodeista.
- Voit ottaa mallia
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 käyttää
apumetodia, johon kirjataan se osa algoritmista, joka on näille
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 se on
tarkoitettu vain District
-tyypin sisäiseen käyttöön.
Osaatko toteuttaa apumetodin countVotes
ja toteuttaa
totalVotes
-metodit siten, että ne kutsuvat countVotes
-metodia
antaen sille asianmukaisen parametriarvon?
Ja vielä...
AuctionHouse
-luokan metodeissa ja luultavasti myös useassa District
-luokkaan
laatimassasi metodissa toistuu täsmälleen 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) { ... }
- Lopuksi palautetaan tulosmuuttujan arvo, joka määritettiin silmukan aikana.
Tämän kaavan johdosta 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 6.4.
Palauttaminen
A+ esittää tässä kohdassa tehtävän palautuslomakkeen.
Pikkutehtävä: kuvan muodostaminen silmukalla
FlappyBug-tehtävä (osa 16/17: enemmän esteitä)
Tehtävänanto
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.
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 siten, että se liikuttaa kutakin vektoriin tallennetuista esteistä.- Vinkki: käytä
for
-silmukkaa. Varsin yksinkertainen silmukka riittää.
- Vinkki: käytä
- Kuolinehdon päivitys: muuta
Game
-luokanisLost
-metodia siten, että se palauttaatrue
, jos ötökkä ei ole pelialueella tai jos mikä tahansa esteistä koskettaa ötökkää.- Vinkki: tässä kohdassa on vähän enemmän
tekemistä (toistaiseksi opituilla
konsteilla) kuin edellisessä. Yksi
mahdollisuus on käyttää
for
-silmukan apunaBoolean
-tyyppistävar
-muuttujaa: pidä kirjaa siitä, onko esteitä läpikäyvän silmukan suorituksen aikana löytynyt sellainen este, joka koskettaa ötökkää. Määrittele muuttuja metodin paikalliseksi muuttujaksi ennenfor
silmukan alkua.
- Vinkki: tässä kohdassa on vähän enemmän
tekemistä (toistaiseksi opituilla
konsteilla) kuin edellisessä. Yksi
mahdollisuus on käyttää
- Kukin esteistä näkyviin: muuta käyttöliittymän
makePic
-metodia siten, 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: voit käyttää
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ä.)
- Vinkki: voit käyttää
Palauttaminen
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 koko estevektorin läpi.
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 6.4 ja 7.1 työkalupakkimme painavoituessa.
Sisäkkäiset silmukat
Silmukoita voi laittaa 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ä: Task8.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) {
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
}
}
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ä.leveinKohta
ja yksityisen
metodin kuten leveys
tai rivi
käyttöalue on koko kyseinen luokka
(mahdollisine kumppaniolioineen; luku 5.3).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) {
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
}
}
Kun siirrät hiiren kursorin seuraavien laatikoiden päälle, korostuvat mainittujen muuttujien käyttöalueet.
merkki
tai montako
on määritelty koko
kyseisen metodin ohjelmakoodissa. Sitä voi käyttää sieltä mistä vain.tyhjaaVasemmalle
, on käytettävissä määrittelykohdasta alkaen
metodin koodin loppuun.kokoRivi
kohdalla.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) {
println("Moi")
val joku = 10
ulko += keski * 2 + joku
if (keski < ulko + parametri) {
val sisa = readLine("Anna luku: ").toInt
ulko += sisa + joku
}
}
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:
joku
-muuttuja on käytettävissä
silmukan sisällä sen määrittelykäskyn jälkeen.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ä...joku
-nimistä viittaa sisempään muuttujaan...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
-silmukkaa
tai if
-käskyn haaraa. Lohkot voivat olla sisäkkäisiä; tyylikkäästi kirjoitetussa
koodissa 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. Paikalliset muuttujat kannattaa sijoittaa 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) {
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
}
// ...
}
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 (jollaisiin emme erityisesti tähtää tällä kurssilla).
Yhteenvetoa
for
-silmukalla voi käydä vektorien ja puskurien alkioiden lisäksi läpi myös esimerkiksi lukuja ja merkkijonojen merkkejä.- Silmukoita voi laittaa 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!
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.
Joidenkin lukujen lopuissa on lukukohtaisia lisäyksiä tähän tekijäluetteloon.
Char
-tyyppisiä arvoja (luku 5.2) — ja sitäkin voi käyttää tässä nuolen oikealla puolella.