Powered by AppSignal & Oban Pro
Would you like to see your link here? Contact us

Day 11

day11.livemd

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)