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

Day 08

day08.livemd

Day 08

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

IEx.Helpers.c("/Users/johnb/dev/2022adventOfCode/advent_of_code.ex")
alias AdventOfCode, as: AOC
alias Kino.Input

# Note: when making the next template, something like this works well:
#   `cat day04.livemd | sed 's/03/04/' > day04.livemd`
#

Installation and Data

input_p1example = Kino.Input.textarea("Example Data")
input_p1puzzleInput = Kino.Input.textarea("Puzzle Input")
input_source_select =
  Kino.Input.select("Source", [{:example, "example"}, {:puzzle_input, "puzzle input"}])
p1data = fn ->
  (Kino.Input.read(input_source_select) == :example &&
     Kino.Input.read(input_p1example)) ||
    Kino.Input.read(input_p1puzzleInput)
end

Part 1

Day 8: Treetop Tree House

The expedition comes across a peculiar patch of tall trees all planted carefully in a grid. The Elves explain that a previous expedition planted these trees as a reforestation effort. Now, they’re curious if this would be a good location for a tree house.

First, determine whether there is enough tree cover here to keep a tree house hidden. To do this, you need to count the number of trees that are visible from outside the grid when looking directly along a row or column.

The Elves have already launched a quadcopter to generate a map with the height of each tree (your puzzle input). For example:

30373
25512
65332
33549
35390

Each tree is represented as a single digit whose value is its height, where 0 is the shortest and 9 is the tallest.

A tree is visible if all of the other trees between it and an edge of the grid are shorter than it. Only consider trees in the same row or column; that is, only look up, down, left, or right from any given tree.

All of the trees around the edge of the grid are visible - since they are already on the edge, there are no trees to block the view. In this example, that only leaves the interior nine trees to consider:

  • The top-left 5 is visible from the left and top. (It isn’t visible from the right or bottom since other trees of height 5 are in the way.)
  • The top-middle 5 is visible from the top and right.
  • The top-right 1 is not visible from any direction; for it to be visible, there would need to only be trees of height 0 between it and an edge.
  • The left-middle 5 is visible, but only from the right.
  • The center 3 is not visible from any direction; for it to be visible, there would need to be only trees of at most height 2 between it and an edge.
  • The right-middle 3 is visible from the right.
  • In the bottom row, the middle 5 is visible, but the 3 and 4 are not.
  • With 16 trees visible on the edge and another 5 visible in the interior, a total of 21 trees are visible in this arrangement.

Consider your map; how many trees are visible from outside the grid?

###

defmodule Day08 do
  def grid_rows(grid) do
    Enum.map(0..(grid.grid_height - 1), fn y ->
      Enum.map(0..(grid.grid_width - 1), fn x ->
        grid[x + y * grid.grid_width]
      end)
    end)
  end

  def grid_columns(grid) do
    Enum.map(0..(grid.grid_width - 1), fn x ->
      Enum.map(0..(grid.grid_height - 1), fn y ->
        grid[x + y * grid.grid_width]
      end)
    end)
  end

  def count_visible(slice) do
    slice
    |> Enum.chunk_every(2, 1, :discard)
    |> Enum.reduce_while(0, fn [t1, t2], acc ->
      cond do
        t1 < t2 -> {:cont, acc + 1}
        true -> {:halt, acc}
      end
    end)

    # |> IO.inspect(label: Enum.join(slice))
  end

  def count_edge_trees(grid) do
    grid.grid_width * 2 + grid.grid_height * 2 - 4
    # |> IO.inspect(label: "edge trees")
  end

  def shadow_grid(grid) do
    %{
      grid_width: grid.grid_width,
      grid_height: grid.grid_height,
      last_cell: grid.last_cell
    }
  end

  def grid_index_at(grid, {x, y}) do
    x + y * grid.grid_width
  end

  def until_first_nil(list) do
    list
    |> Enum.reduce_while([], fn cell_value, acc ->
      cond do
        is_nil(cell_value) -> {:halt, Enum.reverse(acc)}
        true -> {:cont, [cell_value | acc]}
      end
    end)
  end

  def _cells(_grid, _center, _delta, _limit = 0) do
    []
  end

  def _cells(grid, center, delta, limit) do
    Enum.map(1..limit, fn offset -> grid[center + offset * delta] end)
  end

  def _cells(grid, center, +1 = delta) do
    center_right = grid.grid_width - rem(center, grid.grid_width) - 1
    _cells(grid, center, delta, center_right)
  end

  def _cells(grid, center, -1 = delta) do
    left_center = rem(center, grid.grid_width)
    _cells(grid, center, delta, left_center)
  end

  def _cells(grid, center, delta) do
    Enum.map(1..grid.grid_width, fn offset -> grid[center + offset * delta] end)
    |> until_first_nil()
  end

  def grid_cells_in_a_direction(grid, center, :up), do: _cells(grid, center, -grid.grid_width)
  def grid_cells_in_a_direction(grid, center, :down), do: _cells(grid, center, +grid.grid_width)
  def grid_cells_in_a_direction(grid, center, :left), do: _cells(grid, center, -1)
  def grid_cells_in_a_direction(grid, center, :right), do: _cells(grid, center, +1)

  def can_see_to_edge?(grid, coordinate) do
    current_index = grid_index_at(grid, coordinate)
    current_height = grid[current_index]

    [:up, :down, :left, :right]
    |> Enum.map(fn direction -> grid_cells_in_a_direction(grid, current_index, direction) end)
    |> Enum.any?(fn grid_cells ->
      Enum.all?(grid_cells, fn cell -> current_height > cell end)
    end)
  end

  def grid_set(grid, current_index, value) do
    put_in(grid, [current_index], value)
  end

  def gridcount(grid) do
    # 3 to remove grid_width, grid_height, and last_cell
    Enum.count(grid) - 3
  end

  def central_coordinates(grid) do
    for x <- 1..(grid.grid_width - 2), y <- 1..(grid.grid_height - 2), do: {x, y}
  end

  def count_visible_from_edges(grid) do
    grid
    |> central_coordinates()
    |> Enum.reduce(shadow_grid(grid), fn coordinate, shadow ->
      current_index = grid_index_at(grid, coordinate)

      cond do
        shadow[current_index] == "visible" ->
          shadow

        can_see_to_edge?(grid, coordinate) ->
          grid_set(shadow, current_index, "visible")

        true ->
          shadow
      end
    end)
    # |> IO.inspect(label: "shadow")
    |> gridcount()
  end

  def solve(text) do
    grid = AOC.as_grid_of_digits(text)

    count_edge_trees(grid) + count_visible_from_edges(grid)
  end

  def solve2(text) do
    grid = AOC.as_grid_of_digits(text)

    grid
    |> central_coordinates()
    |> Enum.reduce(shadow_grid(grid), fn coordinate, shadow ->
      current_index = grid_index_at(grid, coordinate)
      current_height = grid[current_index]

      score =
        [:up, :down, :left, :right]
        |> Enum.map(fn direction -> grid_cells_in_a_direction(grid, current_index, direction) end)
        |> Enum.reduce(1, fn grid_cells, acc ->
          distance =
            grid_cells
            |> Enum.reduce_while(0, fn cell_value, counter ->
              cond do
                current_height <= cell_value -> {:halt, counter + 1}
                true -> {:cont, counter + 1}
              end
            end)

          acc * distance
        end)

      get_and_update_in(shadow, [current_index], fn current ->
        cond do
          is_nil(current) -> {nil, score}
          true -> {current, current * score}
        end
      end)
      |> elem(1)
    end)
    |> Map.reject(fn {k, _v} -> k in [:grid_height, :grid_width, :last_cell] end)
    |> Map.values()
    |> Enum.max()
  end
end

p1data.()
|> Day08.solve()
|> IO.inspect(label: "\n*** Part 1 solution (example: 21)")

# 1669

p1data.()
|> Day08.solve2()
|> IO.inspect(label: "\n*** Part 2 solution (example: 8)")

# 331344

Part Two

Of the non-edge trees, which one can see the most trees - meaning the same height or shorter in each of the cardinal directions? Multiply these numbers to get the tree’s “score”. What is the highest score?