Well, it’s kind of cheating if I need to hack the programming language to do the Advent of Code assignment in that programming language, but I’d been meaning to implement integer comparison (<, ≤, ≥, >) for weeks, and it was absolutely essential here. (I’d previously gotten by on calculating the required less-than relation as a table.)

Part 1

The whole solution, sans parsing, is basically two rules. id Id Dest is N meant that the seed id Id, when mapped to the domain Dest, had domain-specific id N. The first rule handles the case where there is a specified remap, and the second rule provides the identity remap as a backup.

**id Id Dest is (plus DestStart (minus N SourceStart)) :-
   id Id Source is N,
   remap Source Dest SourceStart DestStart Extent,
   SourceStart <= N, N < plus SourceStart Extent.

id Id Dest is { N? } :-
   id Id Source is N,
   remap Source Dest _ _ _.**

Here’s the program on dusa.rocks%20%3A-%0A%20%20%20id%20Id%20Source%20is%20N%2C%0A%20%20%20remap%20Source%20Dest%20SourceStart%20DestStart%20Extent%2C%0A%20%20%20SourceStart%20%3C%3D%20N%2C%20N%20%3C%20plus%20SourceStart%20Extent.%0A%0Aid%20Id%20Dest%20is%20%7B%20N%3F%20%7D%20%3A-%0A%20%20%20id%20Id%20Source%20is%20N%2C%0A%20%20%20remap%20Source%20Dest%20_%20_%20_.%0A%0Agoal%20N%20%3A-%20id%20_%20%22location%22%20is%20N.%0A%0A%23%20Buncha%20facts%20representing%20the%20sample%20solution%0A%0Afield%20ref35%200%20is%2056.%0Afield%20ref35%201%20is%2093.%0Afield%20ref35%202%20is%204.%0Afield%20ref34%200%20is%2060.%0Afield%20ref34%201%20is%2056.%0Afield%20ref34%202%20is%2037.%0Afield%20ref33%200%20is%20ref34.%0Afield%20ref33%201%20is%20ref35.%0Afield%20ref32%20%22from%22%20is%20%22humidity%22.%0Afield%20ref32%20%22to%22%20is%20%22location%22.%0Afield%20ref32%20%22data%22%20is%20ref33.%0Afield%20ref31%200%20is%201.%0Afield%20ref31%201%20is%200.%0Afield%20ref31%202%20is%2069.%0Afield%20ref30%200%20is%200.%0Afield%20ref30%201%20is%2069.%0Afield%20ref30%202%20is%201.%0Afield%20ref29%200%20is%20ref30.%0Afield%20ref29%201%20is%20ref31.%0Afield%20ref28%20%22from%22%20is%20%22temperature%22.%0Afield%20ref28%20%22to%22%20is%20%22humidity%22.%0Afield%20ref28%20%22data%22%20is%20ref29.%0Afield%20ref27%200%20is%2068.%0Afield%20ref27%201%20is%2064.%0Afield%20ref27%202%20is%2013.%0Afield%20ref26%200%20is%2081.%0Afield%20ref26%201%20is%2045.%0Afield%20ref26%202%20is%2019.%0Afield%20ref25%200%20is%2045.%0Afield%20ref25%201%20is%2077.%0Afield%20ref25%202%20is%2023.%0Afield%20ref24%200%20is%20ref25.%0Afield%20ref24%201%20is%20ref26.%0Afield%20ref24%202%20is%20ref27.%0Afield%20ref23%20%22from%22%20is%20%22light%22.%0Afield%20ref23%20%22to%22%20is%20%22temperature%22.%0Afield%20ref23%20%22data%22%20is%20ref24.%0Afield%20ref22%200%20is%2018.%0Afield%20ref22%201%20is%2025.%0Afield%20ref22%202%20is%2070.%0Afield%20ref21%200%20is%2088.%0Afield%20ref21%201%20is%2018.%0Afield%20ref21%202%20is%207.%0Afield%20ref20%200%20is%20ref21.%0Afield%20ref20%201%20is%20ref22.%0Afield%20ref19%20%22from%22%20is%20%22water%22.%0Afield%20ref19%20%22to%22%20is%20%22light%22.%0Afield%20ref19%20%22data%22%20is%20ref20.%0Afield%20ref18%200%20is%2057.%0Afield%20ref18%201%20is%207.%0Afield%20ref18%202%20is%204.%0Afield%20ref17%200%20is%2042.%0Afield%20ref17%201%20is%200.%0Afield%20ref17%202%20is%207.%0Afield%20ref16%200%20is%200.%0Afield%20ref16%201%20is%2011.%0Afield%20ref16%202%20is%2042.%0Afield%20ref15%200%20is%2049.%0Afield%20ref15%201%20is%2053.%0Afield%20ref15%202%20is%208.%0Afield%20ref14%200%20is%20ref15.%0Afield%20ref14%201%20is%20ref16.%0Afield%20ref14%202%20is%20ref17.%0Afield%20ref14%203%20is%20ref18.%0Afield%20ref13%20%22from%22%20is%20%22fertilizer%22.%0Afield%20ref13%20%22to%22%20is%20%22water%22.%0Afield%20ref13%20%22data%22%20is%20ref14.%0Afield%20ref12%200%20is%2039.%0Afield%20ref12%201%20is%200.%0Afield%20ref12%202%20is%2015.%0Afield%20ref11%200%20is%2037.%0Afield%20ref11%201%20is%2052.%0Afield%20ref11%202%20is%202.%0Afield%20ref10%200%20is%200.%0Afield%20ref10%201%20is%2015.%0Afield%20ref10%202%20is%2037.%0Afield%20ref9%200%20is%20ref10.%0Afield%20ref9%201%20is%20ref11.%0Afield%20ref9%202%20is%20ref12.%0Afield%20ref8%20%22from%22%20is%20%22soil%22.%0Afield%20ref8%20%22to%22%20is%20%22fertilizer%22.%0Afield%20ref8%20%22data%22%20is%20ref9.%0Afield%20ref7%200%20is%2052.%0Afield%20ref7%201%20is%2050.%0Afield%20ref7%202%20is%2048.%0Afield%20ref6%200%20is%2050.%0Afield%20ref6%201%20is%2098.%0Afield%20ref6%202%20is%202.%0Afield%20ref5%200%20is%20ref6.%0Afield%20ref5%201%20is%20ref7.%0Afield%20ref4%20%22from%22%20is%20%22seed%22.%0Afield%20ref4%20%22to%22%20is%20%22soil%22.%0Afield%20ref4%20%22data%22%20is%20ref5.%0Afield%20ref3%200%20is%20ref4.%0Afield%20ref3%201%20is%20ref8.%0Afield%20ref3%202%20is%20ref13.%0Afield%20ref3%203%20is%20ref19.%0Afield%20ref3%204%20is%20ref23.%0Afield%20ref3%205%20is%20ref28.%0Afield%20ref3%206%20is%20ref32.%0Afield%20ref2%200%20is%2079.%0Afield%20ref2%201%20is%2014.%0Afield%20ref2%202%20is%2055.%0Afield%20ref2%203%20is%2013.%0Afield%20ref1%20%22seeds%22%20is%20ref2.%0Afield%20ref1%20%22maps%22%20is%20ref3.%0A)

Here’s the program on StackBlitz

Part 2

Here’s the program on dusa.rocks%20are%20all%20seeds%0A%0AidRange%20%22seed%22%20Start%20(plus%20Start%20Extent)%20%3A-%0A%20%20%20seedList%20N%20is%20start%2C%0A%20%20%20field%20_%20%22seeds%22%20is%20SeedList%2C%0A%20%20%20field%20SeedList%20N%20is%20Start%2C%0A%20%20%20field%20SeedList%20(plus%20N%201)%20is%20Extent.%0A%0A%23%20Split%20an%20idRange%20if%20it%20falls%20across%20a%20boundary%20in%20its%20domain%0AidRange%20Domain%20Start%20Mid%20%3A-%0A%20%20%20%20%20idRange%20Domain%20Start%20End%2C%0A%20%20%20%20%20after%20S%20Mid%20is%20_%2C%0A%20%20%20%20%20Start%20%3C%20Mid%2C%20Mid%20%3C%20End.%0AidRange%20Domain%20Mid%20End%20%3A-%0A%20%20%20%20%20idRange%20Domain%20Start%20End%2C%0A%20%20%20%20%20after%20Domain%20Mid%20is%20_%2C%0A%20%20%20%20%20Start%20%3C%20Mid%2C%20Mid%20%3C%20End.%0A%0A%23%20Transfer%20idRanges%20to%20the%20next%20level%20if%20they%20fall%20in%20a%20single%20domain%0AidRange%20Dest%20(plus%20A%20Delta)%20(plus%20B%20Delta)%20%3A-%0A%20%20%20%20%20idRange%20Source%20A%20B%2C%0A%20%20%20%20%20remap%20Source%20Dest%20StartA%20StartB%20is%20(pair%20DestA%20DestB)%2C%0A%20%20%20%20%20StartA%20%3C%3D%20A%2C%20B%20%3C%3D%20StartB%2C%0A%20%20%20%20%20Delta%20%3D%3D%20minus%20DestA%20StartA.%0A%20%20%20%20%20%0Agoal%20N%20%3A-%20idRange%20%22location%22%20N%20_.%0A%0A%23%20Buncha%20facts%20representing%20the%20sample%20solution%0A%0Afield%20ref35%200%20is%2056.%0Afield%20ref35%201%20is%2093.%0Afield%20ref35%202%20is%204.%0Afield%20ref34%200%20is%2060.%0Afield%20ref34%201%20is%2056.%0Afield%20ref34%202%20is%2037.%0Afield%20ref33%200%20is%20ref34.%0Afield%20ref33%201%20is%20ref35.%0Afield%20ref32%20%22from%22%20is%20%22humidity%22.%0Afield%20ref32%20%22to%22%20is%20%22location%22.%0Afield%20ref32%20%22data%22%20is%20ref33.%0Afield%20ref31%200%20is%201.%0Afield%20ref31%201%20is%200.%0Afield%20ref31%202%20is%2069.%0Afield%20ref30%200%20is%200.%0Afield%20ref30%201%20is%2069.%0Afield%20ref30%202%20is%201.%0Afield%20ref29%200%20is%20ref30.%0Afield%20ref29%201%20is%20ref31.%0Afield%20ref28%20%22from%22%20is%20%22temperature%22.%0Afield%20ref28%20%22to%22%20is%20%22humidity%22.%0Afield%20ref28%20%22data%22%20is%20ref29.%0Afield%20ref27%200%20is%2068.%0Afield%20ref27%201%20is%2064.%0Afield%20ref27%202%20is%2013.%0Afield%20ref26%200%20is%2081.%0Afield%20ref26%201%20is%2045.%0Afield%20ref26%202%20is%2019.%0Afield%20ref25%200%20is%2045.%0Afield%20ref25%201%20is%2077.%0Afield%20ref25%202%20is%2023.%0Afield%20ref24%200%20is%20ref25.%0Afield%20ref24%201%20is%20ref26.%0Afield%20ref24%202%20is%20ref27.%0Afield%20ref23%20%22from%22%20is%20%22light%22.%0Afield%20ref23%20%22to%22%20is%20%22temperature%22.%0Afield%20ref23%20%22data%22%20is%20ref24.%0Afield%20ref22%200%20is%2018.%0Afield%20ref22%201%20is%2025.%0Afield%20ref22%202%20is%2070.%0Afield%20ref21%200%20is%2088.%0Afield%20ref21%201%20is%2018.%0Afield%20ref21%202%20is%207.%0Afield%20ref20%200%20is%20ref21.%0Afield%20ref20%201%20is%20ref22.%0Afield%20ref19%20%22from%22%20is%20%22water%22.%0Afield%20ref19%20%22to%22%20is%20%22light%22.%0Afield%20ref19%20%22data%22%20is%20ref20.%0Afield%20ref18%200%20is%2057.%0Afield%20ref18%201%20is%207.%0Afield%20ref18%202%20is%204.%0Afield%20ref17%200%20is%2042.%0Afield%20ref17%201%20is%200.%0Afield%20ref17%202%20is%207.%0Afield%20ref16%200%20is%200.%0Afield%20ref16%201%20is%2011.%0Afield%20ref16%202%20is%2042.%0Afield%20ref15%200%20is%2049.%0Afield%20ref15%201%20is%2053.%0Afield%20ref15%202%20is%208.%0Afield%20ref14%200%20is%20ref15.%0Afield%20ref14%201%20is%20ref16.%0Afield%20ref14%202%20is%20ref17.%0Afield%20ref14%203%20is%20ref18.%0Afield%20ref13%20%22from%22%20is%20%22fertilizer%22.%0Afield%20ref13%20%22to%22%20is%20%22water%22.%0Afield%20ref13%20%22data%22%20is%20ref14.%0Afield%20ref12%200%20is%2039.%0Afield%20ref12%201%20is%200.%0Afield%20ref12%202%20is%2015.%0Afield%20ref11%200%20is%2037.%0Afield%20ref11%201%20is%2052.%0Afield%20ref11%202%20is%202.%0Afield%20ref10%200%20is%200.%0Afield%20ref10%201%20is%2015.%0Afield%20ref10%202%20is%2037.%0Afield%20ref9%200%20is%20ref10.%0Afield%20ref9%201%20is%20ref11.%0Afield%20ref9%202%20is%20ref12.%0Afield%20ref8%20%22from%22%20is%20%22soil%22.%0Afield%20ref8%20%22to%22%20is%20%22fertilizer%22.%0Afield%20ref8%20%22data%22%20is%20ref9.%0Afield%20ref7%200%20is%2052.%0Afield%20ref7%201%20is%2050.%0Afield%20ref7%202%20is%2048.%0Afield%20ref6%200%20is%2050.%0Afield%20ref6%201%20is%2098.%0Afield%20ref6%202%20is%202.%0Afield%20ref5%200%20is%20ref6.%0Afield%20ref5%201%20is%20ref7.%0Afield%20ref4%20%22from%22%20is%20%22seed%22.%0Afield%20ref4%20%22to%22%20is%20%22soil%22.%0Afield%20ref4%20%22data%22%20is%20ref5.%0Afield%20ref3%200%20is%20ref4.%0Afield%20ref3%201%20is%20ref8.%0Afield%20ref3%202%20is%20ref13.%0Afield%20ref3%203%20is%20ref19.%0Afield%20ref3%204%20is%20ref23.%0Afield%20ref3%205%20is%20ref28.%0Afield%20ref3%206%20is%20ref32.%0Afield%20ref2%200%20is%2079.%0Afield%20ref2%201%20is%2014.%0Afield%20ref2%202%20is%2055.%0Afield%20ref2%203%20is%2013.%0Afield%20ref1%20%22seeds%22%20is%20ref2.%0Afield%20ref1%20%22maps%22%20is%20ref3.%0A)

Here’s the program on StackBlitz

This is sloooowwwww… it’s barely adequate as a solution at all.

I’m gonna work backwards through the solution:

The interesting bit

The algorithm worked on facts of the form idRange Domain Start End, which expressed that we cared about, in domain Domain, all the ids in the range [Start…End).

If a range of ids [A..B) falls entirely within one contiguous remap boundary, it’s possible to forward that range of ids as one unit to the next domain:

**idRange Dest (plus A Delta) (plus B Delta) :-
     idRange Source A B,
     remap Source Dest StartA StartB is (pair DestA DestB),
     StartA <= A, B <= StartB,
     Delta == minus DestA StartA.**

If a range falls across a remap boundary, split it into more ranges! Eventually all the ranges in a domain will be within a remap boundary. These two rules have the same premises, I really should make it so a rule can have multiple, conjunctive, conclusions to avoid this redundancy.

**idRange Domain Start Mid :-
     idRange Domain Start End,
     after Domain Mid is _,
     Start < Mid, Mid < End.
idRange Domain Mid End :-
     idRange Domain Start End,
     after Domain Mid is _,
     Start < Mid, Mid < End.**

This is a little wasteful… really I’d like it if at each level I re-merged contiguous ranges… but there were only on the order of hundereds of total ranges output when I took this approach, so it was more than good enough for this problem!

Deriving all the remap ranges