From 9d3820e01fd6a7c3e4b8aa605f1f35022d355565 Mon Sep 17 00:00:00 2001 From: zainab-ali Date: Tue, 17 Mar 2020 20:00:44 +0000 Subject: [PATCH 1/5] Add Day 1 exercise --- src/main/scala/advent/solutions/Day1.scala | 47 +++++++++++----------- 1 file changed, 23 insertions(+), 24 deletions(-) diff --git a/src/main/scala/advent/solutions/Day1.scala b/src/main/scala/advent/solutions/Day1.scala index b57d01e..5aa3ad7 100644 --- a/src/main/scala/advent/solutions/Day1.scala +++ b/src/main/scala/advent/solutions/Day1.scala @@ -1,7 +1,5 @@ package advent.solutions -import scala.annotation.tailrec - /** Day 1: The Tyranny of the Rocket Equation * * @see https://adventofcode.com/2019/day/1 @@ -16,7 +14,7 @@ object Day1 { * @return The fuel required to to launch the module */ def fuel(mass: Int): Int = { - (mass / 3) - 2 + ??? } /** Calculates the sum of the fuel required to launch each module of a given mass @@ -24,10 +22,24 @@ object Day1 { * @param masses The masses of each module * @return The sum of the fuel required to launch each module */ - def sumOfFuel(masses: List[Int]): Int = { - masses.map(fuel).sum + def sumOfFuel( + masses: List[Int] + ): Int = { + ??? } + // Use the two functions below to complete the exercise + // private def calculateFuels( + // masses: List[Int] + // ): List[Int] = { + // ??? + // } + + // private def sumFuels( + // fuels: List[Int] + // ): Int = { + // ??? + // } } object Part2 { @@ -38,13 +50,7 @@ object Day1 { * @return The fuel required to launch the module, plus the fuel required to launch that fuel */ def totalFuel(mass: Int): Int = { - @tailrec - def go(currentFuel: Int, accum: Int): Int = - if (currentFuel < 0) accum - else go(Part1.fuel(currentFuel), accum + currentFuel) - - go(Part1.fuel(mass), 0) - + ??? } /** Calculates the sum of the total fuel required to launch each module of a given mass @@ -52,8 +58,10 @@ object Day1 { * @param masses The masses of each module * @return The sum of the total fuel required to launch each module */ - def sumOfTotalFuel(masses: List[Int]): Int = { - masses.map(totalFuel).sum + def sumOfTotalFuel( + masses: List[Int] + ): Int = { + ??? } } @@ -62,20 +70,11 @@ object Day1 { def main(args: Array[String]): Unit = { // Copy the puzzle input from https://adventofcode.com/2019/day/1/input - val puzzleInput: List[Int] = List(1, 12, 2, 3, 1, 1, 2, 3, 1, 3, 4, 3, 1, 5, - 0, 3, 2, 10, 1, 19, 1, 19, 9, 23, 1, 23, 6, 27, 1, 9, 27, 31, 1, 31, 10, - 35, 2, 13, 35, 39, 1, 39, 10, 43, 1, 43, 9, 47, 1, 47, 13, 51, 1, 51, 13, - 55, 2, 55, 6, 59, 1, 59, 5, 63, 2, 10, 63, 67, 1, 67, 9, 71, 1, 71, 13, - 75, 1, 6, 75, 79, 1, 10, 79, 83, 2, 9, 83, 87, 1, 87, 5, 91, 2, 91, 9, 95, - 1, 6, 95, 99, 1, 99, 5, 103, 2, 103, 10, 107, 1, 107, 6, 111, 2, 9, 111, - 115, 2, 9, 115, 119, 2, 13, 119, 123, 1, 123, 9, 127, 1, 5, 127, 131, 1, - 131, 2, 135, 1, 135, 6, 0, 99, 2, 0, 14, 0) + val puzzleInput: List[Int] = Nil // Solve your puzzle using the functions in parts 1 and 2 val part1 = Part1.sumOfFuel(puzzleInput) println(part1) - val part2 = Part2.sumOfTotalFuel(puzzleInput) - println(part2) } // scalastyle:on } From 6844dcb27b082e81a1f2cf3698f3346fa5c453d1 Mon Sep 17 00:00:00 2001 From: zainab-ali Date: Tue, 24 Mar 2020 14:13:58 +0000 Subject: [PATCH 2/5] Delete day 2 solutions --- src/main/scala/advent/solutions/Day2.scala | 117 -------- src/main/scala/advent/solutions/Day3.scala | 312 --------------------- src/test/scala/advent/Day2Spec.scala | 29 -- src/test/scala/advent/Day3Spec.scala | 70 ----- 4 files changed, 528 deletions(-) delete mode 100644 src/main/scala/advent/solutions/Day2.scala delete mode 100644 src/main/scala/advent/solutions/Day3.scala delete mode 100644 src/test/scala/advent/Day2Spec.scala delete mode 100644 src/test/scala/advent/Day3Spec.scala diff --git a/src/main/scala/advent/solutions/Day2.scala b/src/main/scala/advent/solutions/Day2.scala deleted file mode 100644 index cd2da52..0000000 --- a/src/main/scala/advent/solutions/Day2.scala +++ /dev/null @@ -1,117 +0,0 @@ -package advent.solutions - -/** Day 2: 1202 Program Alarm - * - * @see https://adventofcode.com/2019/day/2 - */ -object Day2 { - - object Part1 { - - /** Runs an Intcode program - * - * @param program A list of opcodes and positions - * @return The program after having been run on itself - */ - def run(program: List[Int]): List[Int] = { - val start: Int = 0 - val step: Int = 4 - val indexList = List.range(start, program.length, step) - indexList.foldLeft(program)((acc, indexOfOpcode) => - repair(acc, indexOfOpcode) - ) - } - - final case class Entry(index: Int, value: Int) - - private val additionCode = 1 - private val multiplicationCode = 2 - private val lookupAndAdd = lookupAndOperate(_ + _)(_, _) - private val lookupAndMultiply = lookupAndOperate(_ * _)(_, _) - - private def repair(program: List[Int], indexOfOpcode: Int): List[Int] = { - if (program(indexOfOpcode) == additionCode) - repairProgram(program, lookupAndAdd(indexOfOpcode, program)) - else if (program(indexOfOpcode) == multiplicationCode) - repairProgram(program, lookupAndMultiply(indexOfOpcode, program)) - else program - } - - private def lookupAndOperate( - f: (Int, Int) => Int - )(indexOfOpcode: Int, program: List[Int]): Entry = { - Entry( - program(indexOfOpcode + 3), - f( - program(program(indexOfOpcode + 1)), - program(program(indexOfOpcode + 2)) - ) - ) - } - - private def repairProgram( - program: List[Int], - entry: Entry - ): List[Int] = { - program.updated(entry.index, entry.value) - } - } - - object Part2 { - - import Day2.Part1.run - - /** Represents the two numbers provided at addresses 1 and 2 of an Intcode program */ - final case class Input(noun: Int, verb: Int) - - /** Calculates the input required to produce a given output - * - * @param program A list of opcodes and positions. The positions at address 1 and 2 will be replaced with input - * @param output The given output of the program - * @return The input that would be entered in the program to produce the given output - */ - def inputForOutput(program: List[Int], output: Int): Option[Input] = { - val start: Int = 0 - val step: Int = 1 - val end: Int = 100 - val nouns = LazyList.range(start, end, step) - val verbs = LazyList.range(start, end, step) - val inputs: LazyList[Input] = - nouns.flatMap(n => verbs.map(v => Input(n, v))) - - def check(input: Input): Boolean = { - val modifiedProgram = generateUpdatedProgram(input, program) - val result = run(modifiedProgram).head - result == output - } - - inputs.find(check(_)) - } - - def generateUpdatedProgram(input: Input, program: List[Int]): List[Int] = { - program.updated(1, input.noun).updated(2, input.verb) - } - } - - // scalastyle:off - @SuppressWarnings(Array("org.wartremover.warts.All")) - def main(args: Array[String]): Unit = { - - // Copy the puzzle input from https://adventofcode.com/2019/day/2/input - val puzzleInput: List[Int] = List(1, 12, 2, 3, 1, 1, 2, 3, 1, 3, 4, 3, 1, 5, - 0, 3, 2, 10, 1, 19, 1, 19, 9, 23, 1, 23, 6, 27, 1, 9, 27, 31, 1, 31, 10, - 35, 2, 13, 35, 39, 1, 39, 10, 43, 1, 43, 9, 47, 1, 47, 13, 51, 1, 51, 13, - 55, 2, 55, 6, 59, 1, 59, 5, 63, 2, 10, 63, 67, 1, 67, 9, 71, 1, 71, 13, - 75, 1, 6, 75, 79, 1, 10, 79, 83, 2, 9, 83, 87, 1, 87, 5, 91, 2, 91, 9, 95, - 1, 6, 95, 99, 1, 99, 5, 103, 2, 103, 10, 107, 1, 107, 6, 111, 2, 9, 111, - 115, 2, 9, 115, 119, 2, 13, 119, 123, 1, 123, 9, 127, 1, 5, 127, 131, 1, - 131, 2, 135, 1, 135, 6, 0, 99, 2, 0, 14, 0) - - // Solve your puzzle using the functions in parts 1 and 2 - val part1 = Day2.Part1.run(puzzleInput).head - println(part1) - val part2 = Day2.Part2.inputForOutput(puzzleInput, 19690720) - println(100 * part2.get.noun + part2.get.verb) - } - // scalastyle:on -} diff --git a/src/main/scala/advent/solutions/Day3.scala b/src/main/scala/advent/solutions/Day3.scala deleted file mode 100644 index 60b37b1..0000000 --- a/src/main/scala/advent/solutions/Day3.scala +++ /dev/null @@ -1,312 +0,0 @@ -package advent.solutions - -import advent.solutions.Day3.WireParseError.{InvalidDirection, InvalidDistance} - -/** Day 3: Crossed Wires - * - * @see https://adventofcode.com/2019/day/3 - */ -object Day3 { - - /** The direction that a wire travels in */ - sealed trait Direction - - object Direction { - case object Left extends Direction - case object Right extends Direction - case object Up extends Direction - case object Down extends Direction - } - - /** The displacement that a wire travels - * - * This is comprised of a direction and a positive distance - */ - final case class Displacement(direction: Direction, distance: Int) - - final case class Wire(path: List[Displacement]) - - /** Any error which occurs when creating a [[Wire]] from a string of text */ - sealed trait WireParseError - - object WireParseError { - - /** The first character of a displacement does not represent a direction e.g. "F45" - * - * @param direction The invalid direction e.g "F" in "F45" - */ - final case class InvalidDirection(direction: String) extends WireParseError - - /** The distance characters do not represent a positive integer e.g "R-32" or "RL" - * - * @param distance The invalid distance e.g "-32" in "R-32" - */ - final case class InvalidDistance(distance: String) extends WireParseError - } - - /** Parse a string input into a Wire - * - * Parsing is the process of taking a string and analysing it to produce a datatype - * - * @param text A comma separated string of displacements e.g "R75,D30,R83" - */ - def parse(text: String): Either[List[WireParseError], Wire] = { - val texts: List[(String, String)] = - text - .split(",") - .map(t => (t.filter(_.isLetter), t.filter(!_.isLetter))) - .toList - - val (lefts, rights) = checkTexts(texts).partitionMap(identity) - - if (lefts.isEmpty) Right[List[WireParseError], Wire](Wire(rights)) - else Left[List[WireParseError], Wire](lefts) - } - - private def checkTexts( - texts: List[(String, String)] - ): List[Either[WireParseError, Displacement]] = { - val out = texts.map(t => - validateDirection(t._1) match { - case Right(direction) => - validateDistance(t._2) match { - case Right(distance) => - Right[WireParseError, Displacement]( - Displacement(direction, distance.toInt) - ) - case Left(value) => Left[WireParseError, Displacement](value) - } - case Left(value) => Left[WireParseError, Displacement](value) - } - ) - out - } - - private def validateDirection( - s: String - ): Either[InvalidDirection, Direction] = { - if (s == "L" | s == "R" | s == "U" | s == "D") { - s match { - case "L" => Right[InvalidDirection, Direction](Direction.Left) - case "R" => Right[InvalidDirection, Direction](Direction.Right) - case "U" => Right[InvalidDirection, Direction](Direction.Up) - case "D" => Right[InvalidDirection, Direction](Direction.Down) - } - } else Left[InvalidDirection, Direction](InvalidDirection(s)) - } - - private def validateDistance(s: String): Either[InvalidDistance, String] = { - if (s.toInt > 0) Right[InvalidDistance, String](s) - else Left[InvalidDistance, String](InvalidDistance(s)) - } - - final case class Coordinate(x: Int, y: Int) - - final case class StartingAndEndingPoints(start: Coordinate, end: Coordinate) - - import Direction._ - - /** - * - * @param wire - * @return - */ - def allCoordinates(wire: Wire): List[Coordinate] = { - val origin = Coordinate(0, 0) - wire.path - .foldLeft(List[Coordinate]() -> origin) { - case ((acc, currentCoordinate), displacement) => - val path: List[Coordinate] = - coordinatesAlong(displacement, currentCoordinate) - (acc ::: path, path.last) - } - ._1 - } - - /** - * Given a starting displacement & coordinate, work out all the points of the wire from starting to ending. - * - * @param displacement - * @param currentCoordinate - * @return - */ - def coordinatesAlong( - displacement: Displacement, - currentCoordinate: Coordinate - ): List[Coordinate] = { - val endingPoint = add(displacement, currentCoordinate) - val path = pathTravelled( - displacement, - StartingAndEndingPoints(currentCoordinate, endingPoint) - ) - path - } - - /** - * - * @param displacement - * @param currentCoordinate - * @return - */ - def add( - displacement: Displacement, - currentCoordinate: Coordinate - ): Coordinate = { - import Direction._ - displacement.direction match { - case Right => - Coordinate( - currentCoordinate.x + displacement.distance, - currentCoordinate.y - ) - case Up => - Coordinate( - currentCoordinate.x, - currentCoordinate.y + displacement.distance - ) - case Left => - Coordinate( - currentCoordinate.x - displacement.distance, - currentCoordinate.y - ) - case Down => - Coordinate( - currentCoordinate.x, - currentCoordinate.y - displacement.distance - ) - } - } - - /** - * - * @param displacement - * @param path - * @return - */ - def pathTravelled( - displacement: Displacement, - path: StartingAndEndingPoints - ): List[Coordinate] = { - val out = displacement.direction match { - case Right => - (path.start.x + 1 to path.end.x).map(d => Coordinate(d, path.start.y)) - case Left => - (path.end.x until path.start.x) - .map(d => Coordinate(d, path.start.y)) - .reverse - case Up => - (path.start.y + 1 to path.end.y).map(d => Coordinate(path.start.x, d)) - case Down => - (path.end.y until path.start.y) - .map(d => Coordinate(path.start.x, d)) - .reverse - } - out.toList - } - - object Part1 { - - /** The manhattan distance to the closest intersection - * - * The closest intersection has the smallest manhattan distance - * - * @param wire0 The first wire - * @param wire1 The second wire - * @return The distance to the closest intersection if the wires intersect - * or [[None]] if they don't intersect at all - */ - def distanceToIntersection(wire0: Wire, wire1: Wire): Option[Int] = { - val wire0Coordinates = allCoordinates(wire0) - val wire1Coordinates = allCoordinates(wire1) - - val intersectionPoints = wire0Coordinates - .intersect(wire1Coordinates) - - if (intersectionPoints.isEmpty) None - else Some(intersectionPoints.map(c => math.abs(c.x) + math.abs(c.y)).min) - } - - } - - object Part2 { - - /** The steps to the closest intersection - * - * The closest intersection has the smallest number of steps - * - * @param wire0 The first wire - * @param wire1 The second wire - * @return The number of steps to the closest intersection if the wires intersect - * or [[None]] if they don't intersect at all - */ - def numberOfStepsToIntersection(wire0: Wire, wire1: Wire): Option[Int] = { - val wire0Coordinates = allCoordinates(wire0) - val wire1Coordinates = allCoordinates(wire1) - - intersectionPoints(wire0, wire1).map { points => - points.map { point => - wire0Coordinates.indexOf(point) + 1 + wire1Coordinates.indexOf(point) + 1 - }.min - } - } - - /** - * - * @param wire0 - * @param wire1 - * @return - */ - def intersectionPoints( - wire0: Wire, - wire1: Wire - ): Option[List[Coordinate]] = { - - val wire0Coordinates = allCoordinates(wire0) - val wire1Coordinates = allCoordinates(wire1) - - val intersectionPoints = wire0Coordinates - .intersect(wire1Coordinates) - - if (intersectionPoints.isEmpty) None - else Some(intersectionPoints) - } - - } - - import Part1._ - import Part2._ - - // scalastyle:off - @SuppressWarnings(Array("org.wartremover.warts.All")) - def main(args: Array[String]): Unit = { - // Copy the puzzle input from https://adventofcode.com/2019/day/3/input - val puzzleWire0Input: String = - "R992,U284,L447,D597,R888,D327,R949,U520,R27,U555,L144,D284,R538,U249,R323,U297,R136,U838,L704,D621,R488,U856,R301,U539,L701,U363,R611,D94,L734,D560,L414,U890,R236,D699,L384,D452,R702,D637,L164,U410,R649,U901,L910,D595,R339,D346,R959,U777,R218,D667,R534,D762,R484,D914,L25,U959,R984,D922,R612,U999,L169,D599,L604,D357,L217,D327,L730,D949,L565,D332,L114,D512,R460,D495,L187,D697,R313,U319,L8,D915,L518,D513,R738,U9,R137,U542,L188,U440,R576,D307,R734,U58,R285,D401,R166,U156,L859,U132,L10,U753,L933,U915,R459,D50,R231,D166,L253,U844,R585,D871,L799,U53,R785,U336,R622,D108,R555,D918,L217,D668,L220,U738,L997,D998,R964,D456,L54,U930,R985,D244,L613,D116,L994,D20,R949,D245,L704,D564,L210,D13,R998,U951,L482,U579,L793,U680,L285,U770,L975,D54,R79,U613,L907,U467,L256,D783,R883,U810,R409,D508,L898,D286,L40,U741,L759,D549,R210,U411,R638,D643,L784,U538,L739,U771,L773,U491,L303,D425,L891,U182,R412,U951,L381,U501,R482,D625,R870,D320,L464,U555,R566,D781,L540,D754,L211,U73,L321,D869,R994,D177,R496,U383,R911,U819,L651,D774,L591,U666,L883,U767,R232,U822,L499,U44,L45,U873,L98,D487,L47,U803,R855,U256,R567,D88,R138,D678,L37,U38,R783,U569,L646,D261,L597,U275,L527,U48,R433,D324,L631,D160,L145,D128,R894,U223,R664,U510,R756,D700,R297,D361,R837,U996,L769,U813,L477,U420,L172,U482,R891,D379,L329,U55,R284,U155,L816,U659,L671,U996,R997,U252,R514,D718,L661,D625,R910,D960,L39,U610,R853,U859,R174,U215,L603,U745,L587,D736,R365,U78,R306,U158,L813,U885,R558,U631,L110,D232,L519,D366,R909,D10,R294" - val puzzleWire1Input: String = - "L1001,D833,L855,D123,R36,U295,L319,D700,L164,U576,L68,D757,R192,D738,L640,D660,R940,D778,R888,U772,R771,U900,L188,D464,L572,U184,R889,D991,L961,U751,R560,D490,L887,D748,R37,U910,L424,D401,L385,U415,L929,U193,R710,D855,L596,D323,L966,D505,L422,D139,L108,D135,R737,U176,R538,D173,R21,D951,R949,D61,L343,U704,R127,U468,L240,D834,L858,D127,R328,D863,R329,U477,R131,U864,R997,D38,R418,U611,R28,U705,R148,D414,R786,U264,L785,D650,R201,D250,R528,D910,R670,U309,L658,U190,R704,U21,R288,D7,R930,U62,R782,U621,R328,D725,R305,U700,R494,D137,R969,U142,L867,U577,R300,U162,L13,D698,R333,U865,R941,U796,L60,U902,L784,U832,R78,D578,R196,D390,R728,D922,R858,D994,L457,U547,R238,D345,R329,D498,R873,D212,R501,U474,L657,U910,L335,U133,R213,U417,R698,U829,L2,U704,L273,D83,R231,D247,R675,D23,L692,D472,L325,D659,L408,U746,L715,U395,L596,U296,R52,D849,L713,U815,R684,D551,L319,U768,R176,D182,R557,U731,R314,D543,L9,D256,R38,D809,L567,D332,R375,D572,R81,D479,L71,U968,L831,D247,R989,U390,R463,D576,R740,D539,R488,U367,L596,U375,L763,D824,R70,U448,R979,D977,L744,D379,R488,D671,L516,D334,L542,U517,L488,D390,L713,D932,L28,U924,L448,D229,L488,D501,R19,D910,L979,D411,R711,D824,L973,U291,R794,D485,R208,U370,R655,U450,L40,D804,L374,D671,R962,D829,L209,U111,L84,D876,L832,D747,L733,D560,L702,D972,R188,U817,L111,U26,L492,U485,L71,D59,L269,D870,L152,U539,R65,D918,L932,D260,L485,U77,L699,U254,R924,U643,L264,U96,R395,D917,R360,U354,R101,D682,R854,U450,L376,D378,R872,D311,L881,U630,R77,D766,R672" - - // Solve your puzzle using the functions in parts 1 and 2 -// parse(puzzleWire0Input) match { -// case Left(_) => println("Invalid inputs") -// case Right(input1) => -// parse(puzzleWire1Input) match { -// case Left(_) => println("Invalid inputs") -// case Right(input2) => println(distanceToIntersection(input1, input2)) -// } -// } - println( - distanceToIntersection( - parse(puzzleWire0Input).getOrElse(Wire(List())), - parse(puzzleWire1Input).getOrElse(Wire(List())) - ) - ) - - println( - numberOfStepsToIntersection( - parse(puzzleWire0Input).getOrElse(Wire(List())), - parse(puzzleWire1Input).getOrElse(Wire(List())) - ) - ) - } - // scalastyle:on -} diff --git a/src/test/scala/advent/Day2Spec.scala b/src/test/scala/advent/Day2Spec.scala deleted file mode 100644 index ea5533e..0000000 --- a/src/test/scala/advent/Day2Spec.scala +++ /dev/null @@ -1,29 +0,0 @@ -package advent - -import org.scalatest._ -import advent.solutions._ -import org.scalatest.flatspec.AnyFlatSpec -import org.scalatest.matchers.should.Matchers - -final class Day2Spec extends AnyFlatSpec with Matchers { - - "Part 1" should "run 1,0,0,0,99 to produce 2,0,0,0,99 (1 + 1 = 2)" in { - Day2.Part1.run(List(1, 0, 0, 0, 99)) should be(List(2, 0, 0, 0, 99)) - } - - it should "run 2,3,0,3,99 to produce 2,3,0,6,99 (3 * 2 = 6)" in { - Day2.Part1.run(List(2, 3, 0, 3, 99)) should be(List(2, 3, 0, 6, 99)) - } - - it should "run 2,4,4,5,99,0 to produce 2,4,4,5,99,9801 (99 * 99 = 9801)" in { - Day2.Part1.run(List(2, 4, 4, 5, 99, 0)) should be( - List(2, 4, 4, 5, 99, 9801) - ) - } - - it should "run 1,1,1,4,99,5,6,0,99 to produce 30,1,1,4,2,5,6,0,99" in { - Day2.Part1.run(List(1, 1, 1, 4, 99, 5, 6, 0, 99)) should be( - List(30, 1, 1, 4, 2, 5, 6, 0, 99) - ) - } -} diff --git a/src/test/scala/advent/Day3Spec.scala b/src/test/scala/advent/Day3Spec.scala deleted file mode 100644 index 797a8b7..0000000 --- a/src/test/scala/advent/Day3Spec.scala +++ /dev/null @@ -1,70 +0,0 @@ -package advent - -import advent.solutions.Day3.Direction._ -import advent.solutions.Day3._ -import advent.solutions.Day3.Part2._ -import org.scalatest._ -import advent.solutions._ -import org.scalatest.flatspec.AnyFlatSpec -import org.scalatest.matchers.should.Matchers - -final class Day3Spec - extends AnyFlatSpec - with Matchers - with EitherValues - with OptionValues { - - "part 1" should "find the distance to an intersection" in { - val wire0: Day3.Wire = parse("R75,D30,R83,U83,L12,D49,R71,U7,L72") - val wire1: Day3.Wire = parse("U62,R66,U55,R34,D71,R55,D58,R83") - Day3.Part1.distanceToIntersection(wire0, wire1) shouldBe Some(159) - } - - it should "find the distance to an intersection for another pair of wires" in { - val wire0: Day3.Wire = parse("R98,U47,R26,D63,R33,U87,L62,D20,R33,U53,R51") - val wire1: Day3.Wire = parse("U98,R91,D20,R16,D67,R40,U7,R15,U6,R7") - Day3.Part1.distanceToIntersection(wire0, wire1) shouldBe Some(135) - } - - "part 2" should "find the number of steps to an intersection" in { - val wire0: Day3.Wire = parse("R75,D30,R83,U83,L12,D49,R71,U7,L72") - val wire1: Day3.Wire = parse("U62,R66,U55,R34,D71,R55,D58,R83") - Day3.Part2.numberOfStepsToIntersection(wire0, wire1) shouldBe Some(610) - } - - it should "find the number of steps to an intersection for another pair of wires" in { - val wire0: Day3.Wire = parse("R98,U47,R26,D63,R33,U87,L62,D20,R33,U53,R51") - val wire1: Day3.Wire = parse("U98,R91,D20,R16,D67,R40,U7,R15,U6,R7") - Day3.Part2.numberOfStepsToIntersection(wire0, wire1) shouldBe Some(410) - } - - private def parse(text: String): Day3.Wire = { - val either = Day3.parse(text) - either.isRight shouldBe (true) - either.toOption.get - } - - private val _wire0 = Wire( - List( - Displacement(Right, 8), - Displacement(Up, 5), - Displacement(Left, 5), - Displacement(Down, 3) - ) - ) - private val _wire1 = Wire( - List( - Displacement(Up, 7), - Displacement(Right, 6), - Displacement(Down, 4), - Displacement(Left, 4) - ) - ) - - "intersectionPoints" should "return 2 intersection points" in { - assertResult(Some(List(Coordinate(6, 5), Coordinate(3, 3)))) { - intersectionPoints(_wire0, _wire1) - } - } - -} From e289010617e69980bb2cb35037ad39c300ddf1e9 Mon Sep 17 00:00:00 2001 From: "Kai X. Ang" Date: Sat, 28 Mar 2020 21:19:48 +0000 Subject: [PATCH 3/5] Added blog.md --- blog.md | 63 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 blog.md diff --git a/blog.md b/blog.md new file mode 100644 index 0000000..e3e2fa3 --- /dev/null +++ b/blog.md @@ -0,0 +1,63 @@ + +# Advent of Code with Cats + +## Day1 - Part 1 + +### Functional Solution + +Even though the first part of Day1 problem can be easily solved in one line of code, we purposely break it down into multiple small functions so that we can demonstrate how to apply typeclass to the problem later. We first write a fuel() function that takes in a mass of the module, divides by three and subtracts two to find the fuel required to launch a module of a given mass. In the problem, your puzzle input is a long list of all the modules on your spacecraft. To find the fuel required for each module, you could apply the fuel() function to each module of the long list by using map function. + +This is what we’ve done by writing calculateFuels() function. Next, to sum up the list of fuels, we make use of the sum method available to Iterable like List in our case, and write a sumFuels() function. Finally, we put all of these small functions into sumOfFuel() function and solve our Part 1’s problem. + +The solution of Part 1 can be found in [day-1-step-1-abstraction](https://github.com/lsug/advent-of-code-typelevel/tree/day-1-step-1-abstraction) branch. + +### Type Abstraction + +Before jumping into typeclass, we thought it’d be good to start by introducing generic function and type. Have a look at the three functions below. + +```scala +def returnInt(x : Int): Int = x +def returnLong(x : Long): Long = x +def returnBoolean(x : Boolean): Boolean = x +``` + + +`returnInt` is a function that accepts and returns a value of type `Int` whereas `returnLong` accepts and returns a type `Long` value and finally `returnBoolean`, a `Boolean` type of value. + +Although they each return a different **type** of value, they share the same pattern: return the same value that was used as its argument. Since they do the same thing fundamentally, we could write a single function that does that. In other words, we could abstract over this pattern. + +````scala +def identity[A](a: A): A = a +```` + +`identity[A]` function is such a function and is called a generic function. The square bracket with A letter in it is the type parameter, syntax of generic function in Scala. When we are generalizing, we tend to use letter A, B, C by convention. + +Now, to return an `Int`, we can pass in an `Int` value to the identity function like this, `identity[Int](10)`. When you run this, the output will be a value of 10 which is the same as running `returnInt(10)`. By the same logic, to return a `true` value, we simply write `identity[Boolean](true)`. + +Also, you could have as many generic types, also known as type parameters, in a function as you like. For example, there are three different types in `tuple3[A, B, C]` below + +````scala +def tuple3[A, B, C](a: A, b: B, c: C): (A, B, C) = (a, b, c) +```` + +So, what is a type? You can think of a type as a set of values. For concrete types like `Boolean`, we know it is a set with just two values: true and false.(draw circle) Earlier, we mentioned that A is a generic type which means we don’t know anything about the possible values lie in set A. (draw circle) + +You might be thinking why is having a generic type in a function a good thing? Let’s go back to the `identity[A]` function, remember how it can take in and return an `Int`, `Long` and `Boolean`. Actually, it can do more than that, it can take in and return **any** type of values due to having a generic type. Compared it to `returnInt`, which can only take in and return a concrete `Int` value, `identity[A]` is a lot more flexible and powerful. + +In short, by having a generic function, we can reduce code duplication and achieve polymorphism. + +In [day-1-step-1-abstraction](https://github.com/lsug/advent-of-code-typelevel/tree/day-1-step-1-abstraction) branch, there is an exercise called Abstraction. Please try to make every function generic and see if you can pass all the tests. + +### Type Class + +We’ve seen both type and generic function, let’s go into typeclass. Before bombarding you with lots of definitions of what a typeclass is, we thought it would be good to give you a break and go back to the spacecraft. Remember the `fuel` function, let’s try to rewrite the function and make it generic. + +If you do that, you’ll notice that the function does not compile and the error message is + +````text +value / is not a member of type parameter A +```` + +What the error message is saying is the compiler cannot find `/`, division operation under type `A`. Previously, when the mass parameter was a concrete type `Int`, the function compiled because `/` is defined under type `Int`. Actually, it’s not only value `/` undefined, `-` (minus), `3` and `2` are also undefined under type A. + +To make the generic `fuel[A]` function compile, we need to define all of them and we do that in an interface, `trait Mass[A]`. From 8f0bd43154ebb4a01e7cd4d394473bb639b07cde Mon Sep 17 00:00:00 2001 From: "Kai X. Ang" Date: Sun, 29 Mar 2020 20:24:38 +0100 Subject: [PATCH 4/5] Added Mass typeclass section. --- blog.md | 86 +++++++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 81 insertions(+), 5 deletions(-) diff --git a/blog.md b/blog.md index e3e2fa3..a1ca8b6 100644 --- a/blog.md +++ b/blog.md @@ -5,9 +5,23 @@ ### Functional Solution -Even though the first part of Day1 problem can be easily solved in one line of code, we purposely break it down into multiple small functions so that we can demonstrate how to apply typeclass to the problem later. We first write a fuel() function that takes in a mass of the module, divides by three and subtracts two to find the fuel required to launch a module of a given mass. In the problem, your puzzle input is a long list of all the modules on your spacecraft. To find the fuel required for each module, you could apply the fuel() function to each module of the long list by using map function. - -This is what we’ve done by writing calculateFuels() function. Next, to sum up the list of fuels, we make use of the sum method available to Iterable like List in our case, and write a sumFuels() function. Finally, we put all of these small functions into sumOfFuel() function and solve our Part 1’s problem. +Even though the first part of Day1 problem can be easily solved in one line of code, we purposely break it down into multiple small functions so that we can demonstrate how to apply typeclass to the problem later. We first write a `fuel` function that takes in a mass of the module, divides by three and subtracts two to find the fuel required to launch a module of a given mass. + +```scala +def fuel(mass: Int): Int = mass / 3 - 2 +``` + +In the problem, your puzzle input is a long list of all the modules on your spacecraft. To find the fuel required for each module, you could apply the `fuel` function to each module of the long list by using `map` function. This is what we’ve done by writing `calculateFuels` function. + +```tut +def calculateFuels(masses: List[Int]): List[Int] = masses.map(fuel) +``` + +Next, to sum up the list of fuels, we make use of the sum method available to Iterable like List in our case, and write a sumFuels() function. Finally, we put all of these small functions into `sumOfFuel` function and solve our Part 1’s problem. + +```scala +def sumFuels(fuels: List[Int]): Int = fuels.sum +``` The solution of Part 1 can be found in [day-1-step-1-abstraction](https://github.com/lsug/advent-of-code-typelevel/tree/day-1-step-1-abstraction) branch. @@ -48,10 +62,14 @@ In short, by having a generic function, we can reduce code duplication and achie In [day-1-step-1-abstraction](https://github.com/lsug/advent-of-code-typelevel/tree/day-1-step-1-abstraction) branch, there is an exercise called Abstraction. Please try to make every function generic and see if you can pass all the tests. -### Type Class +### Type Class and Type Class Instance We’ve seen both type and generic function, let’s go into typeclass. Before bombarding you with lots of definitions of what a typeclass is, we thought it would be good to give you a break and go back to the spacecraft. Remember the `fuel` function, let’s try to rewrite the function and make it generic. +```tut +def fuel[A](mass: A): A = mass / 3 - 2 +``` + If you do that, you’ll notice that the function does not compile and the error message is ````text @@ -60,4 +78,62 @@ value / is not a member of type parameter A What the error message is saying is the compiler cannot find `/`, division operation under type `A`. Previously, when the mass parameter was a concrete type `Int`, the function compiled because `/` is defined under type `Int`. Actually, it’s not only value `/` undefined, `-` (minus), `3` and `2` are also undefined under type A. -To make the generic `fuel[A]` function compile, we need to define all of them and we do that in an interface, `trait Mass[A]`. +To make the generic `fuel[A]` function compile, first, we need to define all of them and we do that in an interface, `trait Mass[A]`. + +```scala +trait Mass[A] { + def quotient(a: A, b: A): A + def minus(a: A, b: A): A + def two: A + def three: A + } +``` +> *(quotient is the division of two numbers but discarding the fractional part.*) + +Now, you have the missing components in `Mass[A]`. Next, we need to provide a way for `fuel[A]` to access these components and we do that by adding another parameter to `fuel[A]` function. + +```tut +def fuel[A](mass: A)(M: Mass[A]): A = ??? +``` +Please try and implement the function above before reading on. + +The implementation of `fuel[A]` will look something like this and it will compile. + +```tut +def fuel[A](mass: A)(M: Mass[A]): A = { + M.minus(M.quotient(mass, M.three), M.two) + } +``` + +Job done? Not quite. Says, if you were to find the fuel for a mass of 12, how would you do that? How about this `fuel[Int](12)`? If you run that, you’ll get an error message. + +```text +error: missing argument list for method fuel... +``` + +Remember earlier we added another parameter of type `Mass[A]` to `fuel[A]` and because we are replacing type `A` with type `Int`, we need to provide a value of type `Mass[Int]` to the call. However, where do we find that value from? It turns out we need to do a bit of DIY and construct a value of type `Mass[Int]` ourselves! We use the `new` keyword and replace generic type `A` with the type we care about, in our case, type `Int` to construct a concrete value of `Mass`. Finally, we implement all the functions in `Mass` for type `Int`. + +```tut +val intHasMass: Mass[Int] = new Mass[Int] { + def quotient(a: Int, b: Int): Int = a / b + def minus(a: Int, b: Int): Int = a - b + def two: Int = 2 + def three: Int = 3 + } +``` + +Now, if you run `fuel[Int](12)(intHasMass)`, you should get a value of 2. There you have it, you have just learned to implement and use a typeclass, `Mass` and a typeclass instance, `intHasMass` to solve the problem. + +#### Implicits +Earlier when we tried to run `fuel[Int](12)`, we received the missing parameter error message. Actually, there is a way to make this call work by simply adding an `implicit` keyword to the parameter list of `fuel[A]` and `intHasMass`. + +```tut +def fuel[A](mass: A)(implicit M: Mass[A]): A = ... +implicit val intHasMass: Mass[Int] = new Mass[Int] {..} +``` + +By doing this, the compiler will look for a value with type `Mass[Int]` and pass in the implicit value every time we make a call to `fuel[Int]`. Because we put the implicit typeclass instance in an object, we need to bring it into scope by importing it so that compiler can find the value. + +The codes of this section can be found in [day-1-step-2](https://github.com/lsug/advent-of-code-typelevel/blob/day-1-step-2/src/main/scala/advent/solutions/Day1.scala) branch. + +### Monoid From 0fc07b7fa66a8d2117f9dff75ac7765efc5ccb10 Mon Sep 17 00:00:00 2001 From: "Kai X. Ang" Date: Sat, 23 May 2020 13:07:08 +0100 Subject: [PATCH 5/5] Updated Blog. --- blog.md | 525 +++++++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 484 insertions(+), 41 deletions(-) diff --git a/blog.md b/blog.md index a1ca8b6..a75f7f5 100644 --- a/blog.md +++ b/blog.md @@ -5,29 +5,29 @@ ### Functional Solution -Even though the first part of Day1 problem can be easily solved in one line of code, we purposely break it down into multiple small functions so that we can demonstrate how to apply typeclass to the problem later. We first write a `fuel` function that takes in a mass of the module, divides by three and subtracts two to find the fuel required to launch a module of a given mass. +Even though the first part of the Day1 problem can be easily solved in one line of code, we purposely break it down into multiple small functions so that we can demonstrate how to apply type classes to the problem later. We first write a `fuel` function that takes in the mass of the module, divides it by three and subtracts two to find the fuel required to launch the module. ```scala def fuel(mass: Int): Int = mass / 3 - 2 ``` -In the problem, your puzzle input is a long list of all the modules on your spacecraft. To find the fuel required for each module, you could apply the `fuel` function to each module of the long list by using `map` function. This is what we’ve done by writing `calculateFuels` function. +In the problem, your puzzle input is a long list of all the modules on your spacecraft. To find the fuel required for each module, you could apply the `fuel` function to each module of the long list by using the `map` function. This is what we’ve done by writing a `calculateFuels` function. -```tut +```scala def calculateFuels(masses: List[Int]): List[Int] = masses.map(fuel) ``` -Next, to sum up the list of fuels, we make use of the sum method available to Iterable like List in our case, and write a sumFuels() function. Finally, we put all of these small functions into `sumOfFuel` function and solve our Part 1’s problem. +Next, to sum up the list of fuels, we make use of the `sum` function available to `Iterable`, like `List` in our case, and write a `sumFuels` function. Finally, we put all of these small functions into a `sumOfFuel` function and solve our Part 1’s problem. ```scala def sumFuels(fuels: List[Int]): Int = fuels.sum ``` -The solution of Part 1 can be found in [day-1-step-1-abstraction](https://github.com/lsug/advent-of-code-typelevel/tree/day-1-step-1-abstraction) branch. +The solution of Part 1 can be found in the [day-1-step-1-abstraction](https://github.com/lsug/advent-of-code-typelevel/tree/day-1-step-1-abstraction) branch. ### Type Abstraction -Before jumping into typeclass, we thought it’d be good to start by introducing generic function and type. Have a look at the three functions below. +Before jumping into type classes, we thought it’d be good to start by introducing generic functions and types. Have a look at the three functions below: ```scala def returnInt(x : Int): Int = x @@ -36,49 +36,65 @@ def returnBoolean(x : Boolean): Boolean = x ``` -`returnInt` is a function that accepts and returns a value of type `Int` whereas `returnLong` accepts and returns a type `Long` value and finally `returnBoolean`, a `Boolean` type of value. +`returnInt` is a function that accepts and returns a value of type `Int`, whereas `returnLong` accepts and returns a value of type `Long` and finally `returnBoolean`, a value of type `Boolean`. Although they each return a different **type** of value, they share the same pattern: return the same value that was used as its argument. Since they do the same thing fundamentally, we could write a single function that does that. In other words, we could abstract over this pattern. -````scala +```scala def identity[A](a: A): A = a -```` +``` + +The `identity[A]` function is such a function, and is called a **generic function** because it has been parameterized by type. The letter `A` in a square bracket is the **type parameter**, the syntax for a generic type in Scala. When we are generalizing a type, we tend to use letters `A`, `B` and `C` by convention. -`identity[A]` function is such a function and is called a generic function. The square bracket with A letter in it is the type parameter, syntax of generic function in Scala. When we are generalizing, we tend to use letter A, B, C by convention. +Now, to return an `Int`, we can pass in an `Int` value to the `identity` function like this: -Now, to return an `Int`, we can pass in an `Int` value to the identity function like this, `identity[Int](10)`. When you run this, the output will be a value of 10 which is the same as running `returnInt(10)`. By the same logic, to return a `true` value, we simply write `identity[Boolean](true)`. +```scala +identity[Int](10) +``` -Also, you could have as many generic types, also known as type parameters, in a function as you like. For example, there are three different types in `tuple3[A, B, C]` below +When you run this, the output will be a value of 10. You could also omit the `[Int]` type parameter and get the same result: -````scala +```scala +identity(10) +``` + +By the same logic, to return a `true` value, we simply write `identity[Boolean](true)` or just `identity(true)`. + +You could have as many type parameters, also known as generic types, in a function as you like. For example, there are three different types in `tuple3[A, B, C]` below: + +```scala def tuple3[A, B, C](a: A, b: B, c: C): (A, B, C) = (a, b, c) -```` +``` + +### So, what is a type? + +You can think of a type as a set of values. For concrete types like `Boolean`, we know it is a set with just two values: `true` and `false`. For `Int`, it is a set with values ranging from -2,147,483,648 to 2,147,483,647. For `Long`, it is a set of values ranging from -9,223,372,036,854,775,808 to +9,223,372,036,854,775,807. -So, what is a type? You can think of a type as a set of values. For concrete types like `Boolean`, we know it is a set with just two values: true and false.(draw circle) Earlier, we mentioned that A is a generic type which means we don’t know anything about the possible values lie in set A. (draw circle) +Earlier, we mentioned that `A` is a generic type which means we don’t know anything about the possible values that lie in set `A`. -You might be thinking why is having a generic type in a function a good thing? Let’s go back to the `identity[A]` function, remember how it can take in and return an `Int`, `Long` and `Boolean`. Actually, it can do more than that, it can take in and return **any** type of values due to having a generic type. Compared it to `returnInt`, which can only take in and return a concrete `Int` value, `identity[A]` is a lot more flexible and powerful. +You might be thinking, "Why is having a generic type/type parameter in a function a good thing?". Let’s go back to the `identity[A]` function. Remember how it can take in and return an `Int`, `Long` and `Boolean`. Actually, it can do more than that. It can take in and return **any** type of value due to having a type parameter. Compared it to `returnInt`, which can only take in and return a concrete `Int` value, `identity[A]` is a lot more flexible and powerful. -In short, by having a generic function, we can reduce code duplication and achieve polymorphism. +In [day-1-step-1-abstraction](https://github.com/lsug/advent-of-code-typelevel/tree/day-1-step-1-abstraction) branch, there is an exercise called *Abstraction*. Can you try to make every function generic and see if you can pass all the tests. -In [day-1-step-1-abstraction](https://github.com/lsug/advent-of-code-typelevel/tree/day-1-step-1-abstraction) branch, there is an exercise called Abstraction. Please try to make every function generic and see if you can pass all the tests. +### Type Classes and Type Class Instance -### Type Class and Type Class Instance +We’ve seen both types and generic functions, let’s dive into type classes. Remember the `fuel` function, can you rewrite the function and make it generic so that it can accept not only `Int` value, but other types of number like `Double`, `Long` or `Float`. In short, we are going to parameterize the `fuel` function with type. -We’ve seen both type and generic function, let’s go into typeclass. Before bombarding you with lots of definitions of what a typeclass is, we thought it would be good to give you a break and go back to the spacecraft. Remember the `fuel` function, let’s try to rewrite the function and make it generic. +What did you come up with? Here is our first attempt: -```tut +```scala def fuel[A](mass: A): A = mass / 3 - 2 ``` -If you do that, you’ll notice that the function does not compile and the error message is +You might have noticed our solution is not quite right because the function does not compile. If we type this function into the console, we will see the following error message: -````text +```text value / is not a member of type parameter A -```` +``` -What the error message is saying is the compiler cannot find `/`, division operation under type `A`. Previously, when the mass parameter was a concrete type `Int`, the function compiled because `/` is defined under type `Int`. Actually, it’s not only value `/` undefined, `-` (minus), `3` and `2` are also undefined under type A. +What the error message is saying is the compiler cannot find the `/`, division operation under type `A`. Previously, when the mass parameter was a concrete type `Int`, the function compiled because `/` is defined under type `Int`. Actually, it is not only value `/` undefined, `-` (minus), `3` and `2` are also undefined under type `A`. -To make the generic `fuel[A]` function compile, first, we need to define all of them and we do that in an interface, `trait Mass[A]`. +To make the generic `fuel[A]` function compile, first, we need to define all of these functions. We do that in a trait `Mass[A]`: ```scala trait Mass[A] { @@ -88,32 +104,55 @@ trait Mass[A] { def three: A } ``` -> *(quotient is the division of two numbers but discarding the fractional part.*) +> *(quotient is the division of two numbers, but discards the fractional part.)* +> + +The functions in Mass are abstract (no implementation/body/content) because we don't know what A is. As a type parameter, it could be any type, but we know that these functions make sense only on numbers i.e value with type `Int`, `Long`, `Double` or `Float`, just to name a few. -Now, you have the missing components in `Mass[A]`. Next, we need to provide a way for `fuel[A]` to access these components and we do that by adding another parameter to `fuel[A]` function. +Even though we know nothing about A, we could still re-write the `fuel[A]` with these abstract functions in `Mass[A]` and deal with the implementations of these functions later when we know more about what `A` is. -```tut +Next, we need to provide a way for `fuel[A]` to access these functions and we do that by adding another parameter to `fuel[A]`: + +```scala def fuel[A](mass: A)(M: Mass[A]): A = ??? ``` -Please try and implement the function above before reading on. +Can you try and complete the function? -The implementation of `fuel[A]` will look something like this and it will compile. +What did you come up with? Here is ours: -```tut +```scala def fuel[A](mass: A)(M: Mass[A]): A = { M.minus(M.quotient(mass, M.three), M.two) } ``` - -Job done? Not quite. Says, if you were to find the fuel for a mass of 12, how would you do that? How about this `fuel[Int](12)`? If you run that, you’ll get an error message. +Now, the function compiles, let's try to find the fuel for a mass of 12 (a value with type `Int`). `fuel[Int](12)` should give us a answer of 2, right? Let's run it in the console. ```text error: missing argument list for method fuel... ``` -Remember earlier we added another parameter of type `Mass[A]` to `fuel[A]` and because we are replacing type `A` with type `Int`, we need to provide a value of type `Mass[Int]` to the call. However, where do we find that value from? It turns out we need to do a bit of DIY and construct a value of type `Mass[Int]` ourselves! We use the `new` keyword and replace generic type `A` with the type we care about, in our case, type `Int` to construct a concrete value of `Mass`. Finally, we implement all the functions in `Mass` for type `Int`. +Oops, we forgot the extra parameter of type `Mass[A]` we added earlier. Since we are replacing type parameter `A` with type `Int`, we need to provide a value of type `Mass[Int]` to the function. However, where do we find that value from? Can you take a guess? -```tut +It turns out we need to do a bit of DIY and construct a value of type `Mass[Int]` ourselves! Before concerning ourselves with how to construct this value, let take a step back and answer why we need this value? Let's give this value a name, `intHasMass`. + +First, we know any value of type `Mass[A]` has access to the abstract functions defined in `Mass`. `intHasMass` is the same but we now have more information about this value. It is a value with a concrete type of `Mass[Int]`. Therefore, we can now complete the abstract functions in `Mass`. + +```scala +val intHasMass: Mass[Int] = new Mass[Int] { + def quotient(a: Int, b: Int): Int = ??? + def minus(a: Int, b: Int): Int = ??? + def two: Int = ??? + def three: Int = ??? + } +``` + +By completing all the abstract functions, we provide `Int` implementations to all the functions in `Mass`. Now, as `fuel[A]` function receives a value with type `Int`, it knows how to handle it by looking up the `Mass[Int]` value, following exactly the implementations we have written down and solve the problem for us. + +Can you try to implement the functions in `intHasMass`? + +What did you come up with? Here are ours: + +```scala val intHasMass: Mass[Int] = new Mass[Int] { def quotient(a: Int, b: Int): Int = a / b def minus(a: Int, b: Int): Int = a - b @@ -122,18 +161,422 @@ val intHasMass: Mass[Int] = new Mass[Int] { } ``` -Now, if you run `fuel[Int](12)(intHasMass)`, you should get a value of 2. There you have it, you have just learned to implement and use a typeclass, `Mass` and a typeclass instance, `intHasMass` to solve the problem. +Now, if you run `fuel[Int](12)(intHasMass)`, you should get a value of 2. Easy! + +Here is an interesting question for you. What would happen if we do this `fuel[Int](12.0)(intHasMass)`. It is a multiple-choice question. + +- (A) 2 +- (B) 2.0 +- (C) Type mismatch error + +The answer is `(C)` because mass of 12.0 is a value of type `Double`. Although we know numerically the answer should be 2.0, the `fuel[A]` function does not know it because it does not know how to handle a value of type `Double`. It is not its fault, the compiler had helped it look for the `Mass[Double]` value but just could not find it as we did not write one. + +Can you implement a value of `Mass[Double]`? Let's call it `doubleHasMass`. + +Here is our implementation: + +```scala +val doubleHasMass: Mass[Double] = new Mass[Double] { + def quotient(a: Double, b: Double): Double = a / b + def minus(a: Double, b: Double): Double = a - b + def two: Double = 2.0 + def three: Double = 3.0 + } +``` + +Now, if you run `fuel[Double](12.0)(doubleHasMass)`, you should get a value of 2.0. Notice we put in `doubleHasMass`, not `intHasMass`. + #### Implicits -Earlier when we tried to run `fuel[Int](12)`, we received the missing parameter error message. Actually, there is a way to make this call work by simply adding an `implicit` keyword to the parameter list of `fuel[A]` and `intHasMass`. -```tut +Wouldn't it be nice if we could call fuel(12) or fuel(12.0) and get the right answer without having to bother with the right type of `Mass[A]` value? + +Actually, there is a way by simply adding `implicit` keyword to the parameter list of `fuel[A]`, `intHasMass` and `doubleHasMass` value. + +```scala def fuel[A](mass: A)(implicit M: Mass[A]): A = ... implicit val intHasMass: Mass[Int] = new Mass[Int] {..} +implicit val doubleHasMass: Mass[Double] = new Mass[Double] {...} ``` -By doing this, the compiler will look for a value with type `Mass[Int]` and pass in the implicit value every time we make a call to `fuel[Int]`. Because we put the implicit typeclass instance in an object, we need to bring it into scope by importing it so that compiler can find the value. +By doing this, the compiler will look for a value with type `Mass[Int]` and pass in the value marked with `implicit val` every time we make a call to `fuel[Int]`. Because we put the implicit values in a `Mass` object, we need to import the object with `import Mass._`. This brings the implicit value into scope so that the compiler can find it. The codes of this section can be found in [day-1-step-2](https://github.com/lsug/advent-of-code-typelevel/blob/day-1-step-2/src/main/scala/advent/solutions/Day1.scala) branch. -### Monoid +You may not realise it but you have just learned to use a type class `Mass[A]` and implement two type class instances `intHasMass` and `doubleHasMass` to solve the problem. + + +#### What is a type class? + +In Scala, a type class is a trait that has a group of generic functions used to define some operation. The implementations of these functions depend on the type that supports the operation. + +In our earlier example, `Mass` is a type class that has a group of functions like `quotient`, `minus`, `two` and `three` used to define the operations of some numeric operators that we need to find the fuel of a mass. `Int` and `Double` types support and implement these operations: `intHasMass` and `doubleHasMass`. We say `Int` and `Double` are instances of `Mass` typeclass. + +Of course, other numeric types like `Long` and `Float` can support these operations too, but we did not implement them (a.k.a write a `longHasMass` or `floatHasMass` value) for simplicity sake. Feel free to create them in your project. + +By using a group of generic functions that do not tie to a specific type, we can write more generic code. + + + +### Cats + +Cats is a library that contains typeclasses and standard typeclass instances for them, so we no longer need to write our own type class instances for a lot of the standard types like `Int`, `Double`, `String`, etc. +Libraries that build on cats share a common group of typeclasses, so tend to work well with each other. + +We're going to add cats typeclasses into our code. + + +##### Structure + +Speaking from experience, I find it useful to have this high-level structure in mind when learning a new type class. If possible, I'd also write some simple codes to use it and test its operation. +``` +Name of typeclass: _ +Operation(s) defined by the typeclass: _ +Function(s) to carry out the operation: _ +Some common type instances of the typeclass: _ +``` + +``` +How to use it: _ +``` + +#### Semigroup and Monoid + +The typeclasses we'll explore are Semigroup and Monoid. + +```scala +trait Semigroup[A] { + def combine(a: A, b: A): A +} +``` + +``` +Name of typeclass: Semigroup +operation defined by Semigroup: Combining two values with the same types. +Function to carry out the operation: combine +Some common type instances of Semigroup: Numeric types (Int, Double, Float, Long, etc.), String and List. +``` + +```scala +// How to use it: +val combinedInts: Int = Semigroup.combine(2, 3) // 5 +val combinedStrings: String = Semigroup.combine("Advent ", "of Code") // "Advent of Code" +val combinedListOfInts: List[Int] = Semigroup.combine(List(1, 2), List(3, 4)) // List(1, 2, 3, 4) +val combinedListOfStrings: List[String] = Semigroup.combine(List("Advent of Code"), List("with Cats")) // List(Advent of Code, with Cats) +``` +As you can see, the implementation of `combine` function varies according to the types. It is addition for `Int`, string concatenation for `String` and List concatenation for `List`. Nonetheless, fundamentally, they are doing the same thing: an associative operation which combines two values. + + +[Semigroups](https://github.com/typelevel/cats/blob/master/kernel/src/main/scala/cats/kernel/Semigroup.scala) are useful when we want to combine things together, but don't want to tie ourselves down to what those things are. + +For example, consider the following function: + +```scala +def combineEverything[A](as: List[A], initial: A)(implicit S: Semigroup[A]): A = ??? +``` + +Try implementing this function using `as.foldLeft`. + +We can use this function to combine anything that is an instance of a semigroup. + +```scala +combineEverything(List(1,2, 3), 0) +combineEverything(List("foo", "bar"), "baz") +``` + +would be valid uses. + +Here is our solution: +```scala +def combineEverything[A](as: List[A], initial: A)(implicit S: Semigroup[A]): A = as.foldLeft(initial)(S.combine) +``` + +##### Back to spacecraft: +Let's try using a semigroup and `foldLeft` to sum all our numbers in `sumFuels` function which has been parameterized by type . Can you give it a try? + +```scala +def sumFuels[A](fuels: List[A])(implicit S: Semigroup[A]): A = ??? +``` + +You may have realised that you can't because we don't have quite enough information to sum all our fuels -specifically, we don't know what to do if our list of fuels is empty. + +This is where `Monoid` come in. + + + +#### Monoid: +The [Monoid](https://github.com/typelevel/cats/blob/master/kernel/src/main/scala/cats/kernel/Monoid.scala) typeclass has the Semigroup's `combine` function and an `empty` value. + +```scala +trait Monoid[A] extends Semigroup[A] { + def empty: A +} +``` +``` +Name of typeclass: Monoid +Operations: A semigroup with identity element. +Functions: combine, empty +Type instances: Numeric types (Int, Double, etc.), String and List. +``` + +The `empty` value must be an identity element for the `combine` operation, that is to say + +`combine(a, empty) = combine(empty, a) = a` + +One such `empty` value is `0` on `+` operation of numbers. For example, `combine(2, 0) = combine(0, 2) = 2` + +Can you think of a valid `empty` value for each `combine` operations below? + - `+` on `String` + - `++` on `List[String]` + +Which ones did you come up with? Here are ours: +- value `""` (*empty string*) for `+` on `String` such that `combine("Cats", "") = combine("", "Cats") = "Cats"` +- value `List("")` for `++` on `List[String]` such that `combine(List("Advent of code"), List("")) = combine(List(""), List("Advent of code")) = List("Advent of code")` + +You could try what we just said by writing some codes: + +```scala +// How to use it: +val combinedWithEmptyInt: Int = Monoid.combine(2, Monoid.empty[Int]) // 2 +val combinedWithEmptyStrings = Monoid.combine("Advent ", Monoid.empty[String]) // "Advent" +val combinedWithEmptyListOfStrings = Monoid.combine(Monoid.empty[List[String]], List("Advent of Code")) // List("Advent of Code") +``` + +If you take a look into the cats-kernel-instances library, you can see exactly how [Int](https://github.com/typelevel/cats/blob/master/kernel/src/main/scala/cats/kernel/instances/IntInstances.scala), [String](https://github.com/typelevel/cats/blob/master/kernel/src/main/scala/cats/kernel/instances/StringInstances.scala) and [List](https://github.com/typelevel/cats/blob/master/kernel/src/main/scala/cats/kernel/instances/ListInstances.scala) are implemented as instances of Monoid typeclass. + + +##### Back to spacecraft: +Now, let's use Monoid to sum all numbers in the parameterized `sumFuels[A]` function. + +```scala +def sumFuels[A](fuels: List[A])(implicit M: Monoid[A]) = ??? +``` + +How did your solution look like? Here is ours: + +```scala +def sumFuels[A](fuels: List[A])(implicit M: Monoid[A]) = fuels.foldLeft(M.empty)(M.combine) +``` + +So far, we have parameterized and re-written `fuel` and `sumFuels` using typeclasses. To solve Day1's Part 1 problem, we need to re-write `calculateFuels` and `sumOfFuel` because they have been parameterized by type too. Can you give it a try? + +```scala +def calculateFuels[A](masses: List[A])(implicit M: Mass[A]): List[A] = ??? + +def sumOfFuel[A](masses: List[A])(implicit M: Mass[A], N: Monoid[A]): A = ??? +``` + + +Did you try it? Here are our solutions: + +```scala +def calculateFuels[A](masses: List[A])(implicit M: Mass[A]): List[A] = masses.map(fuel[A]) + +def sumOfFuel[A](masses: List[A])(implicit M: Mass[A], N: Monoid[A]): A = sumFuels(calculateFuels(masses)) +``` + +The codes of this section can be found in [day-1-step-3](https://github.com/lsug/advent-of-code-typelevel/blob/day-1-step-3/src/main/scala/advent/solutions/Day1.scala) branch. + + +## Day1 - Part 2 + +Let's move on to solving Part 2 of Day1. Can you give it a try? + +How did you solve it? Here is here our solution: + +```scala +def totalFuel(mass: Int): Int = { + @tailrec + def go(currentFuel: Int, accum: Int): Int = + if (currentFuel <= 0) accum + else go(Part1.fuel(currentFuel), accum + currentFuel) + + go(Part1.fuel(mass), 0) + } +``` +```scala + def sumOfTotalFuel(masses: List[Int]): Int = masses.map(totalFuel).sum +``` + +We use `totalFuel` to find the total fuel required to launch a module by repeatedly applying `fuel[Int]` defined in Part1 to last calculated fuel value and add the calculated fuel value to the total, `accum` until the fuel value is less than zero. + +We use a tail recursive function `go` in `totalFuel` because Scala automatically compiles the recursion to iterative loops that don’t consume call stack frames for each iteration. + +We then use `sumOfTotalFuel`, which applies `totalFuel` to a list of modules in the spacecraft and sums up all the total fuels, to get the final answer. + +The codes of this section can be found in [day-1-step-4](https://github.com/lsug/advent-of-code-typelevel/blob/day-1-step-4/src/main/scala/advent/solutions/Day1.scala) branch. + + +#### Order typeclass +The next typeclass we'll explore is [Order](https://github.com/typelevel/cats/blob/master/kernel/src/main/scala/cats/kernel/Order.scala) in Cats. + +```scala +trait Order[A] { + def compare(a: A, b: A): Int +} +``` + +``` +Name of typeclass: Order +operation: comparing and ordering two values. +Functions: compare +Type instances: Numeric types (Int, Double, etc.), String and List. +``` + +```scala +// How to use it: +val smaller: Int = Order.compare(2, 3) // -1 +val larger: Int = Order.compare(3, 2) // 1 +val equalValue: Int = Order.compare(3, 3) // 0 +``` + +The `compare` function will return an `Int` whose sign is +- negative iff `a < b` +- positive iff `a > b` +- zero iff `a = b` + +As an example, can you try to implement `isXLessThanY` function such that it returns `true` if `x` is less than or equal to `y`, else `false`. +```scala +def isXLessThanY[A](x: A, y: A)(implicit O: Order[A]): Boolean = ??? +``` +Here is our solution: +```scala +def isXLessThanY[A](x: A, y: A)(implicit O: Order[A]): Boolean = O.compare(x, y) <= 0 +``` + +We can make the solution easier to read by using the infix notation `<=` defined in Cats. + +```scala +def isXLessThanY[A](x: A, y: A)(implicit O: Order[A]): Boolean = x <= y +``` + + +##### Back to spacecraft: +Again, `totalFuel` and `sumOfTotalFuel` have been parameterized by a type in Part 2. Can you try implement them using `Mass`, `Monoid` and `Order` typeclasses? + +```scala +def totalFuel[A](mass: A)(implicit M: Monoid[A], N: Mass[A], O: Order[A]): A = ??? +def sumOfTotalFuel[A](masses: List[A])(implicit M: Monoid[A], N: Mass[A], O: Order[A]): A = ??? +``` + +What are your solutions? Here are ours: + +```scala +def totalFuel[A](mass: A)(implicit M: Monoid[A], N: Mass[A], O: Order[A]): A = { + @tailrec + def go(currentFuel: A, accum: A): A = + if (currentFuel <= M.empty) accum + else go(Part1.fuel(currentFuel), accum combine currentFuel) + + go(Part1.fuel(mass), M.empty) + } +``` +```scala + def sumOfTotalFuel[A](masses: List[A])(implicit M: Monoid[A], N: Mass[A], O: Order[A]): A = { + masses.map(totalFuel[A]).fold(M.empty)(M.combine) + } +``` + +The codes of this section can be found in [day-1-step-5](https://github.com/lsug/advent-of-code-typelevel/blob/day-1-step-5/src/main/scala/advent/solutions/Day1.scala) branch. + + +#### Functor typeclass + +In this section, we will explore [Functor](https://github.com/typelevel/cats/blob/master/core/src/main/scala/cats/Functor.scala) typeclass. + +```scala +trait Functor[F[_]] { + def map[A, B](fa: F[A])(f: A => B): F[B] +} +``` + +``` +Name of typeclass: Functor +Operation: Mapping over type constructor. +Functions: map +Type instances: List, Vector, Option, Tuple, Either etc. +``` + +The `map` function in Functor typeclass is the same as the `map` method in Scala standard library. Therefore, any class that has a `map` method is an instance of Functor. + +##### Type constructor + +These Functor instances are also known as type constructors because they take one type as the parameter to produce a new type. + +For example, you provide a type of `Int` to `List` to create a value of concrete type `List[Int]`. The same thing with `Option`. + +```scala +val numbers: List[Int] = List(1, 2, 3, 4, 5, 6) +val one: Option[Int] = Some(1) +val name: Option[String] = Some("Cats") +``` + +`one` is a value of concrete type `Option[Int]` and `name` is a value of concrete type `Option[String]`. + +You cannot create a value with just type of `List` or `Option` because they are not a type per se but a type constructor. + +In the Functor signature above, you'll notice that, instead of a normal letter `A` as type parameter, `F[_]` is used. In Scala, this is the syntax for a type constructor parameter. + +It may look weird when you first encounter it, it actually is a very descriptive symbol for a type constructor parameter. Remember we said earlier a type constructor needs to take in a type, could be any type, to construct a value. Doesn't the shape of `[_]` look like a hole waiting to be filled in with a type? + + +##### Back to spacecraft: +Now, let's apply this to `calculateFuels` function which has been parameterized by `F[_]` and `A`. Can you try to re-write `calculateFuels[A]` using Functor? + +```scala +private def calculateFuels[F[_], A](masses: F[A])(implicit M: Mass[A], G: Functor[F]): F[A] = ??? +``` + +Did you manage to solve it? Here is our solution. + +```scala +private def calculateFuels[F[_], A](masses: F[A])(implicit M: Mass[A], G: Functor[F]): F[A] = masses.map(fuel[A]) +``` + +##### F[_] vs F[A] +However subtle there is a significant difference between `F[_]` and `F[A]`. `F[A]` is a type, a concrete type even though we don't know what the type is now as it depends on what type we eventually replace it with. `F[A]` could be `List[Int]`, `Vector[Double]` or `Option[Int]`, etc. + +Just as `A` is a symbol for type parameter, `F[_]` is a symbol for type constructor parameter. Thus, it is not a concrete type. Some examples of `F[_]` are `List`, `Vector` and `Option`. Notice, unlike previous examples, there are no `[Int]` and `[Double]` because `F[_]`, the whole thing means a set of type constructors which includes `List`, `Vector`, `Option` and many other type constructors that supports the `map` operation of Functor typeclass. + +This is best illustrated by creating a Functor instance for `List`. Let's name it `listHasFunctor`. + +```scala + val listHasFunctor: Functor[List] = new Functor[List] { + override def map[A, B](fa: List[A])(f: A => B): List[B] = fa.map(f) + } +``` + +Notice that only `List` is used at the place of `F[_]`. This is because Functor only concerns itself with type constructor and does not care what type we put into the type constructor. + + +#### Foldable typeclass + +```scala +trait Foldable[F[_]] { + def fold[A](fa: F[A])(f: (A, A) => A): A +} +``` + +``` +Name of typeclass: Foldable +Operation: Folding a data structure to a summary value. +Functions: fold, foldLeft, foldRight +Type instances: List, Vector, Option, etc. +``` + +The `fold` function in [Foldable](https://github.com/typelevel/cats/blob/master/core/src/main/scala/cats/Foldable.scala) typeclass is the same as the `fold` method in Scala Standard library. Therefore, any class that has `fold` method is an instance of Foldable. + +##### Back to spacecraft: +Now, let's apply this to our `sumOfTotalFuel` function which has been parameterized by `F[_]` and `A`. Can you try to re-write `sumOfTotalFuel[A]` using Functor and Foldable? + +```scala +def sumOfTotalFuel[F[_], A](masses: F[A])(implicit N: Mass[A], O: Order[A], G: Functor[F], H: Foldable[F]): F[A] = ??? +``` + +Hopefully you manage to solve it. Here is our solution: + +```scala +def sumOfTotalFuel[F[_], A](masses: F[A])(implicit N: Mass[A], O: Order[A], G: Functor[F], H: Foldable[F]): F[A] = masses.map(totalFuel[A]).fold +``` + +The codes of this section can be found in [day-1-step-6](https://github.com/lsug/advent-of-code-typelevel/blob/day-1-step-6/src/main/scala/advent/solutions/Day1.scala) branch. \ No newline at end of file