Tämä kurssi on jo päättynyt.

Kurssin viimeisimmän version löydät täältä: O1: 2024

Luku 3.3: Valintoja

Tästä sivusta:

Pääkysymyksiä: Miten saan ohjelman valitsemaan eri vaihtoehtojen välillä? Miten saan suoritettua käskyn vain, jos tietty ehto on voimassa?

Mitä käsitellään? Valintakäsky: if ja else. Valintakäsky lausekkeena; tilaan vaikuttaminen valintakäskyllä. Valintakäskyjen yhdistely peräkkäin ja sisäkkäin.

Mitä tehdään? Luetaan ja tehdään pieniä tehtäviä.

Suuntaa antava työläysarvio:? Tunti.

Pistearvo: A75.

Oheisprojektit: GoodStuff, FlappyBug, Odds. Vapaaehtoisessa tehtävässä esiintyy myös projekti Miscellaneous (uusi).

../_images/person05.png

Valitsemisen tarve

Mitä erilaisimmissa ohjelmissa on tarpeen tehdä valintoja. Halutaan esimerkiksi valita uudeksi suosikkikokemukseksi kahdesta kokemuksesta (lisätty ja vanha suosikki) parempi. Tai halutaan suorittaa toiminto vain, jos käyttäjä vastaa "kyllä" eikä "ei". Ja niin edelleen.

On siis voitava muodostaa ohjelmaan ehtoja: vastasiko käyttäjä "kyllä"; onko kokemus parempi kuin toinen? Ehtojen muodostaminen meiltä jo onnistuukin totuusarvoja käyttäen.

Lisäksi tarvitsemme tavan, jolla voimme määrätä tietyt käskyt suoritettaviksi vain, jos ehto on voimassa. Kätevää olisi sekin, että samalla voisimme määrätä vaihtoehtoiset käskyt suoritettaviksi siinä tapauksessa, että ehto ei toteudu.

Scala tarjoaa useita eri tapoja käskyjen valinnaiseen suorittamiseen. Tässä luvussa niistä käsitellään suoraviivaisinta eli if-valintakäskyä.

if-käsky

Sanoja if ja else käyttäen voi muodostaa lausekkeita, joiden arvo riippuu siitä, toteutuuko tietty ehto evaluointihetkellä eli onko tietyn Boolean-tyyppisen lausekkeen arvo true vai ei. Perusidea on pseudokoodina tämä:

if (ehto) arvo ehdon toteutuessa else arvo muutoin

Käskyn toimintaa on helppo tutkia REPLissä:

if (10 > 100) "on isompi" else "ei ole isompi"res0: String = ei ole isompi
if (100 > 10) "on isompi" else "ei ole isompi"res1: String = on isompi
Huomaa avainsanat if ja else. Huomaa myös ehtolauseketta ympäröivät sulut, jotka ovat pakolliset.
Ehtolausekkeen on oltava tyyppiä Boolean, jolloin sen arvoksi saadaan evaluoitaessa joko true tai false. Tässä ehtolauseke on muodostettu vertailuoperaattorilla. Kun if-käsky suoritetaan, evaluoidaan aluksi tämä ehtolauseke kertaalleen ja sen perusteella määrittyy, mitä seuraavaksi tapahtuu.
Jos ehtolausekkeen arvo on false, suoritetaan else-sanan jälkeinen ohjelmakoodi eli ns. "else-haara". Koko if-lausekkeen arvoksi saadaan else-haaran arvo.
Jos ehtolausekkeen arvo on true, suoritetaan heti ehtolausekkeen perässä oleva ohjelmakoodi eli ns. "then-haara". Koko if-lausekkeen arvoksi saadaan then-haaran arvo. (Mitään "then"-sanaahan ei Scala-koodiin kirjoiteta, mutta if-käsky voidaan englanniksi lukea if X then A else B.)

Esimerkkejä

Ehtolausekkeena voi olla vertailun sijaan mikä vain Boolean-tyyppinen lauseke, vaikkapa Boolean-muuttujan nimi:

val maapalloOnLittea = falsemaapalloOnLittea: Boolean = false
if (maapalloOnLittea) "Niin varmaan" else "Ihanko tosi?"res2: String = Ihanko tosi?

Tai totuusarvoliteraali true tai false (joskaan tästä ei ole liiemmin hyötyä):

if (true) "Tämä valittiin" else "Tätä ei"res3: String = Tämä valittiin

Äskeiset esimerkit valitsivat kahden merkkijonolausekkeen välillä, mutta then- ja else-haaroina voi toki käyttää myös vaikkapa lukuarvoisia lausekkeita:

val luku = 100luku: Int = 100
if (luku > 0) luku * 2 else 0res4: Int = 200
if (luku < 0) luku * 2 else 0res5: Int = 0
Koko if-lausekkeen arvon tyyppi määräytyy sen mukaan, mitä valinnaisiin haaroihin kirjoitetaan.

if-käsky lausekkeena

Valintakäskyä voi käyttää lausekkeena missä tahansa yhteydessä, johon sen tietotyyppi sopii, vaikkapa parametrilausekkeessa toisen käskyn osana, muuttujaan sijoitettaessa tai aritmeettisen laskutoimituksen osana. Esimerkiksi seuraavat käskyt ovat mahdollisia:

println(if (luku > 100) 10 else 20)20
val valinnanTulos = if (luku > 100) 10 else 20valinnanTulos: Int = 20
(if (luku > 100) 10 else 20) * (if (luku <= 100) -luku else luku) + 1res6: Int = -1999

Viimeinen annetuista käskyistä on tosin jo sen verran haarakas, että mieluummin kannattaa ottaa if-lausekkeiden arvot tilapäissäilöön muuttujiin ja vasta sitten kertoa ne keskenään.

Pikkutehtäviä

Suoritetaan nämä käskyt REPLissä:

val suosikki = "laama"
val elain = "kissa"
val kommentti = if (elain == suosikki) "Jee" else "Jaa"

Mikä on nyt viimeksi määritellyn muuttujan arvo?

Oletetaan edelliset käskyt suoritetuiksi ja suoritetaan nyt tämä:

println(if (kommentti == "Jee") "positiivinen" else "neutraali" + " reaktio")

Kumpi seuraavista pitää paikkansa?

Jatketaan suorittamalla seuraava käsky:

println(if (kommentti == "Jaa") "neutraali" else "positiivinen" + " reaktio")

Kumpi seuraavista pitää paikkansa?

Jatketaan suorittamalla seuraava käsky:

println((if (kommentti == "Jaa") "neutraali" else "positiivinen") + " reaktio")

Kumpi seuraavista pitää paikkansa?

Jatketaan suorittamalla seuraava käsky:

println("Se oli " + if (kommentti == "Jaa") "neutraali" else "positiivinen" + " reaktio.")

Mikä seuraavista pitää paikkansa?

Jatketaan suorittamalla seuraava käsky:

println("Se oli " + (if (kommentti == "Jaa") "neutraali" else "positiivinen") + " reaktio.")

Kumpi seuraavista pitää paikkansa?

if-käskyn rivittämisestä ja sisentämisestä

Mainittakoon tässä välissä, että jos valintakäskystä muodostuisi muuten pitkä ja hankalalukuinen, niin sen voi jakaa usealle riville esimerkiksi näin:

val pidempiKommentti =
  if (elain == suosikki)
    "Mahtavaa! Se on " + elain + ", joka on oma suosikkini."
  else
    "Ihan kiva eläinhän se on " + elain + "kin."
Huomaa, että "then-haara" ja "else-haara" on monirivisessä if-käskyssä tapana sisentää syvemmälle kuin if- ja else-alkuiset rivit. Kun tähän tapaan tottuu, niin if-käskyn kokonaisuus on näin nopeampi hahmottaa. Tapaa on syytä noudattaa. Sinänsä näilläkään sisennyksillä ei ole Scala-ohjelman toimivuuteen vaikutusta.

Minitehtävä: kuvan kuvailu, osa 1/2

Kirjoita Miscellaneous-projektin misc.scala-tiedostoon vaikutukseton funktio describe, joka:

  • ottaa yhden, Pic-tyyppisen parametrin, ja
  • palauttaa merkkijonon "portrait", jos kuvan korkeus on leveyttä suurempi ja merkkijonon "landscape" muutoin (eli jos kuva on neliön muotoinen tai leveämpi kuin korkea).

A+ esittää tässä kohdassa tehtävän palautuslomakkeen.

Experience-luokka valmiiksi

Voimme käyttää if-valintakäskyä luokan ohjelmakoodissa. Näin mahdollistamme, että oliot voivat tehdä valintoja niiden metodeita kutsuttaessa.

Valitseva chooseBetter-metodi

Meillä on jo luvusta 3.2 osittainen toteutus Experience-luokalle. Luokasta jäi tällöin toteuttamatta chooseBetter-metodi, joka vertailee kahta kokemusta ja palauttaa niistä arvosanaltaan paremman. Kunhan saamme metodin toteutettua, sen pitäisi toimia tähän tapaan:

val wine1 = new Experience("Il Barco 2001", "ookoo", 6.69, 5)wine1: o1.goodstuff.Experience = o1.goodstuff.Experience@1b101ae
val wine2 = new Experience("Tollo Rosso", "turhahko", 6.19, 3)wine2: o1.goodstuff.Experience = o1.goodstuff.Experience@233b80
val betterOfTheTwo = wine1.chooseBetter(wine2)betterOfTheTwo: o1.goodstuff.Experience = o1.goodstuff.Experience@1b101ae
betterOfTheTwo.nameres7: String = Il Barco 2001

chooseBetter-metodin ajatus on siis samankaltainen kuin max-funktion (luku 1.6), joka palauttaa kahdesta vertailemastaan luvusta suuremman.

Tässä ensin Experience-toteutus, jossa chooseBetter on pseudokoodina:

class Experience(val name: String, val description: String, val price: Double, val rating: Int) {

  def valueForMoney = this.rating / this.price

  def isBetterThan(another: Experience) = this.rating > another.rating

  def chooseBetter(another: Experience) = {
    Selvitä, onko tämä kokemus arvosanaltaan parempi kuin another-kokemus.
    Jos oli, palauta viittaus this-olioon, muuten palauta another-muuttujan sisältämä viittaus.
  }

}

Ja sitten metodin koodi Scalalla:

def chooseBetter(another: Experience) = {
  val thisIsBetter = this.rating > another.rating
  if (thisIsBetter) this else another
}
this-sana yksinään muodostaa lausekkeen, jonka arvo on viittaus metodia suorittavaan olioon itseensä.
chooseBetterin suoritus päättyy if-lausekkeeseen; metodi palauttaa tämän if-lausekkeen arvon. Se on viittaus metodia suorittavaan olioon, jos muuttujan thisIsBetter arvo on true, tai parametriksi saatu viittaus, jos thisIsBetter on false.

Parempi ratkaisu omaa metodia kutsumalla

Äskeinen toteutus toimii. Voisimme kuitenkin haluta poistaa Experience-luokasta tarpeettoman toiston: nythän metodeissa isBetterThan ja chooseBetter on täsmälleen sama vertailuoperaatio this.rating > another.rating. Kokemusten paremmuuden määrittely on siis tehty kahteen kertaan eri paikoissa.

Tämä ei ole kovin eleganttia ja heikentää ohjelman muokattavuutta. Jos esimerkiksi haluaisimme muuttaa ohjelmaa niin, että vertailu tapahtuukin hinta–laatu-suhteen eikä arvosanan perusteella, niin pitäisi muuttaa kahta kohtaa. Tämä ei ole tässä pienessä ohjelmassa kauheaa, mutta isommissa ohjelmissa toisteisuudesta seuraa pian ylläpidettävyysongelmia.

Koetetaan tehdä vähän paremmin.

Olet jo useasti nähnyt, että olio voi "lähettää itselleen viestin" eli kutsua omaa metodiaan: this.metodi(parametrit). Niinpä voimme muotoilla uuden version chooseBetter-metodista:

def chooseBetter(another: Experience) = {
  val thisIsBetter = this.isBetterThan(another)
  if (thisIsBetter) this else another
}
Experience-olio ikään kuin kysyy itseltään: "Oletko parempi kuin tämä toinen kokemus?"

Nyt chooseBetter käyttää juuri sitä tapaa vertailla paremmuutta, joka on määritelty isBetterThan-metodissa, eikä toistoa ole.

Tiiviimpi ratkaisu: metodikutsu ehtolausekkeena

Varustaudutaan vielä tiedolla, että if-käskyssä voi käyttää myös metodikutsua ehtolausekkeena, kunhan vain metodin palautusarvo on tyyppiä Boolean. Tämä tulee ilmi seuraavasta REPL-esimerkistä (jossa oletetaan wine1 ja wine2 määritellyiksi kuin edellä):

if (wine1.isBetterThan(wine2)) "oli parempi" else "ei ollut parempi"res8: String = "oli parempi"
if (wine2.isBetterThan(wine1)) "oli parempi" else "ei ollut parempi"res9: String = "ei ollut parempi"
if (wine1.isBetterThan(wine1)) "oli parempi" else "ei ollut parempi"res10: String = "ei ollut parempi"

Ehtolausekkeen evaluointi tarkoittaa tässä tapauksessa metodin kutsumista; lausekkeen arvoksi saadaan metodin palauttama totuusarvo. Tämän jälkeen jatketaan palautusarvon mukaiseen haaraan.

Tältä pohjalta voimme lyhentää chooseBetter-toteutustamme hieman. Paikallista muuttujaa thisIsBetter ei välttämättä tarvita:

def chooseBetter(another: Experience) = if (this.isBetterThan(another)) this else another

Experience-luokkamme on valmis. GoodStuff-projektin toiseen keskeiseen luokkaan Category tartumme myöhemmin luvussa 4.1.

Odds-tehtävä (osa 7/9)

Palataan taas hetkeksi edellisistä luvuista tuttuun Odds-hankkeeseemme ja lisätään sinne metodi, jolla Odds-olion kuvaama tieto voidaan esittää ns. moneyline-muodossa, jota erityisesti amerikkalaiset vedonlyöntitoimistot käyttävät. Tämä hieman kummallinen esitysmuoto toimii seuraavasti:

(Alla P ja Q viittaavat lukuihin, joista todennäköisyyden fractional-muotoinen esitys P/Q muodostuu.)

Jos tapahtuman on arvioitu tapahtuvan korkeintaan 50 %:n todennäköisyydellä, sen moneyline-luku on positiivinen ja saadaan laskemalla 100 * P / Q. Esimerkiksi tapausta 7/2 vastaa moneyline-luku 350, koska 100 * 7 / 2 = 350. Tämän positiivisen luvun voi tulkita siten, että panostamalla 100 rahaa vedonlyöjä voi tehdä 350 rahaa voittoa sen lisäksi, että saa panoksensa takaisin. Fifty–fifty-tilannetta 1/1 vastaa moneyline-luku 100.

Jos taas todennäköisyydeksi on arvioitu yli 50 %, moneyline-luku on negatiivinen ja saadaan laskemalla -100 * Q / P. Esimerkiksi tapausta 1/5 vastaa moneyline-luku -500, koska -100 * 5 / 1 = -500. Tämän negatiivisen luvun voi tulkita siten, että tehdäkseen 100 rahaa voittoa vedonlyöjän on panostettava 500 rahaa.

Tehtävänanto

Laadi Odds-luokkaan metodi moneyline, joka palauttaa Odds-oliota vastaavan moneyline-luvun Int-arvona:

val norwayWin = new Odds(5, 2) norwayWin: Odds = o1.odds.Odds@171c36b
norwayWin.moneylineres11: Int = 250

Lisää myös käynnistysolioon OddsTest1 rivi, joka tulostaa moneyline-luvun. Ohjelman tulosteen on nyt näytettävä tällaiselta:

Please enter the odds of an event as two integers on separate lines.
For instance, to enter the odds 5/1 (one in six chance of happening), write 5 and 1 on separate lines.
11
13
The odds you entered are:
In fractional format: 11/13
In decimal format: 1.8461538461538463
In moneyline format: -118
Event probability: 0.5416666666666666
Reverse odds: 13/11
Odds of happening twice: 407/169
Please enter the size of a bet:
200
If successful, the bettor would claim 369.2307692307692
Please enter the odds of a second event as two integers on separate lines.
10
1
The odds of both events happening are: 251/13
The odds of one or both happening are: 110/154
Yksi pieni lisäys riittää.

Ohjeita ja vinkkejä

  • Käytä if-lauseketta.
  • moneyline-metodin on tässä palautettava kokonaisluku. Jätä desimaaliosa kokonaan pois eli pyöristä aina kohti nollaa. Kokonaislukujen jakolaskuhan tekee tämän puolestasi (luku 1.3), joten helpoimmalla pääset, kun vain teet laskutoimitukset sopivassa järjestyksessä: kerro ensin, jaa sitten.
  • Se on yhdyssana, joten moneyline eikä moneyLine.

Palauttaminen

A+ esittää tässä kohdassa tehtävän palautuslomakkeen.

Apokalyptistä ohjelmointia

../_images/hazel_grouse.jpg

Selvä pyy.

Suomalaisessa kansanperinteessä Jumala rankaisi pyytä tuomiten sen vähitellen pienenemään maailman loppuun saakka. Lopun tiedetään olevan liki, kun pyy on häviävän pieni. Tätä perua on sanonta "pienenee kuin pyy maailmanlopun edellä".

Simuloidaan tilanne. Tässä on pyiden mallintamiseen sopiva luokka:

class Pyy {

  private var koko = 400
  private val peruskuva = Pic("bird.png")

  def loppuKoitti = this.koko <= 0

  def pienene() = {
    if (this.koko > 0) {
      this.koko = this.koko - 1
    }
  }

  def kuvaksi = this.peruskuva.scaleTo(this.koko)

}

Löydät tuon luokan ja sitä käyttävän käyttöliittymän Pikkusovelluksia-projektin pakkauksesta o1.pyy. Käyttöliittymä käyttää luvussa 2.8 opittuja konsteja pienentääkseen valkoista taustaa vasten näkyvää pyytä kunnes se katoaa näkyvistä.

Tehtäväsi on tutustua annettuun koodiin ja muokata käyttöliittymän makePic-metodia siten, että se vaihtaa koko kuvan mustaksi lopun koittaessa. Metodin pitää siis palauttaa:

  • linnun kuva valkoista taustaa vasten (kuten annetussa koodissa) vain silloin, jos pyyn loppuKoitti-metodi palauttaa false; ja
  • täysmusta maailmanlopunKuva, jos tuo metodi palauttaa true.

Käytännössä sinun täytyy vain lisätä metodiin yksi if-lauseke.

A+ esittää tässä kohdassa tehtävän palautuslomakkeen.

Tilaan vaikuttaminen if-käskyssä

if-käskyn haarat voivat sisältää myös käskyjä, joilla on tilaa muuttavia vaikutuksia (luku 1.6) kuten tulostamista, muuttujien arvon vaihtamista yms. Tässä esimerkki:

if (luku > 0) {
  println("Luku on positiivinen.")
  println("Tarkemmin sanoen se on: " + luku)
} else {
  println("Kyseessä ei ole positiivinen luku.")
}
println("Olen puhunut.")
Aaltosulkeiden sisään voi laittaa useitakin käskyjä, jotka suoritetaan peräjälkeen mikäli ehtolausekkeen arvo on sopiva.
Aaltosulutetuissa tapauksissakin on tapana sisentää sulkeiden väliset rivit.
Esimerkin viimeinen tulostuskäsky ei kuulu if-käskyyn vaan on sen perässä. Tuo tulostuskäsky suoritetaan siis ehtolausekkeen arvosta riippumatta, kunhan ensin on suoritettu jompikumpi yllä olevista "haaroista".

Seuraavat animaatiot kuvaavat esimerkkikoodin toiminnan ensin positiivisella ja sitten negatiivisella lukuarvolla.

Huomaa: Aiemmin tässä luvussa käytimme if-käskyä lausekkeiden eli "ilmaisujen, joilla on arvo" muodostamiseen. Valitsimme if-käskyllä kahden arvon välillä. Äskeisessä animoidussa koodissa puolestaan käytettiin if-käskyä valitsemaan tiettyjen tilaa muuttavien vaikutusten välillä. Tässä vielä pseudokoodi, joka kuvaa tuon yleisen ajatuksen:

if (ehto) {
  käskyjä, jotka suoritetaan, jos ehtolausekkeen arvo oli tosi
} else {
  käskyjä, jotka suoritetaan, jos ehtolausekkeen arvo oli epätosi
}

Kun if-käskyä käytetään tilan muuttamiseen, käytetään yleensä rivinvaihtoja, sisennyksiä ja aaltosulkuja kuten äskeisissä esimerkeissä. Näin teemme tälläkin kurssilla, kuten kurssin tyyliopaskin kertoo.

Jotkut Scala-ohjelmoijat kylläkin...

... jättävät aaltosulut pois myös vaikutuksellisista if-käskyistä, jos "haaroissa" on vain yksi käsky, mutta me emme tee niin.

Unit if-käskyn arvona

Vaikka seuraava sijoituskäsky ei olekaan kovin fiksu, on sitä hyvä pohtia hetki:

val turhaTulos =
  if (luku > 1000) {
    println("Aika iso")
  } else {
    println("Ei niin iso")
  }Ei niin iso
turhaTulos: Unit = ()

Tässä sijoituskäskyn osana käytetyllä if-käskyllä ei ole merkityksellistä arvoa. Koodi kyllä tulostaa lukumuuttujan arvon perusteella jommankumman tekstin, mutta se ei sijoita muuttujaan turhaTulos mitään järkevää (vaan vain sisällöttömän Unit-arvon; luku 1.6). Näin tapahtuu siksi, että if-käskyn haarat päättyvät tulostuskäskyyn, joka ei palauta merkityksellistä arvoa. Tällaista if-käskyä on siis turha käyttää muuttujasijoituksessa.

Voi olla sivistävää vielä verrata äskeistä koodia näihin:

val tulos = if (luku > 1000) "Aika iso" else "Ei niin iso"tulos: String = Ei niin iso
println(tulos)Ei niin iso
println(if (luku > 1000) "Aika iso" else "Ei niin iso")Ei niin iso

if ilman elseä

Kun käytämme if-käskyä ohjelman tilaan vaikuttamiseen, on yleistä, että else-haaraa ei edes tarvita. Usein haluamme suorittaa jonkin käskyn tai käskyt vain ehdon täyttyessä, mutta muuten ei tehdä mitään.

Tietysti voisimme tehdä jotain tällaista:

if (ehto) {
  käskyjä, jotka suoritetaan, jos ehto oli tosi
} else {
}
Tyhjä else-haara ei tee mitään. Mutta sitä ei edes tarvitse kirjoittaa; voimme jättää tämän pois.

Jos else-haaraa ei ole ja ehtolausekkeen arvoksi tulee false, niin loput if-käskystä yksinkertaisesti ohitetaan:

if (ehto) {
  käskyjä, jotka suoritetaan, jos ehto oli tosi, ja ohitetaan muutoin
}

Esimerkki:

if (luku != 0) {
  println("Osamäärä on: " + 1000 / luku)
}  
println("Loppu.")
else-haara on jätetty pois.
Tämä if-käsky on vaikutuksellinen (se tulostaa), ja siksi käytämme rivitystä ja aaltosulkeita.
Jälkimmäinen tulostuskäsky ei kuulu if-käskyyn vaan on sen perässä. Tämä koodinpätkä siis tulostaa lopuksi "Loppu." riippumatta siitä, oliko luku-muuttujan arvo nolla vai ei. Mikäli oli, ei tämä koodi muuta tulostakaan.

Voit katsoa tämänkin koodin suorituksesta lyhyen animaation, jos haluat:

Pikkutehtävä

Hiljenny hetkeksi tämän koodin äärelle.

var first = 20
var second = 10

if (first < second) {
  first = first / 2
}

if (first < 2 * second) {
  first = first * 2
  second = second / 2
} else {
  first = first + 1
  second = second - 1
}

val theyAreTheSame = (first == second)

if (theyAreTheSame) {
  first = first + 1
}

println(first + " " + second)

Mitä tapahtuu missäkin vaiheessa, kun koodi suoritetaan (kerran)?

Montako kertaa koko koodissa yhteensä evaluoidaan jonkin if-käskyn ehtolauseke?
Montako kertaa jonkin if-käskyn ehtolausekkeen arvoksi saadaan true?
Montako kertaa päädytään suorittamaan keskimmäisen if-käskyn else-haara?
Mitä viimeinen rivi tulostaa?

FlappyBug-tehtävä (osa 11/16: hienosäätöä)

Tehtävänanto

Lisää FlappyBug-ohjelmaan kaksi if-käskyä siten, että:

  • Ötökkä ponnahtaa ylöspäin vain, jos painettu näppäin on välilyönti (eikä millä tahansa näppäimellä kuten tähän asti).
  • Ötökän nopeus kiihtyy (eli yVelocity-ilmentymämuuttujan arvo kasvaa) vain, jos ötökkä on ilmassa (eli maanpinnan yläpuolella).

Ohjeita ja vinkkejä

  • Lisäykset tulevat FlappyBugApp-ohjelman onKeyDown-metodiin ja Bug-luokan fall-metodiin. Molemmat olivat esillä, kun peliä muokattiin luvussa 2.8.
  • Tapahtumankäsittelijämetodi onKeyDown saa parametriksi tiedon siitä, mitä näppäintä painettiin. Voit verrata tätä arvoa yhtäsuuruusoperaattorilla == arvoon Key.Space, joka on välilyöntinäppäintä tarkoittava vakioarvo.
  • Et tarvitse else-haaroja.

Palauttaminen

A+ esittää tässä kohdassa tehtävän palautuslomakkeen.

if-käskyjen yhdistelemisestä

else if -ketjutus

Jos haluat valita useammasta kuin kahdesta vaihtoehdosta, voit kirjoittaa if-käskyn else-haaraan toisen if-käskyn:

val kuvaus = if (luku < 0) "negatiivinen" else if (luku > 0) "positiivinen" else "nolla"

Sulut ehkä selkiyttävät käskyä hieman:

val kuvaus = if (luku < 0) "negatiivinen" else (if (luku > 0) "positiivinen" else "nolla")

Parhaiten monihaaraisuus ehkä näkyy rivittämällä ja sisentämällä esimerkiksi näin:

val kuvaus =
  if (luku < 0)
    "negatiivinen"
  else if (luku > 0)
    "positiivinen"
  else
    "nolla"

Tällainen else if-ketjuttaminen toki toimii myös, kun valitaan vaikutuksellisten käskyjen välillä:

if (luku < 0) {
  println("Luku on negatiivinen.")
} else if (luku > 0) {
  println("Luku on positiivinen.")
} else {
  println("Luku on nolla.")
}

Minitehtävä: kuvan kuvailu, osa 2/2

Muokkaa ylemmässä minitehtävässä misc.scalaan laatimaasi describe-funktiota siten, että se palauttaa:

  • merkkijonon "portrait", jos kuvan korkeus on leveyttä suurempi,
  • merkkijonon "landscape", jos kuvan leveys on korkeutta suurempi, ja
  • merkkijonon "square", jos kuvan leveys on prikulleen sama kuin sen korkeus.

A+ esittää tässä kohdassa tehtävän palautuslomakkeen.

if-käskyjä sisäkkäin

if-käskyjä voi muutenkin laittaa sisäkkäin. Tällöin pitää olla tarkkana sen kanssa, mihin if-käskyyn kukin else-haara liittyy. Tutustu asiaan seuraavan REPL-esimerkin kautta:

val luku = 100luku: Int = 100
if (luku > 0) {
  println("On positiivinen.")
  if (luku > 1000) {
    println("On yli tuhat.")
  } else {
     println("On positiivinen muttei yli tuhat.")
  }
}  On positiivinen.
On positiivinen muttei yli tuhat.
Luvun ollessa 100 suoritetaan ulomman if-käskyn ehdollinen osa, johon sisältyy ensimmäinen tulostuskäsky.
Ehdolliseen osaan sisältyy myös toinen if-käsky. Koska tämän sisemmän if-käskyn ehto ei toteudu, päädytään sen else-osaan.
Huomaa: sisemmän if-käskyn haarat ovat vielä sisennetymmät.
Kun sisennykset on tehty vallitsevan tyylin mukaisesti, if-sanan alku ja ehdollisia haaroja rajaavat aaltosulut ovat pystysuorassa linjassa keskenään sekä sisemmässä...
... että ulommassa valintakäskyssä. Muista kuitenkin, että vaikka aaltosulut vaikuttavat koodin toimintaan, niin sisennykset ovat Scalassa vain tyylikeino.
Ulommassa käskyssä ei tässä esimerkissämme ole else-haaraa ollenkaan.

Äskeisessä esimerkissä siis else-sana liittyi "lähimpään" if-käskyyn. Tuo else-osa suoritettiin nimenomaan siksi, että ulomman käskyn ehto toteutui mutta sisemmän ei. Jos haluamme liittää elsen ulompaan if-käskyyn, aaltosulkujen siirtäminen auttaa:

if (luku > 0) {
  println("On positiivinen.")
  if (luku > 1000) {
    println("On yli tuhat.")
  }  
} else {
  println("On nolla tai negatiivinen.")
}On positiivinen.
Nyt ulommalla if-käskyllä on kaksi haaraa, ja else-haara suoritettaisiin vain, jos luku ei olisi positiivinen.
else-haara kytkeytyy lähimpään päättyneeseen "then-haaraan". Tässä viimeksi on aaltosuljettu ulomman if-käskyn then-haara.
Sisemmällä if-käskyllä ei tässä esimerkissä ole else-haaraa lainkaan. Tulosterivejä tulee vain yksi.

Sisäkkäisyystehtävä

Huomaa seuraavaa koodinpätkää lukiessasi aaltosulkeiden ja sisennysten käyttö.

Esimerkissä käytetään luvun 1.7 esittelemää modulo-operaattoria % parillisuuden selvittämiseen.

if (number > 0) {
  if (number % 2 == 0) {
    println("A")
  } else {
    println("B")
  }
  println("C")
}
println("D")

if (number > 0) {
  if (number % 2 == 0) {
    println("E")
  }
  println("F")
} else {
  println("G")
}
println("H")
Kirjoita tähän ne kirjaimet, jotka tulostuvat, kun yllä oleva koodi suoritetaan number-muuttujan arvon ollessa 5. (Vain kirjaimet, kiitos; esim. ACFG, jos se olisi oikein.)
Kirjoita tähän ne kirjaimet, jotka tulostuvat, kun yllä oleva koodi suoritetaan number-muuttujan arvon ollessa 6.
Kirjoita tähän ne kirjaimet, jotka tulostuvat, kun yllä oleva koodi suoritetaan number-muuttujan arvon ollessa -5.
Kirjoita tähän ne kirjaimet, jotka tulostuvat, kun yllä oleva koodi suoritetaan number-muuttujan arvon ollessa -6.

Odds-tehtävä (osa 8/9)

Tehtävänanto

Luvun 3.2 OddsTest2-käynnistysolio tulostaa eräitä tietoja kahdesta Odds-oliosta. Muuta sitä:

  1. Poista isLikely- ja isLikelierThan-metodien palautusarvoja tulostavat println-käskyt. (Siis ne, joissa tuloste alkaa The-sanalla.) Tämä siksi, että tarkoitus on nyt raportoida Odds-olioiden tietoja hieman toisin kuin ennen.
  2. Nyt laadittavan ohjelman on tutkittava, onko ensimmäinen syötetty Odds todennäköisempi kuin toinen ja tulostettava sen perusteella jompikumpi seuraavista lauseista: The first event is likelier than the second. tai The first event is not likelier than the second.
  3. Tämän jälkeen ohjelman on tulostettava Each of the events is odds-on to happen., mikäli molemmat tapahtumat ovat todennäköisiä. Jos kumpi tahansa tapahtumista ei ole todennäköinen, ei tässä kohtaa tulosteta mitään.
    • Kuten aiemmin määrittelimme, tapahtuma on todennäköinen, jos se toteutuu luultavammin kuin ei toteudu eli jos sen isLikely-metodi palauttaa true.
  4. Thank you -alkuisen lopputervehdyksen on edelleen tulostuttava kaikissa tapauksissa.

Ohjelman tulosteen pitää siis vastata täsmälleen näitä ajoesimerkkejä:

Please enter the odds of the first event as two integers on separate lines.
5
1
Please enter the odds of the second event as two integers on separate lines.
1
2
The first event is not likelier than the second.
Thank you for using OddsTest2. Please come back often. Have a nice day!
Please enter the odds of the first event as two integers on separate lines.
1
1
Please enter the odds of the second event as two integers on separate lines.
2
1
The first event is likelier than the second.
Thank you for using OddsTest2. Please come back often. Have a nice day!
Please enter the odds of the first event as two integers on separate lines.
1
2
Please enter the odds of the second event as two integers on separate lines.
2
3
The first event is likelier than the second.
Each of the events is odds-on to happen.
Thank you for using OddsTest2. Please come back often. Have a nice day!

Ohjeita ja vinkkejä

  • Käytä if-käskyjä.
  • Selvittäessäsi, ovatko molemmat tapahtumista todennäköisiä, voit laittaa kaksi if-käskyä sisäkkäin. Vaihtoehtoisesti voit kurkistaa jo lukuun 4.4 ja noukkia sieltä toisen keinon kahden ehdon yhdistämiseen.

Palauttaminen

A+ esittää tässä kohdassa tehtävän palautuslomakkeen.

Yhteenvetoa

  • if-valintakäskyllä voi valita kumpi kahdesta käskystä tai käskyjen sarjasta suoritetaan.
  • Valintakäskyllä voi myös määrätä tiettyjä osia ohjelmakoodista valinnaisiksi. Ne suoritetaan vain, jos tietty ehto on voimassa.
  • Mitä tahansa Boolean-tyyppistä lauseketta voi käyttää valintakäskyn ehtona.
  • if-käskyjä voi yhdistellä laittamalla niitä peräkkäin tai sisäkkäin.
  • Lukuun liittyviä termejä sanastosivulla: if; totuusarvo; lauseke; DRY.

Lisätehtävä: tarkkuuspeli

Kirjoita kokonaan uusi ohjelma, jossa käyttäjän pitää yrittää osua hiiren klikkauksella keskelle ikkunaa. Tämä yksinkertainen peli päättyy, jos käyttäjä onnistuu näpäyttämään esim. kolmen pikselin etäisyydelle ikkunan keskikohdasta; tällöin ruudulle ilmestyy jokin valitsemasi kuva merkiksi onnistumisesta.

Voit luoda aihealueen malliksi yksinkertaisen olion, joka pitää Boolean-tyyppisessä muuttujassa kirjaa siitä, onko peli päättynyt. Tarvitset myös käyttöliittymän, joka tekee pelitilanteen näkyväksi.

Tästä vapaamuotoisesta lisätehtävästä ei ole tarjolla automaattista palautetta.

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 Riku Autio, Jaakko Kantojärvi, Teemu Lehtinen, 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.

../_images/imho3.png
Palautusta lähetetään...