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

part2

2022/elixir/day-08/part2.livemd

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(&amp;get_relevant_tree_heights(&amp;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(&amp;{&amp;1, get_tree_height(forest, &amp;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(&amp;(String.split(&amp;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(&amp;Forest.position/2)
# |> Enum.map(fn x -> IO.inspect(x) end)
|> Enum.map(&amp;Forest.is_visible?/1)
|> Enum.map(fn {_, {_, bool, _}} -> bool end)
|> Enum.filter(&amp;(&amp;1 == true))
|> Enum.count()
# Part2
forest
|> Forest.worker(&amp;Forest.view_from_the_tree/2)
|> Enum.map(fn {pos, {height, _, trees}} ->
  scores =
    trees
    |> Enum.map(&amp;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, []}]}},