After an easyish day 2, the difficulty level bumped up a bit again for day 3.

# Day 3 - Gear Ratios

See Day 3 for a detailed description of the problem.

To run the Jactl shown here save the code into a file (e.g. `advent03.jactl`) and run it like this (where advent03.txt is your input file from the Advent of Code site for Day 3):

``````\$ cat advent03.txt | java -jar jactl-2.0.0.jar advent03.jactl
``````

## Part 1

In part 1 we are given a grid consisting of digits, dots, and symbols (such as ‘+’, ‘#’, ‘*’). The idea is to find in the grid sequences of digits and treat these sequences as a number and sum all numbers in the grid that match the criteria that one of the digits in the number must be adjacent to a symbol. Adjacent, for this puzzle, means any neighbour in the 8 surrounding squares of the grid.

For example:

``````..123..44.
..+3..*...
.32..677..
..111.....
.......\$9.
``````

As usual, for puzzles involving grids, I added a border of dots around the grid to avoid having to check for out-of-bounds conditions when looking for neighbours at the edge of the grid.

Here is the code:

``````def (rows, D) = [stream(nextLine), [-1,0,1]]
def g = ['.' * (rows[0].size()+2)] + rows.map{ '.'+it+'.' } + ['.' * (rows[0].size()+2)]

def numNearSym(x,y) { g[y][x] =~ /\d/ && D.flatMap{ dx -> D.map{ dy -> [x+dx,y+dy] } }.anyMatch{ x1,y1 -> g[y1][x1] !~ /[.\d]/ } }

g.mapWithIndex{ r,y -> r.size().map{ x -> g[y][x] + (numNearSym(x, y) ? 'S' : '') }.join() }
.flatMap{ it.split(/[^\dS]+/).filter{ 'S' in it }.map{ s/[^\d]+//g }
.map{ it as int } }.sum()
``````

The first line assigns all input lines to `rows` and assigns `[-1,0,1]` to `D` for use when calculating deltas to `x,y` coordinates later.

Then we create our grid of lines in the variable `g`, adding a row at the beginning and end and adding a dot to the beginning and end of each row.

The function `numNearSym()` checks if at `x,y` we have a digit and then looks for a neighbour that is not a digit and is not a `.` and returns true if we are a digit with such a neighbour.

Then we iterate over the rows using `mapWithIndex()` and append `S` to each digit in the row that has a neighbouring symbol.

We take the resulting lines with these `S` characters and use the regex split to split the line into a list of substrings that were separated by anything that was not a digit or `S`. This gives us our candidate numbers. We filter for only numbers with an embeded `S` (since these are the ones with symbol neighbours), remove the `S` characters and convert into numbers before summing them.

## Part 2

For part 2 we are only interested in numbers that have a neighbour that is a `*` and only if that `*` has exactly two numbers for which it is a neighbour. Then we multiply the two numbers together and sum the resulting products.

``````01 def lines = stream(nextLine)
02 def g = ['.' * (lines[0].size()+2)] + lines.map{ '.' + it + '.' } + ['.' * (lines[0].size()+2)]
03
04 def nearest2Nums(x,y) {
05   def nums = [[-1,0,1].flatMap{ dx -> [-1,0,1].map{dy -> [x+dx, y+dy] } }
06                       .filter{ x1, y1 -> g[y1][x1] =~ /\d/ }
07                       .map{ x1,y1 -> [x1 - (x1+1).filter{ g[y1][x1-it] !~ /\d/ }.limit(1)[0] + 1, y1] }
08                       .sort().unique()].filter{ it.size() == 2 }[0]
09   nums ? nums.map{ x1,y1 -> g[y1].substring(x1,(g[y1].size()-x1).map{x1+it+1}.filter{ g[y1][it] !~ /\d/ }.limit(1)[0]) as int }
10             .grouped(2).map{ it[0] * it[1] }[0]
11        : null
12 }
13
14 g.size().flatMap{ y -> g[y].size().filter{ g[y][it] == '*' }.flatMap{ nearest2Nums(it, y) } }.sum()
``````

As in part 1, we read the lines and add a border of dots on all sides.

We define a function `nearest2Nums(x,y)` that for a given location check if any neighbours are digits (line 6). Then at line 7 we find the start location in the current row for the number that contains that digit and return the `[x,y]` location for that number.

At line 8 we sort these coordinates and eliminate duplicates (since one number might have multiple digits neighbouring that ‘*’) and then filter solutions where there are exactly 2 numbers. Since we have wrapped the result in `[` and `]` we actually have a list of a list of coordinate pairs and so the filter will return either this list (if it has 2 elements) or null if the list is filtered out. The `[0]` unwraps the resulting list back into a simple list of coordinate pairs.

At line 9, if the `nums` result was non-null, we map the coordinates into the numbers at those locations by finding the ending digit, grabbing the substring and converting to an `int`.

Line 10 turns the 2 element list into a list of a list of 2 elements so we can multiply the two numbers together.

Line 11 returns `null` if the location did not have exactly two neighbouring numbers.

Line 14 searches all grid locations for ‘*’ and uses the `nearest2Nums()` function to return null or the product of the 2 numbers (if they exist). By using `flatMap()` we ignore `null` values and can then invoke `sum()` to sum everything together.