lud
Advent of Code 2023 - Day 23
For part 2 I transformed the map into a graph because I wanted to see it and check if there was a bottleneck point or something.
But no, so I guess the code for P1 would have worked too, the only difference is that I run all possible states 1 step ahead to the next intersection (instead of running just one), and then I keep only the 3000 longest.
Most Liked
bjorng
According to Wikipedia, the longest path problem is NP-hard. Fortunately, the reduced graph (with all straight-line garden paths reduced into single vertices) is sufficiently small that it is practical to calculate the length of all possible graphs. After optimizing my solution it solves both parts in 16 seconds on my computer.
I assume that there is a divide-and-conquer approach for finding the longest path for this particular graph much faster, but I didn’t pursue it.
midouest
Similar to everyone else, I built a graph for the map by finding the junctions and the edges between them. I initially thought that I would need to handle both directed and undirected edges for part 1. However, I rendered the map and the junctions with Kino.HTML and saw that all of the edges were directed. Funny that my code would have needed almost no changes for part 2 if I had implemented that behavior in part 1!
Part 1
defmodule Part1 do
@deltas [{1, 0}, {0, 1}, {-1, 0}, {0, -1}]
def parse(input) do
lines = String.split(input, "\n", trim: true)
size = length(lines)
map =
for {line, y} <- Enum.with_index(lines),
{char, x} <- String.to_charlist(line) |> Enum.with_index(),
char != ?#,
into: %{} do
{{y, x}, char}
end
{map, size}
end
def find_nodes(map, size) do
map
|> Map.keys()
|> Enum.filter(fn {y, x} ->
y == 0 or y == size - 1 or
@deltas
|> Enum.map(fn {dy, dx} -> {y + dy, x + dx} end)
|> Enum.count_until(fn {y, x} -> Map.has_key?(map, {y, x}) end, 3) == 3
end)
end
def find_edges(map, nodes, directed \\ true), do: find_edges(map, nodes, directed, nodes, %{})
def find_edges(_, _, _, [], acc), do: acc
def find_edges(map, nodes, directed, [node | rest], acc) do
acc =
for delta <- @deltas,
{start, finish} <- find_edge(map, nodes, directed, node, delta),
reduce: acc do
acc ->
Map.update(acc, start, [finish], fn existing ->
[finish | existing]
|> Enum.uniq()
end)
end
find_edges(map, nodes, directed, rest, acc)
end
def find_edge(map, nodes, directed, start, delta),
do: find_edge(map, nodes, directed, start, 0, start, [delta])
def find_edge(_, _, _, _, _, _, []), do: []
def find_edge(map, nodes, directed, start, len, {y1, x1} = prev, [{dy1, dx1} | prev_deltas]) do
next = {y1 + dy1, x1 + dx1}
char = map[next]
cond do
char == nil ->
find_edge(map, nodes, directed, start, len, prev, prev_deltas)
directed and
((char == ?^ and dy1 == 1) or
(char == ?< and dx1 == 1) or
(char == ?v and dy1 == -1) or
(char == ?> and dx1 == -1)) ->
[]
Enum.member?(nodes, next) ->
if directed,
do: [{start, {next, len}}],
else: [{start, {next, len}}, {next, {start, len}}]
true ->
next_deltas = Enum.reject(@deltas, fn {dy2, dx2} -> dy2 == -dy1 and dx2 == -dx1 end)
find_edge(map, nodes, directed, start, len + 1, next, next_deltas)
end
end
def find_paths(edges, size), do: find_paths(edges, size, [[{{0, 1}, 0}]], [])
def find_paths(_, _, [], acc), do: acc
def find_paths(edges, size, [[{{y, _} = prev, _} | _] = path | rest], acc) do
if y == size - 1 do
find_paths(edges, size, rest, [path | acc])
else
next =
edges[prev]
|> Enum.reject(fn {neighbor, _} ->
Enum.any?(path, fn {visited, _} -> neighbor == visited end)
end)
|> Enum.map(&[&1 | path])
find_paths(edges, size, next ++ rest, acc)
end
end
def path_length(path) do
path
|> Enum.map(&elem(&1, 1))
|> Enum.sum()
|> Kernel.+(length(path) - 1)
end
def html(map, size, nodes \\ []) do
text =
for y <- 0..(size - 1) do
for x <- 0..(size - 1) do
case map[{y, x}] do
nil ->
?#
char ->
if Enum.member?(nodes, {y, x}) do
~c"<b>O</b>"
else
char
end
end
end
|> List.flatten([?\n])
end
|> Enum.join()
color = if length(nodes) > 0, do: "gray", else: "lightgray"
Kino.HTML.new("""
<style>
html {
font-size: 7px;
}
pre {
color: #{color};
background-color: black;
padding: 5px;
line-height: 1em;
letter-spacing: 0.3em;
}
b {
color: white;
}
</style>
<pre><code>#{text}</code></pre>
""")
end
end
{map, size} = Part1.parse(input)
nodes = Part1.find_nodes(map, size)
Kino.render(Part1.html(map, size, nodes))
edges = Part1.find_edges(map, nodes)
paths = Part1.find_paths(edges, size)
paths
|> Enum.map(&Part1.path_length/1)
|> Enum.max()
Part 2
edges = Part1.find_edges(map, nodes, false)
paths = Part1.find_paths(edges, size)
paths
|> Enum.map(&Part1.path_length/1)
|> Enum.max()








