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:
You can find more information on maps on the Scala Docs (https://docs.scala-lang.org/overviews/collections/maps.html).
Some examples with the most used functions can be found below.
We represent a gradebook as a map which holds, for each student (encoded as String), its grade (an Int). We implement a gradebook as a case class, as follows:
case class Gradebook(book: Map[String,Int])
5.1.1. Add a method +
to the class, which adds a new entry to the gradebook.
def + (entry: (String, Int)): Gradebook = ???
5.1.2. Add a method setGrade
which modifies the grade of a given student. Note that your method should return an updated gradebook, not modify the current one, since the latter is immutable.
def setGrade(name: String, newGrade: Int): Gradebook = ???
5.1.3. Add a method ++
which merges two gradebooks, if a student is in both gradebook, take the higher of the two grades.
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. ??? }
5.1.4. (!) Add a method which returns a map, containing 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).
def gradeNo: Map[Int,Int] = ???
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:
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:
trait Expr[A] { def eval(): A }
5.2.1. Implement case classes BoolAtom
, BoolAdd
and BoolMult
, which evaluates Add
as boolean or and Mult
as boolean and.
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] { override def eval(): Boolean = ??? } case class BoolMult(left: Expr[Boolean], right: Expr[Boolean]) extends Expr[Boolean] { override def eval(): Boolean = ??? }
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 case. In order to make our code more extensible in this sense, we can add a few ingredients.
We will add a 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.
case class Strategy[A] (add: (A,A) => A, mult: (A,A) => A) trait Expr[A] { def eval(s: Strategy[A]): A }
5.2.2. Adjust the rest of your code with the new definitions (NOTE: eval
should have a more general implementation now). Implement boolStrategy
.
val boolStrategy = Strategy[Boolean]( (left, right) => , // add implementation (left, right) => // mult implementation ) 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
5.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
.
case class Atom[A](a: A) extends Expr[A] { override def eval(f: Strategy[A]) = ??? } case class Add[A](left: Expr[A], right: Expr[A]) extends Expr[A] { override def eval(f: Strategy[A]): A = ??? } case class Mult[A](left: Expr[A], right: Expr[A]) extends Expr[A] { override def eval(f: Strategy[A]): A = ??? }
5.2.4. Currently, writing down expressions is cumbersome, due to the escalating number of parentheses. It 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!
Hint:
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:
type Store[A] = Map[String, A]
to be a mapping from values of type String
(variable names) to their values.
5.2.5.
eval
function to also take a store as an argument, modify the code accordinglyVar
which allows creating variables as expressions (e.g. Var(“x”) + Atom(1)
should be a valid expression)
5.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:
def eval (s: Strategy[A], store: Store[A]): Option[A]
Modify your implementation accordingly. If an expression uses variables not present in the store, eval
should return None
.
Consider a polynomial encoded as a map, where each present key denotes a power of x, and a value denotes its coefficient.
Map(2 -> 1, 1 -> 2, 0 -> 1) // encodes x^2 + 2*x + 1
case class Polynomial (terms: Map[Int,Int])
5.3.1. Add a method *
which multiplies the polynomial by a given coeficient:
def * (n: Int): Polynomial = ???
5.3.2. Implement a method hasRoot
which checks if a given integer is a root of the polynomial.
def hasRoot(r: Int): Boolean = ???
5.3.3. Implement a method +
which adds a given polynomial to this one (Hint: this operation is very similar to gradebook merging).
def + (p2: Polynomial): Polynomial = ???
5.3.4. Implement a method *
which multiplies two polynomials.
def * (p2: Polynomial): Polynomial = ???
5.3.5. Implement polynomialStrategy
and try to evaluate a few expressions with polynomials.