Day 11
Mix.install([
{:kino, "~> 0.14.2"},
{:kino_aoc, "~> 0.1.7"}
])
Part 1
{_, input} = if false do
KinoAOC.download_puzzle("2024", "11", System.fetch_env!("LB_AOC_SESSION"))
else
{:ok, "125 17"}
end
input = input |> String.trim() |> String.split() |> Enum.map(&String.to_integer/1)
defmodule Blinker do
def change(0), do: [1]
def change(stone) do
digits = Integer.digits(stone) |> length() # Digit count
cond do
rem(digits, 2) == 0 ->
factor = :math.pow(10, digits / 2) |> trunc()
[
:math.floor(stone / factor) |> trunc(), # Left digits
rem(stone, factor) # Right digits
]
true -> [stone * 2024]
end
end
def blink(stones, count, acc \\ 0, cache \\ %{})
def blink(_, -1, _, cache), do: {1, cache}
def blink([], _, acc, cache), do: {acc, cache}
def blink([first | stones], count, acc, cache) do
cached_val = Map.get(cache, {first, count})
{sum, cache} = if cached_val == nil do
{sum, out_cache} = change(first) |> blink(count - 1, 0, cache)
new_cache = Map.put(cache, {first, count}, sum) |> Map.merge(out_cache)
{sum, new_cache}
else
{cached_val, cache}
end
blink(stones, count, acc + sum, cache)
end
end
{result, _} = Blinker.blink(input, 25)
result
Part 2
{result, _} = Blinker.blink(input, 25)
result
Experiments
Based on work that isn’t mine – I really admired the solution that @mexicat posted in the day 11 thread on ElixirForum and wanted to try my hand at a similar implementation.
defmodule MapBlinker do
def blink(stones, 0), do: stones |> Map.values() |> Enum.sum()
def blink(stones, count) do
stones |> Map.to_list() |> do_blink(%{}) |> blink(count - 1)
end
def do_blink([], output), do: output
def do_blink([{0, count} | rest], output) do
output = Map.update(output, 1, count, & &1 + count)
do_blink(rest, output)
end
def do_blink([{val, count} | rest], output) do
digits = Integer.digits(val) |> length() # Digit count
output = cond do
rem(digits, 2) == 0 ->
factor = :math.pow(10, digits / 2) |> trunc()
left = :math.floor(val / factor) |> trunc()
right = rem(val, factor)
# Add each side of split val to output map
Map.update(output, left, count, & &1 + count)
|> Map.update(right, count, & &1 + count)
true -> Map.update(output, val * 2024, count, & &1 + count)
end
do_blink(rest, output)
end
end
input_map = Enum.reduce(input, %{}, fn val, acc -> Map.put(acc, val, 1) end)
result = MapBlinker.blink(input_map, 75)