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?

  1. Efficiency: By precomputing the counts of each number in the right array with tally, we avoid recalculating counts for every element in left.

  2. Cleaner Code: Using occurrences[e] for a direct lookup is simpler and more readable than right.select { |r| r == e }.count.

  3. Handling Edge Cases: If a number from left doesn’t appear in right, occurrences[e] returns nil. The .to_i ensures it’s treated as 0 instead of throwing an exception.