#!/usr/bin/env elixir
# Advent of Code 2025. Day 11
Mix.install([
{:memoize, "~> 1.4.4"},
])
defmodule M do
def paths_1(map), do: paths_1(map, ["you"])
defp paths_1(map, froms) do
tos = Enum.reduce(froms, [], fn
"out", tos -> ["out" | tos]
from, tos -> map[from] ++ tos
end)
if Enum.all?(tos, & &1 == "out"), do: tos, else: paths_1(map, tos)
end
#########################################################
# Start of extra code to display paths
def tab(depth, display_first \\ false)
def tab(_depth, true), do: ""
def tab(depth, false), do: String.duplicate(" ", depth)
def seen(seen_dac, seen_fft), do: "#{if seen_dac, do: "!", else: "."}#{if seen_fft, do: "!", else: "."}"
def display_paths_2(map), do: display_paths_2(map, "svr", false, false, true, 0)
def display_paths_2(_map, "out", true, true, display_first, display_depth) do
IO.puts "#{tab(display_depth, display_first)} out !"
end
def display_paths_2(_map, "out", _, _, display_first, display_depth) do
IO.puts "#{tab(display_depth, display_first)} out"
end
def display_paths_2(map, "dac" = from, seen_dac, seen_fft, display_first, display_depth) do
IO.write "#{tab(display_depth, display_first)} #{from}#{seen(seen_dac, seen_fft)} -> "
[fst | rest] = map[from]
display_paths_2(map, fst, true, seen_fft, true, display_depth+1)
Enum.each(rest, fn to ->
display_paths_2(map, to, true, seen_fft, false, display_depth+1)
end)
end
def display_paths_2(map, "fft" = from, seen_dac, seen_fft, display_first, display_depth) do
IO.write "#{tab(display_depth, display_first)} #{from}#{seen(seen_dac, seen_fft)} -> "
[fst | rest] = map[from]
display_paths_2(map, fst, seen_dac, true, true, display_depth+1)
Enum.each(rest, fn to ->
display_paths_2(map, to, seen_dac, true, false, display_depth+1)
end)
end
def display_paths_2(map, from, seen_dac, seen_fft, display_first, display_depth) do
IO.write "#{tab(display_depth, display_first)} #{from}#{seen(seen_dac, seen_fft)} -> "
[fst | rest] = map[from]
display_paths_2(map, fst, seen_dac, seen_fft, true, display_depth+1)
Enum.each(rest, fn to ->
display_paths_2(map, to, seen_dac, seen_fft, false, display_depth+1)
end)
end
# End of extra code to display paths
#########################################################
# Memoization using the memoize library
def paths_2(map), do: paths_2(map, "svr", false, false)
defp paths_2(_map, "out", true, true), do: 1
defp paths_2(_map, "out", _seen_dac, _seen_fft), do: 0
defp paths_2(map, from, seen_dac, seen_fft) do
seen_dac = (if from == "dac", do: true, else: seen_dac)
seen_fft = (if from == "fft", do: true, else: seen_fft)
Memoize.Cache.get_or_run({__MODULE__, :paths, [from, seen_dac, seen_fft]}, fn ->
Enum.reduce(map[from], 0, fn to, total ->
val = paths_2(map, to, seen_dac, seen_fft)
total+val
end)
end)
end
# Memoization without a library
def paths_3(map) do
{_memo, total} = paths_3(map, "svr", false, false, %{})
total
end
defp paths_3(_map, "out", true, true, memo), do: {memo, 1}
defp paths_3(_map, "out", _seen_dac, _seen_fft, memo), do: {memo, 0}
defp paths_3(map, from, seen_dac, seen_fft, memo) do
seen_dac = (if from == "dac", do: true, else: seen_dac)
seen_fft = (if from == "fft", do: true, else: seen_fft)
case Map.get(memo, [from, seen_dac, seen_fft]) do
nil ->
{memo, total} = Enum.reduce(map[from], {memo, 0}, fn to, {memo, total} ->
{memo, val} = paths_3(map, to, seen_dac, seen_fft, memo)
{Map.put(memo, [from, seen_dac, seen_fft], total+val), total+val}
end)
{Map.put(memo, [from, seen_dac, seen_fft], total), total}
total -> {memo, total}
end
end
end
map = File.read!("../day11.txt")
|> String.trim_trailing()
|> String.split("\n")
|> Enum.reduce(%{}, fn line, map ->
{d, ds} = String.split(line, ": ") |> then(fn [d, ds] -> {d, String.split(ds)} end)
Map.put(map, d, ds)
end)
M.paths_1(map) |> length() |> IO.inspect(label: "Day 11. Part 1")
# M.display_paths_2(map) # useful with test data
M.paths_2(map) |> IO.inspect(label: "Day 11. Part 2")
M.paths_3(map) |> IO.inspect(label: "Day 11. Part 2")