Day 5 was one where generalising the code made things simpler. I started by being explicit about filling out lines, but ended up with generalising the solution to something much neater.
This being a problem with coordinates and counting intersections, I built my solution around a Map
from Point
s to Int
s (the counts of the number of lines crossing each point). A Point
comes from the linear
package, the V2
object for two-dimensional points.
That gives me these data types, and the parser to read them.
import qualified Data.Map.Strict as M
import Linear (V2(..), (^+^))
type Point = V2 Int
type Grid = M.Map Point Int
data Line = Line Point Point deriving (Eq, Show)
traceP = lineP `sepBy` endOfLine
lineP = Line <$> (pointP <* " -> ") <*> pointP
pointP = V2 <$> (decimal <* ",") <*> decimal
The other operation is to expand a line from the two endpoints to the set of points it represents. I can define a delta for each line, how the location changes at each step along the line. The constraint that lines are horizontal, vertical, or at 45⁰ means the components of that delta are the signum of the differences between the line ends.
Expanding the line then becomes repeatedly applying the delta to the starting point, taking enough points to include both ends.
expandLine :: Line -> [Point]
expandLine line@(Line p0 _p1) = take numPoints $ iterate (^+^ delta) p0
where numPoints = findLen line + 1
delta = findDelta line
findLen :: Line -> Int
findLen (Line (V2 x0 y0) (V2 x1 y1)) =
max (abs (x1 - x0)) (abs (y1 - y0))
findDelta :: Line -> Point
findDelta (Line (V2 x0 y0) (V2 x1 y1)) = (V2 dx dy)
where dx = signum (x1 - x0)
dy = signum (y1 - y0)
To find the counts, I expand all the lines into points and include them in the diagram
with nested fold
s around M.insertWith
. I find the answers by filtering the diagram and finding its size.
part2 trace = M.size $ M.filter (> 1) diagram
where allLines = map expandLine trace
diagram = addLines M.empty allLines
addLines :: Grid -> [[Point]] -> Grid
addLines = foldr insertLine
where insertLine l d = foldr insertPoint d l
insertPoint p d = M.insertWith (+) p 1 d
Part 1 becomes slightly more complex, as I have to include only the horizontal and vertical lines. A quick predicate to define the lines to include, then a filter to retain only the lines I want.
part1 trace = M.size $ M.filter (> 1) diagram
where hvLines = map expandLine $ filter isHorizOrVert trace
diagram = addLines M.empty hvLines
isHoriz :: Line -> Bool
isHoriz (Line (V2 x0 _y0) (V2 x1 _y1)) = x0 == x1
isVert :: Line -> Bool
isVert (Line (V2 _x0 y0) (V2 _x1 y1)) = y0 == y1
isHorizOrVert :: Line -> Bool
isHorizOrVert line = (isHoriz line) || (isVert line)
Code
You can get the code from my locally-hosed Git repo, or from Gitlab.