Advent of Code - Day 16
Mix.install([
{:kino_aoc, "~> 0.1"}
])
Introduction
–> Content
Puzzle
{:ok, puzzle_input} =
KinoAOC.download_puzzle("2023", "16", System.fetch_env!("LB_AOC_SESSION"))
Parser
Code - Parser
defmodule Parser do
def parse(input) do
input
|> String.split("\n", trim: true)
|> Enum.map(fn line -> String.split(line, "", trim: true) end)
end
end
Tests - Parser
ExUnit.start(autorun: false)
defmodule ParserTest do
use ExUnit.Case, async: true
import Parser
@input """
.|...\\....
|.-.\\.....
.....|-...
........|.
..........
.........\\
..../.\\\\..
.-.-/..|..
.|....-|.\\
..//.|....
"""
@expected [
[".", "|", ".", ".", ".", "\\", ".", ".", ".", "."],
["|", ".", "-", ".", "\\", ".", ".", ".", ".", "."],
[".", ".", ".", ".", ".", "|", "-", ".", ".", "."],
[".", ".", ".", ".", ".", ".", ".", ".", "|", "."],
[".", ".", ".", ".", ".", ".", ".", ".", ".", "."],
[".", ".", ".", ".", ".", ".", ".", ".", ".", "\\"],
[".", ".", ".", ".", "/", ".", "\\", "\\", ".", "."],
[".", "-", ".", "-", "/", ".", ".", "|", ".", "."],
[".", "|", ".", ".", ".", ".", "-", "|", ".", "\\"],
[".", ".", "/", "/", ".", "|", ".", ".", ".", "."]
]
describe "parse/1" do
test "simple example" do
assert parse(@input) == @expected
end
end
end
ExUnit.run()
Part One
Code - Part 1
defmodule PartOne do
def solve(input) do
IO.puts("--- Part One ---")
IO.puts("Result: #{run(input)}")
end
def run(tiles_string) when is_bitstring(tiles_string), do: run(Parser.parse(tiles_string))
def run(tiles) do
trace_beams(tiles)
|> Enum.count()
end
def trace_beams(tiles),
do: trace_beams(tiles, [{0, 0, :right}])
def trace_beams(tiles, beams),
do: trace_beams(tiles, beams, MapSet.new())
def trace_beams(_tiles, [], res),
do: Enum.map(res, fn {beam_i, beam_j, _} -> {beam_i, beam_j} end) |> Enum.uniq()
def trace_beams(tiles, beams, res) do
res = MapSet.union(res, MapSet.new(beams))
next_beams =
Enum.map(beams, fn {beam_i, beam_j, beam_dir} ->
cell = tiles |> Enum.at(beam_i) |> Enum.at(beam_j)
case cell do
"." ->
pass_through(tiles, {beam_i, beam_j, beam_dir})
"\\" ->
reflect(tiles, {beam_i, beam_j, beam_dir}, "\\")
"/" ->
reflect(tiles, {beam_i, beam_j, beam_dir}, "/")
"|" ->
if beam_dir in [:left, :right] do
[
pass_through(tiles, {beam_i, beam_j, :down}),
pass_through(tiles, {beam_i, beam_j, :up})
]
else
pass_through(tiles, {beam_i, beam_j, beam_dir})
end
"-" ->
if beam_dir in [:down, :up] do
[
pass_through(tiles, {beam_i, beam_j, :left}),
pass_through(tiles, {beam_i, beam_j, :right})
]
else
pass_through(tiles, {beam_i, beam_j, beam_dir})
end
nil ->
nil
end
end)
|> List.flatten()
|> Enum.reject(fn v -> v == nil || MapSet.member?(res, v) end)
trace_beams(tiles, next_beams, res)
end
defp pass_through(tiles, {beam_i, beam_j, beam_dir}) do
{new_beam_i, new_beam_j} =
case beam_dir do
:right -> {beam_i, beam_j + 1}
:left -> {beam_i, beam_j - 1}
:down -> {beam_i + 1, beam_j}
:up -> {beam_i - 1, beam_j}
end
if min(new_beam_i, new_beam_j) < 0 ||
new_beam_i >= length(tiles) ||
new_beam_j >= length(hd(tiles)) do
nil
else
{new_beam_i, new_beam_j, beam_dir}
end
end
defp reflect(tiles, {beam_i, beam_j, :right}, "/"),
do: pass_through(tiles, {beam_i, beam_j, :up})
defp reflect(tiles, {beam_i, beam_j, :left}, "/"),
do: pass_through(tiles, {beam_i, beam_j, :down})
defp reflect(tiles, {beam_i, beam_j, :down}, "/"),
do: pass_through(tiles, {beam_i, beam_j, :left})
defp reflect(tiles, {beam_i, beam_j, :up}, "/"),
do: pass_through(tiles, {beam_i, beam_j, :right})
defp reflect(tiles, {beam_i, beam_j, :right}, "\\"),
do: pass_through(tiles, {beam_i, beam_j, :down})
defp reflect(tiles, {beam_i, beam_j, :left}, "\\"),
do: pass_through(tiles, {beam_i, beam_j, :up})
defp reflect(tiles, {beam_i, beam_j, :down}, "\\"),
do: pass_through(tiles, {beam_i, beam_j, :right})
defp reflect(tiles, {beam_i, beam_j, :up}, "\\"),
do: pass_through(tiles, {beam_i, beam_j, :left})
def place_on_map(tiles, coords), do: place_on_map(tiles, coords, "#")
def place_on_map(tiles, nil, symbol), do: place_on_map(tiles, [], symbol)
def place_on_map(tiles, coords, "#") do
Enum.map(0..(length(tiles) - 1), fn i ->
Enum.map(0..(length(hd(tiles)) - 1), fn j ->
if {i, j} in coords, do: "#", else: "."
end)
|> Enum.join()
end)
end
end
Tests - Part 1
ExUnit.start(autorun: false)
defmodule PartOneTest do
use ExUnit.Case, async: true
import PartOne
@raw_input """
.|...\\....
|.-.\\.....
.....|-...
........|.
..........
.........\\
..../.\\\\..
.-.-/..|..
.|....-|.\\
..//.|....
"""
@input Parser.parse(@raw_input)
describe "run/1" do
test "main example" do
assert run(@raw_input) == 46
end
end
describe "trace_beams/2" do
test "main example" do
assert place_on_map(@input, trace_beams(@input)) == [
"######....",
".#...#....",
".#...#####",
".#...##...",
".#...##...",
".#...##...",
".#..####..",
"########..",
".#######..",
".#...#.#.."
]
end
end
end
ExUnit.run()
Solution - Part 1
PartOne.solve(puzzle_input)
Part Two
Code - Part 2
defmodule PartTwo do
import PartOne, except: [run: 1]
def solve(input) do
IO.puts("--- Part Two ---")
IO.puts("Result: #{run(input)}")
end
def run(tiles_string) when is_bitstring(tiles_string), do: run(Parser.parse(tiles_string))
def run(tiles) do
# left and right side entering
(Enum.map(0..(length(tiles) - 1), fn entering_row ->
[{entering_row, 0, :right}, {entering_row, length(hd(tiles)) - 1, :left}]
end) ++
Enum.map(0..(length(hd(tiles)) - 1), fn entering_column ->
# top and bottom side entering
[{0, entering_column, :down}, {length(tiles) - 1, entering_column, :up}]
end))
|> List.flatten()
|> Enum.map(fn starting_point ->
trace_beams(tiles, [starting_point])
|> Enum.count()
end)
|> Enum.max()
end
end
Tests - Part 2
ExUnit.start(autorun: false)
defmodule PartTwoTest do
use ExUnit.Case, async: true
import PartTwo
@raw_input """
.|...\\....
|.-.\\.....
.....|-...
........|.
..........
.........\\
..../.\\\\..
.-.-/..|..
.|....-|.\\
..//.|....
"""
@input Parser.parse(@raw_input)
describe "run/1" do
test "main example" do
assert run(@raw_input) == 51
end
end
end
ExUnit.run()
Solution - Part 2
PartTwo.solve(puzzle_input)
# 8674