Advent of Code — Day 3
And so the painful puzzles begin!
Today's puzzle revolved around an ascii schematic and we were tasked with parsing it and finding adjacent elements in the map.
These ones always trip me up for some reason — and I think it's because I don't normally work with data structures where some elements are related, by space, to others.
Part 1
Here's our schematic:
467..114..
...*......
..35..633.
......#...
617*......
.....+.58.
..592.....
......755.
...$.*....
.664.598..
In this part, we need to find out how many numbers are directly adjacent to (horizontally, vertically and diagonally) to a symbol (.
does not count). Those numbers then need to be summed up.
For this, I first built up a list of all the symbol positions on each line:
symbol_positions = {}
input.each_with_index{ |l ,idx|
symbol_positions[idx] = []
l.enum_for(:scan, /[^a-zA-Z\d[.]:]/).each {
symbol_positions[idx] << Regexp.last_match.begin(0)
}
}
This resulted in something like {0 => [], 1=> [3], ..., 8 => [3,5], ... }
Next, I needed the positions of each of the numbers:
number_positions = input
.flat_map
.each_with_index { |l, idx| l.enum_for(:scan, /\d+/).map { [idx, [Regexp.last_match.offset(0), Regexp.last_match.to_s.to_i]] } }
This resulted in an array like: [[0, [[0, 2], 467], [5,7], 114]], [1,[...]]]
([[line, [match_range, match]]]
)
Then, it was just a matter of working through that list of numbers to see if any had an adjacent symbol. Where an adjacent symbol is one that appears in the previous line, the same line, or the next line within the match_range
of the number (increased by 1 each side to account for diagonals).
number_positions.map do |pos|
line = pos[0]
match_range = pos[1][0]
match = pos[1][1]
same_line = symbol_positions[line].any? { |pos| adjacent(match_range, pos) }
above = line < (input.count - 1) && symbol_positions[line + 1].any? { |pos| adjacent(match_range, pos) }
below = line > 0 && symbol_positions[line - 1].any? { |pos| adjacent(match_range, pos) }
match if same_line || above || below
end.compact.inject(:+)
def adjacent(range, item)
(range[0] - 1...range[1] + 1).include? item
end
I initially had the range defined as (min..max)
and my tests passed, but he answer I submitted was not correct!
Cleaned that up and ⭐️ success!
Part 2
Part 2 drove me nuts!
The idea was simple. Given the same schematic, now only find "gears". Where a "gear" is a *
symbol that has exactly 2 adjacent numbers. Multiply the two numbers for each gear, and sum the total.
I started this one off in a similar way, list all the potential gear positions and list all of the number positions.
Then, look over each potential position, and check if there is a number above, next to, or below it. What tripped my up though was my wahackadoo data structure above. I was trying to make today's effort a little simpler than yesterday's, but that meant it was trickier to debug and reason about as I was jumping between cooking dinner and this at the same time.
In the end, I got my tests passing — but oh no! my solution was too high?!
I had a look through the input data and found a "gear" that looked like:
.....755..
....*.....
.664.598..
Which was obviously incorrect as the *
was adjacent to 3 numbers, when it's only allowed to be adjacent to exactly 2.
I started puts
-ing out what my code thought was a gear and it returned [755, 664]
and [755, 598]
as 2 seperate gears!!
It turned out the bug was in how I was determining what numbers were around a *
, and I was just taking the first — so when I checked to see if a gear had more than 3 numbers, I was missing the 3rd!
This is how that code ended up in the end:
gears = potential_gear_positions.flat_map do |line, positions|
positions.map do |gear|
lines = [
line < (input.count - 1) && number_positions[line + 1],
line > 0 && number_positions[line - 1],
number_positions[line]
].compact
nums = lines
.flat_map { |l| l.select { |ll| adjacent(ll[0], gear) }.map(&:last) }
.flatten
.compact
next 0 if nums.count != 2
nums.inject(:*)
end
end
gears.inject(:+)
⭐️ Success! 🫠
The code: https://github.com/dNitza/advent-of-code/tree/main/2023/03
Performance:
Rehearsal ------------------------------------------
part 1 0.004141 0.000094 0.004235 ( 0.004255)
part 2 0.002621 0.000048 0.002669 ( 0.002673)
--------------------------------- total: 0.006904sec
user system total real
part 1 0.003456 0.000042 0.003498 ( 0.003516)
part 2 0.002341 0.000022 0.002363 ( 0.002366)
in posts
Tagged