Advent of Code 2025

No, that’s not it: talking of Day 1, the data is finite lists of rotations.

Please choose a more relevant thread, or create one?

Discussing solutions is of course relevant…

If you want to see the output of the program, you must perform some IO, and so the program will not reversible.

We can isolate pure logical parts of a program (that we care to keep logical) and call those predicates from impure code.

But those are separate things. Slightly changing variable names:

A prob_1(?Rotations, ?Count) is “relational” over the whole domain of pairs rotations-count. Then we can e.g. call it as parse_1(RawData, Rotations), prob_1(Rotations, Count), which uses just one of the possible invocation patters.

The cost of that “relationality” is inefficiency re the specific given problem, the advantage of course is that now prob_1 works in all directions, so we can ask many more kinds of questions to it. But I am still thinking whether we can do better than that: keeping the “relationality”, as in arguments in all directions, still using something like “iterators”…

@brebs I’m sorry for high jacking your thread, but I can’t resist ^^

I believe this is equivalent to my foldl(rotate, Pairs, 50-0, _-Count), where Pairs = Rotations.
This can be run in all direction. And with additional labeling, you can enumerate every possible rotations for a given start and end state-count pair.

And I would add, a specific problem in a specific mode.
I often view a relation as having a kind of “performance” budget that you can spend over one specific mode only (functional) or spread on multiple mode (logic).

In the context of logic programming, I associate iterators to laziness/constraints.
If you see logic program as producing a stream of solutions, we can avoid thrashing using laziness.
In imperative programming, iterators are used to model a list of thing without actually instantiating the full list.
In logic programming, laziness/constraints allows us to model a list/group/family of solutions without actually iterating through them.

AFAICT, iterators/generators are essentially a functional notion, indeed coming from the functional languages, together with, more generally, higher-order functions (functions that take or return functions, aka functions as first-class): in imperative programming we just do loops…

Ah, OK: indeed I had missed that. Let me work more on it.

Meanwhile, thank you and everybody for the feedback, appreciated.

I made an animation of day 8 of the AoC.

2 Likes

I found the problem description of day 8 part 2 to be very confusing. Had to re-read it many times.

It talks about the first connection… then in the next paragraph talks about the last 2 junction boxes - but that’s really the same thing!

Solved using assertz/1 for automatic indexing :grinning_face:

If anyone is interested in a clpfd solution for day 3, I have managed to write the following as a combination of constraints + careful labeling:

Spoilers
:- use_module(library(dcg/basics)).
:- use_module(library(dcg/high_order)).
:- use_module(library(clpfd)).

bank([H | Bank]) -->
    battery(H), sequence(battery, Bank), eol.
banks(Banks) -->
    sequence(bank, Banks).

battery(Jolt) -->
    digit(D), {number_chars(Jolt, [D])}.

main(N, Value) :-
    phrase_from_file(banks(Banks), "input-3.txt"),
    maplist(joltage(N), Banks, Pairs),
    maplist(maplist(label_pair), Pairs),
    maplist(concat, Pairs, Values),
    sum_list(Values, Value).

joltage(N, Bank, Selected) :-
    length(Bank, M),
    numlist(1, M, Indices),
    transpose([Bank, Indices], Tuples),
    length(Batteries, N),
    length(SelectedIndices, N),
    chain(SelectedIndices, #<),
    transpose([Batteries, SelectedIndices], Selected),
    tuples_in(Selected, Tuples).

label_pair([V, I]) :-
    labeling([max(V), ff], [V, I]).

concat(Pairs, Value) :-
    length(Pairs, N),
    numlist(1, N, L),
    reverse(L, L1),
    maplist([I, [V, _], R]>>(R is V*10^(I-1)), L1, Pairs, Values),
    sum_list(Values, Value).

So basically, tuples_in to label pairs of [Value-Index] and enforce a chain on the list of selected indices.

It’s not as fast as I would like, 14 seconds for part 2. So if anyone has a good suggestion with better constraints or labeling strategy, please comment :slight_smile:

I made a visualization of Day12 : @frank-schwidom.bsky.social on Bluesky

Solutions in GNU Prolog: https://adamcrussell.livejournal.com/tag/prolog/

Solutions in Picat:

Interesting alternative solution method for day 10 part 2: Bifurcate

1 Like

As seen in Day 10 - the likes of clpfd and clpBNR suffer enormous slowdown with non-trivial simultaneous equations.

2 efficient methods to use instead:

Thank you for the tip, but would it be possible to explain why ?
Is this related to the fact that clpfd and clpBNR uses constraints propagation ?

I haven’t done day 10 yet, but I don’t really see the simultaneous equations in day 3.
Could somebody explain it ?

One issue of CLP(FD) a la Markus Triska and mutual equations
is the presence of domain reduction. It can happen that the solver
makes a very small domain reduction of a variable X, which then

triggers a very small domain reduction of variable Y, which then
triggers again variable X, again variable Y and so on. If the initial
domain was large this iterative algorithm can take quite some

CPU cycles, slowing down the solver. Easiest example:

:- use_module(library(clpfd)).

test(N) :- 
    L = [X,Y],
    L ins 0..N,
    X #> Y,
    X #< Y.

Results from SWI Tinker:

?- time(test(1000)).
% 46,056 inferences, 0.007 CPU in 0.007 seconds 
(100% CPU, 6579233 Lips)
false. 

?- time(test(10000)).
% 455,956 inferences, 0.235 CPU in 0.235 seconds
 (100% CPU, 1940237 Lips)
false. 
2 Likes

clpfd and clpBNR are based on general purpose constraint propagation that is usually too weak to produce the desired results. So, in addition, each provides a search mechanism (labeling or solve), again general purpose but rather brute force, using additional constraints to divide the problem recursively into sub-problems that local constraint propagation can solve. (Searching is also useful when more than one solution exists.)

This is generally effective but really doesn’t take advantage of any specific knowledge of the problem. For example, it can find the roots of any degree polynomial, but isn’t the most efficient way of finding the roots of a quadratic, where a known formula can be directly applied.

Linear systems are analogous; searching mechanisms can be used but there are well known techniques for solving these systems that are much more efficient than brute force search. For example, the CLP(BNR) Users Guide describes how to use Cramer’s Rule and arrays to solve linear systems of equations more efficiently. And the clpBNR_toolkit module provides predicates to find optima for linear systems which are just wrappers around library(simplex) calls. These are better alternatives to searching when the problem specification allows.

clpqr takes a different approach by building support for linear constraints into the constraint mechanism. But this doesn’t handle non-linear constraints other than by deferring until other constraints can be applied that may reduce them to linear constraints.

3 Likes

I think it’s possible - list all the possible orders of the digits, then use min/max or bb_inf/3 to find the best i.e. required solution. But I doubt it would be performant.

Instead, this is a more relational solution (than my original Day 3): Day 3 using clpq. Is mainly list processing.

Sorry, maybe I am still missing something but that is not it: foldl is not bidirectional, nor phrase is by the way: neither streaming nor bidirectional…

Indeed, I guess that is what I am looking for, a way to approach these problems that is both streaming and bidirectional: anyway, don’t mind too much, this is more of a side-quest of mine.

Strange, works on my side. Most predicates that are pure
are automatically bidirectional if they terminate. foldl/4 is pure ,
it does not use cut or if-then-else in its implementation:

foldl(Goal, List, V0, V) :-
   foldl_(List, Goal, V0, V).

foldl_([], _, V, V).
foldl_([H|T], Goal, V0, V) :-
   call(Goal, H, V0, V1),
   foldl_(T, Goal, V1, V).

But it is higher order, i.e. takes a closure argument. So
to have bidirectionality, the supplied closure needs to be
pure as well. One benefit of CLP(FD), besides that it sometimes

stumbles over mutual equations, is its ability to turn
arithmetic into something pure to some extend via delayed
execution. Take this definition and combine it with foldl/4:

:- use_module(library(clpfd)).

add(X,Y,Z) :- Z #= X+Y.

Now SWI-Tinker confirms a bidirectional accumulator sumlist:

?- foldl(add, [1,2,3], 0, X).
X = 6.

?- foldl(add, [1,2,3], X, 6).
X = 0.

This explains a little bit the popularity of CLP(FD) a la Markus
Triska, its greedy forward checking and/or domain reduction,
without even calling label/1 or labeling/2.

Oh well, it’s not documented as such, but it’s still not “streaming and bidirectional” in the full sense I meant: we need the whole list in input…

The devil is in the details: maybe I should simply open a separate discussion with a proper case.

Most of the higher order predicates have some flexibility
in the modes they accept, when they are pure. That the
foldl/4 mode is too strictly documented was noted by

LogicalCaptain around 4 years ago here:

Overbearing mode indicators in the manual
https://github.com/dtonhofer/prolog_notes/blob/45f15e13a093c7ea8da5f90f6af662592f7bd891/swipl_notes/about_foldl/README.md#overbearing-mode-indicators-in-the-manual

Another use case where foldl/4 would be used with
different mode than in the SWI documentation, are
difference lists. foldl/4 itself can be defined via DCG:

foldl(C, []) --> [].
foldl(C, [X|Y]) --> call(X), foldl(C, Y).

When you use it to generate text, the mode will be foldl(:Goal, +List, -V0, +V) , and not foldl(:Goal, +List, +V0, -V).
Here is a DCG defined closure used with foldl/4:

twice(X) --> [X, X].

?- foldl(twice, [a,b,c], X, []).
X = [a, a, b, b, c, c].

The above test case works in SWI Tinker with the default
foldl/4. So foldl/4 has indead some streaming genes, if we
are so free to associate DCG generated lists with streaming.

1 Like

The use you showed is not documented, and even less that predicate is “pure”.

LogicalCaptain around 4 years ago here: Overbearing mode indicators in the manual

That would be the opposite problem…

But it’s apparently pointless that I keep explaining.

Happy holidays.