Things are starting to get tricky—or at least they seem that way at first glance.


The problem

In part 1, whenever validation failed, the report was automatically marked as unsafe, and I could move on.

However, in part 2, the problem dampener changes things. Now, I have to check if a report could be considered safe if one of the levels is excluded.


The plan

Initially, I assumed (mistakenly) that a report with only one issue could be saved and that reports with more than one issue would remain unsafe, no matter what. To test this theory, I modified the validate method to count the number of issues in the report:

issues_found = 0

def validate(ar, action)
  ar.each_cons(2) do |a,b|
    return false if issues_found > 1

    issues_found += 1 if a.send(action.to_sym, b) || !(a - b).abs.between?(1,3)
  end

  true
end

When I ran this on the example dataset, it marked all reports as safe—because every report had only one problematic level. Clearly, my assumption was wrong.


A closer look

After carefully re-reading the description, it became clear that I needed to check whether removing a single level from an unsafe report would make it safe. The problem wasn’t that the reports themselves were overly problematic—it was me rushing through the requirements late at night. :D

The only solution that came to mind was to validate all “permutations” of the array, where each permutation is the original array with one level removed.


A new method: validate_permutations

To implement this, I created a validate_permutations method. This method takes a report (array) as input, removes one level at a time, and runs the original validate method on the resulting array.

Here’s the method:

def validate_permutations(ar)
  (0...ar.size).each do |i|
    permutation = ar.reject.with_index { |_, j| j == i }

    action = permutation[0] > permutation[1] ? "<" : ">"

    return true if validate(permutation, action)
  end

  false
end

Key details:

  1. For each index in the array, a new “permutation” is created by removing the element at that index.
  2. The validate method is called on the modified array.
  3. If any permutation passes validation, the method returns true immediately. Otherwise, it completes the loop and returns false.

The final task is to count how many reports have at least one valid permutation.


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

loose_safe_count = 0

def validate(ar, action)
  ar.each_cons(2) do |a,b|
    return false if a.send(action.to_sym, b) || !(a - b).abs.between?(1,3)
  end

  true
end

def validate_permutations(ar)
  (0...ar.size).each do |i|
    permutation = ar.reject.with_index { |_, j| j == i }

    action = permutation[0] > permutation[1] ? "<" : ">"

    return true if validate(permutation, action)
  end

  false
end

reports.each do |r|
  action = r[0] > r[1] ? "<" : ">"

  safe_count += 1 if validate(r, action)

  loose_safe_count += 1 if validate_permutations(r)
end

puts safe_count
puts loose_safe_count

Possible improvements

For larger datasets, the validate_permutations method could become inefficient due to the creation and validation of multiple permutations. Here are some potential optimizations:

  1. Stop Early: The method already stops early when a valid permutation is found, but ensuring minimal intermediate array creation could reduce overhead.

  2. Custom Validation Logic: Instead of generating permutations, tweak the validate method to simulate the removal of one level during the iteration, bypassing the need for a separate method.

These optimizations could further improve performance, but for the current problem size, the implementation works well as-is.