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

AOC 2022 - Day 08

aoc2022/day08.livemd

AOC 2022 - Day 08

Mix.install([
  {:kino_aoc, "~> 0.1"}
])

AOC Helper

{:ok, puzzle_input} =
  KinoAOC.download_puzzle("2022", "8", System.fetch_env!("LB_AOC_SESSION"))

Part 1

Code

defmodule PartOne do
  def visible_trees(trees) do
    columns = Enum.count(trees)
    rows = trees |> Enum.at(0) |> Enum.count()
    edge_count = columns * 2 + rows * 2 - 4

    Enum.reduce(1..(columns - 2), edge_count, fn y, acc ->
      Enum.reduce(1..(rows - 2), acc, fn x, acc ->
        if visible?(trees, {x, y}), do: acc + 1, else: acc
      end)
    end)
  end

  def visible?(trees, {x, y}) do
    height = trees |> Enum.at(y) |> Enum.at(x)

    visible_left?(trees, {x, y}, height) ||
      visible_right?(trees, {x, y}, height) ||
      visible_top?(trees, {x, y}, height) ||
      visible_bottom?(trees, {x, y}, height)
  end

  def visible_left?(trees, {x, y}, height) do
    trees
    |> Enum.at(y)
    |> Enum.slice(0..(x - 1))
    |> Enum.map(&amp;(&amp;1 < height))
    |> Enum.all?()
  end

  def visible_right?(trees, {x, y}, height) do
    trees
    |> Enum.at(y)
    |> Enum.slice((x + 1)..-1)
    |> Enum.map(&amp;(&amp;1 < height))
    |> Enum.all?()
  end

  def visible_top?(trees, {x, y}, height) do
    trees
    |> Enum.map(&amp;Enum.at(&amp;1, x))
    |> Enum.slice(0..(y - 1))
    |> Enum.map(&amp;(&amp;1 < height))
    |> Enum.all?()
  end

  def visible_bottom?(trees, {x, y}, height) do
    trees
    |> Enum.map(&amp;Enum.at(&amp;1, x))
    |> Enum.slice((y + 1)..-1)
    |> Enum.map(&amp;(&amp;1 < height))
    |> Enum.all?()
  end

  def parse_row(row),
    do: row |> String.trim() |> String.codepoints() |> Enum.map(&amp;String.to_integer/1)

  def solve(input) do
    IO.puts("--- Part One ---")
    IO.puts("Result: #{run(input)}")
  end

  def run(input) do
    input
    |> String.split("\n")
    |> Enum.map(&amp;parse_row/1)
    |> visible_trees()
  end
end

Test

ExUnit.start(autorun: false)

defmodule PartOneTest do
  use ExUnit.Case, async: true
  import PartOne

  @input "30373
  25512
  65332
  33549
  35390"
  @expected 21

  test "part one" do
    actual = run(@input)
    assert actual == @expected
  end
end

ExUnit.run()

Solution

PartOne.solve(puzzle_input)

Part 2

Code

defmodule PartTwo do
  def find_build_spot(trees) do
    columns = Enum.count(trees)
    rows = trees |> Enum.at(0) |> Enum.count()

    Enum.reduce(1..(columns - 2), 0, fn y, high_score ->
      Enum.reduce(1..(rows - 2), high_score, fn x, high_score ->
        max(high_score, calc_scenic_score(trees, {x, y}))
      end)
    end)
  end

  def calc_scenic_score(trees, {x, y}) do
    height = trees |> Enum.at(y) |> Enum.at(x)

    [
      Enum.max([1, calc_top(trees, {x, y}, height)]),
      Enum.max([1, calc_left(trees, {x, y}, height)]),
      Enum.max([1, calc_right(trees, {x, y}, height)]),
      Enum.max([1, calc_bottom(trees, {x, y}, height)])
    ]
    |> Enum.reduce(1, fn score, total -> score * total end)
  end

  def calc_left(trees, {x, y}, height) do
    trees
    |> Enum.at(y)
    |> Enum.slice(0..(x - 1))
    |> Enum.reverse()
    |> calc_score(height)
  end

  def calc_right(trees, {x, y}, height) do
    trees
    |> Enum.at(y)
    |> Enum.slice((x + 1)..-1)
    |> calc_score(height)
  end

  def calc_top(trees, {x, y}, height) do
    trees
    |> Enum.map(&amp;Enum.at(&amp;1, x))
    |> Enum.slice(0..(y - 1))
    |> Enum.reverse()
    |> calc_score(height)
  end

  def calc_bottom(trees, {x, y}, height) do
    trees
    |> Enum.map(&amp;Enum.at(&amp;1, x))
    |> Enum.slice((y + 1)..-1)
    |> calc_score(height)
  end

  def calc_score(neighbors, height) do
    Enum.reduce_while(neighbors, 0, fn value, score ->
      if value < height do
        {:cont, score + 1}
      else
        {:halt, score + 1}
      end
    end)
  end

  def parse_row(row),
    do: row |> String.trim() |> String.codepoints() |> Enum.map(&amp;String.to_integer/1)

  def solve(input) do
    IO.puts("--- Part Two ---")
    IO.puts("Result: #{run(input)}")
  end

  def run(input) do
    input
    |> String.split("\n")
    |> Enum.map(&amp;parse_row/1)
    |> find_build_spot()
  end
end

Test

ExUnit.start(autorun: false)

defmodule PartTwoTest do
  use ExUnit.Case, async: true
  import PartTwo

  @input "30373
  25512
  65332
  33549
  35390"
  @expected 8

  test "part two" do
    actual = run(@input)
    assert actual == @expected
  end
end

ExUnit.run()

Solution

PartTwo.solve(puzzle_input)