Advent of Code 2024

Hello all, AoC is upon us again :slight_smile: I’ve chosen Prolog again this year, and now that I have a bit of experience with it, it is a delight to be tinkering again.

I’m keeping my solutions here, with a special effort this year to write code using declarative idioms unique to the Prolog approach.

Please share your thoughts and solutions!

Here are the first couple days:

Day 1

This uses re_foldl/6 and a nth0/3 index matching trick I saw @jan do once to solve both parts concisely.

acc(_{0:_, l1:X, l2:Y}, Xs-Ys, [X|Xs]-[Y|Ys]).

solve(In, Part1, Part2) :-
    read_file_to_string(In, S, []),
    re_foldl(acc, "(?<l1_I>\\d+) +(?<l2_I>\\d+)", S, []-[], Xs_-Ys_, []),
    maplist(msort, [Xs_,Ys_], [Xs,Ys]),
    aggregate_all(sum(abs(X-Y)), (nth0(Idx,Xs,X), nth0(Idx,Ys,Y)), Part1),
    aggregate_all(sum(A*C), (member(A,Xs), aggregate_all(count, member(A,Ys), C)), Part2).

Day 2

This uses the magical append/3 and select/3 predicates to concisely and declaratively implement the safety tests.

safe(Xs) :-
    Xs \= [],
    \+ (append(_, [A,B|_], Xs), A>=B, append(_, [C,D|_], Xs), D>=C),
    \+ (append(_, [A,B|_], Xs), (abs(A-B) =:= 0; abs(A-B) > 3)).

mod_then_safe(Xs) :- member(X, Xs), select(X, Xs, Ys), safe(Ys), !.

acc(_{0:_,n:X}, Xs, [X|Xs]).

nums(S,Xs) :- re_foldl(acc, "(?<n_I>\\d+)", S, [], Xs, []).

solve(In, Part1, Part2) :-
    read_file_to_string(In, S, []),
    split_string(S, "\n", "", Ss),
    maplist(nums, Ss, Xs),
    aggregate_all(count, (member(X, Xs), safe(X)), Part1),
    aggregate_all(count, (member(X, Xs), mod_then_safe(X)), Part2).
3 Likes

Awesome, compact code ! Thanks so much !
I keep learning from your work, just didn’t know the existence of re_foldl/6.

Day3 is pretty easy, here is my solution, based on standard DCG. Use of regex seems problematic due to the recursive formulation.

:- module(day03,
          [part1/2, part2/2]).

:- use_module(library(dcg/basics)).

part1(Kind,Sum) :-
    phrase_from_file(multiplications(Ms),Kind),
    sumlist(Ms,Sum).

part2(Kind,Sum) :-
    phrase_from_file(multiplications(Ms,1),Kind),
    sumlist(Ms,Sum).

multiplications([]) --> [].
multiplications([M|Ms]) -->
    multiplication(M),
    multiplications(Ms).
multiplications(Ms) --> [_], multiplications(Ms).

multiplication(M) -->
    "mul(", operand(A), ",", operand(B), ")",
    {M is A*B}.

operand(V) --> integer(V).
operand(V) --> multiplication(V).

on_off(1) --> "do()".
on_off(0) --> "don't()".

multiplications([],_On) --> [].
multiplications([M|Ms],On) -->
    multiplication(M,On),
    multiplications(Ms,On).
multiplications(Ms,_On) -->
    on_off(OnNext),
    multiplications(Ms,OnNext).
multiplications(Ms,On) -->
    [_],
    multiplications(Ms,On).

multiplication(M,On) --> multiplication(M1), {M is On * M1}.

2 Likes

Here is one way to make use of re_foldl/6 for Day 03:

mul_do_acc(_{0:_,2:_,op:"mul",x:X,y:Y}, A-V0-V1, A-V2-V3) :-
    V2 is V0+(X*Y), V3 is V1+A*(X*Y).
mul_do_acc(_{0:_, op:"do"}, _-V0-V1, 1-V0-V1).
mul_do_acc(_{0:_, op:"don't"}, _-V0-V1, 0-V0-V1).

solve(In, Part1, Part2) :-
    Regex = "(?<op>mul|do|don't)\\(((?<x_I>\\d+),(?<y_I>\\d+))?\\)",    
    read_file_to_string(In, S, []),
    re_foldl(mul_do_acc, Regex, S, 1-0-0, _-Part1-Part2, []).
1 Like

Maybe I unnecessarily complicated my DCG, accounting for recursion in mul(A,B). Now will test your code with an hand made input…

I used DCGs extensively for 2023, but to be honest I found regex much easier for these simple inputs. Pattern matching against groups is a very nice capability. You see above, I was able to capture many things with the regex, and then treat them separately in the mul_do_acc/3 predicate. Its so convenient.

Just tried this input: xmul(2,mul(3,4)).
Your code yields 12, mine yields 24.
Seems my reading of specs was useless complicated…

Pretty impressive that you can handle recursion in such short code! In this case, I think the input is supposed to just be corrupted code, so there is no recursive evaluation implied.