Powered by AppSignal & Oban Pro
Would you like to see your link here? Contact us

Advent of Code 2021 day 09

2021/priv/day-09.livemd

Advent of Code 2021 day 09

Setup

Mix.install([
  {:kino, "~> 0.4.1"},
  {:vega_lite, "~> 0.1.2"}
])
input = Kino.Input.textarea("Please paste your input:")
grid =
  input
  |> Kino.Input.read()
  |> String.split("\n", trim: true)
  |> Enum.with_index()
  |> Enum.flat_map(fn {row, i} ->
    row
    |> String.split("", trim: true)
    |> Enum.with_index()
    |> Enum.map(fn {v, j} -> {{i, j}, String.to_integer(v)} end)
  end)
  |> Enum.into(%{})

Part 1

neighbor_keys = fn {i, j} -> [{i, j - 1}, {i, j + 1}, {i - 1, j}, {i + 1, j}] end

low_point? = fn {key, value}, map ->
  key
  |> then(&neighbor_keys.(&1))
  |> Enum.map(&Map.get(map, &1))
  # |> Enum.reject(&is_nil/1) # we don't need to filter
  |> Enum.all?(fn v -> value < v end)
end

grid
|> Enum.filter(&amp;low_point?.(&amp;1, grid))
|> Enum.map(&amp;(elem(&amp;1, 1) + 1))
|> Enum.sum()

Part 2

defmodule Recur do
  def basin(cord, set, grid) do
    if cord in set || grid[cord] in [nil, 9] do
      set
    else
      set = MapSet.put(set, cord)

      cord
      |> neighbor_keys()
      |> Enum.reduce(set, &amp;basin(&amp;1, &amp;2, grid))
    end
  end

  def neighbor_keys({i, j}) do
    [{i, j - 1}, {i, j + 1}, {i - 1, j}, {i + 1, j}]
  end
end

low_points =
  grid
  |> Enum.filter(&amp;low_point?.(&amp;1, grid))
  |> Enum.map(&amp;elem(&amp;1, 0))

low_points
|> Enum.map(&amp;Recur.basin(&amp;1, MapSet.new(), grid))
|> Enum.map(&amp;MapSet.size/1)
|> Enum.sort(:desc)
|> Enum.take(3)
|> Enum.product()

Part 2 - Visualization

alias VegaLite, as: Vl

Vl.new(width: 700, height: 700)
|> Vl.data_from_values(
  Enum.map(grid, fn {{x, y}, h} ->
    %{x: x, y: y, h: h, lowpoint: {x, y} in low_points}
  end)
)
|> Vl.encode_field(:x, "x", type: :quantitative, axis: false)
|> Vl.encode_field(:y, "y", type: :quantitative, axis: false)
|> Vl.layers([
  Vl.new()
  |> Vl.mark(:circle, size: 60, opacity: 0.8)
  |> Vl.encode_field(:color, "h", type: :quantitative, scale: [range: ["red", "white", "brown"]]),
  Vl.new()
  |> Vl.mark(:text, text: "🔥", size: 15)
  |> Vl.transform(filter: "datum.lowpoint == true")
])