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

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

Luku 5.1: Logiikkaa, verta ja ostoksia

Tästä sivusta:

Pääkysymyksiä: Miten sanon "jos tuo tai tuo ehto toteutuu"? Tai "vain jos ne molemmat toteutuvat"? Osaanko jo laatia Scala-luokkia itsenäisemmin?

Mitä käsitellään? Uutena asiana logiikkaoperaattorit. Niiden lisäksi tehtävät yhdistelevät monia aiempia aiheita.

Mitä tehdään? Enimmäkseen ohjelmoidaan, vaikka alussa on vähän luettavaakin.

Suuntaa antava työläysarvio:? Pari, kolme tuntia? Huomattavasti pidempään, jos teet kaikki vapaaehtoisetkin ohjelmointitehtävät.

Pistearvo: A120.

Oheismoduulit: FlappyBug, Miscellaneous, Blood (uusi), AuctionHouse1 (uusi).

../_images/person03.png

Johdanto: vaikeutus FlappyBugiin

Palataan FlappyBug-peliin. Luvussa 3.3 lisäsit Game-pelioliolle isLost-metodin, joka määrää pelin poikki, jos este koskee ötökkää:

def isLost = this.obstacle.touches(this.bug)

Kertaus: touches-metodi palauttaa true tai false. Vastaavasti myös isLost-metodi palauttaa jommankumman Boolean-arvon.

Nykyisessä pelissä on turhan helppo edetä lymyämällä ylä- tai alareunassa. Jospa muokkaamme tuota ehtoa niin, että peli loppuu, jos törmää esteeseen tai ylä- tai alareunaan.

Jotenkin näin se voisi mennä:

def isLost =
  if this.obstacle.touches(this.bug) then
    true
  else if this.bug.pos.y <= 0 then
    true
  else
    this.bug.pos.y >= GroundY

Tappio tulee, jos on osuttu esteeseen.

Tai jos ei ole osuttu mutta jos ollaan liian korkealla.

Tai jos ei olla liian korkeallakaan mutta ollaan liian matalalla.

Toimii se noinkin, mutta tuo on aika vaivalloinen tapa sanoa yksinkertainen asia. Mieluummin kirjoittaisimme: "Jos tuo tai tuo ehto on tosi." Ehkä olet ehtinyt kaipailla tällaista mahdollisuutta jo aiempien lukujen aikana.

Tässä luvussa opit käyttämään logiikkaoperaattoreita kuten "ja" ja "tai", joilla Boolean-arvoja voi yhdistellä. Niiden yleisesittelyn jälkeen palaamme mm. FlappyBugiin.

Logiikkaoperaattorit

Scala-kielen tarjoamia logiikkaoperaattoreita (logical operators) on taulukoitu alla:

Operaattori

Nimi

Esimerkki

Vastaa tarpeeseen

&&

ja (and)

jokuVaite && toinenVaite

"Ovatko molemmat totuusarvot true?"

||

tai (or)

jokuVaite || toinenVaite

"Onko ainakin toinen totuusarvoista true?"

^

joko–tai eli poissulkeva tai
(exclusive or eli xor)

jokuVaite ^ toinenVaite

"Onko tasan yksi totuusarvoista true?"

!

ei tai negaatio
(not tai negation)

!jokuVaite

"Onko totuusarvo false?"

Tämän kurssin kannalta hyödyllisimpiä ovat &&, || ja !, joista viimeinen onkin jo aiemmista luvuista tuttu.

Logiikkaoperaattoreilla rakennetaan Boolean-tyyppisistä lausekkeista monimutkaisempia lausekkeita, joilla on niilläkin Boolean-tyyppinen arvo.

val luku = 50luku >= 0 && luku <= 5res0: Boolean = false
if luku >= 0 && luku <= 5 then "on kelvollinen arvosana" else "ei ole kelvollinen arvosana"res1: String = "ei ole kelvollinen arvosana"
luku == 100 || luku == 50res2: Boolean = true
!(luku >= 0)res3: Boolean = false

&&- eli ja-operaattori tuottaa truen vain, jos molemmat osalausekkeista ovat true. Tässä ensimmäinen on mutta toinen ei, joten koko lausekkeen arvo on false.

||- eli tai-operaattori tuottaa truen, jos kumpi tahansa osalausekkeista on true (tai molemmat ovat). Tässä ensimmäinen ei ole, mutta toinen on, joten koko lausekkeen arvo on true.

Termi: osalausekkeita, joita jokin operaattori käsittelee, sanotaan usein operandeiksi (operand). Tässä esitellyistä operaattoreista negaatio-operaattorille ! riittää yksi operandi, kun taas muut logiikkaoperaattorit vaativat kaksi.

Logiikkaoperaattorien väljä evaluointi

&&- ja ||-operaattorien evaluointi on väljää (non-strict). Tässä väljyys tarkoittaa käytännössä sitä, että vasemmanpuoleinen operandi evaluoidaan ensin, ja jos se riittää määräämään koko lausekkeen totuusarvon, niin oikeanpuoleista operandia ei evaluoida lainkaan. (Väljää evaluointia käsitellään laajemmin luvussa 7.2.)

Asialla on käytännön merkitystä, kuten seuraavasta esimerkistä selviää.

Määritellään aluksi pari muuttujaa, joilla on jotkin lukuarvot. Tässä ne ovat eräät literaalit, mutta luvut voitaisiin myös vaikkapa kysyä ohjelman käyttäjältä.

val jaettava = 50000jaettava: Int = 50000
var jakaja = 100jakaja: Int = 100

Tutkitaan jakolaskun lopputuloksen suuruutta, kunhan ensin varmistetaan, että jakaja ei ole nolla. Tarkastus onnistuu joko &&- tai ||-operaattorilla:

jakaja != 0 && jaettava / jakaja < 10res4: Boolean = false
jakaja == 0 || jaettava / jakaja >= 10res5: Boolean = true

Silloin, kun jakajana ei ole nolla, voi operandien järjestyksen vaihtaa ja lopputulos pysyy samana:

jaettava / jakaja >= 10 || jakaja == 0res6: Boolean = true

Vaan entä jos jakaja onkin nolla?

jakaja = 0jakaja: Int = 0
jaettava / jakaja >= 10 || jakaja == 0java.lang.ArithmeticException: / by zero
...

Nyt jakolasku suoritetaan ensimmäistä operandia evaluoitaessa. Syntyy ajonaikainen virhe: nollalla ei voi jakaa. Lauseketta jakaja == 0 ei koskaan edes ehditty aloittaa evaluoimaan.

Jos taas tarkastus on ensimmäisessä operandissa, homma toimii:

jakaja == 0 || jaettava / jakaja >= 10res7: Boolean = true

Koska jakaja oli nolla, niin lausekkeen jakaja == 0 arvoksi saatiin true. Näin ollen loppuosaa lausekkeesta ei edes evaluoitu sillä perusteella, että true || mitaTahansa on aina true. Näin myös vältettiin virhe. Järjestyksellä on väliä!

Oletetaan, että käytössä on muuttujat jakaja ja jaettava kuten edellä ja että jakaja on nolla. Mitkä kaikki seuraavista väitteistä pitävät paikkansa, kun tarkastellaan lauseketta jakaja != 0 && jaettava / jakaja < 10?

Operaattorien yhdistelemisestä

Kuten aritmeettisia lausekkeitakin, myös logiikka- ja vertailuoperaattoreita yhdistelemällä muodostettuja lausekkeita voi ryhmitellä kaarisulkeilla.

if (luku > 0 && luku % 2 == 0) || luku < 20 then
  println("Joko sekä positiivinen että parillinen tai 20:ä pienempi.")

if luku > 0 && (luku % 2 == 0 || luku < 20) then
  println("Positiivinen; lisäksi joko parillinen tai 20:ä pienempi.")

Logiikkaoperaattoriharjoittelua

Tarkastele seuraavaa koodinpätkää ja mieti huolellisesti, mitä tapahtuu, kun se suoritetaan. Muistiinpanolappusen käyttäminen ei ole huono idea.

val first = 20
val second = 10
var third = 50

if first > second && first + second < third then
  third = 30
  println("One")

val result = first > second && first + second < third

if first < second || result then
  println("Two")

if first > second || second == third then
  println("Three")

third = 10

if !(result && third >= 50) then
  println("Four")
else
  println("Five")

Tuossa koodissa ||-operaattori jakaa toisen if-käskyn ehtolausekkeen first < second || result kahteen osaan. Mitä tapahtuu, kun tämä ehtolauseke evaluoidaan?

||-operaattori jakaa kolmannen if-käskyn ehtolausekkeen first > second || second == third kahteen osaan. Mitä tapahtuu, kun tämä ehtolauseke evaluoidaan?

Mitä println-käskyt tulostavat, kun koko koodinpätkä ajetaan?

Parempi pullokone

Logiikkaoperaattorit ovat monessa tilanteessa kätevämpi vaihtoehto kuin if-käskyjen yhdistelmä.

Palautetaan mieleen luvun 3.5 sellBottle-metodi, josta on tässä alkuperäinen Int-paluuarvoinen versio.

def sellBottle() =
  if this.isSoldOut then
    -1
  else if !this.enoughMoneyInserted then
    -1
  else
    this.earnedCash = this.earnedCash + this.bottlePrice
    this.bottleCount = this.bottleCount - 1
    val changeGiven = this.insertedCash - this.bottlePrice
    this.insertedCash = 0
    changeGiven

Tutki seuraavia kahta yritystä kirjoittaa tämä metodi logiikkaoperaattoria käyttäen:

def sellBottle() =
  if this.isSoldOut || !this.enoughMoneyInserted then
    -1
  else
    this.earnedCash = this.earnedCash + this.bottlePrice
    // jne. kuten edellä
def sellBottle() =
  if this.enoughMoneyInserted && !this.isSoldOut then
    this.earnedCash = this.earnedCash + this.bottlePrice
    // jne. kuten edellä
  else
    -1

Arvioi näiden lyhyempien toteutusten toimivuutta.

FlappyBug-tehtävä (osa 15/17: tappavat reunat)

Vaikeuta FlappyBug-peliä johdannossa suunnitellulla tavalla. Tee se näin:

  1. Lisää Bug-luokkaan parametriton, vaikutukseton metodi isInBounds, jonka palauttama Boolean-arvo kertoo, onko ötökkä sallitulla pelialueella eli nollakoordinaatin ja maanpinnan välissä (rajat poislukien). Käytä sopivaa logiikkaoperattoria.

  2. Muokkaa Game-luokan isLost-metodia niin, että se ilmoittaa pelin päättyneeksi myös silloin, jos ötökkä on sallitun pelialueen ulkopuolella. Käytä logiikkaoperaattoreita ja isInBounds-metodia.

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

Verityyppitehtävä

Tehtävänanto

Blood-moduulissa on useita numeroituja pakkauksia. Ota nyt esiin o1.blood1 ja sieltä luokka BloodType. Toteuta se moduulin Scaladoc-dokumentaation mukaiseksi.

Ohjeita ja vinkkejä

  • Tässä yksi esimerkki siitä, miten luokan pitäisi toimia:

    import o1.blood1.*val myBlood = BloodType("AB", true)myBlood: o1.blood.BloodType = AB+
    val yourBlood = BloodType("A", true)yourBlood: o1.blood.BloodType = A+
    val theirBlood = BloodType("O", false)theirBlood: o1.blood.BloodType = O-
    myBlood.canDonateTo(yourBlood)res8: Boolean = false
    yourBlood.canDonateTo(myBlood)res9: Boolean = true
    theirBlood.canDonateTo(yourBlood)res10: Boolean = true
    
  • Voit myös ajaa annetun test-ohjelman kokeillaksesi kaikkia eri yhdistelmiä.

  • Tässäkään tehtävässä ei tarvitse välittää sellaisista erikoistapauksista ja virhekäyttötilanteista, joita ei ole erikseen mainittu. Saat luottaa siihen, että BloodTypelle annetaan vain mielekkäitä merkkijonoja luontiparametriksi.

  • Pyri toteuttamaan pyydetyt metodit mahdollisimman yksinkertaisesti ja toistamatta samaa koodinpätkää eri paikoissa.

    • Käytä logiikkaoperaattoreita. Pärjäätkö kokonaan ilman if- ja match-valintakäskyjä (muissa metodeissa kuin toStringissä)?

    • Kutsu muita saman luokan metodeita. Lisävinkki: myös viittauksen this-olioon voi antaa parametriksi metodille.

Palauttaminen

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

Tehtävä: FixedPriceSale

Moduulin AuctionHouse1 Scaladoc-dokumentit kuvailevat useita luokkia, jotka mallintavat kuvitteellisella verkkohuutokauppasivustolla myytäviä esineitä. Tässä tehtävässä keskitymme yhteen myyntitapaan, jossa esineillä on tietty vakiomyyntihinta. Alempana olevissa valinnaisissa tehtävissä toteutetaan huutokauppoja, joissa hinta muuttuu.

Tehtävänanto

Nouda moduuli AuctionHouse1 ja avaa sen Scaladoc-dokumentaatio. Toteuta pakkauksen o1.auctionhouse luokka FixedPriceSale dokumentaation mukaiseksi. Kirjoita ratkaisusi tiedostoon FixedPriceSale.scala.

Ohjeita ja vinkkejä

  • On hyvä ajatus käyttää Option-tyyppistä ilmentymämuuttujaa, jonka alkuarvo on None. Jos teet niin, muista että sinun täytyy merkitä sille tietotyyppi (kuten luvussa 4.3). Siis: muuttujanNimi: Option[Tyyppi].

    Lisävinkkejä ilmentymämuuttujista

    Pari kolmesta luontiparametrista on syytä määritellä ilmentymämuuttujiksi. Näet nuo julkiset ilmentymämuuttujat dokumentaatiosta.

    Lisäksi tarvitset pari ilmentymämuuttujaa: yhden, jossa pidät kirjaa esineen vähenevästä aukioloajasta, ja toisen, jossa pidät kirjaa ostajan nimestä.

    Koska ostajaa ei välttämättä ole, sopii jälkimmäisen tyypiksi Option[String].

    Viimeksi mainittuja ilmentymämuuttujia ei ole mainittu dokumentaatiossa, vaan ne ovat osa sisäistä toteutusta. Tee niistä yksityisiä ja nimeä ne haluamallasi tavalla.

  • Luokan osat on kuvattu Scaladocissa aakkosjärjestyksessä, mutta se ei liene kätevin toteuttamisjärjestys (eikä metodeita myöskään ole koodiin pakko kirjoittaa tähän järjestykseen). Voit valita järjestyksen itse; tässä on yksi ehdotus, joka edennee suunnilleen helpoimmasta vaikeimpaan: toString, daysLeft, buyer, isExpired, isOpen, advanceOneDay, buy.

    Vinkkejä metodien toteutuksesta

    Lue Scaladoc tarkasti. Huomaa esimerkiksi, että open ja expired eivät ole toistensa suoria vastakohtia, vaikka esine ei voikaan olla molempia yhtäaikaisesti.

    Suurin osa metodeista on vaikutuksettomia. Niiden tulee vain palauttaa ilmentymämuuttujien arvoja tai selvittää jokin ilmentymämuuttujiin liittyvä tieto. Nämä metodit voi toteuttaa yksinkertaisilla yhden rivin lausekkeilla.

    Esineillä on kaksi vaikutuksellista metodia: advanceOneDay muuttaa jäljellä olevien päivien lukumäärää ja buy ostajaa. Nuo tiedot ovat tallessa ilmentymämuuttujissa. Muista kummassakin metodissa tarkistaa, onko esine auki; voit käyttää isOpen-metodia.

  • Sinulle on annettu valmis testiohjelma testFixedPrice. Käytä sitä!

    • Tämän testiohjelman määrittely aiheuttaa aluksi virheilmoituksia, jotka johtuvat vain siitä, ettet ole vielä toteuttanut niitä metodeita, joita ohjelma kutsuu. Ilmoitukset häviävät kyllä, kun teet tehtävän. (Muista: virheilmoituksen syy ei aina ole siellä, mihin virheilmoitus osoittaa.)

    • Voit aluksi "kommentoida ulos" osan testiohjelman koodista, niin pystyt testaamaan osittaista toteutusta luokastasi.

  • Voit lisäksi ajaa pakkauksesta o1.auctionhouse.gui löytyvän toisen testisovelluksen TestApp, jonka avulla voit luoda ja koekäyttää FixedPriceSale-olioita vuorovaikutteisesti tällaisessa ikkunassa:

    ../_images/auctionhouse1_gui.png

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

Lisätehtävä: Moment ja Interval

Loput luvusta koostuu vapaaehtoisista mutta suositeltavista lisätehtävistä, joilla voit kehittää ohjelmointirutiiniasi. Suosittelemme näitäkin tehtäviä, vaikka sitten niin, että teet ensin kierroksen pisteytetyt tehtävät ja palaat sen jälkeen tänne. Alla oleviin tehtäviin menee helposti useita tunteja.

Pakkauksen o1.time esittely

Tässä tehtävässä pääset soveltamaan logiikkaoperaattoreita hieman aiempia mutkikkaammin ja muutenkin treenaamaan opittuja työkaluja.

Ajanhetkiä ja aikavälejä

Oletetaan, että on ohjelma (tai ohjelmia), joiden on tarpeellista käsitellä aikavälejä, jotka alkavat tietystä ajanhetkestä ja jatkuvat toiseen hetkeen saakka. Tätä varten halutaan laatia luokat Moment kuvaamaan yksittäistä ajanhetkeä ja Interval kuvaamaan aikaväliä. Kuhunkin Interval-olioon liittyy kaksi Moment-oliota, jotka kuvaavat aikavälin alku- ja loppuhetkeä.

Moduulissa Miscellaneous on pakkaus o1.time, josta löytyy jo alku Moment-luokan toteutukselle. Interval-luokka puuttuu vielä kokonaan.

Aloitetaan luokasta Moment, jonka pohjakoodista on selitys alla.

import scala.math.abs

class Moment(private val time: Int):

  override def toString = this.time.toString

  def distance(another: Moment) = abs(another.time - this.time)

  def isLaterThan(another: Moment) = this.time > another.time

  def later(another: Moment) = if this.isLaterThan(another) then this else another

  def earlier(another: Moment) = if this.isLaterThan(another) then another else this

end Moment

Moment-oliota luotaessa ilmoitetaan mistä ajanhetkestä on kyse. Tässä toteutuksessa ei oteta lainkaan kantaa siihen, mitä yksikköä ajan mittaamiseen käytetään, vaan mitä tahansa kokonaislukumuotoista voi käyttää (nanosekunteja, päiviä, vuosilukuja jne.).

Moment-olio delegoi toString-toteutuksen kokonaisuudessaan kokonaisluvun huoleksi. Huomaa: Int-arvollekin voi kutsua metodia! Kokonaisluvun toString-metodi palauttaa lukua kuvaavat merkit, esimerkiksi luvulle 123 kutsuttuna merkkijonon "123". (Int-arvojen metodeista lisää seuraavassa luvussa 5.2.)

distance-metodi määrittää kahden hetken etäisyyden.

isLaterThan-, later- ja earlier-metodeilla voi vertailla ajanhetkiä toisiinsa.

Yleinen vinkki: hyödynnä abstraktioita

Yksi tämän harjoituksen teemoista on itse laatimiesi metodien käyttö kirjoittaessasi toisia metodeita. Toki tuota on tehty aiemmissa tehtävissäkin, mutta tässä se erityisesti korostuu.

Jos toteutat seuraavat metodit aivan toisistaan riippumattomasti, tulet toistaneeksi samoja koodinpätkiä. Älä kuitenkaan tee niin, vaan mieti joka kohdassa, mitä aiemmin laatimaasi metodia voisit hyödyntää. Koeta saada koodistasi mahdollisimman DRY, eli don’t repeat yourself. Toisteeton koodi on helpommin muokattavaa ja vähemmän virhealtista.

Hienommin sanoen: Jokainen metodi on abstraktio (luku 1.6). Rakenna uusia abstraktioita vanhojen päälle. Esimerkki: Yllä kuvattu isLaterThan-metodi on abstraktio siitä konkreettisesta toteutuksesta, jossa katsotaan kahden Moment-olion ilmentymämuuttujien arvot ja verrataan niitä vertailuoperaattorilla toisiinsa: this.time > another.time. Toiset metodit, joilla on tarvetta vertailla kahta Moment-oliota voivat käyttää tätä metodia, eikä niiden tarvitse itse ruveta vertailemaan muuttujien arvoja keskenään.

Harjoitus neljässä vaiheessa

Täydennä Interval ja Moment dokumentaation mukaisiksi. Alla on ehdotus työvaiheista ja niihin liittyviä vinkkejä, mutta voit edetä muussakin järjestyksessä.

Vaihe 1/4: Interval-luokka alkuun

  1. Lue moduulin Scaladoc-dokumentaatiosta nyt ainakin luokkien yleiskuvaukset sekä kuvaukset Interval-luokan metodeista length ja toString. Ne toteutetaan tässä vaiheessa.

  2. Luo uusi tiedosto Interval-luokkaa varten.

  3. Kirjoita luontiparametrit. Koska molemmat parametriarvot on saatava talteen metodien toteuttamiseksi, voi niille samalla määritellä ilmentymämuuttujat otsikkorivillä. Pitääkö näiden ilmentymämuuttujien olla yksityisiä?

  4. Laadi metodi length. Käytä apuna Moment-luokan metodia.

    • Vaikka kiusaus iskisi, niin älä laadi nyt eikä myöhemmin Moment-luokkaan mitään sellaisia julkisia osia, joita ei ole erikseen pyydetty. Erityisesti: älä tee time-ilmentymämuuttujasta julkista.

  5. Laadi metodi toString. Se on vähän monimutkaisempi. Vinkkejä:

    • Hyödynnä aiemmin laatimaasi metodia.

    • Käytä if-käskyjä ja else-osioita erottaaksesi toisistaan tarvittavat kolme tapausta.

    • Muista, että merkkijononkin voi "kertoa", esim. "ho" * 3 tuottaa "hohoho".

  6. Siirry timeTest-ohjelmaan, jolle on pohjustus valmiina. Lisää sinne käskyjä, joilla testaat Interval-olioiden luomista ja uusia metodeitasi.

Ajanhetkien yhtäsuuruudesta

Ajanhetkien yhtäsuuruusvertailu operaattoreilla == ja != on identiteettivertailu ("Osoittavatko viittaukset juuri samaan Moment-olioon?"; ks. luku 3.3.) Se siis ei vertaa sitä, kuvaavatko kaksi eri Moment-oliota samaa ajanhetkeä eli onko niiden time-muuttujalla sama kokonaislukuarvo.

Identiteettivertailua yhtäsuuruusoperaattoreilla ei tarvitse tässä tehtävässä lainkaan eikä siitä tässä oikein hyötyäkään ole. Muilla metodeilla pärjää mainiosti!

Vaihe 2/4: isLaterThan ja metodinimen kuormitusta

Aikavälitehtävä oli sangen mullistava kokemus.

Olin jo vauhdikkaasti tekemässä jokaiselle metodille omaa if-lausetta ja jättämässä metodien uusiokäytön vähille, kun sattumalta paikalla olleet pelottavat ja tarkkasilmäiset assistentit huomauttivat asiasta.

En uskaltanut uhmata viisaita assareita, joten muutaman tovin mietin hikoillen, miten ihmeessä nyt voin ilman yhtäkään if-lausetta selvitä. Kun lopulta keksin isLaterThan(Interval)-metodin ratkaisun (assarin vahvasti vinkattua), tuntui kuin olisin keksinyt pyörän. Sehän oli nerokasta!

  1. Laadi Interval-luokan metodi nimeltä isLaterThan. Huomaa, että metodinimeä on kuormitettu (luku 4.1) ja tällaisia metodeita on kaksi. Yksi ottaa Moment-tyyppisen parametrin ja toinen Interval-tyyppisen. Laadi näistä nyt ensinmainittu. Käytä apuna erästä jo olemassa olevaa metodia.

  2. Laadi sitten toinen Interval-luokan isLaterThan-metodeista. Käytä apuna äsken laatimaasi metodia. Niin tehdessäsi muista luvun 4.1 mainitsema pikkuseikka: jos kuormitetuista Scala-metodeista yksi kutsuu kaimaansa, on kutsujalle kirjattava paluuarvon tyyppi koodiin. Nyt tyyppi on siis kirjoitettava vähintäänkin yhdelle isLaterThan-metodeista.

  3. Lisää taas timeTest-ohjelmaasi uusia käskyjä ja aja se. Kutsu testiohjelmasta molempia isLaterThan-metodeita ja varmista, että ne toimivat.

  4. Lisää myös Moment-luokkaan sieltä vielä puuttuva isLaterThan-metodi, joka ottaa Interval-tyyppisen parametrin.

Vaihe 3/4: logiikkaoperaatioita

Toteuta lisää metodeita Interval- ja Moment-luokkiin:

  1. Toteuta se Interval-luokan contains-nimisistä metodeista, joka ottaa parametriksi Moment-olion. Hyödynnä logiikkaoperaattoreita ja aiemmin laadittuja metodeita.

  2. Toteuta toinenkin contains-metodi. Hyödynnä tässäkin logiikkaoperaattoreita ja aiemmin laadittuja metodeita. Muista lisäksi yllä kerrattu asia metodinimien kuormittamisesta.

  3. Toteuta Moment-luokan isIn-metodi. Se tekee käytännössä "saman asian toisin päin" kuin eräs aiemmin laatimasi metodi, joten sen voi toteuttaa yksinkertaisella tavalla.

  4. Toteuta Interval-luokan overlaps-metodi. Logiikkaoperaattoreista ja edellä laadituista metodeista on jälleen apua. Muistithan huomioida kaikki erilaiset tapaukset?

  5. Lisää uusia metodikutsuja timeTest-ohjelmaan. Toimivatko uudet metodisi?

Vaihe 4/4: uusia olioita vanhoja yhdistelemällä

Toteuta ja testaa vielä Interval-luokasta puuttuvat metodit union ja intersection, joille on yhteistä uusien Interval-olioiden luominen.

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

Kuinka yksityinen on private?

Kysyttyä: toimiiko tuo Moment tosiaan? timehän on yksityinen!

Katsotaan vähän tarkemmin, miksi äskeisen tehtävän koodi toimii eikä tuota virheilmoitusta.

class Moment(private val time: Int):
  def isLaterThan(another: Moment) = this.time > another.time

Muuttuja time on yksityinen. Sitä ei ole tarkoitus käyttää ulkopuolelta. Mutta minkä ulkopuolelta?

Moment-olio voi "kysyä omaa timeään".

Moment-olio voi myös "kysyä toisen Moment-olion timeä". Se käy, kun pisteen edessä on jokin toiseen olioon viittaava lauseke, tässä muuttujan another nimi.

private luokan koodissa tarkoittaa, että kyseinen ohjelman osa on käytettävissä vain tuon luokan sisältä. Tuo osa ei siis ole vain yhden olion saatavilla vaan kyseisen luokan olioiden yhteinen yksityisasia.

Lisätehtäviä: huutokaupat

DutchAuction-tehtävä

Ota esiin AuctionHouse1-moduuli. Toteuta dokumentaation kuvaama luokka DutchAuction.

Ohjeita ja vinkkejä:

  • Pidä ensinnäkin huolta siitä, että luet kuvauksen "hollantilaistyyppisistä huutokaupoista" tunnollisesti! Kuvaus löytyy luokan DutchAuction dokumentaatiosta. Ellei sinulla ole kirkasta käsitystä siitä, mitä luokan pitäisi tehdä, on Scala-toteutuksen laatiminen piinallista.

  • Huomaat jo dokumentaatiota lukiessasi, että DutchAuction-luokka on joiltain osin aivan samanlainen kuin ylemmän tehtävän FixedPriceSale. Voit napata koodia ratkaisustasi DutchAuction-luokkaasi.

  • Voit käyttää samaa TestApp-ohjelmaa pakkauksesta o1.auctionhouse.gui kokeillaksesi ratkaisusi toimivuutta. Voit toki myös kirjoittaa oman tekstipohjaisen testiohjelman.

  • Kokonaisluvusta saa vastaavan desimaaliluvun joko

    • luvun 1.3 konstein: 1.0 * kokonaisluku, tai

    • seuraavan luvun 5.2 konstein: kokonaisluku.toDouble

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

WETWET?

Harmittiko kirjoittaa samoja metodeita uudestaan DutchAuction-luokkaan tai kopioida niitä tiedostosta toiseen? Verratonta! Olet siis motivoitunut oppimaan aiheesta, jota käsitellään luvussa 7.3 ja jolla moinen toisto vältetään.

EnglishAuction-tehtävä

Jatka AuctionHouse1-moduulin parissa. Toteuta EnglishAuction.

Ohjeita ja vinkkejä:

  • Varmista taas ensin, että ymmärrät miten "englantilaistyyppisten huutokauppojen" on tarkoitus toimia.

  • Käytä apuna annettua pientä Bid-luokkaa. EnglishAuction-luokasta on annettu vain vähän pohjakoodia.

  • Vaikka tehtävän voi ratkaista käyttäen puskureita, joihin talletetaan kaikki huutokauppoihin tehdyt huudot, niin puskurittakin pärjäät hyvin. Ihan kaikkia tehtyjä huutoja ei tarvitse tallentaa vaatimusten täyttämiseksi...

  • ... vaan pohjaksi annetussa koodissa käytetään kahta muuttujaa, joissa on kaksi ylintä huutoa. Kiinnitä huomiota siihen, millaiset alkuarvot näille muuttujille on annettu: kun varsinaisia huutoja ei vielä ole, muuttujissa on huutajattomat alkuhintaa kuvaavat Bid-oliot.

  • TestApp-kokeiluohjelmaa sopii käyttää tässäkin.

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

Yhteenvetoa

  • Logiikkaoperaattoreilla voi yhdistellä totuusarvoja. Niiden avulla voi käsitellä moniosaisia ehtoja: "X ja Y", "X tai Y".

  • Lukuun liittyviä termejä sanastosivulla: logiikkaoperaattori, kuormittaa; DRY.

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, Niklas Kröger, Kalle Laitinen, Teemu Lehtinen, Mikael Lenander, Ilona Ma, Jaakko Nakaza, Strasdosky Otewa, Timi Seppälä, Teemu Sirkiä, 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 ja Juha Sorva. 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. Pääkehittäjänä on nyt Markku Riekkinen, jonka lisäksi A+:aa 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.

a drop of ink
Palautusta lähetetään...