Powered by AppSignal & Oban Pro

Day 8: Treetop Tree House

2022/day_08.livemd

Day 8: Treetop Tree House

Mix.install([:kino])

input = Kino.Input.textarea("Please paste your input:")

Part 1

Run in Livebook

https://adventofcode.com/2022/day/8

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

vertical =
  horizontal
  |> Enum.zip_with(& &1)

right =
  horizontal
  |> Enum.with_index()
  |> Enum.map(fn {row, row_index} ->
    {visibles, _} =
      row
      |> Enum.with_index()
      |> Enum.reduce(
        {_visibles = [], _max_height = nil},
        fn {height, column_index}, {visibles, max_height} ->
          case is_nil(max_height) or height > max_height do
            true -> {[{row_index, column_index} | visibles], height}
            false -> {visibles, max_height}
          end
        end
      )

    visibles
  end)

left =
  horizontal
  |> Enum.with_index()
  |> Enum.map(fn {row, row_index} ->
    {visibles, _} =
      row
      |> Enum.with_index()
      |> Enum.reverse()
      |> Enum.reduce(
        {_visibles = [], _max_height = nil},
        fn {height, column_index}, {visibles, max_height} ->
          case is_nil(max_height) or height > max_height do
            true -> {[{row_index, column_index} | visibles], height}
            false -> {visibles, max_height}
          end
        end
      )

    visibles
  end)

top =
  vertical
  |> Enum.with_index()
  |> Enum.map(fn {column, column_index} ->
    {visibles, _} =
      column
      |> Enum.with_index()
      |> Enum.reduce(
        {_visibles = [], _max_height = nil},
        fn {height, row_index}, {visibles, max_height} ->
          case is_nil(max_height) or height > max_height do
            true -> {[{row_index, column_index} | visibles], height}
            false -> {visibles, max_height}
          end
        end
      )

    visibles
  end)

bottom =
  vertical
  |> Enum.with_index()
  |> Enum.map(fn {column, column_index} ->
    {visibles, _} =
      column
      |> Enum.with_index()
      |> Enum.reverse()
      |> Enum.reduce(
        {_visibles = [], _max_height = nil},
        fn {height, row_index}, {visibles, max_height} ->
          case is_nil(max_height) or height > max_height do
            true -> {[{row_index, column_index} | visibles], height}
            false -> {visibles, max_height}
          end
        end
      )

    visibles
  end)

(right ++ left ++ top ++ bottom)
|> List.flatten()
|> Enum.uniq()
|> Enum.count()

Part 2

https://adventofcode.com/2022/day/8#part2

row_count = horizontal |> Enum.count()
column_count = horizontal |> List.first() |> Enum.count()

trees =
  horizontal
  |> Enum.with_index()
  |> Enum.map(fn {row, row_index} ->
    row
    |> Enum.with_index()
    |> Enum.map(fn {height, column_index} ->
      {{row_index, column_index}, height}
    end)
  end)
  |> List.flatten()
  |> Map.new()

defmodule Solver do
  @directions [{1, 0}, {-1, 0}, {0, 1}, {0, -1}]

  def run(trees) do
    trees
    |> Enum.map(fn {index, _height} -> {index, calc_scenic_score(trees, index)} end)
    |> Enum.max_by(fn {_index, scenic_score} -> scenic_score end)
  end

  def calc_scenic_score(trees, index) do
    @directions
    |> Enum.map(fn direction -> calc_viewing_distance(trees, index, direction) end)
    |> Enum.reduce(&(&1 * &2))
  end

  def calc_viewing_distance(trees, index, direction) do
    origin_height = trees |> Map.get(index)

    do_calc_viewing_distance(
      trees,
      next_index(index, direction),
      direction,
      origin_height,
      0
    )
  end

  defp do_calc_viewing_distance(trees, index, direction, origin_height, viewing_distance) do
    height = trees |> Map.get(index)

    case is_nil(height) do
      true ->
        viewing_distance

      false ->
        case height < origin_height do
          true ->
            do_calc_viewing_distance(
              trees,
              next_index(index, direction),
              direction,
              origin_height,
              viewing_distance + 1
            )

          false ->
            viewing_distance + 1
        end
    end
  end

  defp next_index({row_index, column_index}, {row_direction, column_direction}) do
    {row_index + row_direction, column_index + column_direction}
  end
end

Solver.run(trees)