Powered by AppSignal & Oban Pro

Day 9

2021-lb/day_9.livemd

Day 9

Setup

Mix.install([
  {:kino, "~> 0.4.1"}
])
input =
  Kino.Input.textarea("Paste your input",
    default: """
    2199943210
    3987894921
    9856789892
    8767896789
    9899965678
    """
  )

Part 1

heightmap =
  input
  |> Kino.Input.read()
  |> String.split("\n")
  |> Stream.with_index()
  |> Stream.flat_map(fn {row, y} ->
    row
    |> String.split("", trim: true)
    |> Stream.with_index()
    |> Stream.map(fn {height, x} -> {{x, y}, String.to_integer(height)} end)
  end)
  |> Enum.into(%{})
lowpoints =
  heightmap
  |> Stream.filter(fn {{x, y}, height} ->
    neighbors = [
      Map.get(heightmap, {x - 1, y}, 99),
      Map.get(heightmap, {x + 1, y}, 99),
      Map.get(heightmap, {x, y - 1}, 99),
      Map.get(heightmap, {x, y + 1}, 99)
    ]

    Enum.all?(neighbors, &(&1 > height))
  end)
  |> Enum.into([])
lowpoints
|> Stream.map(fn {_, h} -> h end)
|> Stream.map(&(&1 + 1))
|> Enum.sum()

Part 2

defmodule Part2 do
  def get_basin(coords, heightmap) do
    traverse_basin(%{}, coords, heightmap)
  end

  defp traverse_basin(basin, {x, y} = coords, heightmap) do
    # IO.inspect({coords, Map.get(heightmap, coords)})

    case Map.get(heightmap, coords) do
      9 ->
        basin

      nil ->
        basin

      height ->
        if Map.has_key?(basin, coords) do
          basin
        else
          basin
          |> Map.put(coords, height)
          |> traverse_basin({x - 1, y}, heightmap)
          |> traverse_basin({x + 1, y}, heightmap)
          |> traverse_basin({x, y - 1}, heightmap)
          |> traverse_basin({x, y + 1}, heightmap)
        end
    end
  end
end
lowpoints
|> Stream.map(fn {lowpoint_coords, _} ->
  Part2.get_basin(lowpoint_coords, heightmap)
end)
|> Stream.map(&Enum.count/1)
|> Enum.sort(:desc)
|> Stream.take(3)
|> Enum.product()