Advent of Code: Day 08
Helpers
defmodule Helpers do
@spec read_file_contents(String.t()) :: binary()
def read_file_contents(filename) do
file_path = "/Users/charlie/github.com/charlieroth/advent-of-code/2022/#{filename}"
case File.read(file_path) do
{:ok, contents} -> contents
{:error, _} -> raise("Failed to read file contents")
end
end
end
Part 01
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 height5
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 height0
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 height2
between it and an edge. -
The right-middle
3
is visible from the right. -
In the bottom row, the middle
5
is visible, but the3
and4
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 PartOne do
def solution(input) do
rows = input |> String.split("\n", trim: true)
grid = scan(rows)
n = length(rows)
pos = for x <- 2..(n - 1), y <- 2..(n - 1), do: {x, y}
interior_num_visible = find_visible_trees(n, grid, pos)
exterior_num_visible = n + (n - 1) * 2 + (n - 2)
exterior_num_visible + interior_num_visible
end
defp scan(rows) do
rows
|> Enum.map(&String.split(&1, "", trim: true))
|> Enum.map(fn row -> Enum.map(row, &String.to_integer/1) end)
|> Enum.with_index(1)
|> Enum.map(fn {row, index} ->
row
|> Enum.with_index(fn el, col -> {col + 1, el} end)
|> Map.new()
|> (&{index, &1}).()
end)
|> Map.new()
end
defp find_visible_trees(num_rows, grid, pos) do
pos
|> Enum.map(&look(num_rows, grid, &1))
|> Enum.filter(&(&1 == true))
|> Enum.count()
end
defp look(num_rows, grid, {x, y}) do
tree = grid |> Map.fetch!(x) |> Map.fetch!(y)
up =
Enum.to_list((x - 1)..1)
|> Enum.map(&{&1, y})
|> is_visible?(grid, tree)
down =
Enum.to_list((x + 1)..num_rows)
|> Enum.map(&{&1, y})
|> is_visible?(grid, tree)
left =
Enum.to_list((y - 1)..1)
|> Enum.map(&{x, &1})
|> is_visible?(grid, tree)
right =
Enum.to_list((y + 1)..num_rows)
|> Enum.map(&{x, &1})
|> is_visible?(grid, tree)
up or down or left or right
end
defp is_visible?(path, grid, tree) do
Enum.reduce_while(path, true, fn {x, y}, acc ->
next_tree = grid |> Map.fetch!(x) |> Map.fetch!(y)
if tree > next_tree do
{:cont, acc}
else
{:halt, false}
end
end)
end
end
Helpers.read_file_contents("inputs/Day08.txt")
|> PartOne.solution()
Part 02
https://adventofcode.com/2022/day/8#part2
To measure the viewing distance from a given tree, look up, down, left, and right from that tree; stop if you reach an edge or at the first tree that is the same height or taller than the tree under consideration. (If a tree is right on the edge, at least one of its viewing distances will be zero.)
The Elves don’t care about distant trees taller than those found by the rules above; the proposed tree house has large eaves to keep it dry, so they wouldn’t be able to see higher than the tree house anyway.
In the example, the middle 5
in the second row:
30373
25512
65332
33549
35390
-
Looking up, its view is not blocked; it can see
1
tree (of height 3). - Looking left, its view is blocked immediately; it can see only 1 tree (of height 5, right next to it).
- Looking right, its view is not blocked; it can see 2 trees.
- Looking down, its view is blocked eventually; it can see 2 trees (one of height 3, then the tree of height 5 that blocks its view).
A tree’s scenic score is found by multiplying together its viewing distance in each of the four directions. For this tree, this is 4 = 1 * 1 * 2 * 2
.
However, you can do even better: consider the tree of height 5 in the middle of the fourth row:
30373
25512
65332
33549
35390
- Looking up, its view is blocked at 2 trees (by another tree with a height of 5).
- Looking left, its view is not blocked; it can see 2 trees.
- Looking down, its view is also not blocked; it can see 1 tree.
- Looking right, its view is blocked at 2 trees (by a massive tree of height 9).
This tree’s scenic score is 8 = 2 * 2 * 1 * 2
; this is the ideal spot for the tree house.
Consider each tree on your map. What is the highest scenic score possible for any tree?
defmodule PartTwo do
def solution(input) do
rows = input |> String.split("\n", trim: true)
grid = scan(rows)
n = length(rows)
pos = for x <- 2..(n - 1), y <- 2..(n - 1), do: {x, y}
get_scenic_scores(n, grid, pos)
end
defp scan(rows) do
rows
|> Enum.map(&String.split(&1, "", trim: true))
|> Enum.map(fn row -> Enum.map(row, &String.to_integer/1) end)
|> Enum.with_index(1)
|> Enum.map(fn {row, index} ->
row
|> Enum.with_index(fn el, col -> {col + 1, el} end)
|> Map.new()
|> (&{index, &1}).()
end)
|> Map.new()
end
defp get_scenic_scores(num_rows, grid, pos) do
pos
|> Enum.map(&look(num_rows, grid, &1))
|> Enum.sort(:desc)
|> hd
end
defp look(num_rows, grid, {x, y}) do
tree = grid |> Map.fetch!(x) |> Map.fetch!(y)
up =
Enum.to_list((x - 1)..1)
|> Enum.map(&{&1, y})
|> scenic_score(grid, tree)
down =
Enum.to_list((x + 1)..num_rows)
|> Enum.map(&{&1, y})
|> scenic_score(grid, tree)
left =
Enum.to_list((y - 1)..1)
|> Enum.map(&{x, &1})
|> scenic_score(grid, tree)
right =
Enum.to_list((y + 1)..num_rows)
|> Enum.map(&{x, &1})
|> scenic_score(grid, tree)
up * down * left * right
end
defp scenic_score(path, grid, tree) do
Enum.reduce_while(path, 0, fn {x, y}, acc ->
next_tree = grid |> Map.fetch!(x) |> Map.fetch!(y)
cond do
tree > next_tree -> {:cont, acc + 1}
tree <= next_tree -> {:halt, acc + 1}
end
end)
end
end
Helpers.read_file_contents("inputs/Day08.txt")
|> PartTwo.solution()