Day 06
Mix.install([
{:kino, "~> 0.14.2"}
])
IEx.Helpers.c("/Users/johnb/dev/2024adventOfCode/advent_of_code.ex")
alias AdventOfCode, as: AOC
alias Kino.Input
# Note: when making the next template, something like this works well:
# `cat day04.livemd | sed 's/06/04/' > day04.livemd`
#
# When inspecting lists of numbers, use "charlists: :as_lists"
Installation and Data
input_p1example = Kino.Input.textarea("Example Data", monospace: true)
input_p1puzzleInput = Kino.Input.textarea("Puzzle Input", monospace: true)
input_source_select =
Kino.Input.select("Source", [{:example, "example"}, {:puzzle_input, "puzzle input"}])
p1data = fn ->
(Kino.Input.read(input_source_select) == :example &&
Kino.Input.read(input_p1example)) ||
Kino.Input.read(input_p1puzzleInput)
end
Solution
defmodule Day06 do
@guards ["^", ">", "v", "<"]
@floor "."
@obstacle "#"
@looping_obstacle "O"
@right_turn %{"^" => ">", ">" => "v", "v" => "<", "<" => "^"}
@forever 1..20000 #_500_000
def find_guard(grid) do
Enum.find(AOC.grid_cells(grid), fn c -> grid[c] in @guards end)
end
def next_pos(grid, pos, dir) do
case {dir, AOC.grid_x(grid, pos), AOC.grid_y(grid, pos)} do
{"^", _, 0} -> :off_board
{"^", _, _} -> pos - grid.grid_width
{">", rrr, _} when rrr == (grid.grid_width - 1) -> :off_board
{">", _, _} -> pos + 1
{"v", _, ddd} when ddd == (grid.grid_height - 1) -> :off_board
{"v", _, _} -> pos + grid.grid_width
{"<", 0, _} -> :off_board
{"<", _, _} -> pos - 1
end
end
def turn_right(grid, pos, dir) do
new_dir = @right_turn[dir]
new_pos = next_pos(grid, pos, new_dir)
{new_pos, new_dir}
end
def find_route(grid, position) do
original_direction = grid[position]
grid = Map.put(grid, position, @floor)
path = MapSet.new([position])
result = Enum.reduce_while(@forever, {position, original_direction, path}, fn _, {pos, dir, set} ->
pos2 = next_pos(grid, pos, dir)
{new_pos, new_dir} = turn_right(grid, pos, dir)
case {pos2, grid[pos2], new_dir}
do
{:off_board, _, _} -> {:halt, {grid, set}}
{_, @floor, _} -> {:cont, {pos2, dir, MapSet.put(set, pos2)}}
{_, @obstacle, :off_board} -> {:halt, {grid, set}}
{_, @obstacle, _} -> {:cont, {new_pos, new_dir, MapSet.put(set, new_pos)}}
end
end)
case result do
{grid, set} -> {grid, set}
{_position, _direction, set} -> {:huh, set}
end
end
def find_route_and_mark_path(original_grid, position, block_pos) do
Enum.reduce_while(@forever,
{position, original_grid[position], MapSet.new(),
Map.put(original_grid, block_pos, @looping_obstacle)
},
fn _iteration, {pos, ahead_dir, path, grid} ->
ahead_pos = next_pos(grid, pos, ahead_dir)
case {ahead_pos, ahead_dir, grid[ahead_pos],
MapSet.member?(path, {ahead_pos, ahead_dir})
} do
{:off_board, _, _, _} ->
{:halt, {:off_board, path, grid}}
{_, _, obstacle, _} when obstacle in [@obstacle, @looping_obstacle] ->
{:cont, {pos, @right_turn[ahead_dir], path, grid
# |> AOC.display_grid("73")
}}
{_, _, _, true} ->
{:halt, {:loop, path, grid |> Map.put(ahead_pos, "L")}}
{_, _, _, _} ->
{:cont, {ahead_pos, ahead_dir, MapSet.put(path, {ahead_pos, ahead_dir}),
Map.put(grid, pos, ahead_dir)}}
end
end)
end
def solve1(text) do
grid = AOC.as_grid(text)
position = find_guard(grid)
{_result, path} = find_route(grid, position)
Enum.count(path)
end
def solve2(text) do
grid = AOC.as_grid(text)
position = find_guard(grid)
{_result, path} = find_route(grid, position)
possible_obstacles = MapSet.to_list(path) -- [position]
possible_obstacles
|> Enum.reduce(0, fn blocking_spot, acc ->
result = find_route_and_mark_path(grid, position, blocking_spot)
case result do
{:loop, _path, _grid} -> acc + 1
{:off_board, _path, _grid} -> acc
_ -> acc
end
end)
end
end
# Example:
p1data.()
|> Day06.solve1()
|> IO.inspect(label: "\n*** Part 1 solution (example: 41)")
# 4711
p1data.()
|> Day06.solve2()
|> IO.inspect(label: "\n*** Part 2 solution (example: 6)")
# 1562