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

Untitled notebook

advent_of_code_2022/day8.livemd

Untitled notebook

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

Input

input = Kino.Input.textarea("Input")

Data

We prepare the input into a data structure for easier computation later. It might not be the most performant approach, but it gives some clarity and insight into the data.

# original input, a list of rows
original =
  input
  |> Kino.Input.read()
  |> String.split("\n")
  |> Enum.map(&String.split(&1, "", trim: true))
  |> Enum.map(fn row -> Enum.map(row, &String.to_integer/1) end)

# a list of columns
transposed = Enum.zip_with(original, & &1)

data =
  original
  |> Enum.with_index()
  |> Enum.map(fn {row, row_index} ->
    row
    |> Enum.with_index()
    |> Enum.map(fn {cell, col_index} ->
      {left, curr_and_right} = Enum.split(row, col_index)
      right = Enum.drop(curr_and_right, 1)

      column = Enum.at(transposed, col_index)
      {top, curr_and_bottom} = Enum.split(column, row_index)
      bottom = Enum.drop(curr_and_bottom, 1)

      %{
        value: cell,
        left: left,
        right: right,
        top: top,
        bottom: bottom,
        # extra bit of info for debugging, we don't actually need it
        col_index: col_index,
        row_index: row_index
      }
    end)
  end)
  |> List.flatten()

Part 1

With the data structure in place, determining visibility is simple as running a conditional check on each element and counting those that satisfy it.

is_visible = fn data ->
  # trees at the edge are always visible
  # other trees are visible if there are no taller 
  # or equally tall trees in any direction
  data.left == [] ||
    data.right == [] ||
    data.top == [] ||
    data.bottom == [] ||
    Enum.max(data.left) < data.value ||
    Enum.max(data.right) < data.value ||
    Enum.max(data.top) < data.value ||
    Enum.max(data.bottom) < data.value
end

Enum.count(data, &amp;is_visible.(&amp;1))

Part 2

Again, with the data parsed and in place, all we need to do here is write a function to compute the score for an element, then use it to find the max.

count_visible = fn
  # right at the edge means scenic score is 0
  _value, [] ->
    0

  value, trees ->
    trees
    |> Enum.take_while(&amp;(&amp;1 < value))
    |> Enum.count()
    # Maybe we reached the edge here, or maybe we 
    # reached a tree that blocked the view.
    # We add + 1 for the tree, then make sure we 
    # don't return more than total count of trees.
    |> Kernel.+(1)
    |> Kernel.min(length(trees))
end

scenic_score = fn data ->
  # we have to move in the opposite direction for these two
  trees_left = count_visible.(data.value, data.left |> Enum.reverse())
  trees_top = count_visible.(data.value, data.top |> Enum.reverse())

  trees_right = count_visible.(data.value, data.right)
  trees_bottom = count_visible.(data.value, data.bottom)

  trees_left * trees_right * trees_top * trees_bottom
end

data
|> Enum.map(&amp;scenic_score.(&amp;1))
|> Enum.max()