For day 5 of Advent of Code, “Hydrothermal Venture”, we are to count distinct intersections between lines. In the general case, this would be a tricky problem whose naïve solution is quadratic. But here, we’re lucky enough to have the following simplifiers:
- we’re only handling grid-aligned lines (horizontal, vertical, diagonal)
- the grid is small enough
So we can just draw the lines and count, and have a solution linear in grid area instead of quadratic in number of lines. Which, given my puzzle input, is more or less the same anyway.
This post is literate Haskell; let’s get the imports out of the way.
import Control.Arrow ((&&&),(***))
import Data.Array (accumArray,elems)
import Data.List.Split (wordsBy)
As far as I could tell, the coordinates in my input are all between 0
and 1000, so they fit in a standard Int
.
type Coords = (Int,Int)
readCoords :: String -> Coords
== ',') -> [x,y]) = (read x,read y)
readCoords (wordsBy (
type Line = (Coords,Coords)
parse :: String -> Line
words -> [a,"->",b]) = (readCoords a,readCoords b) parse (
I’m abusing ViewPatterns again, though this time it seems slightly more reasonable.
Next we need to be able to convert a line to the list of integer coordinates it lies on.
line2coords :: Line -> [Coords]
= [ (x1+i*dx,y1+i*dy) | i <- [0 .. max w h ] ]
line2coords ((x1,y1),(x2,y2)) where (dx,w) = sgnAbs (x2-x1)
= sgnAbs (y2-y1)
(dy,h) = signum &&& abs sgnAbs
We now have all we need to “draw” the lines on a virtual grid, counting the number of overwrites.
count :: [Line] -> Int
= length . filter (>= 2) . elems .
count +) 0 ((0,0),(999,999)) .
accumArray (flip zip (repeat (1 :: Int)) .
concatMap line2coords
For part 1, we only consider horizontal and vertical lines, so I need a discriminator for that.
isHV :: Line -> Bool
= x1 == x2 || y1 == y2 isHV ((x1,y1),(x2,y2))
And a simple wrapper to do it all.
main :: IO ()
= interact $ show . (curry (count *** count) =<< filter isHV) .
main map parse . lines
This concludes today’s solution. See you tomorrow!