# Advent of Code 2019 day 3

December 03, 2019 in

Day 3 is back to familiar ground: walking across a grid of points, looking for interesting things along the way.

## Data structures

The first thing to fix is the data structures I'll be using to address the problem. As we want to find the points that intersect between paths, a Set of Locations seems like a good place to start.

type Location = V2 Int -- x, y

type Visited = S.Set Location


That's where we want to end up, but what does the input look like? It's a sequence of path Segments, each with a Direction and a length.

data Direction = East | South | West | North deriving (Show, Eq)

data Segment = Segment { _direction :: Direction
, _steps :: Int
} deriving (Show, Eq)


The first part of the problem is going to be expanding those segments into the Path taken by each wire. As we're building the path, we need to keep track of both the locations visited, and the current _tip of the wire so we know where to add the next segment's locations.

data Path = Path { _visited :: Visited
, _tip :: Location
}
deriving (Show, Eq)


Now the data structures are fixed, most of the rest of the work is just translating from one to another.

## Parsing

We have, at last, something almost interesting to parse! Here's the parser, using the applicative style. Briefly, <$> is like function composition, <*> joins arguments, and <|> shows choices between alternatives. wiresP = many pathP pathP = segmentP sepBy1 comma segmentP = segmentify <$> directionP <*> integer
where segmentify direction steps =
Segment { _direction = direction, _steps = steps }

directionP = eP <|> sP <|> wP <|> nP
eP = (symb "R" *> pure East)
sP = (symb "D" *> pure South)
wP = (symb "L" *> pure West)
nP = (symb "U" *> pure North)


Paraphrasing, this shows that the wires are many (0 or more) paths, and each path is a sequence of one or more segments separated by commas. A segment is a combination of a direction and a number of steps, and the function segmentify builds the Segment record from them. The direction parser just takes a symbol and returns the direction, using pure to turn it into an applicative value.

I had to use sepBy1 in order to prevent Megaparsec creating an infinite number of zero-length pathPs. sepBy1 requires there be at least one segment in the path. Yes, there are only two paths in the input and this allows for any number, but this way was almost easier.

## Part 1

The core of this part is the function travelSegment, which takes a single Segment and a Path, and extends the path with the segment. Most of it should be simple enough to read. The main work is done in the foldl' function; the iterate generates an infinite number of steps in the specified direction, the right number are taken, and the foldl' takes each step and inserts it into the visited set. (The arithmetic signs with ^ are for manipulating the vectors I'm using for locations.)

travelSegment :: Path -> Segment -> Path
travelSegment path segment = path { _tip = tip', _visited = visited' }
where   delta = facing $_direction segment distance = _steps segment start = _tip path visited = _visited path visited' = foldl' (flip S.insert) visited$ take distance $drop 1$ iterate (^+^ delta) start
tip' = start ^+^ distance *^ delta

facing :: Direction -> Location
facing East = V2 1 0
facing South = V2 0 (-1)
facing West = V2 (-1) 0
facing North = V2 0 1


Travelling along the whole path is just foldling all the segments, and we do all the paths by mapping over all the segment specifications.

travelAllPaths :: [[Segment]] -> [Path]
travelAllPaths = map travelPath

travelPath :: [Segment] -> Path
travelPath segments = foldl' travelSegment path0 segments
where   path0 = Path { _visited = S.empty, _tip = V2 0 0 }


We now have all the sets of points visited by each wire. We can find the common points by intersecting those sets, and then find the closed one. crossovers finds where the wires cross, and closest find the crossover closer to the origin. Note that fold1' takes the first element of the list as the base case for the fold.

closest :: Visited -> Int
closest points = S.findMin $S.map manhattan points crossovers :: [Path] -> Visited crossovers travelledPaths = foldl1' S.intersection$ map _visited travelledPaths


The code for this part is on Github. Part 2 is a bit different…

## Part 2

Part 2 adds an interesting wrinkle. Rather than just keeping track of the crossings, we now have to annotate each step with the distance to get there. There's two ways to do this. One is to keep the code as above, and re-create the distances once I've found the crossings. The other is to re-engineer the existing code to keep the "distance travelled" annotations as we go. I decided to do the latter.

The main difference is changing the type of Visited from being a Set of Locations to being a Map of Locations to Ints (as the distances). This isn't too bad, as the keys in a Map naturally form a Set. Not much else needs to change, beyond using functions from the Map library rather than the Set library. The Path record type now needs to keep track of the _currentLength as well as as the tip.

The travelSegment function needs to change, so that it includes these distances as each segment is processed. The foldl' now operates on pairs of (distance, location), and that needs a special insertStep function to handle the pair and record the shortest distances if a point is revisited.

travelSegment :: Path -> Segment -> Path
travelSegment path segment = path { _tip = tip', _visited = visited', _currentLength = len'}
where   delta = facing $_direction segment distance = _steps segment start = _tip path len = _currentLength path len' = len + distance visited = _visited path visited' = foldl' insertStep visited$ take distance $drop 1$ zip [len, (len + 1) ..] $iterate (^+^ delta) start tip' = start ^+^ distance *^ delta insertStep visits (dist, loc) = if loc M.member visits then visits else M.insert loc dist visits  crossovers also needs to change, so that each crossover is stored with the sum of path lengths that lead to it; M.intersectionWith (+) does that. (Thanks to Reddit user mstksg for pointing that out to me.) crossovers :: [Path] -> Visited crossovers travelledPaths = foldl1' (M.intersectionWith (+))$ map _visited travelledPaths


shortestPaths then finds the smallest element in the map.

shortestPaths :: Visited -> Int
shortestPaths crossings = minimum \$ M.elems crossings


## Code

And that's it! The full code is on Github. Let me know what you think.

Next Post