Skip to content

Commit

Permalink
Added new excercises
Browse files Browse the repository at this point in the history
Option and pattern matching exercises added
  • Loading branch information
calvin-thomas committed Mar 25, 2022
1 parent e07d759 commit 692055c
Show file tree
Hide file tree
Showing 5 changed files with 264 additions and 1 deletion.
2 changes: 1 addition & 1 deletion build.sbt
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
scalaVersion := "2.13.8"
scalaVersion := "2.13.7"

name := "scala-exercises"
organization := "io.turntabl"
Expand Down
34 changes: 34 additions & 0 deletions src/main/scala/OptionFunctions.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import java.time.LocalDate

// Address case class to store an address where only line1 and postcode are mandatory
case class Address(
line1: String,
line2: Option[String],
line3: Option[String],
line4: Option[String], // None, Some("Badger Farm").
line5: Option[String],
postcode: String)

// Person case class forename and address are optional, with address using the Address type from above
case class Person(forename: Option[String], surname: String, dateOfBirth: LocalDate, address: Option[Address])

object OptionFunctions {

/** Return everyone from the list passed in who has a forename */
def getPeopleWithForenames(listOfPeople: List[Person]): List[Person] = {
???
}

/** Return everyone from the list but default their forename to unknown if it doesn't exist */
def getPeopleWithDefaultForename(listOfPeople: List[Person]): List[Person] = {
???
}

/** Get the first line and postcode of all the addresses from the list of People*/
def getAddresses(listOfPeople: List[Person]): List[(String, String)] = {
???
}

}


54 changes: 54 additions & 0 deletions src/main/scala/PatternMatching.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/** All exercises in here should be completed using pattern matching even when
* there are better solutions using higher order functions */

object PatternMatching {

/* If number is < 0 return " <0 "
0 to 18 raturn "0<=number<=18"
19 to 35 return "19<=number<=35"
36 to 65 return "36<=number<=54"
Over 65 return " >65
// Hint, pattern matching allows for guards
*/
def numberInRange(number: Int):String = {
???
}

/* Take a List and if it is empty return 0, if it contains 3 elements return the third, otherwise return the first */
def thirdOrFirst(l: List[Int]): Int = {
???
}

/* If List starts with 0, 1 then return "Starts with 0,1"
If list starts with 1,2 then return "starts with 1,2"
If list starts with anything else return "Doesn't start with 0,1 or 1,2
*/
def startsWithCheck(l:List[Int]): String = {
???
}

/* Write a function that does the following:
* Gives the length of a list, Gives the size of a map, list or vector or gives -1 otherwise */
def generalSize(x: Any):Int = {
???
}

sealed trait Expression
case class Variable(name:String) extends Expression
case class Number(number: Double) extends Expression
case class UnaryOperation(operator: String, argument: Expression) extends Expression
case class BinaryOperation(operator: String, left: Expression, right: Expression) extends Expression

// Write a function that uses pattern matching to simplify double negation, adding and multiplying by 1
// e.g. UnOp("-", UnOp("-", e) == e (think --1 == 1)

// Think carefully about what is actually happening here, this a powerful example of pattern matching
// where the pattern matching is deep. It's matching not just the type of the Object, but the patterns within
// the object too and then where there are constructors, the values within those too.

// Hint - You only need one line for each case and a default case
def simplify(expr: Expression): Expression = {
???
}
}
38 changes: 38 additions & 0 deletions src/test/scala/OptionFunctionSuite.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import munit.FunSuite

import java.time.LocalDate

class OptionFunctionSuite extends FunSuite{

/** Simple dummy test data */
val testAddress1 = Address("1 Test Street", None, None, None, None, "TT11 1TT")
val testAddress2 = Address("2 Test Street", Some("Test Area"), Some("Test Bigger Area"), Some("Test City"), Some("Test County"), "TT11 1TT")

val testPerson1 = Person(None, "Thomas", LocalDate.of(1982, 7, 26), Some(testAddress1))
val testPerson2 = Person(Some("Thomas"), "Thomas", LocalDate.of(1980, 1, 1), Some(testAddress2))
val testPerson3 = Person(Some("Mark"), "Markinson", LocalDate.of(2001, 3, 3), None)

test("getPeopleWithForenames returns an empty list if there are no forenames for any people in the list") {
val listOfPeople = testPerson1 :: Nil
assertEquals(OptionFunctions.getPeopleWithForenames(listOfPeople), Nil)
}

test("getPeopleWithForenames returns a list with two people in when there are two forenames in the list") {
val listOfPeople = testPerson1 :: testPerson2 :: testPerson3 :: Nil
assertEquals(OptionFunctions.getPeopleWithForenames(listOfPeople).size, 2)
}

test("getPeopleWithDefaultForenames returns a list with all the people however the forename is set to Unknown if the person doesn't have a forename") {
val listOfPeople = testPerson1 :: testPerson2 :: testPerson3 :: Nil
val listOfPeopleWithDefaultForename = OptionFunctions.getPeopleWithDefaultForename(listOfPeople)

assertEquals(listOfPeopleWithDefaultForename.size, 3)
assertEquals(listOfPeopleWithDefaultForename.head.forename, Some("Unknown"))
assertEquals(listOfPeopleWithDefaultForename.tail.head.forename, Some("Thomas"))
}

test("getFirstLineAndPostcodes should return a list of tuples containing each of the first lines and postcodes from the list") {
val listOfPeople = testPerson1 :: testPerson2 :: testPerson3 :: Nil
assertEquals(OptionFunctions.getAddresses(listOfPeople), List(("1 Test Street","TT11 1TT"), ("2 Test Street", "TT11 1TT")))
}
}
137 changes: 137 additions & 0 deletions src/test/scala/PatternMatchingSuite.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
import PatternMatching.{BinaryOperation, Number, UnaryOperation, Variable, simplify}
import munit.{FunSuite, ScalaCheckSuite}
import org.scalacheck.Gen
import org.scalacheck.Prop.{forAll, propBoolean}

class PatternMatchingSuite extends FunSuite with ScalaCheckSuite {

test("Number in range returns <0 for a negative number") {
forAll(Gen.negNum[Int]) { n =>
PatternMatching.numberInRange(n) == s"${n}<0"
}
}

test("Number in range 0 to 18 returns as expected") {
forAll(Gen.choose[Int](0,18)) { n =>
PatternMatching.numberInRange(n) == s"0<=${n}<=18"
}
}

test("Number in range 19 to 35 returns as expected") {
forAll(Gen.choose[Int](19, 35)) { n =>
PatternMatching.numberInRange(n) == s"19<=${n}<=35"
}
}

test("Number in range 36 to 65 returns as expected") {
forAll(Gen.choose[Int](36, 65)) { n =>
PatternMatching.numberInRange(n) == s"36<=${n}<=65"
}
}

test("Number > 65 returns as expected") {
forAll(Gen.choose[Int](66, Int.MaxValue)) { n =>
PatternMatching.numberInRange(n) == s"${n}>65"
}
}

test("If list length is three then the third element is returned") {
val l = List(1,2,3)
PatternMatching.thirdOrFirst(l) == 3
}

test("If list size is empty then thirdOrFirst should return 0 ") {
val l:List[Int] = Nil
PatternMatching.thirdOrFirst(l) == 0
}

test("If list size is not three or empty then the head of the list should be returned") {
forAll { (l:List[Int]) =>
(l.size != 3 && l.nonEmpty) ==> {
PatternMatching.thirdOrFirst(l) == l.head
}
}
}

test("List starting with 0,1 returns the correct string") {
val l = List(0,1,2,3,4,5,6,7,8,9,10)
assertEquals(PatternMatching.startsWithCheck(l), "Starts with 0,1")
assertEquals(PatternMatching.startsWithCheck(l.take(2)), "Starts with 0,1")

}

test("List starting with 1,2 returns the correct string") {
val l = List(1,2,3,4,5,6,7,8,9,10)
assertEquals(PatternMatching.startsWithCheck(l), "Starts with 1,2")
assertEquals(PatternMatching.startsWithCheck(l.take(2)), "Starts with 1,2")
}

test("List not starting with 0,1 or 1,2 returns the correct string") {
val l = List(2,3,4,5)
assertEquals(PatternMatching.startsWithCheck(l), "Doesn't start with 0,1 or 1,2")
}

test("General size returns the correct result for lists") {
forAll { (l: List[Int]) =>
PatternMatching.generalSize(l) == l.size
}
}

test("General size returns the correct result for vectors") {
forAll { (v: Vector[Int]) =>
PatternMatching.generalSize(v) == v.size
}
}

test("General size returns the correct result for lists") {
forAll { (m: Map[Int, Int]) =>
PatternMatching.generalSize(m) == m.size
}
}

test("General size returns the correct result for lists") {
forAll { (s: String) =>
PatternMatching.generalSize(s) == s.length
}
}

/*
Here we define a series of case classes that extend the trait. Think of this like an enum
that we can use to compose expressions together.
As they're case classes they'll have:
* Factory method (no need to use new)
* Val prefixes, i.e. we can do v.name for a v Variable
* Equals and hash code are implemented to compare sensibly
* A copy method is available
*/

test("Double negation simplification ensures that it is simplified to just the expression") {
val numbers = for(n <- Gen.double) yield Number(n)

forAll(numbers) { (n: Number) =>
simplify(UnaryOperation("-", UnaryOperation("-", n))) == n
}
}

test("Adding zero to a variable number simplifies to just the variable it is simplified to just the expression") {

val numbers = for(n <- Gen.double) yield Number(n)

forAll(numbers) { (n: Number) =>
simplify(BinaryOperation("+", n, Number(0))) == n
}
}

test("Multiplying by 1 to a variable number simplifies to just the variable it is simplified to just the expression") {

val numbers = for(n <- Gen.double) yield Number(n)

forAll(numbers) { (n: Number) =>
simplify(BinaryOperation("*", n, Number(1))) == n
}
}

}


0 comments on commit 692055c

Please sign in to comment.