aoc2022/day8.livemd
2023-01-02 18:36:35 +01:00

5.6 KiB

AOC 2022 - Day 8

Mix.install(
  [
    {:req, "~> 0.3.3"},
    {:nx, "~> 0.4.1"},
    {:exla, "~> 0.4.1"}
  ],
  config: [nx: [default_backend: EXLA.Backend]]
)

Puzzle description

Day 8: Treetop Tree House.

Input

defmodule Load do
  def input do
    aoc_session = System.fetch_env!("LB_AOC_SESSION")
    input_url = "https://adventofcode.com/2022/day/8/input"
    Req.get!(input_url, headers: [cookie: "session=#{aoc_session}"]).body
  end
end

Solution

defmodule Util do
  def process(input) do
    input
    |> String.split("\n", trim: true)
    |> Enum.map(fn row ->
      row
      |> String.split("", trim: true)
      |> Enum.map(&String.to_integer/1)
    end)
  end

  def process2(input) do
    input
    |> String.split()
    |> Enum.with_index()
    |> Enum.flat_map(&parse_line/1)
    |> Map.new(fn {v, k} -> {k, v} end)
  end

  defp parse_line({string, row}) do
    string
    |> String.graphemes()
    |> Enum.with_index()
    |> Enum.map(fn {char, column} ->
      {String.to_integer(char), {row + 1, column + 1}}
    end)
  end

  def count_visible(line) do
    line
    |> Enum.with_index()
    |> Enum.map_reduce(0, fn {h, n}, max ->
      cond do
        n == 0 ->
          {true, h}

        h <= max ->
          {false, max}

        true ->
          {true, h}
      end
    end)
    |> elem(0)
  end

  def transpose(v) do
    v
    |> List.zip()
    |> Enum.map(&Tuple.to_list/1)
  end

  def score(trees, row, column, rows, columns) do
    left_score(trees, row, column, rows, columns) *
      right_score(trees, row, column, rows, columns) *
      up_score(trees, row, column, rows, columns) *
      down_score(trees, row, column, rows, columns)
  end

  defp left_score(trees, row, column, _rows, _columns) do
    list =
      for c <- (column - 1)..1 do
        trees[{row, c}]
      end
      |> Enum.with_index()

    len = length(list)

    list
    |> Enum.reduce_while(1, fn {height, position}, score ->
      if trees[{row, column}] > height and position + 1 != len,
        do: {:cont, score + 1},
        else: {:halt, score}
    end)
  end

  defp right_score(trees, row, column, _rows, columns) do
    list =
      for c <- (column + 1)..columns do
        trees[{row, c}]
      end
      |> Enum.with_index()

    len = length(list)

    list
    |> Enum.reduce_while(1, fn {height, position}, score ->
      if trees[{row, column}] > height and position + 1 != len,
        do: {:cont, score + 1},
        else: {:halt, score}
    end)
  end

  defp up_score(trees, row, column, _rows, _columns) do
    list =
      for r <- (row - 1)..1 do
        trees[{r, column}]
      end
      |> Enum.with_index()

    len = length(list)

    list
    |> Enum.reduce_while(1, fn {height, position}, score ->
      if trees[{row, column}] > height and position + 1 != len,
        do: {:cont, score + 1},
        else: {:halt, score}
    end)
  end

  defp down_score(trees, row, column, rows, _columns) do
    list =
      for r <- (row + 1)..rows do
        trees[{r, column}]
      end
      |> Enum.with_index()

    len = length(list)

    list
    |> Enum.reduce_while(1, fn {height, position}, score ->
      if trees[{row, column}] > height and position + 1 != len,
        do: {:cont, score + 1},
        else: {:halt, score}
    end)
  end
end
defmodule Part1 do
  def run(input) do
    trees =
      input
      |> Util.process()

    {trees, horizontal} =
      trees
      |> Enum.map_reduce([], fn line, acc ->
        visible =
          [
            Util.count_visible(line),
            line |> Enum.reverse() |> Util.count_visible() |> Enum.reverse()
          ]
          |> Enum.zip()
          |> Enum.map(&(elem(&1, 0) or elem(&1, 1)))

        {line, [visible | acc]}
      end)
      |> then(fn {trees, horizontal} ->
        {trees, Enum.reverse(horizontal)}
      end)

    {_, vertical} =
      trees
      |> Util.transpose()
      |> Enum.map_reduce([], fn col, acc ->
        visible =
          [
            Util.count_visible(col),
            col |> Enum.reverse() |> Util.count_visible() |> Enum.reverse()
          ]
          |> Enum.zip()
          |> Enum.map(&(elem(&1, 0) or elem(&1, 1)))

        {col, [visible | acc]}
      end)
      |> then(fn {trees, vertical} ->
        {trees, Enum.reverse(vertical) |> Util.transpose()}
      end)

    horizontal
    |> Enum.zip(vertical)
    |> Enum.map(fn {h, v} ->
      h
      |> Enum.zip(v)
      |> Enum.map(&(elem(&1, 0) or elem(&1, 1)))
    end)
    |> Enum.map(fn line ->
      Enum.count(line, & &1)
    end)
    |> Enum.sum()
  end
end

defmodule Part2 do
  def run(input) do
    trees = Util.process2(input)

    {rows, columns} =
      trees
      |> Map.keys()
      |> Enum.max()

    for i <- 2..(rows - 1), j <- 2..(columns - 1) do
      {i, j, Util.score(trees, i, j, rows, columns)}
    end
    |> Enum.max_by(fn {_, _, x} -> x end)
    |> elem(2)
  end
end

ExUnit.start(autorun: false)

defmodule Test do
  use ExUnit.Case, async: true
  @example_input ~s(30373
25512
65332
33549
35390)
  @input Load.input()

  test "it loads the input" do
    assert String.length(@input) > 0
  end

  test "part 1 example" do
    assert Part1.run(@example_input) === 21
  end

  test "part 1" do
    assert Part1.run(@input) === 1816
  end

  test "part 2 example" do
    assert Part2.run(@example_input) === 8
  end

  test "part 2" do
    assert Part2.run(@input) === 0
  end
end

ExUnit.run()

# [
#    [1, 1, 1, 1, 1],
#    [1, 1, 1, 0, 1],
#    [1, 1, 0, 1, 1],
#    [1, 0, 1, 0, 1],
#    [1, 1, 1, 1, 1]
#  ]