forked from Godwin-turntabl/scala-exercises
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Option and pattern matching exercises added
- Loading branch information
1 parent
e07d759
commit 692055c
Showing
5 changed files
with
264 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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" | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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)] = { | ||
??? | ||
} | ||
|
||
} | ||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 = { | ||
??? | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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"))) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} | ||
} | ||
|
||
} | ||
|
||
|