Advent of Code 2025

Following on from Advent of Code 2024 , the Advent of Code 2025 has started :grinning_face:

It lasts for 12 days.

My solutions will be at advent-of-code/2025 at main · brebs-gh/advent-of-code · GitHub

Day 1 part 2 was a bit fiddly, getting the number of times that 0 is crossed over, without double-counting.

4 Likes

I enjoyed using Prolog last year. Same plan this year.

2 Likes

I’m doing it in Scryer Prolog this year: https://garklein.github.io/aoc25pl/

So far, it has been quite tricky, especially for a Prolog noob like me… today took many hours of head-scratching before I found a solution that runs in a reasonable amount of time (~10s).

Companion discussions: Advent of Code 2025 · mthom/scryer-prolog · Discussion #3184 · GitHub (I know this isn’t SWI Prolog, but the Prolog community is so small that I am curious to see others’ solutions, even in a different dialect of Prolog, and I hope you will enjoy my solutions as well)

4 Likes

For day 2: part 1 was fast in clpfd… then part 2 appeared, which was not really suitable for clpfd, so I switched the methods to list processing :grinning_face:

Thanks @brebs and everybody else for contributing to this thread.

I’ve uploaded the days so far at advent_of_code/swipl/2025 at main · CapelliC/advent_of_code · GitHub .

There are also my solutions in Dart, that i started studying recently because I’m commiting to Flutter.

And I hope I’ll find the time to upload some selected Picat as well (maybe just in case the language really helps).

1 Like

Here’s some Prolog solutions to Day 1, which are all different:

Day 1 is a maths puzzle. Is maths relational? It can be, e.g.:

In clpfd:

?- A #= B + 1.
B+1#=A.

In clpBNR:

?- { A == B + 1 }.
A::real(-1.0Inf, 1.0Inf),
B::real(-1.0Inf, 1.0Inf).

None of the above 4 solutions to Day 1 start with numlist(0, 99, L) and then treat the dial as a sliding window over that list, with append. Which sounds like a relational approach, although probably inefficient. Because maths is intuitively the easy method :slightly_smiling_face:

I expect in the 12 days there will be better examples…

1 Like

Tongue-in-cheek:

All maths is relational. Expressing A = B + 1 is purely (in Peano arithmetic, for natural numbers):

plus1(0, s(0)).
plus1(s(P), s(s(P)).

General case:

?- plus1(P, P1).
P = 0,
P1 = s(0) ;
P = s(_A),
P1 = s(s(_A)).

This works in Prolog when neither A nor B are defined, is that not excellent? :grinning_face:

So? What’s wrong with a Prolog predicate being basically a function, when that is all that is needed at the time?

Predicates can have the potential to be usable in all directions, but for these Advent challenges we only care about the question space, i.e. there’s exactly 1 question per gold star to be answered.

Anyway, on to day 6, which looks at first glance like a good use of DCG and transpose/2 …

I chose to document my AoC progress here: @frank-schwidom.bsky.social on Bluesky

And Just in case someone is interested in my AoC code: AdventOfCode/2025 at master · schwidom/AdventOfCode · GitHub .

1 Like

Day 7 was funny !

I was struggling to get the small data solution of part 1, with tabling.

Then I quickly wrote part 1 in Dart, and once unlocked part 2, LOL, the answer for the small data was the same I got (wrongly) from tabled part 1.

So, tried with the large data, and got the puzzle solved !

1 Like

Here is a solution for the day 1 part 2 using clpfd + foldl:

  • rotate(Direction-Distance, State-Count, NewState-NewCount) is fully pure, and relates the state and count of passing by zero before and after applying a rotation.
    • the implementation is not fully deterministic for performance reason, where I pattern match on the direction to choose the correct formula
    • this means you can apply a rotation with arguments in any modes

Is this Prolog-ish/relational enough for you ? :slight_smile:
Or is your “functional” a qualifier for the successive application of rotation, i.e. foldl ?

Spoiler implementation
lines(Pairs) -->
    sequence(line_, Pairs).
line_(Direction-Distance) -->
    direction(Direction), number(Distance), eol.
direction(-1) --> "L".
direction(1) --> "R".

rotate(Direction-Distance, State-Count, NewState-NewCount) :-
    Rotation #= Distance // 100,
    Rest #= Distance mod 100,
    OverFlowState #= State + Direction*Rest,
    NewState #= OverFlowState mod 100,
    pass_by_zero(Direction, State, OverFlowState, PassByZero),
    NewCount #= Count + Rotation + PassByZero.

% (       (State #> 0 #/\ OverFlowState #=< 0)
%     #\/ OverFlowState #>= 100
% ) #<==> PassByZero,
pass_by_zero(-1, State, OverFlowState, PassByZero) :-
    % left rotation
    % (State #> 0 #/\ OverFlowState #=< 0) #<==> PassByZero
    StatePositive #= (State + 99) // 100,
    OverFlowNonPositive #= (-OverFlowState + 100) // 100,
    PassByZero #= StatePositive * OverFlowNonPositive.
pass_by_zero(1, _, OverFlowState, PassByZero) :-
    % right rotation
    % OverFlowState #>= 100 #<==> PassByZero
    PassByZero #= OverFlowState // 100.


main(Count) :-
    once(phrase_from_file(lines(Pairs), "input-1.txt")),
    foldl(rotate, Pairs, 50-0, _-Count),
    format("Count ~d~n", [Count]).
2 Likes

Hopefully of interest: https://stackoverflow.com/questions/8297574/difference-between-logic-programming-and-functional-programming

The Advent of Code questions:

  • Ask just 2 precise questions (part 1 and part 2) each, and don’t care about Prolog’s generality, i.e. potentially being capable of answering further questions about the problem space quite easily
  • After answering the questions, what seems to matter is:
    • Code elegance and shortness (although I would add readability to this list) - which partly favours more popular languages which have more code libraries available
    • Performance (milliseconds) - which favours languages which have had more effort put into their compilers

In the repos mentioned in this thread, I think there’s at least one elegant Prolog solution to each Advent question so far :grinning_face:

Well, I have only skimmed through the other solution but the specificity of mine is that it is logically pure, can be used in any direction/mode.
And concretely, for me, that is kind of the specificity of the logical programming paradigm:

Make your code work in any mode/direction

And that implies thinking about the broader relation that underpin the problem you are trying to solve.

If I could list the stereotypes for each paradigm:

  • imperative: give order to computer one after the other
  • functional: hm, mutation is bad, I don’t want to deal with state
  • logical: nice, but what if I could run that function in reverse ?

The topic of this thread, that I started, is “Advent of Code 2025”.

Please start your own thread, for whatever your question now is, or maybe check the Philosophy sections of Reddit :wink:

Back on topic - just starting Day 8 (I’m probably slipping behind, since these questions are not getting easier), and it’s screaming clpfd :grinning_face:

… or clpBNR.

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.

@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.

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