part2
Mix.install([
{:kino, "~> 0.7.0"}
])
Section
input = Kino.Input.textarea("Please paste your input")
defmodule Forest do
@enforce_keys [:trees]
defstruct trees: %{}, width: nil, height: nil
def new(trees) when is_map(trees) do
{height, width} =
trees
|> Enum.map(fn {pos, _} -> pos end)
|> Enum.max()
# |> IO.inspect()
%Forest{trees: trees, width: width, height: height}
end
def position({row, col}, {width, height}) do
%{
up: for(i <- 0..row, i != row, do: {i, col}),
right: for(i <- col..width, i != col, do: {row, i}),
down: for(i <- row..height, i != row, do: {i, col}),
left: for(i <- 0..col, i != col, do: {row, i})
}
end
def view_from_the_tree({row, col}, {width, height}) do
%{
up: for(i <- row..0, i != row, do: {i, col}),
right: for(i <- col..width, i != col, do: {row, i}),
down: for(i <- row..height, i != row, do: {i, col}),
left: for(i <- col..0, i != col, do: {row, i})
}
end
def get_tree_height(%Forest{trees: trees}, {row, col}) do
elem(trees[{row, col}], 0)
end
def worker(%Forest{trees: trees, width: width, height: height} = forest, tree_selector) do
# update each with relevant positions
# new_trees =
trees
|> Enum.map(fn {pos, stuff} ->
{pos, Tuple.insert_at(stuff, 2, tree_selector.(pos, {width, height}))}
end)
# {{4, 4},
# {0, false,
# %{
# down: [],
# left: [{4, 0}, {4, 1}, {4, 2}, {4, 3}],
# right: [],
# up: [{0, 4}, {1, 4}, {2, 4}, {3, 4}]
# }}}
# |> IO.inspect()
|> Enum.map(&get_relevant_tree_heights(&1, forest))
end
# takes output from get_relevant_tree_heights
# {pos, {height, false, updated_trees |> Map.new() }}
def is_visible?({pos, {height, _, trees}}) do
updated_trees =
trees
# {:left, [{{4, 0} <- position, 3 <- height }, {{4, 1}, 5}]}
|> Enum.map(fn {key, val} ->
# {:left, [{{3, 0}, 3}, {{4, 0}, 3}]}
heights = Enum.map(val, fn {_, h} -> h end)
if length(heights) == 0 do
{key, [{0, true} | val]}
else
max = heights |> Enum.max()
{key, [{max, height > max} | val]}
end
end)
# %{
# down: [{0, true}],
# left: [{6, false}, {{1, 0}, 2}, {{2, 0}, 6}, {{3, 0}, 3}, {{4, 0}, 3}],
# right: [{7, false}, {{0, 1}, 0}, {{0, 2}, 3}, {{0, 3}, 7}, {{0, 4}, 3}],
# up: [{0, true}]
# }
visiblility =
updated_trees
|> Enum.map(fn {_, [{_, bool} | _]} -> bool end)
|> Enum.any?()
{pos, {height, visiblility, updated_trees |> Map.new()}}
end
def get_relevant_tree_heights({pos, {height, _, other_trees}}, forest) do
# {
# {0, 0},
# {3, false,
# %{
# down: [{1, 0}, {2, 0}, {3, 0}, {4, 0}],
# left: [],
# right: [{0, 1}, {0, 2}, {0, 3}, {0, 4}],
# up: []
# }}}
updated_trees =
other_trees
|> Enum.map(fn {key, locations} ->
upd_locations =
locations
|> Enum.map(&{&1, get_tree_height(forest, &1)})
# [{{4, 0}, 3}, {{4, 1}, 5}]
{key, upd_locations}
end)
# [
# down: []
# left: [{{4, 0}, 3}, {{4, 1}, 5}, {{4, 2}, 3}, {{4, 3}, 9}],
# right: [],
# up: [{{0, 4}, 3}, {{1, 4}, 2}, {{2, 4}, 2}, {{3, 4}, 9}]
# ]
{pos, {height, false, updated_trees |> Map.new()}}
# {{4, 4},
# {0, false,
# %{
# down: [{0, true}],
# left: [{9, false}, {{4, 0}, 3}, {{4, 1}, 5}, {{4, 2}, 3}, {{4, 3}, 9}],
# right: [{0, true}],
# up: [{9, false}, {{0, 4}, 3}, {{1, 4}, 2}, {{2, 4}, 2}, {{3, 4}, 9}]
# }}}
end
def scenic_scorer({_, neighbours}) do
# neighbours = [{{4, 0} <- position, 3 <- height }, {{4, 1}, 5}]
neighbours
|> Enum.map(fn {_, h} -> h end)
|> Enum.with_index(1)
# [{2 <- height of the neighbour tree, 1 <- steps to it }, {6, 2}, {3, 3}, {3, 4}],
end
end
trees =
input
|> Kino.Input.read()
|> String.split("\n")
|> Enum.map(&(String.split(&1, "", trim: true) |> Enum.with_index()))
|> Enum.with_index()
|> Enum.flat_map(fn {list, row} ->
list
|> Enum.map(fn {height, col} ->
{{row, col}, String.to_integer(height)}
end)
end)
forest = Forest.new(for {pos, height} <- trees, into: %{}, do: {pos, {height, false}})
''
# Part1
forest
|> Forest.worker(&Forest.position/2)
# |> Enum.map(fn x -> IO.inspect(x) end)
|> Enum.map(&Forest.is_visible?/1)
|> Enum.map(fn {_, {_, bool, _}} -> bool end)
|> Enum.filter(&(&1 == true))
|> Enum.count()
# Part2
forest
|> Forest.worker(&Forest.view_from_the_tree/2)
|> Enum.map(fn {pos, {height, _, trees}} ->
scores =
trees
|> Enum.map(&Forest.scenic_scorer/1)
|> Enum.map(fn x ->
rv =
Enum.find(x, fn {n_height, _steps} ->
n_height >= height
end)
if length(x) == 0 do
0
else
# take last step
# {[{3, 1}, {0, 2}, {3, 3}], nil}
if rv == nil, do: elem(Enum.at(x, -1), 1), else: elem(rv, 1)
end
end)
{pos, scores}
# {{0, 0}, 3, [2, 0, 2, 0]},
end)
# now filter out all edges
|> Enum.filter(fn {{row, col}, _scores} ->
row != 0 and col != 0 and row != forest.height and col != forest.width
end)
|> Enum.map(fn {_, scores} ->
Enum.reduce(scores, 1, fn el, acc -> el * acc end)
end)
|> Enum.max()
# [4, 1, 2, 12, 3, 1, 1, 4, 1, 2, 16, 6, 1, 2, 1, 1, 1, 8, 3, 12, 1, 4, 1, 12, 1]
# {7, nil,
# [{4, [{1, 1}, {3, 2}, {4, 3}, {9, 4}]}, {3, [{3, 1}, {0, 2}, {3, 3}]}, {1, [{3, 1}]}, {1, []}]}},