This was an interesting puzzle with a nice solution in Dusa, but I don’t find myself too inspired to say a lot about it or writing up the whole thing for this one.

The solution is longer than it needs to be: I calculate the width of the grid in a silly way, really I should have just fed the width in as a fact, but instead I use the same possibility trick I used with a less-than relation and both of the Day 2 solutions: each column could be the width, but if a column is smaller than another column it’s definitely not.

Core of Part 1 solution

I added an commented translation for what the core logic of the Part 1 solution is doing. To me it seems quite pleasant! I picked up a couple of the techniques I used from Adam Smith’s Map Generation Speedrun for Answer Set Programming.

**delta 1.
delta 0.
delta -1.

*# The number N has an adjacent symbol when*
numWithAdjacentSymbol Line Start is N :- 
   *# There's a number at (Line, Start)* 
   numAt Line Start is N,
   *# And that number extends to (Line, Col)*
   numPrefixAt Line Start Col is _,
   *# And for (AdjCol, AdjLine) within 1 square of (Line, Col)*
   delta Dx, delta Dy,
   AdjCol == plus Dx Col,
   AdjLine == plus Dy Line,
   *# There's a symbol at (AdjLine, AdjCol)*
   charAt AdjLine AdjCol is Ch,
   isSymbol Ch is true.**

For both Part 1 and Part 2, I decided not to bother trying to do the summing-up part in Dusa — I think it would have been possible but a bit silly. Instead I queried all the numWithAdjacentSymbol facts and added up their values for the solution.

Here’s the program on dusa.rocks

Here’s the program on StackBlitz

Core of Part 2 solution

This is a really nice solution, because I can extend numWithAdjacentSymbol just a bit to track both the line and number that has an adjacent character, but also the location of that adjacent character and what the adjacent character is.

**numWithAdjacentSymbol Line Start AdjLine AdjCol is (pair N Ch) :-**

The thing that Datalog languages do effortlessly that’s a pain in the ass for other languages (IMO) is re-associate that information: it’s a four-liner to find all the gears adjacent to two distinct numbers.

**gear Line Col is (times N1 N2) :-
   numWithAdjacentSymbol Line1 Col1 Line Col is (pair N1 "*"),
   numWithAdjacentSymbol Line2 Col2 Line Col is (pair N2 "*"),
   pair Line1 Col1 != pair Line2 Col2.**

(This solution will also call a * adjacent to three symbols a gear, incorrectly. My actual solution defends against that, but I didn’t check whether that defensiveness was necessary.)

Here’s the program on dusa.rocks%20%3A-%0A%20%20%20%20charAt%20Line%20Next%20is%20Digit%2C%0A%20%20%20%20digit%20Digit%20is%20Ones%2C%0A%20%20%20%20numPrefixAt%20Line%20Start%20(minus%20Next%201)%20is%20Tens.%0A%0AnumAt%20Line%20Col%20is%20N%20%3A-%0A%20%20%20%20numPrefixAt%20Line%20Col%20End%20is%20N%2C%0A%20%20%20%20posEndsNum%20Line%20End.%0A%0A%23%20Find%20numbers%20that%20have%20adjacent%20symbols%0A%0Adelta%201.%0Adelta%200.%0Adelta%20-1.%0AnumWithAdjacentSymbol%20Line%20Start%20AdjLine%20AdjCol%20is%20(pair%20N%20Ch)%20%3A-%20%0A%20%20%20%23%20There's%20a%20number%20at%20(Line%2C%20Start)%20%0A%20%20%20numAt%20Line%20Start%20is%20N%2C%0A%20%20%20%23%20And%20that%20number%20extends%20to%20(Line%2C%20Col)%0A%20%20%20numPrefixAt%20Line%20Start%20Col%20is%20_%2C%0A%20%20%20%23%20And%20for%20(AdjCol%2C%20AdjLine)%20within%201%20square%20of%20(Line%2C%20Col)%0A%20%20%20delta%20Dx%2C%20delta%20Dy%2C%0A%20%20%20AdjCol%20%3D%3D%20plus%20Dx%20Col%2C%0A%20%20%20AdjLine%20%3D%3D%20plus%20Dy%20Line%2C%0A%20%20%20%23%20There's%20a%20symbol%20at%20(AdjLine%2C%20AdjCol)%0A%20%20%20charAt%20AdjLine%20AdjCol%20is%20Ch%2C%0A%20%20%20isSymbol%20Ch%20is%20true.%0A%0ApotentialGearNeighbors%20Line%20Col%20is%20%7B%20just%20(times%20N1%20N2)%3F%20%7D%20%3A-%0A%20%20%20numWithAdjacentSymbol%20Line1%20Col1%20Line%20Col%20is%20(pair%20N1%20%22*%22)%2C%0A%20%20%20numWithAdjacentSymbol%20Line2%20Col2%20Line%20Col%20is%20(pair%20N2%20%22*%22)%2C%0A%20%20%20pair%20Line1%20Col1%20!%3D%20pair%20Line2%20Col2.%0A%0ApotentialGearNeighbors%20Line%20Col%20is%20nothing%20%3A-%0A%20%20%20numWithAdjacentSymbol%20Line1%20Col1%20Line%20Col%20is%20(pair%20_%20%22*%22)%2C%0A%20%20%20numWithAdjacentSymbol%20Line2%20Col2%20Line%20Col%20is%20(pair%20_%20%22*%22)%2C%0A%20%20%20numWithAdjacentSymbol%20Line3%20Col3%20Line%20Col%20is%20(pair%20_%20%22*%22)%2C%0A%20%20%20pair%20Line1%20Col1%20!%3D%20pair%20Line2%20Col2%2C%0A%20%20%20pair%20Line2%20Col2%20!%3D%20pair%20Line3%20Col3%2C%0A%20%20%20pair%20Line1%20Col1%20!%3D%20pair%20Line3%20Col3.%0A%0Agear%20Line%20Col%20is%20N%20%3A-%0A%20%20%20potentialGearNeighbors%20Line%20Col%20is%20just%20N.%0A%0A%23%20Buncha%20facts%20representing%20the%20sample%20solution%0A%0AcharAt%200%200%20is%20%224%22.%0AcharAt%200%201%20is%20%226%22.%0AcharAt%200%202%20is%20%227%22.%0AcharAt%200%203%20is%20%22.%22.%0AcharAt%200%204%20is%20%22.%22.%0AcharAt%200%205%20is%20%221%22.%0AcharAt%200%206%20is%20%221%22.%0AcharAt%200%207%20is%20%224%22.%0AcharAt%200%208%20is%20%22.%22.%0AcharAt%200%209%20is%20%22.%22.%0AcharAt%201%200%20is%20%22.%22.%0AcharAt%201%201%20is%20%22.%22.%0AcharAt%201%202%20is%20%22.%22.%0AcharAt%201%203%20is%20%22*%22.%0AcharAt%201%204%20is%20%22.%22.%0AcharAt%201%205%20is%20%22.%22.%0AcharAt%201%206%20is%20%22.%22.%0AcharAt%201%207%20is%20%22.%22.%0AcharAt%201%208%20is%20%22.%22.%0AcharAt%201%209%20is%20%22.%22.%0AcharAt%202%200%20is%20%22.%22.%0AcharAt%202%201%20is%20%22.%22.%0AcharAt%202%202%20is%20%223%22.%0AcharAt%202%203%20is%20%225%22.%0AcharAt%202%204%20is%20%22.%22.%0AcharAt%202%205%20is%20%22.%22.%0AcharAt%202%206%20is%20%226%22.%0AcharAt%202%207%20is%20%223%22.%0AcharAt%202%208%20is%20%223%22.%0AcharAt%202%209%20is%20%22.%22.%0AcharAt%203%200%20is%20%22.%22.%0AcharAt%203%201%20is%20%22.%22.%0AcharAt%203%202%20is%20%22.%22.%0AcharAt%203%203%20is%20%22.%22.%0AcharAt%203%204%20is%20%22.%22.%0AcharAt%203%205%20is%20%22.%22.%0AcharAt%203%206%20is%20%22%23%22.%0AcharAt%203%207%20is%20%22.%22.%0AcharAt%203%208%20is%20%22.%22.%0AcharAt%203%209%20is%20%22.%22.%0AcharAt%204%200%20is%20%226%22.%0AcharAt%204%201%20is%20%221%22.%0AcharAt%204%202%20is%20%227%22.%0AcharAt%204%203%20is%20%22*%22.%0AcharAt%204%204%20is%20%22.%22.%0AcharAt%204%205%20is%20%22.%22.%0AcharAt%204%206%20is%20%22.%22.%0AcharAt%204%207%20is%20%22.%22.%0AcharAt%204%208%20is%20%22.%22.%0AcharAt%204%209%20is%20%22.%22.%0AcharAt%205%200%20is%20%22.%22.%0AcharAt%205%201%20is%20%22.%22.%0AcharAt%205%202%20is%20%22.%22.%0AcharAt%205%203%20is%20%22.%22.%0AcharAt%205%204%20is%20%22.%22.%0AcharAt%205%205%20is%20%22%2B%22.%0AcharAt%205%206%20is%20%22.%22.%0AcharAt%205%207%20is%20%225%22.%0AcharAt%205%208%20is%20%228%22.%0AcharAt%205%209%20is%20%22.%22.%0AcharAt%206%200%20is%20%22.%22.%0AcharAt%206%201%20is%20%22.%22.%0AcharAt%206%202%20is%20%225%22.%0AcharAt%206%203%20is%20%229%22.%0AcharAt%206%204%20is%20%222%22.%0AcharAt%206%205%20is%20%22.%22.%0AcharAt%206%206%20is%20%22.%22.%0AcharAt%206%207%20is%20%22.%22.%0AcharAt%206%208%20is%20%22.%22.%0AcharAt%206%209%20is%20%22.%22.%0AcharAt%207%200%20is%20%22.%22.%0AcharAt%207%201%20is%20%22.%22.%0AcharAt%207%202%20is%20%22.%22.%0AcharAt%207%203%20is%20%22.%22.%0AcharAt%207%204%20is%20%22.%22.%0AcharAt%207%205%20is%20%22.%22.%0AcharAt%207%206%20is%20%227%22.%0AcharAt%207%207%20is%20%225%22.%0AcharAt%207%208%20is%20%225%22.%0AcharAt%207%209%20is%20%22.%22.%0AcharAt%208%200%20is%20%22.%22.%0AcharAt%208%201%20is%20%22.%22.%0AcharAt%208%202%20is%20%22.%22.%0AcharAt%208%203%20is%20%22%24%22.%0AcharAt%208%204%20is%20%22.%22.%0AcharAt%208%205%20is%20%22*%22.%0AcharAt%208%206%20is%20%22.%22.%0AcharAt%208%207%20is%20%22.%22.%0AcharAt%208%208%20is%20%22.%22.%0AcharAt%208%209%20is%20%22.%22.%0AcharAt%209%200%20is%20%22.%22.%0AcharAt%209%201%20is%20%226%22.%0AcharAt%209%202%20is%20%226%22.%0AcharAt%209%203%20is%20%224%22.%0AcharAt%209%204%20is%20%22.%22.%0AcharAt%209%205%20is%20%225%22.%0AcharAt%209%206%20is%20%229%22.%0AcharAt%209%207%20is%20%228%22.%0AcharAt%209%208%20is%20%22.%22.%0AcharAt%209%209%20is%20%22.%22.%0A)

Here’s the program on StackBlitz