AoC Day 2 – Red-Nosed Reports

Day 2 of AoC starts off with a fairly straightforward problem. Our input is a series of lines, with each line containing an arbitrary number of integers. The example data shows each line having 5 numbers, but the actual input has more or less depending on the exact line you’re on (so it’s not a one size fits all thing). Our task is to count the number of lines that meet three rules.

  1. The series of numbers on the line must be either always increasing, or always decreasing. They can’t start going up, and then move down, or have a repeated number in them. Meaning 1, 2, 3 is fine but 1, 3, 2 is not fine.
  2. There can be no more than a gap of 3 between each number meaning 1, 4, 5 is fine but 1, 5, 6 is not fine.

I started by converting all of the numbers in each row into ints from the initial strings that are read in.

line=line.strip().split()
for i in range(len(line)):
     line[i] = int(line[i])

Once I got each line converted over, I built a very simple validation function.

def validate(level):
    increase = all(i > j for i, j in zip(level, level[1:]))
    decrease = all(i < j for i, j in zip(level, level[1:]))
    increment = all(abs(i - j) <= 3 for i, j in zip(level, level[1:]))
    return (increase or decrease) and increment

What each of the first three lines is doing is taking the start of the list of integers for that line, as well as the second element (pos 1) of the list and zipping them together to give me pairs of numbers. Then it checks to make sure that they are all either going up, or down, and that the difference between each number is no more than three. Notice the all() function which takes the output of every test, and if any of them fail, it will return False. The last line makes sure that we pass one of the directional tests, and the increment test.

Part 2

For part 2, we have to take it the next step, and try to “repair” the bad sequences. A valid repair consists of being able to remove a single number from the line which will allow the sequence to pass the rules above. So for example 1, 2, 4, 3 would fail because the 3 is a decrease in value while the rest is an increase. However, if I remove the 3 at the end, it will pass.

There is probably a more efficient or elegant way to make this work, but I essentially brute forced it. I would again feed each line and see if it would validate. If it didn’t validate, then I would try to repair it. My repair attempt was basically making a copy of the initial line, and iterating through removing one element at a time and seeing if it would validate. If it didn’t I would recreate the initial list, and then remove the next element.

def repair(level):
    for i in range(len(level)):
        tmp = level[:]
        tmp.pop(i)
        if validate(tmp):
            return True
    return False

You’ll notice the third line (tmp = level[:]). The reason I did it that way is because initially I did tmp = level but I quickly found that when I would remove an element from tmp, it would also remove the element from level as well. That is one thing that always bothers me about Python is that sometimes it will pass a variable by value, and other times by reference, and it’s not always clear which one its doing. You can see my entire code on Github.