Relax, take a step back and start from the business logic

Another year of advent of code and this time I decided to participate. Not only that but I thought it would be a great way to teach myself Ruby.
This means that my train of thought gets interrupted quite often since for every little thing I come up with I have to stop and search how Ruby does it.

Day 3 – Gear Ratios

In today’s challenge the target is to calculate the sum of all numbers that are adjacent, even diagonally, to a symbol. The input looks like this:

467..114..
...*......
..35..633.
......#...
617*......
.....+.58.
..592.....
......755.
...$.*....
.664.598..

and a symbol is everything that is not a number or a dot (.).

Head first

Seeing this input combined with the fact that I don’t know Ruby throw me in a crazy rabbit hole where I was searching for two-dimensional arrays at one point and parsing strings at another.

Then I remembered that the adjacent part includes diagonals too so I dropped everything and start thinking of how I will combine numbers from one line with symbols from another.

This is getting big. Should I start smaller? Should I try to approach this in a 2×2 array? Should I do this or that? Chaos!

Start from the business logic

Thankfully after taking a break and drinking some water I realized that my need to answer all my unknowns had taken the best of me and I was viewing things wrong.

It does not matter how the input looks. It is just that, an input. I shouldn’t start from there.
It does not matter what/how Ruby does things. It is just a tool.

What matters is the business logic and in this case its quite simple:

  • Our business entities are Numbers and Symbols.
  • Our business logic dictates that a Number is next to a Symbol if it lies to the area that surrounds it.

Translating this to code:

class Symbol
attr_reader :value, :row, :column
def initialize(value, row, column)
@value = value
@row = row
@column = column
end
end
class Number
attr_reader :value, :row, :starting_column, :ending_column
def initialize(value, row, starting_column, ending_column)
@value = value
@row = row
@starting_column = starting_column
@ending_column = ending_column
end
def is_next_to?(symbol)
return false if (@row <= symbol.row – 2) || (@row >= symbol.row + 2)
return false if (@ending_column <= symbol.column – 2) || (@starting_column >= symbol.column + 2)
true
end
end
view raw gear_ratios.rb hosted with ❤ by GitHub

made things so much simpler:

class GearRatios
def initialize(numbers, symbols)
@numbers = numbers
@symbols = symbols
end
def sum
@numbers.filter { |number| @symbols.any? { |symbol| number.is_next_to? symbol } }
.sum { |number| number.value }
end
end

After writing and testing the business logic all I had left to do was to write the code that will produce our lists for numbers and symbols. In this case it just happens to be a two-dimensional array with string values.

Start from what matters

Being overwhelmed or not, relax, take a step back and start from what matters.

Presentation is important but it shouldn’t drive an approach since it might change often. Input is also important but it shouldn’t matter if we are dealing with a database, a web service or the file system.

Start from the business, make it work and then try to figure out how everything else can be plugged in.