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

Advent of Code 2021 day 11

2021/priv/day-11.livemd

Advent of Code 2021 day 11

Setup

Mix.install([
  {:kino, "~> 0.4.1"},
  {:vega_lite, "~> 0.1.2"}
])
input = Kino.Input.textarea("Input:")
grid =
  input
  |> Kino.Input.read()
  |> String.split("\n", trim: true)
  |> Enum.map(&String.to_charlist/1)
  |> Enum.with_index(fn line, i ->
    Enum.with_index(line, fn energy, j -> {{i, j}, energy - ?0} end)
  end)
  |> List.flatten()
  |> Enum.into(%{})

Part 1

defmodule Day10 do
  def step(grid) do
    flash(Map.keys(grid), grid, MapSet.new())
  end

  defp flash([cord | rest], grid, flashed) do
    energy = grid[cord]

    cond do
      is_nil(energy) or cord in flashed ->
        flash(rest, grid, flashed)

      energy >= 9 ->
        cords = neighbors(cord) ++ rest
        flash(cords, Map.put(grid, cord, 0), MapSet.put(flashed, cord))

      true ->
        flash(rest, Map.put(grid, cord, energy + 1), flashed)
    end
  end

  # match the map_reduce return
  defp flash([], grid, flashed), do: {MapSet.size(flashed), grid}

  defp neighbors({x, y} = cord) do
    for col <- Range.new(x - 1, x + 1), row <- Range.new(y - 1, y + 1) do
      {col, row}
    end
    |> then(fn list -> list -- [cord] end)
  end
end

1..100
|> Enum.map_reduce(grid, fn _, grid ->
  Day10.step(grid)
end)
|> elem(0)
|> Enum.sum()

Part 2

Stream.iterate(1, &amp;(&amp;1 + 1))
|> Enum.reduce_while(grid, fn i, grid ->
  case Day10.step(grid) do
    {flashes, grid} when map_size(grid) == flashes -> {:halt, i}
    {_flashes, grid} -> {:cont, grid}
  end
end)

Visualization

alias VegaLite, as: Vl

graph =
  Vl.new(width: 500, height: 500)
  # |> Vl.config(view: [stroke: :transparent])
  |> Vl.mark(:circle, opacity: 0.8)
  |> Vl.encode_field(:x, "x", type: :quantitative, axis: false)
  |> Vl.encode_field(:y, "y", type: :quantitative, axis: false)
  |> Vl.encode_field(:color, "h",
    type: :quantitative,
    # scale: [domain: [0, 9], range: ["brown", "red", "yellow"]]
    # scale: [domain: [0, 9], range: ["purple", "white", "pink"]]
    scale: [domain: [0, 9], range: ["blue", "white", "yellow"]]
  )
  |> Vl.encode_field(:size, "h", type: :quantitative, scale: [domain: [0, 9], range: [20, 400]])
  |> Vl.encode_field(:tooltip, "h", format_type: "number")
  |> Kino.VegaLite.new()
  |> Kino.render()

Kino.VegaLite.periodically(
  graph,
  100,
  grid,
  fn grid ->
    {flashes, grid} = Day10.step(grid)

    data =
      Enum.map(grid, fn {{x, y}, h} ->
        %{"x" => x, "y" => y, "h" => if(h == 0, do: 9, else: h - 1)}
      end)

    Kino.VegaLite.clear(graph)
    Kino.VegaLite.push_many(graph, data)

    if map_size(grid) == flashes do
      # IO.inspect data, label: :grid
      :halt
    else
      {:cont, grid}
    end
  end
)