Advent of Code Day 3: Lazy Tobogganing

Posted on December 3, 2020

The third Advent of Code puzzle is about riding a toboggan in a straight line downhill through a forest. The puzzle input is a map of which squares are open and which squares have trees:

..##.......
#...#...#..
.#....#..#.
..#.#...#.#
.#...##..#.
..#.##.....
.#.#.#....#
.#........#
#.##...#...
#...##....#
.#..#...#.#

with the wrinkle that rather than having the toboggan course end at the right boundary, the pattern instead repeats infinitely to the right. We’re to determine how many tree’s we’ll run into for a given straight-line path through the forest (where paths are specified by slope: e.g. right 3, down 1; right 1, down 2; etc).

This is a super, super nice problem to model in Haskell thanks to its laziness. Rather than fiddle about with modulo indices / manually wrapping around a traversal back to the start of an array, we can just turn each row of our input into an infinitely-cycled list:

parseLine :: Text -> [Bool]
parseLine line = cycle $ map (== '#') $ Text.unpack line

and the runtime will turn our code that uses this into the correct looping behavior. As a result, the code for counting the number of trees we run into is very straightforward, with almost no logical thinking required:

numTreesHit :: [[Bool]] -> Int -> Int -> Int
numTreesHit [] _ _ = 0
numTreesHit inp@((treeHere : _) : _) right down =
  (if treeHere then 1 else 0) 
    + numTreesHit newInp right down
  where
    newInp = inp & drop down & map (drop right)

It’s a very natural recursion. If we’re already at the bottom (no more rows of input left to traverse), then we’re not hitting any more trees. Otherwise, drop down a number of rows, drop a right number of columns from each remaining row, and recurse. It’s almost like we’re just recalculating our intrepid tobogganer’s view of the hill instead of explicitly tracking what our position is!

Full solution on GitHub: https://github.com/ewilden/aoc-2020/blob/main/src/Day03.hs