Helsingin yliopisto / tietojenkäsittelytieteen laitos / Ohjelmointitekniikka (Scala) / © Arto Wikla 2016

Ohjelmointitekniikka (Scala): harjoitukset 3

Sivu julkaistu 7.4.2016.

Aiheita: luokkamäärittelyä, for, try-catch, match, lohkoja, ..., funktioita, sulkeumia, ...

Jos käytät Scalan rakenteita, joita ei ole vielä luennoilla käsitelty, kommentoi ohjelmasi kunnolla! Varaudu myös selittämään, miten olet ohjelmoinut ja mitä ohjelmatekstisi tarkoittaa!

    1. Esimerkiksi äänitteitä tai videoita editoitaessa joudutaan tekemään aikalaskelmia, ynnämään, vähentämään, yms. Laadi oma "immutaabeli" tietotyyppi MinSek tällaisten laskutoimitusten tekemiseen:

      MinSek-arvoja (olioita) konstruoidaan antamalla minuutit ja sekunnit, pelkät sekunnit tai ei mitään alkuarvoa. Minuutit ja sekunnit saavat olla mitä tahansa kokonaislukuja, myös negatiivisia. Jos alkuarvoa ei anneta, syntyvä aika on 0:00.

      • Toteuta infix operaatiot + ja - kahdelle MinSek-arvolle. Kaksi aika-arvoa siis joko lasketaan yhteen tai lasketaan niiden erotus.
      • Toteuta infix-operaatiot * ja / MinSek-arvolle ja kokonaisluvulle. Kertominen ja jakaminen siis tarkoittavat ajan kertomista tai jakamista annetulla luvulla. Jakaminen tehdään ajan kokonaissekuntiarvon kokonaisjakona. Nollalla jako saa - ja sen pitää - johtaa ohjelman toiminnan päättymiseen virheeseen.
      • Toteuta unaarinen prefix-operaatio aikamäärän vastaluvun laskentaan. Esimerkiksi 1:23:n vastaluku on -1:23
      • Toteuta tietenkin myös toString, joka osaa esittää ajan muodossa: 2:11, -1:49, 1:06, -95:45, 12345678:00, jne. Sekuntiosassa on siis aina kaksi numeromerkkiä ja mahdollinen etumerkki edeltää koko aika-arvoa.

      Esimerkki:

      // konstruointi ja negatiiviset parametrit
      var a = new MinSek(2,11)
      println(a)               // 2:11
      a = new MinSek(2,-11)
      println(a)               // 1:49
      a = new MinSek(-2,11)
      println(a)               // -1:49
      a = new MinSek(-2,-11)
      println(a)               // -2:11
      
      a = new MinSek(66)
      println(a)               // 1:06   Huom: sekunnit siis aina 2 numerolla!
      a = new MinSek
      println(a)               // 0:00
      println(new MinSek(-21)) // -0:21
      a = new MinSek(1,601)
      println(a)               // 11:01
      
      // infix operaatiot + ja - kahdelle MinSek-arvolle
      val b = new MinSek(2,45)
      val c = new MinSek(49)
      println(b + " ja " + c)  // 2:45 ja 0:49
      a = b + c
      println(a)               // 3:34
      println(b - c)           // 1:56
      println(c - b)           // -1:56
      println(b - c + a)       // 5:30
      println(b - a)           // -0:49
      
      // infix-operaatiot * ja / MinSek-arvolle ja kokonaisluvulle
      a = new MinSek(2,11)
      println(a)               // 2:11
      println(a * 2)           // 4:22
      println(a / 2)           // 1:05
      
      println(a * -2)           // -4:22
      println(a / -2)           // -1:05
      
      // prefix-operaatio - vastaluvun laskentaan
      println(-a)               // -2:11
      println(-(-a))            // 2:11
      
      

    2. Muokkaa edellistä tehtävää siten, että MinSek-arvot ovat "mutaabeleita ja toteuta seuraavat sijoitusoperaatiot:

      • kasvattava ja vähentävä sijoitusoperatio (+= ja -=) kahdelle MinSek-arvolle
      • kasvattava ja vähentävä sijoitusoperatio (+= ja -=) MinSek-arvolle ja kokonaisluvulle, joka tarkoittaa sekuntimäärää
      • kertova ja jakava sijoitusoperaatio (*= ja /=) MinSek-arvolle ja kokonaisluvulle

      Esimerkki:

      // kasvattava ja vähentävä sijoitusoperatio kahdelle MinSek-arvolle
      val d = new MinSek(3, 14)
      val e = new MinSek(-3, -10)
      println(d + " ja " + e)   // 3:14 ja -3:10
      d += e
      println(d)                // 0:04
      d -= e
      println(d)                // 3:14
      
      // kasvattava ja vähentävä sijoitusoperatio MinSek-arvolle ja kokonaisluvulle
      val f = new MinSek(3, 14)
      println(f)                // 3:14
      f += 61
      println(f)                // 4:15
      f -= 6000
      println(f)                // -95:45
      
      // kertova ja jakava sijoitusoperaatio MinSek-arvolle ja kokonaisluvulle
      val g = new MinSek(3, 14)
      println(g)                // 3:14
      g *= 2
      println(g)                // 6:28
      g /= 3
      println(g)                // 2:09
      
      // onko seuraava vaarallista?
      val h = new MinSek
      val i = h
      println(h + " ja " + i)   // 0:00 ja 0:00
      h += 666
      println(h + " ja " + i)   // 11:06 ja 11:06
      
      

    1. Alkuluvut ovat ykköstä suurempia kokonaislukuja, jotka ovat jaollisia vain ykkösellä ja itsellään, ks. Wikipedia-artikkeli.

      1. Laadi funktio, jonka arvo on jono peräkkäisiä alkulukuja kakkosesta alkaen. Parametrina annetaan haluttu alkulukujen lukumäärä.
      2. Laadi funktio, jonka arvo on jono peräkkäisiä alkulukuja kakkosesta alkaen. Parametrina annetaan yläraja, jota suurempia alkulukuja ei oteta jonoon mukaan.
      3. Laadi funktio, jonka arvo on jono peräkkäisiä alkulukuja. Parametreina annetaan alaraja ja yläraja, joiden välille jäävät alkuluvut otetaan jonoon.

    2. Toteuta komentotulkkilogiikalla tiedostojen tutkailuohjelma, joka osaa (ainakin) seuraavat komennot:

      • ls listaa hakemiston kaikki tiedostonimet aakkosjärjestyksessä (String-vertailu kelpaa aakkosjärjestykseksi)
      • ls -e merkkijono listaa aakkosjärjestyksessä hakemiston tiedostonimet, jotka päättyvät annettuun merkkijonoon
      • ls -b merkkijono listaa aakkosjärjestyksessä hakemiston tiedostonimet, jotka alkavat annetulla merkkijonolla
      • ls -c merkkijono listaa aakkosjärjestyksessä hakemiston tiedostonimet, jotka sisältävät annettun merkkijonon
      • lo lopettaa ohjelman suorituksen

      Tee komentotulkista vikasietoinen eli varaudu virhetilanteisiin mahdollisimman kattavasti. Anna myös selkeät virheilmoitukset. Ellet muista, mikä on komentotulkki, katso peruskurssien ikivanhan materiaalin lukua 6.

    1. Tarkastellaan ohjelmaa:
      object Sisakkain extends App {
        val a=1; val b=2; val c=3
        def f = {
          val b=20; val c=30
          def ff = {
            val c=300
            println(a+"/"+b+"/"+c)
          }
          println(a+"/"+b+"/"+c)
          ff
        }
        def g = {
          def gg = {
            f
            60
          }
          val a=40; val b=gg;
          println(a+"/"+b+"/"+c)
          f
        }
        println(a+"/"+b+"/"+c)
        f;
        { val a=1000
          g
          println(a+"/"+b+"/"+c)
        }
      }
      
      On helppoa suorittaa ohjelma ja havaita, että se tulostaa:
      1/2/3
      1/20/30
      1/20/300
      1/20/30
      1/20/300
      40/60/3
      1/20/30
      1/20/300
      1000/2/3
      
      Mutta miksi? Selitä jokaisen println-operaation ohjelmakohdan "nimiavaruus", so. mitä kukin tunnus tarkoittaa missäkin. Selitä ohjelman määrittelyrakenne: miten ohjelman osat sisältävät toisiaan. Selitä myös ohjelman kutsurakenne: millä tavoin ohjelman osat kutsuvat toisiaan.

    2. Kirjastossa on työkalu:
      object Kirjasto {
        def summa(termi: (Int) => Double, lkm: Int) = {
          var s=0.0
          for (i <- 1 to lkm) s += termi(i)
          s
        }
      }
      
      
      Eräs sovellus käyttää kirjaston työkalua:
      object Sovellus extends App {
      
        def harm(i: Int) = 1.0/i
        println(  Kirjasto.summa(harm, 4) )
      
        def geom(i: Int) = 1.0/(i*i)
        println(  Kirjasto.summa(geom, 4) )
      
        println(  Kirjasto.summa(_ +0.5, 4) )
      
        var laskuri=0.0
        def mitaMita(i: Int) = {laskuri+=1; laskuri}
        println(  Kirjasto.summa(mitaMita, 4) )
      
        var ed=1; var seur=1; var alussa=1
        def f(i: Int) = if (alussa<3)
                           {alussa+=1; 1}
                        else
                           {val uus=ed+seur; ed=seur; seur=uus; uus}
      
        println(  Kirjasto.summa(f, 4) )
      }
      
      On helppoa suorittaa sovellus ja todeta sen tulostavan:
      2.083333333333333
      1.4236111111111112
      12.0
      10.0
      7.0
      
      Mutta miksi se näin tulostaa? Näytä miten muuttujien ja parametrien arvot muuttuvat ohjelman suorituksen aikana.