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 = ??? }