Powered by AppSignal & Oban Pro

Day 9

notebooks/day09.livemd

Day 9

Mix.install([
  {:req, "~> 0.4.5"},
  {:kino, "~> 0.11.3"},
  {:vega_lite, "~> 0.1.8"},
  {:kino_vega_lite, "~> 0.1.11"}
])

Input

input =
  Req.get!(
    "https://adventofcode.com/2023/day/9/input",
    headers: [{"Cookie", ~s"session=#{System.fetch_env!("LB_AOC_SESSION")}"}]
  ).body

Kino.Text.new(input, terminal: true)

Part 1

histories =
  for line <- String.split(input, "\n", trim: true) do
    String.split(line)
    |> Enum.map(&amp;String.to_integer/1)
  end
defmodule Part1 do
  def difference(history, fun), do: difference(history, [], fun)

  def difference(prev_diffs, rest_diffs, fun) do
    if Enum.all?(prev_diffs, &amp;(&amp;1 == 0)) do
      fun.(rest_diffs, 0)
    else
      next_diffs =
        prev_diffs
        |> Enum.chunk_every(2, 1, :discard)
        |> Enum.map(fn xs -> Enum.reduce(xs, &amp;Kernel.-/2) end)

      difference(next_diffs, [prev_diffs | rest_diffs], fun)
    end
  end

  def predictr([], extrapolation), do: extrapolation

  def predictr([diffs | rest], extrapolation) do
    predictr(rest, extrapolation + List.last(diffs))
  end
end

histories
|> Enum.map(fn history -> Part1.difference(history, &amp;Part1.predictr/2) end)
|> Enum.sum()

Part 2

defmodule Part2 do
  def predictl([], extrapolation), do: extrapolation

  def predictl([[first | _] | rest], extrapolation) do
    predictl(rest, first - extrapolation)
  end
end

histories
|> Enum.map(fn history -> Part1.difference(history, &amp;Part2.predictl/2) end)
|> Enum.sum()

Viz

alias VegaLite, as: Vl

defmodule Viz do
  def difference(widget, history), do: difference(widget, history, [])

  def difference(widget, prev_diffs, rest_diffs) do
    i = length(rest_diffs)

    data =
      for {diff, x} <- Enum.with_index(prev_diffs) do
        %{"x" => x, "y" => diff, "i" => i}
      end

    Kino.VegaLite.push_many(widget, data)
    Process.sleep(div(500, i + 1))

    if Enum.all?(prev_diffs, &amp;(&amp;1 == 0)) do
      predictr(widget, [prev_diffs | rest_diffs])
    else
      next_diffs =
        prev_diffs
        |> Enum.chunk_every(2, 1, :discard)
        |> Enum.map(fn xs -> Enum.reduce(xs, &amp;Kernel.-/2) end)

      difference(widget, next_diffs, [prev_diffs | rest_diffs])
    end
  end

  def predictr(widget, diffs), do: predictr(widget, diffs, 0)
  def predictr(_, [], extrapolation), do: extrapolation

  def predictr(widget, [diffs | rest], extrapolation) do
    next_extrapolation = extrapolation + List.last(diffs)

    i = length(rest)

    Kino.VegaLite.push(widget, %{
      "x" => length(diffs),
      "y" => next_extrapolation,
      "i" => i
    })

    Process.sleep(div(500, i + 1))
    predictr(widget, rest, next_extrapolation)
  end
end

widget =
  Vl.new(width: 600, height: 300)
  |> Vl.mark(:line)
  |> Vl.encode_field(:x, "x", type: :quantitative)
  |> Vl.encode_field(:y, "y", type: :quantitative)
  |> Vl.encode_field(:color, "i", type: :nominal)
  |> Kino.VegaLite.render()

for history <- histories do
  Kino.VegaLite.clear(widget)
  Viz.difference(widget, history)
end