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

2023 day 9

2023/elixir/day-9.livemd

2023 day 9

Mix.install([
  {:kino, "~> 0.11.3"}
])

Section

input = Kino.Input.textarea("input")
sample_input = """
10 13 16 21 30 45
0 3 6 9 12 15
1 3 6 10 15 21
"""

input = Kino.Input.read(input)

Part 1 & Part 2

defmodule Day9 do
  defmodule Part1 do
    def solve(input_str) do
      input_str
      |> Day9.parse()
      |> Enum.map(&predict_next/1)
      |> Enum.sum()
    end

    defp predict_next(sequence) do
      do_predict_next(sequence, [List.last(sequence)])
    end

    defp do_predict_next(cur_seq, last_values, non_zero_cnt \\ nil)

    defp do_predict_next(_cur_seq, last_values, 0) do
      Enum.sum(last_values)
    end

    defp do_predict_next(cur_seq, last_values, _non_zero_cnt) do
      {_, [last | _] = next_sequence, non_zero_cnt} =
        cur_seq
        |> Enum.reduce({_prev = nil, _diffs = [], _non_zero_cnt = 0}, fn
          num, {nil, [], 0} ->
            {num, [], 0}

          num, {prev, diffs, non_zero_cnt} ->
            diff = num - prev
            non_zero_cnt = if diff != 0, do: non_zero_cnt + 1, else: non_zero_cnt
            {num, [diff | diffs], non_zero_cnt}
        end)

      last_values = [last | last_values]
      do_predict_next(Enum.reverse(next_sequence), last_values, non_zero_cnt)
    end
  end

  defmodule Part2 do
    def solve(input_str) do
      input_str
      |> Day9.parse()
      |> Enum.map(&predict_backward/1)
      |> Enum.sum()
    end

    def predict_backward(sequence) do
      do_predict_backward(sequence, [hd(sequence)])
    end

    defp do_predict_backward(cur_seq, first_values, non_zero_cnt \\ nil)

    defp do_predict_backward(_cur_seq, [0 | first_values], 0) do
      first_values
      |> Enum.reverse()
      |> Enum.map_every(2, fn x -> -x end)
      |> Enum.sum()
      |> then(&(-&1))
    end

    defp do_predict_backward(cur_seq, first_values, _non_zero_cnt) do
      {_, next_sequence, non_zero_cnt} =
        cur_seq
        |> Enum.reduce({_prev = nil, _diffs = [], _non_zero_cnt = 0}, fn
          num, {nil, [], 0} ->
            {num, [], 0}

          num, {prev, diffs, non_zero_cnt} ->
            diff = num - prev
            non_zero_cnt = if diff != 0, do: non_zero_cnt + 1, else: non_zero_cnt
            {num, [diff | diffs], non_zero_cnt}
        end)

      [first | _] = next_sequence = Enum.reverse(next_sequence)
      first_values = [first | first_values]
      do_predict_backward(next_sequence, first_values, non_zero_cnt)
    end
  end

  def parse(input_str) do
    to_integer = &String.to_integer/1

    input_str
    |> String.split("\n", trim: true)
    |> Enum.map(&(String.split(&1) |> Enum.map(to_integer)))
  end
end
Day9.Part1.solve(input)
Day9.Part2.solve(input)

k1 = f1 - 0
k2 = f2 - k1 = f2 - f1 k3 = f3 - k2 = f3 - f2 + f1
k4 = f4 - f3 + f2 - f1