I just realized that there are multiple parts to the same day. Time to revisit Day 1 :D
The task (part 1)
Today’s task is to determine whether the reports provided as input are safe or unsafe.
Here’s the example data:
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
Each line represents a single report, and each number in the report represents a “level.”
A report is considered safe if:
- The levels are either all increasing or all decreasing.
- The difference between two adjacent levels is at least 1 and at most 3.
The result should be the count of all safe reports.
Write-up
Each line in the input represents a single report. So, the first step is to read the file and split the string by newline characters. Since the levels need to be numbers, I’ll also split each report by whitespace and parse the values into integers:
[[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]]
The result is an array of reports, where each report is an array of integers. The goal is to count how many of these reports are safe. Let’s initialize a safe_count variable at 0.
The plan
The initial idea was straightforward: for each report, check if the levels are increasing or decreasing and whether the differences between adjacent levels fall within the range of 1 to 3.
Initially, this required two loops per report—unnecessary and inefficient.
Instead, I realized I only needed to check the first two levels of each report to determine whether it was increasing or decreasing.
I introduced a check_direction method to determine the trend (increasing or decreasing):
def check_direction(a,b)
return :decreasing if a > b
:increasing
end
With this method, I could determine the direction of the report from its first two levels. Next, I wrote the check_decreasing method to validate whether a report was both decreasing and safe:
def check_decreasing(array)
array.each_cons(2) do |a, b|
return false if a < b || !(a - b).abs.between?(1,3)
end
true
end
If any pair of levels fails the conditions (not decreasing or difference out of range), the method returns false early. Otherwise, it completes the loop and returns true.
The check_increasing method was almost identical, with the only difference being the comparison (a > b).
A cleaner approach
It struck me that both check_decreasing and check_increasing were nearly identical. To simplify, I merged them into a single validate method.
Instead of storing the method references in a hash, I stored the string representations of the comparison operators:
actions = {
decreasing: "<",
increasing: ">"
}
Here’s the merged validate method:
def validate(array, action)
array.each_cons(2) do |a, b|
return false if a.send(action, b) || !(a - b).abs.between?(1, 3)
end
true
end
Now, I could dynamically determine the direction of the report and pass the correct operator to validate.
Final implementation
input = File.read("puzzle_input.txt")
# input = File.read "./example.txt"
reports = input.split("\n").map { |i| i.split.map(&:to_i) }
safe_count = 0
def validate(array, action)
array.each_cons(2) do |a, b|
return false if a.send(action.to_sym, b) || !(a - b).abs.between?(1, 3)
end
true
end
reports.each do |report|
action = report[0] > report[1] ? "<" : ">"
safe_count += 1 if validate(report, action)
end
puts safe_count