mirror of
https://github.com/rjNemo/ai_advent_code_2024
synced 2026-06-06 02:26:44 +00:00
127 lines
3.4 KiB
Elixir
127 lines
3.4 KiB
Elixir
defmodule AdventCode2024.Solutions.Day2 do
|
|
@moduledoc """
|
|
Solution for Day 2: Red-Nosed Reports
|
|
"""
|
|
|
|
@default_input "priv/inputs/day02/input.txt"
|
|
|
|
@doc """
|
|
Analyzes reactor reports to count how many are safe according to the rules.
|
|
Returns {:ok, count} for valid input or {:error, reason} for invalid input.
|
|
"""
|
|
def solve(input \\ @default_input)
|
|
def solve(""), do: {:error, :no_valid_reports}
|
|
def solve(input) when is_binary(input) and input != "" do
|
|
if String.contains?(input, "\n") or !String.contains?(input, "/") do
|
|
# Input is content
|
|
solve_content(input)
|
|
else
|
|
# Input is file path
|
|
case File.read(input) do
|
|
{:ok, content} -> solve_content(content)
|
|
{:error, reason} -> {:error, reason}
|
|
end
|
|
end
|
|
end
|
|
|
|
defp solve_content(""), do: {:error, :no_valid_reports}
|
|
defp solve_content(content) when is_binary(content) and content != "" do
|
|
lines = String.split(content, "\n", trim: true)
|
|
|
|
with {:ok, reports} <- parse_reports(lines) do
|
|
safe_count = Enum.count(reports, &safe_report?/1)
|
|
{:ok, safe_count}
|
|
end
|
|
end
|
|
|
|
defp parse_reports(lines) do
|
|
reports = Enum.map(lines, &parse_line/1)
|
|
|
|
if Enum.all?(reports, &is_list/1) do
|
|
{:ok, reports}
|
|
else
|
|
{:error, :invalid_format}
|
|
end
|
|
end
|
|
|
|
defp parse_line(line) do
|
|
line
|
|
|> String.split(" ", trim: true)
|
|
|> Enum.map(fn num ->
|
|
case Integer.parse(num) do
|
|
{n, ""} -> n
|
|
_ -> nil
|
|
end
|
|
end)
|
|
|> then(fn nums ->
|
|
if Enum.any?(nums, &is_nil/1), do: nil, else: nums
|
|
end)
|
|
end
|
|
|
|
defp safe_report?(levels) do
|
|
differences =
|
|
Enum.chunk_every(levels, 2, 1, :discard)
|
|
|> Enum.map(fn [a, b] -> b - a end)
|
|
|
|
case differences do
|
|
[] ->
|
|
false
|
|
|
|
diffs ->
|
|
all_increasing?(diffs) or all_decreasing?(diffs)
|
|
end
|
|
end
|
|
|
|
defp all_increasing?(diffs) do
|
|
Enum.all?(diffs, fn diff -> diff > 0 and diff <= 3 end)
|
|
end
|
|
|
|
defp all_decreasing?(diffs) do
|
|
Enum.all?(diffs, fn diff -> diff < 0 and diff >= -3 end)
|
|
end
|
|
|
|
@doc """
|
|
Analyzes reactor reports with Problem Dampener active to count safe reports.
|
|
A report is safe if it's already safe or becomes safe after removing one number.
|
|
Returns {:ok, count} for valid input or {:error, reason} for invalid input.
|
|
"""
|
|
def solve_part2(input \\ @default_input)
|
|
def solve_part2(""), do: {:error, :no_valid_reports}
|
|
def solve_part2(input) when is_binary(input) and input != "" do
|
|
if String.contains?(input, "\n") or !String.contains?(input, "/") do
|
|
# Input is content
|
|
solve_part2_content(input)
|
|
else
|
|
# Input is file path
|
|
case File.read(input) do
|
|
{:ok, content} -> solve_part2_content(content)
|
|
{:error, reason} -> {:error, reason}
|
|
end
|
|
end
|
|
end
|
|
|
|
defp solve_part2_content(""), do: {:error, :no_valid_reports}
|
|
defp solve_part2_content(content) when is_binary(content) and content != "" do
|
|
lines = String.split(content, "\n", trim: true)
|
|
|
|
with {:ok, reports} <- parse_reports(lines) do
|
|
safe_count = Enum.count(reports, &safe_with_dampener?/1)
|
|
{:ok, safe_count}
|
|
end
|
|
end
|
|
|
|
defp safe_with_dampener?(levels) do
|
|
# Check if already safe without removal
|
|
if safe_report?(levels) do
|
|
true
|
|
else
|
|
# Try removing each number one at a time
|
|
0..(length(levels) - 1)
|
|
|> Enum.any?(fn index ->
|
|
levels
|
|
|> List.delete_at(index)
|
|
|> safe_report?()
|
|
end)
|
|
end
|
|
end
|
|
end
|