Day 2
Mix.install([
{:kino_aoc, "~> 0.1"}
])
Input
{:ok, puzzle_input} =
KinoAOC.download_puzzle("2024", "2", System.fetch_env!("LB_SESSION"))
test input
test_input = """
7 6 4 2 1
1 2 7 8 9
9 7 6 2 1
1 3 2 4 5
8 6 4 4 1
1 3 6 7 9
"""
parser
defmodule RowParser do
def parse(input) do
input
|> String.split("\n", trim: true)
|> Stream.map(&String.trim_leading/1)
|> Stream.map(fn str ->
str
|> String.split()
|> Enum.map(&String.to_integer/1)
end)
|> Enum.to_list()
end
end
RowParser.parse(test_input)
test parser
ExUnit.start(autorun: false)
defmodule RowParserTest do
use ExUnit.Case, async: true
@test_input """
7 6 4 2 1
1 2 7 8 9
9 7 6 2 1
1 3 2 4 5
8 6 4 4 1
1 3 6 7 9
"""
test "row parse test" do
assert RowParser.parse(@test_input) |> Enum.at(0) == [7, 6, 4, 2, 1]
assert RowParser.parse(@test_input) |> Enum.at(5) == [1, 3, 6, 7, 9]
end
end
ExUnit.run()
part 1
defmodule PartOne do
def solve(input) do
IO.puts("--- Part One ---")
IO.puts("Result: #{run(input)}")
end
def run(input) do
input
|> RowParser.parse()
|> Enum.map(&evaluate_report/1)
|> Enum.count(&(&1 == true))
end
def evaluate_report(report) do
levels = report
|> Enum.chunk_every(2, 1, :discard)
in_range = levels
|> Enum.map(&diff/1)
|> Enum.all?(&in_range/1)
all_positive = levels
|> Enum.map(&diff/1)
|> Enum.all?(&positive/1)
all_negative = levels
|> Enum.map(&diff/1)
|> Enum.all?(&negative/1)
in_range && all_positive || in_range && all_negative
end
def diff([a, b]), do: a - b
def in_range(n), do: abs(n) in 1..3
def positive(n), do: n > 0
def negative(n), do: n < 0
end
PartOne.run(test_input)
Section
part 1 - test
ExUnit.start(autorun: false)
defmodule PartOneTest do
use ExUnit.Case
@input puzzle_input
test "part one" do
assert PartOne.run(@input) == 591
end
end
ExUnit.run()
part 2
defmodule PartTwo do
def run(input) do
input
|> RowParser.parse()
|> Enum.map(&evaluate_report/1)
|> Enum.count(&(&1 == true))
end
def evaluate_report(report) do
if valid_sequence?(report) do
true
else
# Try removing each number to see if it makes sequence valid
0..(length(report) - 1)
|> Enum.any?(fn index ->
report
|> List.delete_at(index)
|> valid_sequence?()
end)
end
end
def valid_sequence?(report) do
diff =
report
|> Enum.chunk_every(2, 1, :discard)
|> Enum.map(&diff/1)
all_valid = Enum.all?(diff, &valid_diff?/1)
cond do
Enum.empty?(diff) -> false
not all_valid -> false
Enum.all?(diff, &(&1 > 0)) -> true # all increasing
Enum.all?(diff, &(&1 < 0)) -> true # all decreasing
true -> false
end
end
def diff([a, b]), do: b - a
def valid_diff?(diff), do: abs(diff) in 1..3
end
# test_input
puzzle_input
|> PartTwo.run()
part 2 - test
ExUnit.start(autorun: false)
defmodule PartTwoTest do
use ExUnit.Case
@input puzzle_input
test "part two" do
assert PartTwo.run(@input) == 621
end
end
ExUnit.run()