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(&(&1 < height))
|> Enum.all?()
end
def visible_right?(trees, {x, y}, height) do
trees
|> Enum.at(y)
|> Enum.slice((x + 1)..-1)
|> Enum.map(&(&1 < height))
|> Enum.all?()
end
def visible_top?(trees, {x, y}, height) do
trees
|> Enum.map(&Enum.at(&1, x))
|> Enum.slice(0..(y - 1))
|> Enum.map(&(&1 < height))
|> Enum.all?()
end
def visible_bottom?(trees, {x, y}, height) do
trees
|> Enum.map(&Enum.at(&1, x))
|> Enum.slice((y + 1)..-1)
|> Enum.map(&(&1 < height))
|> Enum.all?()
end
def parse_row(row),
do: row |> String.trim() |> String.codepoints() |> Enum.map(&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(&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(&Enum.at(&1, x))
|> Enum.slice(0..(y - 1))
|> Enum.reverse()
|> calc_score(height)
end
def calc_bottom(trees, {x, y}, height) do
trees
|> Enum.map(&Enum.at(&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(&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(&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)