Powered by AppSignal & Oban Pro

Day 09

2021/day-09.livemd

Day 09

Setup

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

example_input =
  Kino.Input.textarea("example input:")
  |> Kino.render()

real_input = Kino.Input.textarea("real input:")

Part 1

defmodule Grid do
  def basins(_, [], basins), do: basins

  def basins(grid, [{coord, _} | low_points], basins) do
    basins(grid, low_points, [expand_basin(grid, neighbors(coord), MapSet.new([coord])) | basins])
  end

  defp expand_basin(_, [], set), do: set

  defp expand_basin(grid, [coord | rest], set) do
    case Map.get(grid, coord, 9) do
      9 ->
        expand_basin(grid, rest, set)

      _ ->
        case MapSet.member?(set, coord) do
          true ->
            expand_basin(grid, rest, set)

          _ ->
            expand_basin(grid, neighbors(coord) ++ rest, MapSet.put(set, coord))
        end
    end
  end

  def low_points(grid) do
    Enum.reduce(grid, [], fn {coord, v} = point, acc ->
      neighbors(coord)
      |> then(&Map.take(grid, &1))
      |> Map.values()
      |> Enum.all?(&(&1 > v))
      |> then(&if &1, do: [point | acc], else: acc)
    end)
  end

  defp neighbors({i, j}), do: [{i - 1, j}, {i + 1, j}, {i, j - 1}, {i, j + 1}]

  def parse(input) do
    input
    |> String.split()
    |> Enum.with_index(fn line, j ->
      String.split(line, "", trim: true)
      |> Enum.with_index(fn node, i ->
        {{i, j}, String.to_integer(node)}
      end)
    end)
    |> List.flatten()
    |> Map.new()
  end
end
real_input
|> Kino.Input.read()
|> Grid.parse()
|> Grid.low_points()
|> Enum.map(&Kernel.+(elem(&1, 1), 1))
|> Enum.sum()

Part 2

real_input
|> Kino.Input.read()
|> Grid.parse()
|> then(&Grid.basins(&1, Grid.low_points(&1), []))
|> Enum.map(&MapSet.size(&1))
|> Enum.sort(:desc)
|> Enum.take(3)
|> Enum.product()