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

Day 11: Plutonian Pebbles

day11.livemd

Day 11: Plutonian Pebbles

Mix.install([:kino])

Section

input = Kino.Input.textarea("input")
input = Kino.Input.read(input)

Part 1 & 2

defmodule PlutonianPebbles do
  def solve(input) do
    start_memo()

    # part 1
    parse_input(input)
    |> Enum.map(fn i ->
      blink(i, 0, 25)
    end)
    |> Enum.sum()
    |> IO.inspect()

    # part 2
    parse_input(input)
    |> Enum.map(fn i ->
      blink(i, 0, 75)
    end)
    |> Enum.sum()
    |> IO.inspect()
  end

  def blink(stone_number, blink_num, max_blinks) do
    case get_cached_result(stone_number, blink_num, max_blinks) do
      nil ->
        result = compute_blink(stone_number, blink_num, max_blinks)
        cache_result(stone_number, blink_num, max_blinks, result)
        result

      cached_result ->
        cached_result
    end
  end

  defp compute_blink(stone_number, blink_num, max_blinks) do
    cond do
      blink_num == max_blinks ->
        1

      stone_number == 0 ->
        blink(1, blink_num + 1, max_blinks)

      is_even_len?(stone_number) ->
        as_list = integer_to_list(stone_number)
        {left, right} = Enum.split(as_list, half_size(as_list))

        blink(list_to_integer(left), blink_num + 1, max_blinks) +
          blink(list_to_integer(right), blink_num + 1, max_blinks)

      true ->
        blink(stone_number * 2024, blink_num + 1, max_blinks)
    end
  end

  def start_memo do
    case :ets.whereis(:blink_cache) do
      :undefined ->
        :ets.new(:blink_cache, [:set, :public, :named_table])

      _table_ref ->
        :ets.delete_all_objects(:blink_cache)
    end

    :ok
  end

  defp get_cached_result(stone_number, blink_num, max_blinks) do
    case :ets.lookup(:blink_cache, {stone_number, blink_num, max_blinks}) do
      [{_, result}] -> result
      [] -> nil
    end
  end

  defp cache_result(stone_number, blink_num, max_blinks, result) do
    :ets.insert(:blink_cache, {{stone_number, blink_num, max_blinks}, result})
  end

  def integer_to_list(integer) do
    Integer.digits(integer)
  end

  def list_to_integer(list) do
    Integer.undigits(list)
  end

  def half_size(list) do
    div(length(list), 2)
  end

  def is_even_len?(integer) do
    rem(Integer.digits(integer) |> length(), 2) == 0
  end

  defp parse_input(input) do
    String.split(input, " ", trim: true)
    |> Enum.map(&String.to_integer(&1))
  end
end

PlutonianPebbles.solve(input)