Powered by AppSignal & Oban Pro

Advent of Code 2021 - Day 9

2021/day09.livemd

Advent of Code 2021 - Day 9

Setup

Mix.install([
  {:kino, "~> 0.4.1"}
])
:ok
example_input = Kino.Input.textarea("Please enter the example input:")
my_input = Kino.Input.textarea("Please enter your input:")
defmodule Parser do
  def parse_input(input) do
    input
    |> String.split("\n", trim: true)
    |> Enum.map(&String.to_charlist/1)
    |> Enum.map(&Enum.map(&1, fn ch -> ch - ?0 end))
  end
end

example_input =
  example_input
  |> Kino.Input.read()
  |> Parser.parse_input()

my_input =
  my_input
  |> Kino.Input.read()
  |> Parser.parse_input()

:ok
:ok

Part 1

defmodule Day9.Part1 do
  def solve(input) do
    input
    |> low_points()
    |> Enum.map(&height(input, &1))
    |> Enum.map(&(&1 + 1))
    |> Enum.sum()
  end

  def low_points(input) do
    Enum.reduce(0..(Enum.count(input) - 1), [], fn x, low_points ->
      row = Enum.at(input, x)

      Enum.reduce(0..(Enum.count(row) - 1), low_points, fn y, low_points ->
        height = height(input, {x, y})

        min_neighbor_height =
          neighbors({x, y}, Enum.count(input), Enum.count(row))
          |> Enum.map(fn position -> height(input, position) end)
          |> Enum.min()

        if height < min_neighbor_height do
          [{x, y} | low_points]
        else
          low_points
        end
      end)
    end)
  end

  def height(input, {x, y}) do
    input |> Enum.at(x) |> Enum.at(y)
  end

  def neighbors({x, y}, max_x, max_y) do
    [{x - 1, y}, {x + 1, y}, {x, y - 1}, {x, y + 1}]
    |> Enum.filter(fn {x, y} ->
      # remove out of bounds neighbors
      x >= 0 and y >= 0 and x < max_x and y < max_y
    end)
  end
end

Day9.Part1.solve(my_input)
452

Part 2

defmodule Day9.Part2 do
  def solve(input) do
    input
    |> Day9.Part1.low_points()
    |> Enum.map(&amp;basin(&amp;1, input))
    |> Enum.map(&amp;Enum.count/1)
    |> Enum.sort()
    |> Enum.reverse()
    |> Enum.take(3)
    |> Enum.product()
  end

  def basin(position, input, sofar \\ []) do
    sofar = [position | sofar]
    height = Day9.Part1.height(input, position)
    max_x = Enum.count(input)
    max_y = Enum.count(Enum.at(input, 0))

    Day9.Part1.neighbors(position, max_x, max_y)
    |> Enum.filter(fn neighbor ->
      neighbor_height = Day9.Part1.height(input, neighbor)
      neighbor_height > height and neighbor_height < 9
    end)
    |> Enum.reduce(sofar, &amp;basin(&amp;1, input, &amp;2))
    |> Enum.uniq()
  end
end

Day9.Part2.solve(my_input)
1263735