This one has been quite the ride. Struggled at first to find a good data format to suite the problem. I really like how that turned out by separating the map data from coordinates to look at when counting. Part 2 also was imo not well defined. It took me a while to figure out I don’t need to subtract smaller trees behind larger trees anymore.
Solution
defmodule Day8 do
defstruct map: nil, size: nil
def parse(text) do
lines = text |> String.split("\n") |> Enum.reject(&(&1 == ""))
{map, _} =
lines
|> Enum.with_index()
|> Enum.flat_map_reduce(0, fn {line, y}, next ->
line
|> String.split("", trim: true)
|> Enum.with_index()
|> Enum.map_reduce(next, fn {height, x}, next ->
item = {{x, y}, %{id: next, height: String.to_integer(height)}}
{item, next + 1}
end)
end)
map = Map.new(map)
keys = Map.keys(map)
size_x = keys |> Enum.map(fn {x, _} -> x end) |> Enum.max()
size_y = keys |> Enum.map(fn {_, y} -> y end) |> Enum.max()
%__MODULE__{map: map, size: %{x: size_x, y: size_y}}
end
def count_visible_from_outside(text) do
data = parse(text)
from_left_keys =
for y <- 0..data.size.y//1 do
for x <- 0..data.size.x//1, do: {x, y}
end
from_right_keys =
for y <- 0..data.size.y//1 do
for x <- data.size.x..0//-1, do: {x, y}
end
from_top_keys =
for x <- 0..data.size.x//1 do
for y <- 0..data.size.y//1, do: {x, y}
end
from_bottom_keys =
for x <- 0..data.size.x//1 do
for y <- data.size.y..0//-1, do: {x, y}
end
[
from_left_keys,
from_right_keys,
from_top_keys,
from_bottom_keys
]
|> Enum.flat_map(& &1)
|> Enum.flat_map(&count_visible_line(data.map, &1))
|> Enum.uniq()
|> Enum.count()
end
defp count_visible_line(map, line) do
{_, trees} =
line
|> Enum.map(fn coordinate -> Map.fetch!(map, coordinate) end)
|> Enum.reduce({-1, []}, fn
%{height: tree_height} = tree, {line_of_sight, visible}
when tree_height > line_of_sight ->
{tree_height, [tree | visible]}
_, acc ->
acc
end)
trees
end
def count_visible_from_tree_house(text) do
data = parse(text)
for y <- 0..data.size.y//1, x <- 0..data.size.x//1 do
tree = Map.fetch!(data.map, {x, y})
to_left = for x <- (x - 1)..0//-1, do: {x, y}
to_right = for x <- (x + 1)..data.size.x//1, do: {x, y}
to_top = for y <- (y - 1)..0//-1, do: {x, y}
to_bottom = for y <- (y + 1)..data.size.y//1, do: {x, y}
[
to_top,
to_left,
to_right,
to_bottom
]
|> Enum.map(fn line ->
data.map |> count_visible_line_treehouse(line, tree.height)
end)
|> Enum.reduce(&Kernel.*/2)
end
|> Enum.max()
end
defp count_visible_line_treehouse(map, line, limit) do
line
|> Enum.map(fn coordinate -> Map.fetch!(map, coordinate) end)
|> Enum.reduce_while(0, fn
tree, num when tree.height >= limit -> {:halt, num + 1}
_, num -> {:cont, num + 1}
end)
end
end