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

Day 08

day08.livemd

Day 08

Setup

https://adventofcode.com/2022/day/08

defmodule Load do
  def input do
    File.read!(Path.join(Path.absname(__DIR__), "input/08.txt"))
  end
end
# Taken from https://blog.danielberkompas.com/2016/04/23/multidimensional-arrays-in-elixir/
defmodule Matrix do
  @moduledoc """
  Helpers for working with multidimensional lists, also called matrices.
  """

  @doc """
  Converts a multidimensional list into a zero-indexed map.

  ## Example

      iex> list = [["x", "o", "x"]]
      ...> Matrix.from_list(list)
      %{0 => %{0 => "x", 1 => "o", 2 => "x"}}
  """
  def from_list(list) when is_list(list) do
    do_from_list(list)
  end

  defp do_from_list(list, map \\ %{}, index \\ 0)
  defp do_from_list([], map, _index), do: map

  defp do_from_list([h | t], map, index) do
    map = Map.put(map, index, do_from_list(h))
    do_from_list(t, map, index + 1)
  end

  defp do_from_list(other, _, _), do: other

  @doc """
  Converts a zero-indexed map into a multidimensional list.

  ## Example

      iex> matrix = %{0 => %{0 => "x", 1 => "o", 2 => "x"}}
      ...> Matrix.to_list(matrix)
      [["x", "o", "x"]]
  """
  def to_list(matrix) when is_map(matrix) do
    do_to_list(matrix)
  end

  defp do_to_list(matrix) when is_map(matrix) do
    for {_index, value} <- matrix,
        into: [],
        do: do_to_list(value)
  end

  defp do_to_list(other), do: other
end
defmodule Parse do
  def input(inputString) do
    inputString
    |> String.split("\n", trim: true)
    |> Enum.map(fn row ->
      row
      |> String.split("", trim: true)
      |> Enum.map(&amp;elem(Integer.parse(&amp;1), 0))
    end)
    |> Matrix.from_list()
  end
end

testInput =
  """
  30373
  25512
  65332
  33549
  35390
  """
  |> Parse.input()
realInput =
  Load.input()
  |> Parse.input()

Part 1

defmodule Part1 do
  def is_visible_from_top_or_bottom(matrix, x, y) do
    tree_height = matrix[y][x]

    matrix_height = Enum.count(matrix)

    case y do
      y when y == 0 ->
        true

      y when y == matrix_height - 1 ->
        true

      y ->
        visible_from_top =
          0..(y - 1)
          |> Enum.map(fn yPos -> matrix[yPos][x] end)
          |> Enum.all?(fn height -> height < tree_height end)

        visible_from_bottom =
          (y + 1)..(matrix_height - 1)
          |> Enum.map(fn yPos -> matrix[yPos][x] end)
          |> Enum.all?(fn height -> height < tree_height end)

        visible_from_top or visible_from_bottom
    end
  end

  def is_visible_from_side(matrix, x, y) do
    tree_height = matrix[y][x]

    matrix_width = Enum.count(matrix[0])

    case x do
      x when x == 0 ->
        true

      x when x == matrix_width - 1 ->
        true

      x ->
        visible_from_left =
          0..(x - 1)
          |> Enum.map(fn xPos -> matrix[y][xPos] end)
          |> Enum.all?(fn height -> height < tree_height end)

        visible_from_right =
          (x + 1)..(matrix_width - 1)
          |> Enum.map(fn xPos -> matrix[y][xPos] end)
          |> Enum.all?(fn height -> height < tree_height end)

        visible_from_left or visible_from_right
    end
  end

  def is_visible(grid, x, y) do
    is_visible_from_side(grid, x, y) or
      is_visible_from_top_or_bottom(grid, x, y)
  end

  def solve(grid) do
    grid_height = Enum.count(grid)
    grid_width = Enum.count(grid[0])

    0..(grid_height - 1)
    |> Enum.map(fn yPos ->
      0..(grid_width - 1)
      |> Enum.map(fn xPos ->
        is_visible(grid, xPos, yPos)
      end)
    end)
    |> List.flatten()
    |> Enum.count(fn bool -> bool end)
  end
end

Part1.solve(testInput)
Part1.solve(realInput)

Part 2

defmodule Part2 do
  def left_distance_visible(grid, x, y) do
    case x do
      x when x == 0 ->
        0

      x ->
        trees =
          0..(x - 1)
          |> Enum.map(fn xPos -> grid[y][xPos] end)
          |> Enum.reverse()

        visible_distance(grid[y][x], trees, x)
    end
  end

  def right_distance_visible(grid, x, y) do
    grid_width = Enum.count(grid[0])

    case x do
      x when x == grid_width - 1 ->
        0

      x ->
        trees =
          (x + 1)..(grid_width - 1)
          |> Enum.map(fn xPos -> grid[y][xPos] end)

        visible_distance(grid[y][x], trees, grid_width - 1 - x)
    end
  end

  def top_distance_visible(grid, x, y) do
    case y do
      y when y == 0 ->
        0

      y ->
        trees =
          0..(y - 1)
          |> Enum.map(fn yPos -> grid[yPos][x] end)
          |> Enum.reverse()

        visible_distance(grid[y][x], trees, y)
    end
  end

  def bottom_distance_visible(grid, x, y) do
    grid_height = Enum.count(grid)

    case y do
      y when y == grid_height - 1 ->
        0

      y ->
        trees =
          (y + 1)..(grid_height - 1)
          |> Enum.map(fn yPos -> grid[yPos][x] end)

        visible_distance(grid[y][x], trees, grid_height - 1 - y)
    end
  end

  def visible_distance(tree_height, trees, max_distance) do
    distance =
      trees
      |> Enum.take_while(fn height -> height < tree_height end)
      |> Enum.count()

    case distance do
      # we reached the edge
      distance when distance == max_distance -> distance
      # account for the blocking tree
      distance -> distance + 1
    end
  end

  def scenic_score(grid, x, y) do
    top_distance_visible(grid, x, y) *
      left_distance_visible(grid, x, y) *
      right_distance_visible(grid, x, y) *
      bottom_distance_visible(grid, x, y)
  end

  def solve(grid) do
    grid_height = Enum.count(grid)
    grid_width = Enum.count(grid[0])

    0..(grid_height - 1)
    |> Enum.map(fn yPos ->
      0..(grid_width - 1)
      |> Enum.map(fn xPos ->
        scenic_score(grid, xPos, yPos)
      end)
    end)
    |> List.flatten()
    |> Enum.max()
  end
end

Part2.solve(testInput)
Part2.solve(realInput)