The complexity in day 13 came from an unexpected place.
I continued my trend of created possibly too many data types, with explicit data types for everything I could think of (emphasising the "parse, don't validate" mantra). I use V2
from linear
for representing points, and have bespoke data types for both folds and the axis of a fold.
type Coord = V2 Int
type Sheet = S.Set Coord
data Axis = X | Y
deriving (Eq, Ord, Show)
data Fold = Fold Axis Int
deriving (Eq, Ord, Show)
Parsing the input file has some wrinkles that may be worth noting, specifically how to parse values of type Axis
, which have no parameters. Using attoparsec:
inputP = (,) <$> sheetP <* many1 endOfLine <*> foldsP
sheetP = S.fromList <$> dotP `sepBy` endOfLine
dotP = V2 <$> decimal <* "," <*> decimal
foldsP = foldP `sepBy` endOfLine
foldP = Fold <$> ("fold along " *> axisP) <* "=" <*> decimal
axisP = ("x" *> pure X) <|> ("y" *> pure Y)
Note the use of pure
to generate Axis
values.
Folding a sheet involves partition
ing the sheet into the two halves, transforming the dots in the right or bottom half, then merging the two results.
There's some duplicated code here. I could possibly DRY it up only doing a fold in one dimension, and handling the other by transposing the points before and after that fold. But I think this solution is simple enough.
doFold :: Sheet -> Fold -> Sheet
doFold sheet (Fold X i) = S.union left foldedRight
where (left, right) = S.partition ((<= i) . getX) sheet
foldedRight = S.map foldDot right
foldDot (V2 x y) = V2 (i - (x - i)) y
doFold sheet (Fold Y i) = S.union top foldedBottom
where (top, bottom) = S.partition ((<= i) . getY) sheet
foldedBottom = S.map foldDot bottom
foldDot (V2 x y) = V2 x (i - (y - i))
After that, all that's left is to display the final values. I've got OverloadedLists
turned on by default in my Cabal file, which means I need a few explicit type allocations here. That took me too long to work out and solve!
showSheet :: Sheet -> String
showSheet sheet = unlines [ concat [showPoint (V2 x y) | x <- ([0..maxX] :: [Int])
] | y <- ([0..maxY] :: [Int]) ]
where showPoint here = if here `S.member` sheet then "█" else " "
maxX = S.findMax $ S.map getX sheet
maxY = S.findMax $ S.map getY sheet
Code
You can get the code from my locally-hosed Git repo, or from Gitlab.