Powered by AppSignal & Oban Pro

Day 8

2022/day8.livemd

Day 8

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

Setup

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

Common

defmodule Day8 do
  def tree_is_visible(_, _, y) when y == 0, do: true
  def tree_is_visible(array_of_trees, _, y) when length(array_of_trees) == y + 1, do: true
  def tree_is_visible(_, x, _) when x == 0, do: true

  def tree_is_visible(array_of_trees, x, y) do
    # is the tree the last from this row?

    case length(Enum.at(array_of_trees, y)) == x + 1 do
      true ->
        true

      false ->
        biggest_tree_in_column(array_of_trees, x, y) || biggest_tree_in_row(array_of_trees, x, y)
    end
  end

  def calculate_scenic_score(_, _, y) when y == 0, do: 0
  def calculate_scenic_score(array_of_trees, _, y) when length(array_of_trees) == y + 1, do: 0
  def calculate_scenic_score(_, x, _) when x == 0, do: 0

  def calculate_scenic_score(array_of_trees, x, y) do
    case length(Enum.at(array_of_trees, y)) == x + 1 do
      true ->
        0

      false ->
        calculate_view_distance_up(array_of_trees, x, y) *
          calculate_view_distance_down(array_of_trees, x, y) *
          calculate_view_distance_left(array_of_trees, x, y) *
          calculate_view_distance_right(array_of_trees, x, y)
    end
  end

  # Private methods
  defp biggest_tree_in_row(array_of_trees, x, y) do
    tree_height = Enum.at(Enum.at(array_of_trees, y), x)

    {left, right} =
      Enum.at(array_of_trees, y)
      |> Enum.split(x)

    max_left =
      left
      |> Enum.max()

    [_ | remaining_right] = right

    max_right =
      remaining_right
      |> Enum.max()

    max_left < tree_height || max_right < tree_height
  end

  defp biggest_tree_in_column(array_of_trees, x, y) do
    tree_height = Enum.at(Enum.at(array_of_trees, y), x)

    transposed_array_of_trees =
      array_of_trees
      |> Enum.zip()
      |> Enum.map(&Tuple.to_list/1)

    {up, down} =
      Enum.at(transposed_array_of_trees, x)
      |> Enum.split(y)

    max_up =
      up
      |> Enum.max()

    [_ | remaining_down] = down

    max_down =
      remaining_down
      |> Enum.max()

    max_up < tree_height || max_down < tree_height
  end

  defp calculate_view_distance_up(array_of_trees, x, y) do
    tree_height = Enum.at(Enum.at(array_of_trees, y), x)

    transposed_array_of_trees =
      array_of_trees
      |> Enum.zip()
      |> Enum.map(&Tuple.to_list/1)

    {up, _} =
      Enum.at(transposed_array_of_trees, x)
      |> Enum.split(y)

    up
    |> Enum.reverse()
    |> Enum.reduce_while(0, fn el, acc ->
      if el >= tree_height, do: {:halt, acc + 1}, else: {:cont, acc + 1}
    end)
  end

  defp calculate_view_distance_down(array_of_trees, x, y) do
    tree_height = Enum.at(Enum.at(array_of_trees, y), x)

    transposed_array_of_trees =
      array_of_trees
      |> Enum.zip()
      |> Enum.map(&Tuple.to_list/1)

    {_, down} =
      Enum.at(transposed_array_of_trees, x)
      |> Enum.split(y)

    [_ | remaining_down] = down

    remaining_down
    |> Enum.reduce_while(0, fn el, acc ->
      # IO.inspect("element: " <> Integer.to_string(el))
      # IO.inspect("tree_height: " <> Integer.to_string(tree_height))
      if el >= tree_height, do: {:halt, acc + 1}, else: {:cont, acc + 1}
    end)
  end

  defp calculate_view_distance_left(array_of_trees, x, y) do
    tree_height = Enum.at(Enum.at(array_of_trees, y), x)

    {left, _} =
      Enum.at(array_of_trees, y)
      |> Enum.split(x)

    left
    |> Enum.reverse()
    |> Enum.reduce_while(0, fn el, acc ->
      if el >= tree_height, do: {:halt, acc + 1}, else: {:cont, acc + 1}
    end)
  end

  defp calculate_view_distance_right(array_of_trees, x, y) do
    tree_height = Enum.at(Enum.at(array_of_trees, y), x)

    {_, right} =
      Enum.at(array_of_trees, y)
      |> Enum.split(x)

    [_ | remaining_right] = right

    remaining_right
    |> Enum.reduce_while(0, fn el, acc ->
      if el >= tree_height, do: {:halt, acc + 1}, else: {:cont, acc + 1}
    end)
  end
end

Part 1

tree_heights =
  input
  |> Kino.Input.read()
  |> String.split("\n", trim: true)
  |> Enum.map(fn line ->
    line |> String.split("", trim: true) |> Enum.map(&String.to_integer/1)
  end)

Enum.zip(0..length(tree_heights), tree_heights)
|> Enum.map(fn {row, line} ->
  Enum.zip(0..length(line), line)
  |> Enum.map(fn {col, _} ->
    Day8.tree_is_visible(tree_heights, col, row)
  end)
end)
|> Enum.map(fn row ->
  row
  |> Enum.count(fn elem -> elem end)
end)
|> Enum.sum()

Part 2

tree_heights =
  input
  |> Kino.Input.read()
  |> String.split("\n", trim: true)
  |> Enum.map(fn line ->
    line |> String.split("", trim: true) |> Enum.map(&String.to_integer/1)
  end)

Enum.zip(0..length(tree_heights), tree_heights)
|> Enum.map(fn {row, line} ->
  Enum.zip(0..length(line), line)
  |> Enum.map(fn {col, _} ->
    Day8.calculate_scenic_score(tree_heights, col, row)
  end)
end)
|> Enum.map(fn line ->
  line
  |> Enum.max()
end)
|> Enum.max()