Powered by AppSignal & Oban Pro

Day 9

2021/notebooks/day-09.livemd

Day 9

Setup

input = Aoc.get_input(9)
textarea = Kino.Input.textarea("Puzzle input", default: input)
test_textarea = Kino.Input.textarea("Test input")
small_test_textarea = Kino.Input.textarea("Small test input")
options = [
  puzzle: "Puzzle",
  test: "Test",
  small_test: "Small"
]

select = Kino.Input.select("Input source", options)
lines =
  select
  |> Kino.Input.read()
  |> case do
    :puzzle -> input
    :test -> test_textarea |> Kino.Input.read()
    :small_test -> small_test_textarea |> Kino.Input.read()
  end
  |> String.split(["\n"], trim: true)

Part 1

rows = lines |> Enum.count() |> IO.inspect(label: "rows")
cols = lines |> Enum.at(0) |> String.length() |> IO.inspect(label: "cols")

map =
  lines
  |> Enum.map(&String.split(&1, "", trim: true))
  |> Enum.with_index()
  |> Enum.reduce(%{}, fn {row, y}, map ->
    row
    |> Enum.map(&String.to_integer/1)
    |> Enum.with_index()
    |> Enum.reduce(map, fn {val, x}, map ->
      Map.put(map, {x, y}, val)
    end)
  end)
points = for x <- 0..(cols - 1), y <- 0..(rows - 1), into: [], do: {x, y}

points
|> Enum.filter(fn {x, y} ->
  val = Map.get(map, {x, y})

  [
    {-1, 0},
    {1, 0},
    {0, 1},
    {0, -1}
  ]
  |> Enum.map(fn {dx, dy} -> {x + dx, y + dy} end)
  |> Enum.filter(fn {x2, y2} -> x2 >= 0 &amp;&amp; x2 < cols &amp;&amp; y2 >= 0 &amp;&amp; y2 < rows end)
  |> Enum.map(fn {x2, y2} -> Map.get(map, {x2, y2}) end)
  |> Enum.all?(fn h -> val < h end)
end)
|> Enum.map(fn p -> Map.get(map, p) + 1 end)
|> Enum.sum()

Part 2

rows = lines |> Enum.count()
cols = lines |> Enum.at(0) |> String.length()

map =
  lines
  |> Enum.map(&amp;String.split(&amp;1, "", trim: true))
  |> Enum.with_index()
  |> Enum.reduce(%{}, fn {row, y}, map ->
    row
    |> Enum.map(&amp;String.to_integer/1)
    |> Enum.with_index()
    |> Enum.reduce(map, fn {val, x}, map ->
      Map.put(map, {x, y}, val)
    end)
  end)

points = for x <- 0..(cols - 1), y <- 0..(rows - 1), into: [], do: {x, y}

points
|> Enum.reduce({[], MapSet.new()}, fn p, {basins, visited} ->
  if MapSet.member?(visited, p) do
    {basins, visited}
  else
    {visited, size} =
      Stream.iterate(0, &amp; &amp;1)
      |> Enum.reduce_while({[p], visited, 0}, fn _, {to_visit, visited, size} ->
        case to_visit do
          [] ->
            {:halt, {visited, size}}

          [{x, y} = point | remaining] ->
            visited = MapSet.put(visited, point)

            case Map.get(map, point) do
              9 ->
                {:cont, {remaining, visited, size}}

              n when is_integer(n) ->
                expand =
                  [
                    {1, 0},
                    {0, 1},
                    {-1, 0},
                    {0, -1}
                  ]
                  |> Enum.map(fn {dx, dy} -> {dx + x, dy + y} end)
                  |> Enum.filter(fn p -> Map.get(map, p) end)
                  |> Enum.filter(fn p -> !MapSet.member?(visited, p) end)

                {:cont, {(remaining ++ expand) |> Enum.uniq(), visited, size + 1}}
            end
        end
      end)

    {[size | basins], visited}
  end
end)
|> elem(0)
|> Enum.sort(&amp;>/2)
|> Enum.take(3)
|> Enum.product()