Advent of Code 2023
Mix.install([
{:req, "~> 0.3.2"},
{:kino, "~> 0.11.3"}
])
Day 10
input =
"https://adventofcode.com/2023/day/10/input"
|> Req.get!(headers: [cookie: "session=#{System.get_env("AOC_COOKIE")}"])
|> Map.get(:body)
sample = """
7-F7-
.FJ|7
SJLL7
|F--J
LJ.LJ
"""
sample1 = """
...........
.S-------7.
.|F-----7|.
.||.....||.
.||.....||.
.|L-7.F-J|.
.|..|.|..|.
.L--J.L--J.
...........
"""
sample2 = """
..........
.S------7.
.|F----7|.
.||....||.
.||....||.
.|L-7F-J|.
.|..||..|.
.L--JL--J.
..........
"""
sample3 = """
.F----7F7F7F7F-7....
.|F--7||||||||FJ....
.||.FJ||||||||L7....
FJL7L7LJLJ||LJ.L-7..
L--J.L7...LJS7F-7L7.
....F-J..F7FJ|L7L7L7
....L7.F7||L7|.L7L7|
.....|FJLJ|FJ|F7|.LJ
....FJL-7.||.||||...
....L---J.LJ.LJLJ...
"""
sample4 = """
FF7FSF7F7F7F7F7F---7
L|LJ||||||||||||F--J
FL-7LJLJ||||||LJL-77
F--JF--7||LJLJ7F7FJ-
L---JF-JLJ.||-FJLJJ7
|F|F-JF---7F7-L7L|7|
|FFJF7L7F-JF7|JL---7
7-L-JL7||F7|L7F-7F7|
L.L7LFJ|||||FJL7||LJ
L7JLJL-JLJLJL--JLJ.L
"""
defmodule A do
def parse(input) do
input
|> String.split("\n", trim: true)
|> Enum.map(&String.split(&1, "", trim: true))
end
def info(rows) do
{s_row, s_y} =
rows
|> Enum.with_index()
|> Enum.find(&("S" in elem(&1, 0)))
{_s_c, s_x} =
s_row
|> Enum.with_index()
|> Enum.find(&(elem(&1, 0) == "S"))
data =
for {row, y} <- Enum.with_index(rows),
{c, x} <- Enum.with_index(row),
into: %{},
do: {{x, y}, c}
%{
data: data,
h: Enum.count(rows),
w: Enum.count(hd(rows)),
s: {s_x, s_y}
}
# |> IO.inspect()
end
def walk(%{s: s} = info) do
Stream.iterate(0, &(&1 + 1))
|> Enum.reduce_while({[{s, 0}], Map.new()}, fn _i, {open, visited} ->
# if i == 30 do
# raise "too many iterations"
# end
case open do
[cur | rest] ->
{v, steps} = cur
to_visit = visit(cur, visited, info)
visited = Map.put(visited, v, steps)
{:cont, {rest ++ to_visit, visited}}
[] ->
{:halt, visited}
end
end)
end
def draw_visited(visited) do
{{w, _}, _} = Enum.max_by(visited, fn {{x, _y}, _v} -> x end)
{{_, h}, _} = Enum.max_by(visited, fn {{_x, y}, _v} -> y end)
padding =
Enum.max_by(visited, fn {{_x, _y}, v} -> v end)
|> elem(1)
|> Integer.to_string()
|> String.length()
Enum.map(0..h, fn y ->
Enum.map(0..w, fn x ->
Map.get(visited, {x, y})
|> case do
nil ->
" " |> List.duplicate(padding) |> Enum.join()
n ->
n
|> Integer.to_string()
|> String.pad_leading(padding, " ")
end
end)
|> Enum.join("|")
end)
|> Enum.join("\n")
|> IO.puts()
visited
end
def visit({pos, step}, visited, info) do
expand(pos)
|> remove_lower_visited(visited, step)
|> remove_out_of_bounds(info.w, info.h)
|> remove_unwalkable(pos, info.data)
|> Enum.map(&{&1, step + 1})
# |> dbg()
end
def expand({x, y}) do
[
{1, 0},
{-1, 0},
{0, 1},
{0, -1}
]
|> Enum.map(fn {dx, dy} -> {x + dx, y + dy} end)
end
def remove_lower_visited(vs, visited, step) do
vs
|> Enum.filter(fn {x, y} ->
case Map.get(visited, {x, y}) do
nil -> true
n -> n > step
end
end)
end
def remove_out_of_bounds(vs, w, h) do
vs
|> Enum.filter(fn {x, y} ->
x >= 0 && x < w && y >= 0 && y < h
end)
end
def remove_unwalkable(vs, {x, y}, data) do
s = get_symbol(data, x, y)
dirs = symbol_to_dirs(s)
vs
|> Enum.filter(fn {x2, y2} ->
s2 = get_symbol(data, x2, y2)
dirs2 = symbol_to_dirs(s2)
case {x2 - x, y2 - y} do
{0, 1} -> :s in dirs && :n in dirs2
{0, -1} -> :n in dirs && :s in dirs2
{1, 0} -> :e in dirs && :w in dirs2
{-1, 0} -> :w in dirs && :e in dirs2
end
end)
end
def get_symbol(data, x, y) do
Map.fetch!(data, {x, y})
end
def symbol_to_dirs("|"), do: [:n, :s]
def symbol_to_dirs("-"), do: [:e, :w]
def symbol_to_dirs("L"), do: [:n, :e]
def symbol_to_dirs("J"), do: [:n, :w]
def symbol_to_dirs("7"), do: [:s, :w]
def symbol_to_dirs("F"), do: [:s, :e]
def symbol_to_dirs("S"), do: [:n, :s, :e, :w]
def symbol_to_dirs(_), do: []
def part1(input) do
input
|> parse()
|> info()
|> walk()
|> Enum.max_by(&elem(&1, 1))
|> elem(1)
end
def part2(input) do
input
|> parse()
|> info()
|> walk()
end
end
Part 1
input
|> A.part1()
Part 2
zoom = fn input ->
info =
input
|> A.parse()
|> A.info()
visited = info |> A.walk()
# replace S
{{x, y}, _s} = info.data |> Enum.find(fn {_k, v} -> v == "S" end)
new_symbol =
[
{1, 0},
{-1, 0},
{0, 1},
{0, -1}
]
|> Enum.map(fn {x1, y1} -> {x1 + x, y1 + y} end)
|> A.remove_out_of_bounds(info.w, info.h)
|> A.remove_unwalkable({x, y}, info.data)
|> Enum.map(fn {x1, y1} -> {x1 - x, y1 - y} end)
|> Enum.map(fn
{1, 0} -> :e
{-1, 0} -> :w
{0, 1} -> :s
{0, -1} -> :n
end)
|> Enum.sort()
|> case do
[:e, :s] -> "F"
[:e, :w] -> "-"
[:n, :s] -> "|"
[:n, :w] -> "J"
[:s, :w] -> "7"
[:e, :n] -> "L"
end
info = put_in(info, [:data, {x, y}], new_symbol)
blocked =
visited
|> Map.keys()
|> Enum.reduce(MapSet.new(), fn {x, y}, acc ->
case Map.get(info.data, {x, y}) do
"F" -> [{1, 1}, {1, 2}, {2, 1}]
"J" -> [{0, 1}, {1, 1}, {1, 0}]
"7" -> [{0, 1}, {1, 1}, {1, 2}]
"L" -> [{1, 0}, {1, 1}, {2, 1}]
"-" -> [{0, 1}, {1, 1}, {2, 1}]
"|" -> [{1, 0}, {1, 1}, {1, 2}]
_ -> []
end
|> Enum.map(fn {x1, y1} -> {3 * x + x1, 3 * y + y1} end)
|> Enum.reduce(acc, fn v, acc -> MapSet.put(acc, v) end)
end)
{_, outside} =
Stream.iterate(0, &(&1 + 1))
# |> Enum.take(1000)
|> Enum.reduce_while({[{0, 0}], MapSet.new()}, fn i, {open, outside} ->
case open do
[] ->
{:halt, {i, outside}}
[{cx, cy} = cur | rest] ->
to_explore =
[{1, 0}, {-1, 0}, {0, 1}, {0, -1}]
|> Enum.map(fn {dx, dy} -> {cx + dx, cy + dy} end)
|> Enum.filter(fn {x, y} ->
x >= 0 && x < info.w * 3 && y >= 0 && y < info.h * 3
end)
|> Enum.reject(fn v -> MapSet.member?(blocked, v) end)
|> Enum.reject(fn v -> MapSet.member?(outside, v) end)
{:cont, {(rest ++ to_explore) |> Enum.uniq(), MapSet.put(outside, cur)}}
end
end)
# |> IO.inspect()
all = for y <- 0..(info.h * 3), x <- 0..(info.w * 3), into: MapSet.new(), do: {x, y}
inside =
all
|> MapSet.difference(blocked)
|> MapSet.difference(outside)
actual =
inside
|> Enum.filter(fn {x, y} -> rem(x, 3) == 1 && rem(y, 3) == 1 end)
|> Enum.into(MapSet.new())
MapSet.size(actual) |> IO.inspect()
for y <- 0..(info.h * 3) do
for x <- 0..(info.w * 3) do
cond do
MapSet.member?(blocked, {x, y}) -> "█"
MapSet.member?(outside, {x, y}) -> "o"
MapSet.member?(actual, {x, y}) -> "X"
MapSet.member?(inside, {x, y}) -> "I"
true -> " "
end
end
|> Enum.join("")
end
|> Enum.join("\n")
|> then(fn s ->
[
""
,
s,
"
"
]
|> Enum.join("")
|> Kino.Markdown.new()
end)
end
zoom.(input)
Draw
draw = fn input ->
info =
input
|> A.parse()
|> A.info()
visited = info |> A.walk()
for y <- 0..info.h do
for x <- 0..info.w do
if Map.has_key?(visited, {x, y}) do
Map.get(info.data, {x, y})
else
"."
end
end
|> Enum.join("")
end
|> Enum.join("\n")
|> String.replace("F", "╔")
|> String.replace("7", "╗")
|> String.replace("L", "╚")
|> String.replace("J", "╝")
|> String.replace("-", "═")
|> String.replace("|", "║")
|> then(fn s ->
[
""
,
s,
"
"
]
|> Enum.join("")
|> Kino.Markdown.new()
end)
end
draw.(sample1)
draw.(sample2)
draw.(sample3)
draw.(sample4)
draw.(input)