Powered by AppSignal & Oban Pro
Would you like to see your link here? Contact us

Advent of Code - Day 6

livebooks/day6.livemd

Advent of Code - Day 6

Part 1

sample = """
....#.....
.........#
..........
..#.......
.......#..
..........
.#..^.....
........#.
#.........
......#...
"""

sample
  |> String.split("\n", trim: true)
"#........." |> String.graphemes()

Lab guards in 1518 follow a very strict patrol protocol which involves repeatedly following these steps:

If there is something directly in front of you, turn right 90 degrees. Otherwise, take a step forward.

In this example, the guard will visit 41 distinct positions on your map.

Predict the path of the guard. How many distinct positions will the guard visit before leaving the mapped area?

mat = sample
  |> String.split("\n", trim: true)
  |> Enum.with_index()
  |> Enum.reduce(Map.new(), fn {line, row}, map ->
    line
      |> String.graphemes()
      |> Enum.with_index()
      |> Enum.reduce(map, fn {cell, col}, map ->
        Map.put(map, {row, col}, cell)
    end)
  end)
{init_loc, _} = mat 
  |> Enum.find(fn {_key, val} -> 
    val == "^"
  end)

facing = {-1, 0}
defmodule Part1 do
  def parse_input(input) do
    input_by_line = input |> String.split("\n", trim: true)
    
    rows = input_by_line |> length
    cols = input_by_line |> Enum.at(0) |> String.length()
    
    mat = input
      |> String.split("\n", trim: true)
      |> Enum.with_index()
      |> Enum.reduce(Map.new(), fn {line, row}, map ->
        line
          |> String.graphemes()
          |> Enum.with_index()
          |> Enum.reduce(map, fn {cell, col}, map ->
            Map.put(map, {row, col}, cell)
        end)
      end)

    {init_loc, _} = mat 
      |> Enum.find(fn {_key, val} -> 
        val == "^"
      end)


    {mat, init_loc, {-1, 0}, rows, cols}    
  end
  
  def find_visits(mat, init_loc, facing, rows, cols) do
    {_reason, _new_loc, visited} = recurse(mat, init_loc, facing, rows, cols, [init_loc])
    [_ | visited] = visited
    visited
  end
  
  def recurse(mat, init_loc, facing, rows, cols, visited) do
    {reason, new_loc, new_visited} = 
      move_until_obstructed(mat, init_loc, facing, rows, cols, visited)
    
    new_facing = case facing do
          {-1, 0} -> {0, 1}
          {1, 0} -> {0, -1}
          {0, 1} -> {+1, 0}
          {0, -1} -> {-1, 0}
        end
    
    {_reason, _new_loc, _final_visited} = case reason do
      :obstructed -> 
        recurse(mat, new_loc, new_facing, rows, cols, new_visited)
      :out_of_bounds -> 
        {reason, new_loc, new_visited}
    end
  end

  
  def move_until_obstructed(mat, {cur_x, cur_y} = _cur_loc, facing, rows, cols, visited) do
    # try to move in the facing direction until out of bounds
    {facing_x, facing_y} = facing
    
    {axis, range} = case facing do
      {-1, 0} -> {:x, (cur_x-1)..-1//-1} # facing up
      {1, 0} -> {:x, (cur_x+1)..rows//+1} # facing down  
      {0, 1} -> {:y, (cur_y+1)..cols//+1} # facing right
      {0, -1} -> {:y, (cur_y-1)..-1//-1} # facing left
      _ -> raise "error"
    end

    case axis do
      :x ->
        range
          |> Enum.reduce_while({:start, {cur_x, cur_y}, visited}, fn new_x, {_, {cx, cy}, visited} ->
            cond do 
              cx < 0 or cx >= rows ->
                [_ | visited] = visited
                {:halt, {:out_of_bounds, {new_x, cy}, visited}}
              Map.fetch!(mat, {cx, cy}) == "#" ->
                [_ | visited] = visited
                {:halt, {:obstructed, {new_x - 2 * facing_x, cy}, visited}}
              true -> 
                {:cont, {:out_of_bounds, {new_x, cy}, [{new_x, cy} | visited]}}
            end
          end)

      :y ->
        range
          |> Enum.reduce_while({:start, {cur_x, cur_y}, visited}, fn new_y, {_, {cx, cy}, visited} ->
            cond do 
              cy < 0 or cy >= cols ->
                [_ | visited] = visited
                {:halt, {:out_of_bounds, {cx, new_y}, visited}}
              Map.fetch!(mat, {cx, cy}) == "#" ->
                [_ | visited] = visited
                {:halt, {:obstructed, {cx, new_y - 2 *facing_y}, visited}}
              true -> 
                {:cont, {:out_of_bounds, {cx, new_y}, [{cx, new_y} | visited]}}
            end
          end)
      _ -> raise "error"
    end
  end
end
sample

with {mat, init_loc, facing, rows, cols} <- Part1.parse_input(sample) do
  Part1.find_visits(mat, init_loc, facing, rows, cols)
end
|> MapSet.new()
|> MapSet.size()

input = File.read!("/Users/errantsky/elixir-projects/phoenix-projects/advent_of_code_2024/inputs/day6.txt")
with {mat, init_loc, facing, rows, cols} <- Part1.parse_input(input) do
  Part1.find_visits(mat, init_loc, facing, rows, cols)
end
|> MapSet.new()
|> MapSet.size()