126 lines
3.3 KiB
Markdown
126 lines
3.3 KiB
Markdown
# Day 2
|
|
|
|
This day provides us our first little taste of effectful parsing
|
|
|
|
<!-- idris
|
|
module Years.Y2015.Day2
|
|
|
|
import Control.Eff
|
|
|
|
import Runner
|
|
-->
|
|
|
|
```idris
|
|
import Data.List
|
|
import Data.List1
|
|
import Data.String
|
|
```
|
|
|
|
<!-- idris
|
|
%default total
|
|
-->
|
|
|
|
## Box structure
|
|
|
|
A record to hold the parameters of a box and methods to operate on it
|
|
|
|
```idris
|
|
record Box where
|
|
constructor MkBox
|
|
length, width, height : Integer
|
|
```
|
|
|
|
### Box methods
|
|
|
|
`.area` provides the surface area of the box, `.slack` provides the surface area of the smallest face, `.ribbon` provides the smallest perimeter of a face, and `.bow` provides the volume of the box.
|
|
|
|
Names are as described in the problem
|
|
|
|
```idris
|
|
(.area) : Box -> Integer
|
|
(.area) (MkBox length width height) =
|
|
2 * length * width + 2 * width * height + 2 * length * height
|
|
|
|
(.slack) : Box -> Integer
|
|
(.slack) (MkBox length width height) =
|
|
foldl1 min [length * width, width * height, length * height]
|
|
|
|
(.ribbon) : Box -> Integer
|
|
(.ribbon) (MkBox length width height) =
|
|
foldl1 min [2 * (length + width), 2 * (length + height), 2 * (width + height)]
|
|
|
|
(.bow) : Box -> Integer
|
|
(.bow) (MkBox length width height) = length * width * height
|
|
```
|
|
|
|
Provide the total amount of ribbon needed, by adding the ribbon (smallest perimeter) and the bow (volume) values together.
|
|
|
|
```idris
|
|
totalRibbon : Box -> Integer
|
|
totalRibbon x = x.ribbon + x.bow
|
|
```
|
|
|
|
Provide the total amount of paper needed by adding the surface area and the slack (smallest side surface area) together.
|
|
|
|
```idris
|
|
paperNeeded : Box -> Integer
|
|
paperNeeded x = x.area + x.slack
|
|
```
|
|
|
|
### Box parsing
|
|
|
|
Parse a box from an input string of form `lengthxwidthxheight`.
|
|
|
|
```idris
|
|
parseBox : Has (Except String) fs =>
|
|
String -> Eff fs Box
|
|
parseBox str = do
|
|
```
|
|
|
|
First, we split the string into 3 parts by pattern matching on the result of `split`, using `pure` to lift the computation into the effect, and throwing an error if the pattern match fails.
|
|
|
|
```idris
|
|
l ::: [w, h] <- pure $ split (== 'x') str
|
|
| xs => throw "Box did not have exactly 3 components: \{show xs}"
|
|
```
|
|
|
|
Then try to parse each of the three integers, throwing an error if parsing fails, then construct and return our box.
|
|
|
|
```idris
|
|
length <- note "Failed parsing length: \{show l}" $ parsePositive l
|
|
width <- note "Failed parsing width: \{show w}" $ parsePositive w
|
|
height <- note "Failed parsing height: \{show h}" $ parsePositive h
|
|
pure $ MkBox length width height
|
|
```
|
|
|
|
## Part Functions
|
|
|
|
### Part 1
|
|
|
|
Split the input into lines, effectfully traverse our box parser over the resulting list of lines, then sum the amounts of paper needed for each box.
|
|
|
|
We return the list of parsed boxes as the context here to avoid needing to parse again in part 2
|
|
|
|
```idris
|
|
part1 : Eff (PartEff String) (Integer, List Box)
|
|
part1 = do
|
|
input <- map lines $ askAt "input"
|
|
boxes <- traverse parseBox input
|
|
let output = sum . map paperNeeded $ boxes
|
|
pure (output, boxes)
|
|
```
|
|
|
|
### Part 2
|
|
|
|
Much the same as part 1, except with the amount of ribbon needed instead of the amount of paper, and without the parsing, since we received the already parsed list of boxes from part 1 as our context value.
|
|
|
|
```idris
|
|
part2 : (boxes : List Box) -> Eff (PartEff String) Integer
|
|
part2 boxes = pure . sum . map totalRibbon $ boxes
|
|
```
|
|
|
|
<!-- idris
|
|
public export
|
|
day2 : Day
|
|
day2 = Both 2 part1 part2
|
|
-->
|