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

Day 11

lib/day11.livemd

Day 11

Setup

Mix.install([
  {:vega_lite, "~> 0.1.2"},
  {:kino, "~> 0.4.1"}
])
sample = """
5483143223
2745854711
5264556173
6141336146
6357385478
4167524645
2176841721
6882881134
4846848554
5283751526
"""

input = """
2264552475
7681287325
3878781441
6868471776
7175255555
7517441253
3513418848
4628736747
1133155762
8816621663
"""
defmodule Day11 do
  # Part A
  def dimensions(matrix) do
    y_max = length(matrix) - 1
    x_max = length(matrix |> Enum.at(0)) - 1
    {x_max, y_max}
  end

  def get_octopus(matrix, x, y) do
    matrix |> Enum.at(y) |> Enum.at(x)
  end

  # Very inefficient. The matrix should likely be a Tuple of Tuples instead.
  def update_octopus(matrix, x, y, value) do
    matrix
    |> List.update_at(y, fn row ->
      List.update_at(row, x, fn _ -> value end)
    end)
  end

  def increment_octopus(matrix, x, y) do
    case get_octopus(matrix, x, y) do
      :flashed -> matrix
      value -> matrix |> update_octopus(x, y, value + 1)
    end
  end

  def get_neighbors(matrix, x, y) do
    {x_max, y_max} = dimensions(matrix)

    [{-1, -1}, {-1, 0}, {-1, 1}, {0, -1}, {0, 1}, {1, -1}, {1, 0}, {1, 1}]
    |> Enum.map(fn {i, j} -> {i + x, j + y} end)
    |> Enum.filter(fn {i, j} ->
      0 <= i and i <= x_max and 0 <= j and j <= y_max
    end)
  end

  defp find_next_full_octopus(matrix) do
    {x_max, y_max} = dimensions(matrix)

    # very inefficient
    octopuses_at_nine =
      for j <- 0..y_max,
          i <- 0..x_max do
        case get_octopus(matrix, i, j) do
          :flashed -> false
          value when value > 9 -> {i, j}
          _value -> false
        end
      end

    if(octopuses_at_nine |> Enum.any?()) do
      octopuses_at_nine |> Enum.drop_while(&amp;(!&amp;1)) |> Enum.at(0)
    else
      :notfound
    end
  end

  defp flash_next_full_octopus(matrix, x, y) do
    matrix
    |> get_neighbors(x, y)
    |> Enum.reduce(matrix, fn {i, j}, acc -> increment_octopus(acc, i, j) end)
    |> update_octopus(x, y, :flashed)
  end

  defp charge_each_octopus(matrix, flash_count) do
    matrix
    |> Enum.map(fn row -> Enum.map(row, &amp;(&amp;1 + 1)) end)
    |> then(&amp;{&amp;1, flash_count})
  end

  defp reset_each_octopus(matrix, flash_count) do
    matrix
    |> Enum.map(fn row ->
      Enum.map(row, fn
        :flashed -> 0
        n -> n
      end)
    end)
    |> Enum.map(fn row ->
      Enum.map(row, fn x -> min(x, 9) end)
    end)
    |> then(&amp;{&amp;1, flash_count})
  end

  defp flash_next_octopus_until_done(matrix, flash_count) do
    case find_next_full_octopus(matrix) do
      :notfound ->
        {matrix, flash_count}

      {x, y} ->
        matrix
        |> flash_next_full_octopus(x, y)
        |> flash_next_octopus_until_done(flash_count + 1)
    end
  end

  def step(matrix, flash_count) do
    matrix
    |> charge_each_octopus(flash_count)
    |> then(fn {m, f} -> flash_next_octopus_until_done(m, f) end)
    |> then(fn {m, f} -> reset_each_octopus(m, f) end)
  end

  def step_n(matrix, flash_count, 0), do: {matrix, flash_count}

  def step_n(matrix, flash_count, n) do
    {new_matrix, new_flash_count} = step(matrix, flash_count)
    step_n(new_matrix, new_flash_count, n - 1)
  end

  # Part B
  def step_until_full_flash(matrix, flash_count, n) do
    {x_max, y_max} = dimensions(matrix)

    {new_matrix, new_flash_count} = step(matrix, flash_count)
    flash_delta = new_flash_count - flash_count
    # IO.puts "Step #{n} has #{flash_delta} flashes"
    if flash_delta == (x_max + 1) * (y_max + 1) do
      n + 1
    else
      step_until_full_flash(new_matrix, new_flash_count, n + 1)
    end
  end
end

Part a

input
|> String.split("\n", trim: true)
|> Enum.map(&amp;String.graphemes/1)
|> Enum.map(fn row ->
  Enum.map(row, &amp;String.to_integer/1)
end)
|> Day11.step_n(0, 100)
|> elem(1)

# widget =
#   VegaLite.new(width: 400, height: 400)
#   |> VegaLite.mark(:circle)
#   |> VegaLite.encode_field(:x, "x", type: :ordinal)
#   |> VegaLite.encode_field(:y, "y", type: :ordinal)
#   |> VegaLite.encode_field(:size, "size", type: :quantitative)
#   |> VegaLite.encode_field(:color, "size", type: :quantitative)
#   |> Kino.VegaLite.new()
#   |> Kino.render()

# {x_max, y_max} = Day11.dimensions(initial_matrix)

# 1..100
# |> Enum.reduce(
#   {initial_matrix, 0},
#   fn step, {matrix, flash_count} ->
#     points =
#       for j <- 0..y_max,
#           i <- 0..x_max do
#         %{x: i, y: j, size: Day11.get_octopus(matrix, i, j)}
#       end

#     Kino.VegaLite.clear(widget)
#     Kino.VegaLite.push_many(widget, points)
#     Process.sleep(500)
#     IO.puts(step)
#     Day11.step(matrix, flash_count)
#   end
# )

Part b

input
|> String.split("\n", trim: true)
|> Enum.map(&amp;String.graphemes/1)
|> Enum.map(fn row ->
  Enum.map(row, &amp;String.to_integer/1)
end)
|> Day11.step_until_full_flash(0, 0)