advent/src/Years/Y2015/Day2.md

137 lines
3.3 KiB
Markdown

# [Year 2015 Day 2](https://adventofcode.com/2015/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
-->