Differences

This shows you the differences between two versions of the page.

Link to this comparison view

Both sides previous revision Previous revision
Next revision
Previous revision
pp:2023:scala:l06 [2023/04/01 17:11]
tpruteanu [6.2. Polymorphic expressions]
pp:2023:scala:l06 [2023/04/08 07:49] (current)
pdmatei
Line 1: Line 1:
-====== Lab 06. Polymorphism ​======+====== Lab 6. Scala on steroids ​======
  
-===== 6.1. Maps ===== +==== 5-Tic-Tac-Toe ====
-Maps are collections of **(key, value)** pairs. Keys should be unique, and every key is associated with a value. \\  +
-Some of the fundamental operations on maps include: +
-  * retrieving / updating the value associated with a key +
-  * adding a **(key, value)** pair +
-  * removing a **(key, value)** pair +
-You can find more information on maps on the Scala Docs (https://​docs.scala-lang.org/​overviews/​collections/​maps.html). \\ +
-<note important>​ maps are **immutable**,​ functions working with maps return a new updated version instead of modifing the map </​note>​ +
-Some examples with the most used functions can be found below. +
-<​hidden>​ +
-<code scala>​let map = Map(1 -> 2, 3 -> 4): Map[Int, Int]</​code>​ +
-  * Adding a (key, value) pair to a map +
-<code scala> +
-map + (5 -> 6)  // Map(1 -> 2, 3 -> 4, 5 -> 6) +
-map + (3 -> 5)  // Map(1 -> 2, 3 -> 5)  -- if key exists, it updates the value +
-</​code>​ +
-  * Removing the pair associated with a key +
-<code scala> +
-map - (3 -> 4)  // Map(1 -> 2) +
-</​code>​ +
-  * Querying a value +
-<code scala> +
-map get 1  // return 2 +
-map get 3  // return 4 +
-map getOrElse (1, 0)  // return 2 +
-map getOrElse (5, 0)  // return 0 (if key doesn'​t exist, return provided value) +
-map contains 1  // True +
-map contains 5  // False +
-</​code>​ +
-  * Higher-order functions +
-<code scala> +
-map mapValues (x => x + 5)  // Map(1 -> 7, 2 -> 9) +
-map filterKeys (x => x <2)  // Map(1 -> 2) +
-</​code>​ +
-</​hidden>​+
  
-==== Exercises ==== +**Tic Tac Toe** is usually played on 3x3 boardmarking positions by each player in rounds. Our game is slightly different ​(usually called 5-in-a-row): 
-We represent a gradebook as map which holdsfor each student ​(encoded as String), its grade (an Int). We implement a gradebook as a case class, as follows+  * it can be played on a square board of any size **larger or equal to 5**. 
-<code scala> +  * A player wins if it has marked a linecolumn or diagonal of **5 consecutive positions** in a row. 
-case class Gradebook(book:​ Map[String,Int]) +
-</​code>​+
  
-**6.1.1.** Add method ​''​+'' ​to the class, which adds new entry to the gradebook. +Example of winning position for ''​X'' ​on 5x5 board: 
-<​code ​scala+<​code>​ 
-def + (entry: (String, Int)): Gradebook = ???+X...0 
 +0X.0. 
 +..X0. 
 +...X. 
 +.0..X
 </​code>​ </​code>​
  
-**6.1.2.** Add method ​''​setGrade'' ​which modifies the grade of given student. Note that your method should return an updated gradebook, not modify the current one, since the latter is **immutable**. +Example of winning position for ''​0'' ​on 7x7 board: 
-<​code ​scala+<​code>​ 
-def setGrade(name:​ String, newGrade: Int): Gradebook = ???+.X...X. 
 +...0... 
 +...0... 
 +.X.0..X 
 +0..0..0 
 +...0... 
 +...X...
 </​code>​ </​code>​
  
-**6.1.3.** Add a method ''​++''​ which merges two gradebooks, if a student is in both gradebook, take the higher of the two grades. +==== Encodings ====
-<code scala> +
-def ++(other: Gradebook): Gradebook ​+
-  // the best strategy is to first implement the update of an entry into an existing Map... +
-  def updateBook(book:​ Map[String,​Int],​ pair: (String,​Int)):​ Map[String,​Int] ​??? +
-  // and then use a fold to perform updates for all pairs of the current map. +
-  ??? +
-+
-</​code>​+
  
-**6.1.4. (!)** Add a method which returns a mapcontaining ​as key, each possible grade (from 1 to 10) and a value, ​the **number** of students having that grade. ​(Hint: follow the same strategy ​as for the previous exercise).+  ​In your project template''​X''​ is encoded ​as the **first** player ​(''​One''​),​ and ''​0'', ​as ''​Two''​.
 <code scala> <code scala>
-def gradeNo: Map[Int,​Int] = ??? +trait Player {} 
-</​code>​ +case object One extends Player ​
- +  ​override ​def toStringString = "​X"​
-===== 6.2. Polymorphic expressions ===== +
- +
-In this section we will implement a very simple expression evaluation (which might be part of a programming language), and we will experiment with different features that will: \\ +
-  - make the code easier to use +
-  - make the code more general, suitable for a broad range of scenarios (easier to extend) +
- +
-**NOTE**: In this process, we will have to rewrite and delete parts of the code from previous steps in order to improve it. +
- +
-Start with the following polymorphic type definition for a expression:  +
- +
-<code scala> +
-trait Expr[A] ​+
-  def eval()A+
 } }
-</​code>​ +case object Two extends ​Player ​
- +  override def toStringString ​"​0"​
-**6.2.1.** Implement ​case classes ''​BoolAtom'',​ ''​BoolAdd''​ and ''​BoolMult'',​ which evaluates ''​Add''​ as boolean //or// and ''​Mult''​ as boolean //and//. +
-<code scala> +
-case class BoolAtom(b: Boolean) ​extends ​Expr[Boolean] ​+
-  override def eval()Boolean ​b+
 } }
-case class BoolAdd(left:​ Expr[Boolean],​ right: Expr[Boolean]) extends Expr[Boolean] { +case object Empty extends ​Player ​
-  override def eval(): Boolean = ??? +  override def toStringString ​"​."​
-+
-case class BoolMult(left:​ Expr[Boolean],​ right: Expr[Boolean]) ​extends ​Expr[Boolean] ​+
-  override def eval()Boolean ​???+
 } }
 </​code>​ </​code>​
- +  * A ''​Board''​ is encoded ​as a List of Lists of **positions** (i.e. a matrix)where a position ​can be ''​One''​''​Two'' ​or ''​Empty''​. We make no distinction in the code between ​position and a player, although ​''​Empty'' ​cannot be seen as a valid player. This makes the code slightly easier to write.
-The code structure can be easily deployable for other types, such as **Int**, **Double** etc, but it's inconvenient to be defining different types for each such caseIn order to make our code more extensible in this sense, we can add a few ingredients\\ +
-We will add new case class ''​Strategy''​which stores the operations that can be performed, in our case ''​Add'' ​and ''​Mult''​. And we will have our ''​eval'' ​function take a ''​Strategy''​ as a argument. +
 <code scala> <code scala>
-case class Strategy[A(add: (A,A) => A, mult: (A,A) => A)+type Line = List[Player] 
 +type BoardList ​List[Line]
  
-trait Expr[A] { +case class Board(bBoardList
-   def eval(sStrategy[A]): A+  override def toStringString = ???
 } }
 </​code>​ </​code>​
  
-**6.2.2.** Adjust ​the rest of your code with the new definitions (**NOTE**: ''​eval''​ should have more general implementation now)Implement ''​boolStrategy''​. +==== Tasks ==== 
-<code scala>+The following functions have a given signatureHowever, it is up to the student to decide whether these will be methods ​of a class or just simple functions.
  
-val boolStrategy = Strategy[Boolean]( +**6.1.1.** Write a function which converts a string into a ''​Board''​. As a helperyou can use ''​_.split)''​ where c is a separator stringand ''​_.toList''​. The best solution is to use a combination of ''​map''​ calls with the above mentioned functions. A string is encoded exactly as in the examples shown above: 
-    (leftright) => ,  // add implementation +  * there are no whitespaces - empty positions are marked by the character '​.'​ 
-    ​(left, right) =>    // mult implementation +  * lines are delimited by '​\n' ​(the last line does not have a trailing '​\n'​).
-+
- +
-val expr1 = BoolMult(BoolAdd(BoolAtom(true), BoolAtom(false)),​ BoolAtom(false)) +
-val expr2 = BoolMult(BoolAdd(BoolAtom(true),​ BoolAtom(false)),​ BoolAtom(true)) +
- +
-println(expr1.eval(boolStrategy))  // false +
-println(expr2.eval(boolStrategy)) ​ // true +
-</​code>​+
  
-**6.2.3.** If you implemented ''​eval''​ correctly at **6.3.2.**, you might notice that it doesn'​t rely on the ''​Boolean''​ type anymore. Add more general case classes ''​Atom'',​ ''​Add''​ and ''​Mult''​. 
 <code scala> <code scala>
-case class Atom[A](aAextends Expr[A] ​+def makeBoard(sString): Board = 
-    ​override ​def eval(fStrategy[A]) = ??? +    def toPos(cChar): Player ​
-} +      c match { 
- +        case '​X'​ => One 
-case class Add[A](left:​ Expr[A], right: Expr[A]) extends Expr[A] { +        case '​0'​ => Two 
-    ​override def eval(f: Strategy[A]):​ A ??? +        case _ => Empty 
-} +      
- +    ???
-case class Mult[A](left:​ Expr[A], right: Expr[A]) extends Expr[A] { +
-    ​override def eval(f: Strategy[A]):​ A = ???+
 } }
 </​code>​ </​code>​
  
-**6.2.4.** Currently, writing down expressions ​is cumbersome, due to the escalating number of parenthesesIt would be advisable to allow building expressions ​using a friendlier syntax, such as: ''​Atom(1) + Atom(2) * Atom(3)''​. ​This is possible if we define the operations ''​+''​ and ''​*''​ as member functions. Then, for instance ''​Atom(1) + Atom(2)''​ could be translate ​from ''​Atom(1).+(Atom(2))''​. Where would it be most convenient for you - the programmer, to define functions ''​+''​ and ''​*''?​ Define them! \\ +**6.1.2.** Write a function which checks if a position on the board is freeRecall that list indexing can be done using ''​l(_)''​. ​Positions are numbered ​from 0.
-\\ +
-**Hint:** +
-<​hidden>​ +
-In Scala it is possible to define function implementations in traits. Define ''​+''​ and ''​*''​ accordingly. +
-</​hidden>​ +
-\\ +
- +
-So far, our expressions contain just values (of different sorts). Making a step towards a programming language (like we mentioned above), we would like to also introduce **variables** in our expressions. To do so, we need to **interpret** each variable as boolean or int etc. We define a **store**:+
 <code scala> <code scala>
-type Store[A] = Map[StringA]+def isFree(x:​Inty:​Int):​Boolean = ???
 </​code>​ </​code>​
-to be a mapping from values of type ''​String''​ (variable names) to their values. \\ 
  
-**6.2.5.**  +**6.1.3.** Write a function ​which returns the //​opponent//​ of player: 
-  * Modify ''​eval'' ​function ​to also take **store** as an argument, modify the code accordingly +<code scala> 
-  * Add a new **case class** ''​Var''​ which allows creating variables as expressions (e.g. ''​Var("​x"​) + Atom(1)''​ should be a valid expression)+def complement(p: Player): Player = ??? 
 +</​code> ​
  
-<​hidden>​ +**6.1.4.** We want to write a function which converts a board to a stringfollowing ​the same strategy. Complete ​the ''​toString'' ​in the Board classHintinstead of ''​foldRight''​you can use ''​reduce''​ which works quite similarlybut without requiring an accumulator.
-You don't have to modify the expressions from **6.3.5**, you can use ''​eval''​ with just 1 parameter by overloading ​the method and calling ​the new ''​eval'' ​with the empty Map. +
-<code scala> +
-def eval(sStrategy[A]) = eval(sMap[StringA]()) +
-</​code>​ +
-</​hidden>​ +
-\\+
  
-**6.2.6.** We have not treated ​the case when the expression uses variables not found in the store. To do so, we change the type signature ​of eval to:+**6.1.5.** Write a function which returns ​the //​columns// ​of a board:
 <code scala> <code scala>
-def eval (sStrategy[A],​ store: Store[A]): Option[A]+def getColumnsBoard = ???
 </​code>​ </​code>​
-Modify your implementation accordingly. If an expression uses variables not present in the store, ''​eval''​ should return ''​None''​. 
  
-===== 6.3Polynomials ===== +**6.1.6.** Implement the following two functions for extracting the first and second diagonal, as lines, from board. Hint: use for comprehensions.
-Consider a polynomial encoded as a map, where each present key denotes a power of **x**, and a value denotes its coefficient+
 <code scala> <code scala>
-Map(2 -> 1, 1 -> 2, 0 -> 1 // encodes x^2 + 2*x + 1+def getFstDiag(): Line = ??? 
 +def getSndDiag():​ Line = ???
 </​code>​ </​code>​
-<code scala> 
-case class Polynomial (terms: Map[Int,​Int]) ​ 
-</​code>​ 
-<​hidden>​ 
-You can override the ''​toString''​ method to to see your results in a more friendly format: 
-<code scala> 
-case class Polynomial (terms: Map[Int,​Int]) { 
-    override def toString: String = { 
-        def printRule(x:​ (Int, Int)): String = x match { 
-            case (0, coeff) => coeff.toString 
-            case (1, coeff) => coeff.toString ++ "​*x"​ 
-            case (p, coeff) => coeff.toString ++ "​*x^"​ ++ p.toString 
-        } 
  
-        terms.toList.sortWith(_._1 >= _._1) +**6.1.7.** Implement the following functions for extracting diagonals above/below the first/​second diagonal, as lines. It's not really necessary to make sure that at least 5 positions are available, for now. Hint: if one function must be implemented with element-by-element iteration, ​the three other can be implemented using each-other, as single-line calls.
-                      .map(printRule) +
-                      .reduce(_ ++ " + " ++ _) +
-    } +
-+
-</​code>​ +
-</​hidden>​ +
-\\ +
-**6.3.1.** Add a method ​''​*''​ which multiplies ​the polynomial by a given coeficient:+
 <code scala> <code scala>
-def * (nInt)Polynomial ​= ???+def getAboveFstDiagList[Line] = ??? 
 +def getBelowFstDiagList[Line] = ??? 
 +def getAboveSndDiag:​ List[Line] = ??? 
 +def getBelowSndDiag:​ List[Line] ​= ???
 </​code>​ </​code>​
  
-**6.3.2.** Implement ​method ''​hasRoot'' ​which checks if a given integer ​is a root of the polynomial.+**6.1.8.** Write function ​which checks if a player ​is the winner. Hint: functions ''​l.forall(_)''​ and ''​l.exists(_)''​ may be very helpful, together with patterns.
 <code scala> <code scala>
-def hasRoot(rInt): Boolean = ???+def winner(pPlayer): Boolean = ???
 </​code>​ </​code>​
  
-**6.3.3.** Implement ​method ''​+'' ​which adds a given polynomial ​to this one (Hint: this operation is very similar to gradebook merging).+**6.1.9.** Write function ​which updates a position from the board, with a given player. The position need not be empty and you are not required ​to check thisHint: re-use an inner aux-function together with ''​take''​ and ''​drop''​.
 <code scala> <code scala>
-def (p2Polynomial): Polynomial ​= ???+def update(pPlayer)(ln: Int, col: Int) : Board = ??? 
 </​code>​ </​code>​
  
-**6.3.4.** Implement ​method ''​*'' ​which multiplies ​two polynomials.+**6.1.10.** Write function ​which generates all possible next-moves for any of the two players. A next-move consists in a new board, where the player-at-hand played his move.
 <code scala> <code scala>
-def (p2Polynomial): Polynomial ​= ???+def next(pPlayer): List[Board] ​= ???
 </​code>​ </​code>​
  
-**6.3.5.** Implement ''​polynomialStrategy''​ and try to evaluate a few expressions with polynomials.+==== Testing ====
  
-<​hidden>​ +Use the following ​board configurations to test your solutions:
-You can test your code with the following ​expression:+
 <code scala> <code scala>
-val p1 Polynomial(Map(2 -> 1, 1 -> 2, 0 -> 1)) +val t1 
-val p2 = Polynomial(Map(3 -> 1, 1 -> 3, 0 -> 2)) +  """​X0X0X0 
-val expr = (Atom(p1) + Atom(p2)) * Atom(p1)+    ​|0X0X0X 
 +    |X0X0X0 
 +    |.XX0.. 
 +    |X00... 
 +    |X0X0X0"""​.stripMargin
  
-println(expr.eval(polynomialStrategy)) +val t2 = 
-// result: x^5 + 3*x^4 + 8*x^3 + 14*x^2 + 11*x + 3+  """​.....
 +    ​|...... 
 +    |...... 
 +    |.XX... 
 +    |.0000. 
 +    |......"""​.stripMargin 
 + 
 +val t3 = 
 +  """​0X0X0. 
 +    |000.X0 
 +    |0.0X.. 
 +    |0..0.. 
 +    |0X..0X 
 +    |...X.."""​.stripMargin
 </​code>​ </​code>​
-</​hidden>​ 
- 
-