Skip to content
Open

Blog #19

Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 63 additions & 0 deletions blog.md
Original file line number Diff line number Diff line change
@@ -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.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do you think about having the function names marked up as code? E.g.

We first write a fuel function... 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.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do you think about interspersing the actual code snippets with the explanation?

e.g.

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.

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.

def calculateFuels(masses: List[Int]): List[Int] = masses.map(fuel)


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]`.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's write the Mass trait out here too

47 changes: 23 additions & 24 deletions src/main/scala/advent/solutions/Day1.scala
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -16,18 +14,32 @@ 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
*
* @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 {
Expand All @@ -38,22 +50,18 @@ 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
*
* @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 = {
???
}
}

Expand All @@ -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
}
117 changes: 0 additions & 117 deletions src/main/scala/advent/solutions/Day2.scala

This file was deleted.

Loading