Year 2015 Day 2

This day provides us our first little taste of effectful parsing


import Data.List
import Data.List1
import Data.String

Box structure

A record to hold the parameters of a box and methods to operate on it


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


(.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.


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.


paperNeeded : Box -> Integer
paperNeeded x = x.area + x.slack

Box parsing

Parse a box from an input string of form lengthxwidthxheight.


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.


  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.


  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


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.


part2 : (boxes : List Box) -> Eff (PartEff String) Integer
part2 boxes = pure . sum . map totalRibbon $ boxes