Powered by AppSignal & Oban Pro

Day 11: Dumbo Octopus

2021/day_11_dumbo_octopus.livemd

Day 11: Dumbo Octopus

Mix.install([:kino])

input = Kino.Input.textarea("Please paste your input:")

Setup

inputs =
  input
  |> Kino.Input.read()
  |> String.split("\n", trim: true)
  |> Enum.map(fn line -> line |> String.graphemes() |> Enum.map(&String.to_integer/1) end)
  |> Enum.with_index()
  |> Enum.reduce(%{}, fn {line, y}, grid ->
    line
    |> Enum.with_index()
    |> Enum.reduce(grid, fn {value, x}, grid ->
      grid |> Map.put({x, y}, value)
    end)
  end)

Part 1

https://adventofcode.com/2021/day/11

Solve: Part 1

defmodule Day11.Part1 do
  def run(grid, step) do
    size = size(grid)
    do_run(grid, step, size, 0)
  end

  def size(grid) do
    grid
    |> Enum.reduce({0, 0}, fn {{x, y}, _energy_level}, {max_x, max_y} ->
      max_x = max(max_x, x)
      max_y = max(max_y, y)

      {max_x, max_y}
    end)
    |> then(fn {max_x, max_y} -> {max_x + 1, max_y + 1} end)
  end

  defp do_run(_grid, 0, _size, total_flash_count) do
    total_flash_count
  end

  defp do_run(grid, remain_step, size, total_flash_count) do
    new_grid =
      grid
      |> increase_energy_level()
      |> flash(size)

    flash_count = new_grid |> count_flash()

    do_run(new_grid, remain_step - 1, size, total_flash_count + flash_count)
  end

  defp increase_energy_level(grid) do
    grid
    |> Enum.map(fn {position, energy_level} -> {position, energy_level + 1} end)
    |> Map.new()
  end

  defp flash(grid, size) do
    case get_flash_positions(grid) do
      [] ->
        grid

      flash_positions ->
        grid
        |> reset_energy_level()
        |> increase_energy_level_of_adjacent_points(flash_positions, size)
        |> flash(size)
    end
  end

  defp get_flash_positions(grid) do
    grid
    |> Enum.filter(fn {_position, energy_level} -> energy_level >= 10 end)
    |> Enum.map(fn {position, _energy_level} -> position end)
  end

  defp reset_energy_level(grid) do
    grid
    |> Enum.map(fn
      {position, energy_level} when energy_level >= 10 -> {position, 0}
      {position, energy_level} -> {position, energy_level}
    end)
    |> Map.new()
  end

  defp increase_energy_level_of_adjacent_points(grid, flash_positions, {width, height}) do
    flash_positions
    |> Enum.reduce(grid, fn {x, y}, grid ->
      for dx <- -1..1,
          dy <- -1..1,
          adjacent_x = x + dx,
          adjacent_y = y + dy,
          adjacent_x > -1,
          adjacent_x < width,
          adjacent_y > -1,
          adjacent_y < height,
          reduce: grid do
        grid ->
          grid
          |> Map.update!({x + dx, y + dy}, fn
            0 -> 0
            energy_level -> energy_level + 1
          end)
      end
    end)
  end

  defp count_flash(grid) do
    grid
    |> Enum.map(fn
      {_position, 0} -> 1
      _ -> 0
    end)
    |> Enum.sum()
  end
end

inputs
|> Day11.Part1.run(100)

Part 2

https://adventofcode.com/2021/day/11#part2

Solve: Part 2

defmodule Day11.Part2 do
  def run(grid) do
    size = size(grid)
    do_run(grid, size, 1)
  end

  def size(grid) do
    grid
    |> Enum.reduce({0, 0}, fn {{x, y}, _energy_level}, {max_x, max_y} ->
      max_x = max(max_x, x)
      max_y = max(max_y, y)

      {max_x, max_y}
    end)
    |> then(fn {max_x, max_y} -> {max_x + 1, max_y + 1} end)
  end

  defp do_run(grid, {width, height} = size, step) do
    new_grid =
      grid
      |> increase_energy_level()
      |> flash(size)

    flash_count = new_grid |> count_flash()

    case flash_count == width * height do
      true -> step
      false -> do_run(new_grid, size, step + 1)
    end
  end

  defp increase_energy_level(grid) do
    grid
    |> Enum.map(fn {position, energy_level} -> {position, energy_level + 1} end)
    |> Map.new()
  end

  defp flash(grid, size) do
    case get_flash_positions(grid) do
      [] ->
        grid

      flash_positions ->
        grid
        |> reset_energy_level()
        |> increase_energy_level_of_adjacent_points(flash_positions, size)
        |> flash(size)
    end
  end

  defp get_flash_positions(grid) do
    grid
    |> Enum.filter(fn {_position, energy_level} -> energy_level >= 10 end)
    |> Enum.map(fn {position, _energy_level} -> position end)
  end

  defp reset_energy_level(grid) do
    grid
    |> Enum.map(fn
      {position, energy_level} when energy_level >= 10 -> {position, 0}
      {position, energy_level} -> {position, energy_level}
    end)
    |> Map.new()
  end

  defp increase_energy_level_of_adjacent_points(grid, flash_positions, {width, height}) do
    flash_positions
    |> Enum.reduce(grid, fn {x, y}, grid ->
      for dx <- -1..1,
          dy <- -1..1,
          adjacent_x = x + dx,
          adjacent_y = y + dy,
          adjacent_x > -1,
          adjacent_x < width,
          adjacent_y > -1,
          adjacent_y < height,
          reduce: grid do
        grid ->
          grid
          |> Map.update!({x + dx, y + dy}, fn
            0 -> 0
            energy_level -> energy_level + 1
          end)
      end
    end)
  end

  defp count_flash(grid) do
    grid
    |> Enum.map(fn
      {_position, 0} -> 1
      _ -> 0
    end)
    |> Enum.sum()
  end
end

step = inputs |> Day11.Part2.run()