Arvutiteaduse instituut
  1. Kursused
  2. 2019/20 sügis
  3. Programmeerimiskeeled (MTAT.03.006)
EN
Logi sisse

Programmeerimiskeeled 2019/20 sügis

  • Info
  • Õppekava
  • Moodle
  • Loengud & Praksid
  • Lisamaterjalid
  • Küsi abi! (Fleep)

Sissejuhatus Scalasse

Scala tööriistade info leiad kõige esimese praktikumi lehelt.

Scala on mitmeparadigmaline programmeerimiskeel, mis kompileerib Java baitkoodiks. Seetõttu asuvad Scala programmid objektide sees.

object Tervitus {
  def tervitus(nimi: String): Unit = {
    println("Tere, " + nimi + "!")
  }
  val konstantVäli : Int = 10
  var muutujaVäli  : Double = 20.0
}

Üldiselt on meetodite süntaks järgnev:

  def meetod(argument: tüüp): tagastustüüp = avaldis

Avaldis võib omakorda olla blokk, mille väärtus on siis blokki viimasel real olev väärtus. Scala paneb ise semikoolonid rea lõppu ning neid läheb ainult vaja, kui tahta kõik ühel real kirjutada.

Peameetod on signatuuriga:

  def main(args: Array[String]): Unit

Näiteülesanded

Koodi käivitamine

Loome nüüd omad programmi ja käivitame selle! Kirjutage ringi pindala arvutamise meetod ning kutsuge see välja programmi peameetodis. Programm väljastagu umbes selline tekst "Ringi raadiusega 5 cm pindala on 78,5398163397 cm2."

object Basics {
  def ringiPindala(r: Double): Double = ???

  def main(args: Array[String]): Unit = ???
}

Tingimusavaldised

Scalat saab ise avastada oma Haskell ja Java teadmiste põhjal: sisuliselt on asjad nagu Haskell, aga välimuselt nagu Java. Haskelli avaldis if x >= 0 then x else -x saab näiteks Scalas kirjutada järgmiselt:

  def abs(x: Double): Double = {
    if (x >= 0) x else -x
  }

Harjutuseks defineerige and ja or if-avaldise abil.

  def and(x: Boolean, y: Boolean): Boolean = if (???) ??? else ???
  def or (x: Boolean, y: Boolean): Boolean = if (???) ??? else ???

Mittepuhtad arvutused

Erinevalt Haskellist saavad Scala meetodid tekitada nn. kõrvalefekte, s.t. meetodid ei ole puhtad funtksioonid. Lõpetage objekti Summaator definitsioon ja testige seda.

object Summaator {
  var summa = 0
  def liida(x: Int): Unit = ???
  def hetkeseis: Int = ???
}
object TestiSummaator {
  def main(args: Array[String]): Unit = {
    Summaator.liida(10)
    Summaator.liida(100)
    assert(Summaator.hetkeseis == 110)
  }
}

for-tsükklid

Tsükkleid vaatame täpsemalt järgmistel kordadel, aga lihtsaid for-tsükkleid kirjutatakse nii:

  for (muutuja <- algus to lõpp) { ... }       // lõpp kaasaarvatud 
  for (muutuja <- algus until lõpp) { ... }    // lõpp väljaarvatud
  for (muutuja <- kollektsioon) { ... }        // teistes keeltes foreach

Harjutuseks kirjuta Scala programm, mis trükib konsooli n*n korrutustabeli. Programmi väljund n = 7 jaoks oleks näiteks:

 1  2  3  4  5  6  7 
 2  4  6  8 10 12 14 
 3  6  9 12 15 18 21 
 4  8 12 16 20 24 28 
 5 10 15 20 25 30 35 
 6 12 18 24 30 36 42 
 7 14 21 28 35 42 49 

Mustrisobitus ja rekursioon listidel

Järgnevates praktikumides vaatame täpsemalt Listide definitsiooni Scalas. Praeguseks võtke lihtsalt teatavaks, et liste saab valmistada süntaksiga List(1,2,3) aga võib kasutada ka konstruktoreid Nil (tühilist) ja x :: xs (mittetühi list).

Tuletame meelde järgmine Haskelli definitsioon mustrite sobitamisega. Seda tuleks nüüd Scala keelde tõlkida. Selleks kasutame match-avaldist:

  nullid :: [Int] -> Int
  nullid []     = 0
  nullid (0:xs) = 1 + nullid xs
  nullid (_:xs) = nullid xs

  // Täida kõik küsimärgid õigete väärtustega
  def nullid(l: List[Int]): Int = l match {
    case Nil     => ???
    case 0 :: xs => ???
    case _ :: xs => ???
  }

Nüüd defineerige funktsioon, mis kontollib, et sõnes on sulud tasakaalus. Kõigepealt tuleb lisada enda testifailis mõned testid, näiteks assert(isBalanced("xyz(xy)zy")) ja assert(!isBalanced(")(")).

  def isBalanced(chars: List[Char]): Boolean = ???

  // Järgnev abifunktsioon funktsioon laseb meil mugavamalt seda kasutada, 
  // sest erinevalt Haskellist ei ole sõne automaatselt tähtede list.
  def isBalanced(str: String): Boolean  = isBalanced(str.toList)

Kõrgemat järku funktsioonid listidel

Anonüümsete funktsioonide ehk lambdade süntaks Scalas on järgnev

  (muutuja: tüüp, muutja2: tüüp2) => keha

Näiteks listis olevate arvude ühe võrra suurendamiseks:

  println(List(1, 2, 3).map((i: Int) => i + 1))

Sellistel lihtsatel juhtudel ei pea tegelikult parameetri tüüpi deklareerima:

  println(List(1, 2, 3).map(i => i + 1))

Veelgi enam, kuna parameetrit kasutatakse ühes kohas, siis saab parameetri enda samuti anonüümseks jätta:

  println(List(1, 2, 3).map(_ + 1))

Nüüd proovige foldLeft-iga leida listi elementide summat.

  def summa(xs:List[Int]): Int = xs.foldLeft(???)(???)

Ülesanded

Ülesanne 1

Kirjutage meetod summaNullist, mis võtab argumendiks täisarvu x ja tagastab arvu, mis on arvude 0 kuni x summa. Näiteks summaNullist(100) == 5050.

Ülesanne 2

Selles ülesandes harjutame rekursiooni.

Iteratiivne Rekursiivne
  def iterativeSumTo(n: Int): Int = {
    var i = 0
    var sum = 0
    while (i <= n) {
      sum = sum + i
      i = i + 1
    }
    sum
  }
  def recursiveSumTo(n: Int): Int = {
    def helper(i: Int, sum: Int): Int = {
      if (i > n) sum
      else helper(i+1, sum+i)
    }
    helper(0,0)
  }

Proovige nüüd järgmine natuke üldistatum tsükkel ümber kirjutada rekursioonina!

  def iterativeSumFunction(lo: Int, hi: Int, step: Int): Int = {
    var i = lo
    var sum = 0
    while (i < hi) {
      sum += i
      i += step
    }
    sum
  }

  // Täida kõik küsimärgid! Ja kontrolli üle, et arvutab sama väärtus kui ülemine.
  def recursiveSumFunction(lo: Int, hi: Int, step: Int): Int = {
    def helper(i: Int, sum: Int): Int = {
      if (???) ???
      else helper(???, ???)
    }
    helper(???, ???)
  }

Ülesanne 3

Implementeerida meetod loenda, mis võtab argumendiks arvu x ja listi xs. Meetod arvutab välja, mitu korda esineb arv x listis xs. Näiteks

loenda(5, List(1,2,3,4,5,6)) == 1 

Ülesanne 4

Kasutades meetodit foldRight implementeerida sõnede konkateneerimine.

  def concat(xs:List[String]): String = ???

Näide:

concat(List("xx","yy","q")) == "xxyyq"

Ülesanded*

Ülesanne 5*

Haskellis väärtustatakse funktsiooni avaldis ainult siis, kui seda vaja on. Scalas, seevastu, väärtustatakse vaikimisi funktsiooni argumendid enne funktsiooni sisenemist. Laisa väärtustamise jaoks tuleb meetodi argumendi tüübi ette lisada =>. Testige järgnevat koodi:

  def neli(x : => Unit): Int = 4
  println(neli(println("laisk")))

Kirjutage meetod myAssert, mis võtab kaks argumenti: esimese tüüp olgu Boolean ja teise tüübiks String. Kui tõeväärtus on väär, visata erind AssertionError andes argumendiks meetodi teise parameetri. Meetodi kutsel ei tohi sõne väärtustada, kui tõeväärtus on tõene.

Ülesanne 6*

Scala keele detailid ei ole kahjuks nii hästi läbi mõeldud, kui Haskelli omad. Näiteks tüüp (Int,Char) => Boolean võib tähistada nii

  • üheargumendilist funktsiooni, mis võtab paari ja tagastab tõeväärtuse, või
  • kaheargumendilise funktsiooni, mis võtab täisarvu, tähe ja tagastab tõeväärtuse.

Proovige järgnevat koodi.

  def length(xs:List[Char]): Int = xs.foldLeft(0)((p:(Int, Char)) => p._1 + 1)

Kompilaatori veataeade on selgem:

 Error:(341, 67) type mismatch;
  found   : ((Int, Char)) => Int
  required: (Int, Char) => Int

IntelliJ veataeade (tooltip) on eksitav

  Type mismatch, expected: (Int, Char) => Int  actual: (Int, Char) => Int
  • Arvutiteaduse instituut
  • Loodus- ja täppisteaduste valdkond
  • Tartu Ülikool
Tehniliste probleemide või küsimuste korral kirjuta:

Kursuse sisu ja korralduslike küsimustega pöörduge kursuse korraldajate poole.
Õppematerjalide varalised autoriõigused kuuluvad Tartu Ülikoolile. Õppematerjalide kasutamine on lubatud autoriõiguse seaduses ettenähtud teose vaba kasutamise eesmärkidel ja tingimustel. Õppematerjalide kasutamisel on kasutaja kohustatud viitama õppematerjalide autorile.
Õppematerjalide kasutamine muudel eesmärkidel on lubatud ainult Tartu Ülikooli eelneval kirjalikul nõusolekul.
Courses’i keskkonna kasutustingimused