I needed to revisit Day 1 because I didn’t realize the day consists of two parts.
The problem
This time, you’ll need to figure out exactly how often each number from the left list appears in the right list. Calculate a total similarity score by adding up each number in the left list after multiplying it by the number of times that number appears in the right list.
The example data is identical to the first part of the day, and the expected result for it is 31.
The solution
This problem is actually quite straightforward. Since I already have the values split into two arrays (as one should), all I need to do now is count the occurrences.
Here’s the implementation:
# input = File.read("puzzle_input.txt")
input = File.read "example.txt"
numbers = input.split.map(&:to_i)
sum = 0
similarity_score = 0
left, right = numbers.partition.with_index { |_, i| i.even? }
left.sort!
right.sort!
left.each_with_index do |e, i|
sum += (e - right[i]).abs
similarity_score += e * right.select { |r| r == e}.count
end
puts "sum: #{sum}; similarity_score: #{similarity_score}"
Explanation
For each number in the left array:
- Count how many times it appears in the right array.
- Multiply the number by its count in right and add this value to the similarity_score.
Last minute improvement
I realized I could simplify the calculation of occurrences using Ruby’s Array#tally method. This method creates a hash mapping each number to its count in the right array, making lookups faster and cleaner.
Here’s the improved implementation:
input = File.read("puzzle_input.txt")
# input = File.read "example.txt"
numbers = input.split.map(&:to_i)
sum = 0
similarity_score = 0
left, right = numbers.partition.with_index { |_, i| i.even? }
left.sort!
right.sort!
occurrences = right.tally
left.each_with_index do |e, i|
sum += (e - right[i]).abs
similarity_score += e * occurrences[e].to_i
end
puts "sum: #{sum}; similarity_score: #{similarity_score}"
Why tally is better?
-
Efficiency: By precomputing the counts of each number in the right array with tally, we avoid recalculating counts for every element in left.
-
Cleaner Code: Using
occurrences[e]for a direct lookup is simpler and more readable thanright.select { |r| r == e }.count. -
Handling Edge Cases: If a number from left doesn’t appear in right,
occurrences[e]returnsnil. The.to_iensures it’s treated as 0 instead of throwing an exception.