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)

Scala kogumi implementeerimine

Selles praktikumis implementeerime oma järjekorra (queue) kogumi, mis on funktsionaalne ja immutable, ja lisame selle Scala kogumite hierarhiasse.

Kasutame järgmisi import-e, et hiljem kogemata vale AbstractSeq jm ei impordiks.

import scala.collection.{IterableFactoryDefaults, SeqFactory, mutable}
import scala.collection.immutable.{AbstractSeq, LinearSeq, LinearSeqOps}

Defineerime järgmise liidesega järjekorra operatsioonid:

trait JärjekordOps[+A, +CC[_], +C] {
  def onTühi: Boolean
  def lisaLõppu[B >: A](elem: B): CC[B]
  def võtaAlgusest: (A, C)
}

Selle tüübiparameetrid on samasugused nagu Scala kogumite liidestel.

Järjekorra operatsioonide implementeerimine

Kõigepealt implementeerime lihtsa järjekorra andmestruktuuri, mis realiseerib eelmist liidest. Funktsionaalset järjekorda esitame kahe standardse listiga, et kõik järjekorra operatsioonid oleksid efektiivsed.

case class Järjekord[+A](private val algus: List[A], private val lõpp: List[A])
  extends JärjekordOps[A, Järjekord, Järjekord[A]] {

  override def onTühi: Boolean = ???

  override def lisaLõppu[B >: A](elem: B): Järjekord[B] = ???

  override def võtaAlgusest: (A, Järjekord[A]) = ???
}

object TestJärjekord1 {
  def main(args: Array[String]): Unit = {
    val a = Järjekord[Int](List(), List())
    assert(a.onTühi)
    val a2 = a.lisaLõppu(1)
    assert(!a2.onTühi)
    assert(a2 == Järjekord(List(), List(1)))

    val b = Järjekord(List(1, 2, 3), List(6, 5, 4))
    val b2 = b.lisaLõppu(7)
    assert(b2 == Järjekord(List(1, 2, 3), List(7, 6, 5, 4)))

    val (x1, c1) = b.võtaAlgusest
    assert(x1 == 1)
    assert(c1 == Järjekord(List(2, 3), List(6, 5, 4)))
    val (x2, c2) = c1.võtaAlgusest
    assert(x2 == 2)
    assert(c2 == Järjekord(List(3), List(6, 5, 4)))
    val (x3, c3) = c2.võtaAlgusest
    assert(x3 == 3)
    assert(c3 == Järjekord(List(), List(6, 5, 4)))
    val (x4, c4) = c3.võtaAlgusest
    assert(x4 == 4)
    assert(c4 == Järjekord(List(5, 6), List()))
  }
}

Companion objekti lisamine

Järjekordade mugavamaks loomiseks lisame eelmisele klassile companion objekti.

object Järjekord {
  def apply[A](xs: A*): Järjekord[A] = ???
}

object TestJärjekord2 {
  def main(args: Array[String]): Unit = {
    val a = Järjekord[Int]()
    assert(a == Järjekord[Int](List(), List()))

    val b = Järjekord(1, 2, 3, 4, 5, 6)
    assert(b == Järjekord(List(1, 2, 3, 4, 5, 6), List()))
  }
}

Seq implementeerimine LinearSeq kaudu

Täiendame oma järjekorda, et see oleks Scala järjenditüüp, st realiseeriks Seq liidest. Antud juhul on kõige lihtsam implementeerida selle alamliidese LinearSeq meetodid isEmpty, head ja tail. Nende kaudu on LinearSeq-is defineeritud järjendi jaoks vajalik iterator, mille tulemusena saame oma järjekorra andmestruktuuril kasutada kõiki Scala järjendite meetodeid ilma neid ise implementeerimata.

case class Järjekord[+A](private val algus: List[A], private val lõpp: List[A])
  extends AbstractSeq[A] // lisatud
    with LinearSeq[A] // lisatud
    with JärjekordOps[A, Järjekord, Järjekord[A]] {

  // eelnevad onTühi, lisaLõppu, võtaAlgusest

  override def isEmpty: Boolean = ???

  override def head: A = ???

  override def tail: Järjekord[A] = ???

  override def toString: String = s"Järjekord($algus,$lõpp)"
}

object TestJärjekord3 {
  def main(args: Array[String]): Unit = {
    val a = Järjekord(6, 7, 8, 9)

    assert(a.head == 6)
    assert(a.tail == Järjekord(7, 8, 9))
    assert(a.tail.tail == Järjekord(8, 9))

    assert(a.forall(_ < 10))
    assert(!a.forall(_ < 7))
    assert(a.exists(_ == 7))
    assert(!a.exists(_ == 5))

    assert(5 +: a == Järjekord(5, 6, 7, 8, 9))
    assert(a :+ 10 == Järjekord(6, 7, 8, 9, 10))

    assert(a.reverse == Järjekord(9, 8, 7, 6))
    assert(a.map(_ + 10) == Järjekord(16, 17, 18, 19))
  }
}

Seq implementeerimine üldkujul

Üldjuhul Seq liidese implementeerimiseks pole LinearSeq kõige efektiivsem viis. Kuigi siin pole see otseselt vajalik, harjutame ka üldjuhul Seq implementeerimist, milleks on vaja defineerida length, apply ja iterator.

case class Järjekord[+A](private val algus: List[A], private val lõpp: List[A])
  extends AbstractSeq[A]
    with LinearSeq[A]
    with JärjekordOps[A, Järjekord, Järjekord[A]] {

  // eelnevad onTühi, lisaLõppu, võtaAlgusest
  // eelnevad isEmpty, head, tail, toString

  override def length: Int = ???

  override def apply(i: Int): A = ???

  override def iterator: Iterator[A] = ???
}

object TestJärjekord4 {
  def main(args: Array[String]): Unit = {
    val b = Järjekord(List(1, 2, 3), List(6, 5, 4))
    assert(b.length == 6)
    assert(b(0) == 1)
    assert(b(1) == 2)
    assert(b(2) == 3)
    assert(b(3) == 4)
    assert(b(4) == 5)
    assert(b(5) == 6)

    //val c: Järjekord[Int] = b.map(_ + 10) // tulemus vale tüüpi
  }
}

Seq meetodite tagastustüübi täpsustamine

Eelmistest testidest võib märgata, et meie järjekorra map, +:, :+ jt meetodid ei tagasta Järjekord-a vaid hoopis LinearSeq-i. Selle parandamiseks peame eelmise lihtsa compation objekti asendama keerukama SeqFactory implementatsiooniga, et andmestruktuuri tagastavad meetodid oskaks meie defineeritud Järjekord isendeid luua.

case class Järjekord[+A](private val algus: List[A], private val lõpp: List[A])
  extends AbstractSeq[A]
    with LinearSeq[A]
    with LinearSeqOps[A, Järjekord, Järjekord[A]] // lisatud
    with IterableFactoryDefaults[A, Järjekord] // lisatud
    with JärjekordOps[A, Järjekord, Järjekord[A]] {

  // eelnevad onTühi, lisaLõppu, võtaAlgusest
  // eelnevad isEmpty, head, tail, toString
  // eelnevad length, apply, iterator

  override def iterableFactory: SeqFactory[Järjekord] = Järjekord
}

object Järjekord extends SeqFactory[Järjekord] {
  override def from[A](source: IterableOnce[A]): Järjekord[A] = ???

  override def empty[A]: Järjekord[A] = ???

  override def newBuilder[A]: mutable.Builder[A, Järjekord[A]] = new mutable.Builder[A, Järjekord[A]] {
    private var list = List[A]()

    override def clear(): Unit = ???

    override def result(): Järjekord[A] = ???

    override def addOne(elem: A): this.type = {
      ???
      this
    }
  }
}

object TestJärjekord5 {
  def main(args: Array[String]): Unit = {
    val b = Järjekord(List(1, 2, 3), List(6, 5, 4))
    val c: Järjekord[Int] = b.map(_ + 10) // tulemus õiget tüüpi
    assert(c == Järjekord(11, 12, 13, 14, 15, 16))
  }
}

Seq meetodite optimeerimine

Kuigi nüüd on nende meetodite tagastustüübid õiged, siis pole nende vaikimisi implementatsioonid kõige efektiivsemad. Näiteks +: ja :+, mis lisavad elemendi algusesse või lõppu, kopeerivad äsjadefineeritud SeqFactory abil ümber kogu järjendi, selle asemel, et järjekorra sisemisi liste otse muuta.

Nende optimeerimiseks saame ise defineerida mõistlikumad prepended ja appended meetodid. Lisaks saame defineerida paremini reverse, map, forall ja exists.

case class Järjekord[+A](private val algus: List[A], private val lõpp: List[A])
  extends AbstractSeq[A]
    with LinearSeq[A]
    with LinearSeqOps[A, Järjekord, Järjekord[A]]
    with IterableFactoryDefaults[A, Järjekord]
    with JärjekordOps[A, Järjekord, Järjekord[A]] {

  // eelnevad onTühi, lisaLõppu, võtaAlgusest
  // eelnevad isEmpty, head, tail, toString
  // eelnevad length, apply, iterator
  // eelnev iterableFactory

  override def appended[B >: A](elem: B): Järjekord[B] = ???

  override def prepended[B >: A](elem: B): Järjekord[B] = ???

  override def reverse: Järjekord[A] = ???

  override def map[B](f: A => B): Järjekord[B] = ???

  override def forall(p: A => Boolean): Boolean = ???

  override def exists(p: A => Boolean): Boolean = ???
}
  • 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