Advent of Code 2024

If you use library(yall), make sure it is loaded while compiling and make sure all meta predicates through with it is used are loaded. If you don’t, you get the interpreted version of the library that does a lot of copy_term/2.

It is a good idea to use listing/1 to verify all the yall calls are compiled. Alternatively, profile/1 will tell you.

In general clp(fd) will loose from native Prolog arithmetic unless there is a gain from its intelligent pruning of the search space. Once it starts doing something smart during the labeling it impresses.

3 Likes

Day8, with loading the data into a dynamic predicate

:- use_module(library(aggregate)).
:- dynamic antenna/2. % antenna(Freq, X-Y).
:- dynamic size/2. % size(W,H)

load_grid(_, _, []) :- !.
load_grid(_, Y, [10|Rest]) :- succ(Y, Y1), load_grid(0, Y1, Rest), !.
load_grid(X, Y, [A|Rest]) :- (dif(A,46) -> asserta(antenna(A,X-Y)); true), succ(X,X1), load_grid(X1, Y, Rest).

input :-
    retractall(antenna(_,_)),
    retractall(size(_,_)),
    read_file_to_codes("day8.txt", Codes, []),
    once(append([FirstLine, [10], _], Codes)),
    length(FirstLine, Width),
    length(Codes, TotalLen),
    Height is TotalLen / (Width+1),
    load_grid(0,0,Codes),
    asserta(size(Width,Height)).

antinode(X-Y) :- antenna(Freq, X1-Y1), antenna(Freq, X2-Y2), dif(X1-Y1, X2-Y2),
                 Dx is X2 - X1, Dy is Y2 - Y1,
                 ((X is X1 - Dx, Y is Y1 - Dy); (X is X2 + Dx, Y is Y2 + Dy)),
                 X >= 0, Y >= 0,
                 size(W,H),
                 X < W, Y < H.

slope(X1-Y1, X2-Y2, S) :- Dx is X2 - X1, Dy is Y2 - Y1, dif(Dx,0), S is Dy / Dx.
antinode2(X-Y) :- size(W,H), MaxX is W - 1, MaxY is H - 1,
                  between(0, MaxX, X), between(0, MaxY, Y),
                  antenna(Freq, A1), antenna(Freq, A2),
                  dif(A1, A2),
                  slope(X-Y, A1, S), slope(X-Y, A2, S).
antinode2(X-Y) :- antenna(_, X-Y).

solve(P1, P2) :-
    input,
    aggregate_all(count, A, antinode(A), P1),
    aggregate_all(count, A, antinode2(A), P2).

It feels dirty to use dynamic predicate, but the relations look nicer imo when you don’t need to pass in everything.

Day 08

Here it is. CLPFD makes it neat and concise.

:- use_module(library(clpfd)).

coo(Cols, Idx, X-Y) :- X #= Idx // Cols, Y #= mod(Idx,Cols).

solve(In, P1, P2) :-
    read_file_to_string(In, S, []), string_chars(S,Cs0),
    nth0(Cols, Cs0, '\n'), !, exclude(=('\n'), Cs0, Cs),
    length(Cs, N), M is N-1,
    findall(
	Out,
	( nth0(I1, Cs, C1), C1 \= '.', nth0(I2, Cs, C1), I1 < I2,
	  coo(Cols, I1, X1-Y1), coo(Cols, I2, X2-Y2),
	  Idx in 0..M, coo(Cols, Idx, X0-Y0),
	  (X1-X0)*(Y2-Y0) #= (X2-X0)*(Y1-Y0),
	  D1 #= abs(X0-X1) + abs(Y0-Y1),
	  D2 #= abs(X0-X2) + abs(Y0-Y2),
	  label([Idx]),
	  (min(D1,D2) #= abs(D1-D2), Out=[Idx]-[Idx]; Out=[Idx]-[])
	), Ids),
    pairs_keys(Ids, Ks), pairs_values(Ids, Vs),
    maplist([L0,Len]>>(flatten(L0,L),sort(L,Srt),length(Srt,Len)), [Vs,Ks], [P1,P2]).
1 Like

There are different views. In the XSB community this is probably considered the preferred route, possibly exploiting tabling in solving the problem. Also outside this community using dynamic predicates to represent the world in which we are reasoning makes (a lot of) sense IMO. Things get more dubious once you start using assert/retract as part of the computation.

I never understand the use of dif/2 in code like this. It is fully obvious that you apply dif/2 to ground terms, so it is simply the same a \==/2. That is particulrly so in

This is plain wrong. Yes, if A is sufficiently instantiated it is safe as dif/2 does not delay. Calling (dif(_, 46) -> Then ... however will take the Then route, which is probably not intended. Even using constraints together with asserta/1 or other predicates with permanent side effects is dubious as code may be executed while it should not due to pending constraints. For side-effect-free code this is fine as all will be undone anyway.

2 Likes

Re: dynamic predicates, I agree that a solution that does asserts/retracts during computation would be difficult to reason about. But I guess loading the world state before into predicates is considered ok.

Thank you, I don’t know why I used dif here… Replacing all dif with \== actually makes my solution faster (from 2s down to 1.75s).

Here’s my day 8. I’m not at all trying to code-golf, which might be against the spirit of AoC seeing as the only thing we’re scored on is time-to-correct-solution. However, I appreciate how readable and clear Prolog can be, AND that this doesn’t mean it’s slow. As a comparison to the CLPFD version, I have 4 times the code, and I’m sure it took me longer, but I feel it’s pretty readable (especially to a neophyte Prolog programmer), and it’s about 3000 times faster.

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

input(As) :-
    phrase_from_file(column_row_antenas(1,1, As), '8/input.txt').

column_row_antenas(_, _, []) --> eos.

column_row_antenas(C, R, A) --> ".",
    {CN is C + 1},
    column_row_antenas(CN, R, A).

column_row_antenas(_, R, A) --> eol,
    {RN is R + 1},
    column_row_antenas(1, RN, A).

column_row_antenas(C, R, [C:R:Antenna | A]) --> [AntennaCode],
    {char_code(Antenna, AntennaCode), CN is C + 1},
    column_row_antenas(CN, R, A).

antenna_col(C:_:_, C).
antenna_row(_:R:_, R).

antennas_max_col(As, M) :-
    maplist(antenna_col, As, Cs),
    max_list(Cs, M).

antennas_max_row(As, M) :-
    maplist(antenna_row, As, Cs),
    max_list(Cs, M).

list_pair([H|T], H:M) :-
    member(M, T).

list_pair([_|T], P) :-
    list_pair(T, P).

a1_a2_antinode(C1:R1, C2:R2, C:R) :-
    C is 2*C2 - C1, R is 2*R2 - R1.

a1_a2_limit_antinode(A1, A2, L, AN) :-
    a1_a2_step_limit_antinode(A1, A2, 0, L, AN).

a1_a2_step_limit_antinode(C1:R1, C2:R2, S, CL:RL, AC:AR) :-
    C is C2 + S * (C2 - C1),
    R is R2 + S * (R2 - R1),
    1 =< C, C =< CL,
    1 =< R, R =< RL,
    ( AC = C, AR = R
    ; SN is S + 1,
      a1_a2_step_limit_antinode(C1:R1, C2:R2, SN, CL:RL, AC:AR)
    ).

a(T) :-
    input(As),
    antennas_max_col(As, MaxCol),
    antennas_max_row(As, MaxRow),
    findall(C:R,
            ( list_pair(As, (C1:R1:A):(C2:R2:A)),
              ( a1_a2_antinode(C1:R1, C2:R2, C:R)
              ; a1_a2_antinode(C2:R2, C1:R1, C:R)
              ),
              1 =< C, C =< MaxCol,
              1 =< R, R =< MaxRow
            ),
            Locations),
    sort(Locations, UniqueLocations),
    length(UniqueLocations, T).

b(T) :-
    input(As),
    antennas_max_col(As, MaxCol),
    antennas_max_row(As, MaxRow),
    findall(C:R,
            ( list_pair(As, (C1:R1:A):(C2:R2:A)),
              ( a1_a2_limit_antinode(C1:R1, C2:R2, MaxCol:MaxRow, C:R)
              ; a1_a2_limit_antinode(C2:R2, C1:R1, MaxCol:MaxRow, C:R)
              )
            ),
            Locations),
    sort(Locations, UniqueLocations),
    length(UniqueLocations, T).

Day 9

Not really happy with it, my initial list version with append to find and splice lists was way too slow. Even this rbtree solutions takes 38 seconds on my M1 mac to solve part 2.

I tried to use rb_in to walk in sort order to find the first eligible position, but had to add aggregate_all to that as well.

:- use_module(library(rbtrees)).
:- use_module(library(yall)).

digit(Ch,D) :- Ch >= 48, D is Ch - 48.
input(Fm) :- read_file_to_codes('day9.txt', Cs, []),
             convlist(digit, Cs, I),
             to_file_map(file-0, I, Fm).

to_file_map(_, [], []).
to_file_map(file-ID, [Size|Rest], Out) :-
    length(File, Size),
    maplist(=(ID), File),
    succ(ID, ID1),
    to_file_map(free-ID1, Rest, Fm0),
    append(File, Fm0, Out).
to_file_map(free-ID, [Size|Rest], Out) :-
    length(Free, Size),
    maplist(=(f), Free),
    to_file_map(file-ID, Rest, Fm0),
    append(Free, Fm0, Out).

defrag(FileMap, Defragged) :-
    reverse(FileMap, Reverse),
    partition(=(f), Reverse, Free, ToMove0), % blocks to move
    length(Free, Space),
    length(ToMove, Space),
    append(ToMove, _, ToMove0),
    length(ToMove0, FileBlocks),
    %writeln(to_move(ToMove0, ToMove)),
    defrag_(FileBlocks, FileMap, ToMove, Defragged).

defrag_(0, _, _, []) :- !. % all fileblocks processed

defrag_(B, [f|Fm], [M|Move], [M|Rest]) :- B1 is B - 1, defrag_(B1, Fm, Move, Rest), !.
defrag_(B, [ID|Fm], Move, [ID|Rest]) :- B1 is B - 1, defrag_(B1, Fm, Move, Rest).

checksum(_, [], 0) :- !.
checksum(Pos, [f|Rest], CS) :- succ(Pos, Pos1), checksum(Pos1, Rest, CS), !.
checksum(Pos, [ID|Rest], CS) :-
    succ(Pos, Pos1),
    checksum(Pos1, Rest, CS0),
    CS is Pos*ID + CS0.
checksum(FileBlocks, CS) :- checksum(0, FileBlocks, CS).

part1(A) :- input(I), defrag(I, D), checksum(D, A).

% contiguous file blocks
take_all(Val, [Val|Rest], [Val|Before], After) :-
    take_all(Val, Rest, Before, After), !.
take_all(_, [], [], []) :- !.
take_all(Val, [X|Rest], [], [X|Rest]) :- Val \== X.

contiguous([], []) :- !.
contiguous([X|Rest0], [X-Len|Rest1]) :-
    take_all(X, Rest0, Xs, Rest),
    length([X|Xs], Len),
    contiguous(Rest, Rest1), !.

part2(Ans) :-
    input(I),
    contiguous(I, F),
    % make a keyed with Type-Len-Pos
    rb_new(Empty),
    foldl([Type-Len,P0-T0,P1-T1]>>(rb_insert(T0, Type-Len-P0, 1, T1),
                                   P1 is P0 + Len),
          F, 0-Empty, _-Files),
    % get all files to try moving
    rb_fold([T-L-P-1,S0,S1]>>(T=f -> S1 = S0; S1=[T-L-P|S0]), Files, [], ToMove),
    foldl(try_move, ToMove, Files, Moved),
    checksum2(Moved, Ans).

try_move(Type-Len-FPos, FilesIn, FilesOut) :-
    % Try to find a space in files, that has length at least Len,
    (aggregate_all(min(Po, Sp),
                   (rb_in(f-Sp-Po, 1, FilesIn), Sp >= Len, Po < FPos),
                   min(Pos, Space))
    -> (rb_delete(FilesIn, Type-Len-FPos, T0), % delete file from orig pos
        rb_delete(T0, f-Space-Pos, T1), % delete empty space
        rb_insert_new(T1, Type-Len-Pos, 1, T2), % insert file at new pos
        (Space > Len
        -> (Rem is Space - Len, Pos1 is Pos + Len,
            rb_insert_new(T2, f-Rem-Pos1, 1, FilesOut))
        ; FilesOut = T2))
    ; FilesOut = FilesIn).

checksum2(Files, CS) :-
    % Sum all files' positions*id
    rb_fold([T-L-P-1, S0, S1]>>
            ( T=f
            -> S0 = S1
            ; ( P1 is P + L - 1,
                aggregate_all(sum(V), (between(P,P1,N), V is N * T), CS),
                S1 is S0 + CS )),
            Files, 0, CS).

Day 10

Straight forward search for Prolog. Aggregating either the count of either different end points or distinct paths. I moved my load_grid from day8 to a utility, as that comes in handy in many puzzles.

:- use_module(util).
:- use_module(library(aggregate)).
:- dynamic g/2.

at(X-Y, Ch) :- N is Ch - 48, asserta(g(X-Y, N)).
input :-
    retractall(g(_,_)),
    load_grid('day10.txt', at, _).

neighbor(X-Y, X1-Y) :- X1 is X - 1.
neighbor(X-Y, X1-Y) :- X1 is X + 1.
neighbor(X-Y, X-Y1) :- Y1 is Y - 1.
neighbor(X-Y, X-Y1) :- Y1 is Y + 1.

next_step(X1-Y1, X2-Y2) :-
    g(X1-Y1, N),
    succ(N, N1),
    neighbor(X1-Y1, X2-Y2),
    g(X2-Y2, N1).

path(From, To) :- g(From, 8), neighbor(From, To), g(To, 9).
path(From, To) :-
    g(From, N), N < 8,
    next_step(From, Intermediate),
    path(Intermediate, To).

trailhead(X-Y) :- g(X-Y, 0).

score(Pos, Score) :-
    aggregate_all(count, To, path(Pos, To), Score).

score2(Pos, Score) :-
    aggregate_all(count, path(Pos,_), Score).

solve(P1,P2) :-
    input,
    aggregate_all(sum(S), (trailhead(Pos), score(Pos, S)), P1),
    aggregate_all(sum(S), (trailhead(Pos), score2(Pos, S)), P2).
1 Like

Day 09

Catching up! Here it is. Part 1 was fairly elegant and finishes quickly. I think Part 2 requires O(1) data structures to go fast.

add_idx(Xs, Ys) :- findall(I-X, nth1(I, Xs, X), Ys).

expand(Xs0, Ys) :-
    add_idx(Xs0, Xs),
    maplist([I0-X,L]>>(I is mod(I0,2)*(1+I0//2), length(L,X), maplist(=(I),L)), Xs, Xs2),
    flatten(Xs2, Xs3), maplist([A,B]>>(B is A-1), Xs3, Ys).

insert(Xs, Empty, Full, N, Ys) :-
    length(EmptyTrim, N), length(FullTrim, N),
    append(EmptyTrim, _, Empty), append(FullTrim, _, Full),
    maplist([I-A,J-B,C]>>(I<J->C=[J-A,I-B];C=[]), EmptyTrim, FullTrim, Swap0),
    flatten(Swap0, Swap), list_to_assoc(Swap, SwapDict),
    exclude({SwapDict}/[I-_]>>(get_assoc(I, SwapDict, _)), Xs, Rem),
    append(Rem, Swap, Final), sort(Final, Ys).

next_empty(_, N, Ys0, Ys) :- length(Ys0, N), reverse(Ys0, Ys), !.
next_empty([_-V|Xs], N, [], Ys) :- V \= -1, !, next_empty(Xs, N, [], Ys).
next_empty([X-(-1)|Xs], N, Ys, Out) :-  !, next_empty(Xs, N, [X-(-1)|Ys], Out).
next_empty([_-V|Xs], N, _, Ys) :- V \= -1, next_empty(Xs, N, [], Ys).

insert2([V|Vs], Xs, Final) :-
    findall(I-V, member(I-V, Xs), Full),
    length(Full, N),
    next_empty(Xs, N, [], Empty),
    insert(Xs, Empty, Full, N, NewXs),
    !, insert2(Vs, NewXs, Final).
insert2([_|Vs], Xs, Final) :- insert2(Vs, Xs, Final).
insert2([], Final, Final).

solve(In, Part1, Part2) :-
    read_file_to_string(In, S, []), string_chars(S, Cs0),
    exclude(=('\n'), Cs0, Cs1), maplist(atom_number, Cs1, Cs),
    expand(Cs, Exp), add_idx(Exp, ExpIdx),
    include([I-A]>>(A=(-1)), ExpIdx, Empty),
    include([I-A]>>(A\=(-1)), ExpIdx, Full0), reverse(Full0, Full),
    length(Empty, N1), length(Full,N2), N is min(N1,N2),
    insert(ExpIdx, Empty, Full, N, Exp1),
    aggregate_all(sum(I*M), (nth0(I,Exp1,_-M), M \= -1), Part1),
    pairs_values(Full, Values0), sort(Values0,Values1), reverse(Values1,Values),
    insert2(Values, ExpIdx, Exp2),
    aggregate_all(sum(I*M), (nth0(I,Exp2,_-M), M \= -1), Part2).

Hello, I recently started coding in Prolog and decided to do AoC in Prolog.

Day 11

I am having a weird issue with :- table. My code runs the part 2 at the first run.

?- time(solve_part2(O)).
% 10,684,683 inferences, 1.028 CPU in 1.028 seconds (100% CPU, 10394830 Lips)

But, then if

  • I remove :- table next_number_n/3. phrase
  • Run make
  • The solve_part2(O) does not return (expected).
  • Then, I cancel the run by Ctrl-C then abort
  • Then, I added back the table phrase.
  • Run make.
  • The solve_part2(O) still does not return (unexpected).
  • I close the swipl and re run swipl
  • solve_part2(O) returns !

I think something to do with :- table, but it’d be very much appreciated if someone could explain the above. I wasted several hours just debugging in the interactive mode, but then I realized it started working once I restarted it and that’s when I figured it’s something to do with :- table :smiling_face_with_tear:

Here is my full code.

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

row(Row) -->
  sequence(number, white, Row), blanks.

input(Row) :-
  phrase_from_file(row(Row), "day11_input.txt").

:- table next_number/2.
next_number(0, [1]) :- !.
next_number(Number, Next) :-
  Number =\= 0,
  split(Number, Next), !.
next_number(Number, [Next]) :-
  Number =\= 0,
  \+ split(Number, _),
  Next is Number * 2024, !.

split(Number, [Number1, Number2]) :-
  number_codes(Number, Codes),
  length(Codes, Length),
  Length mod 2 =:= 0,
  Mid is Length // 2,
  length(Left, Mid),
  length(Right, Mid),
  append(Left, Right, Codes),
  number_codes(Number1, Left),
  number_codes(Number2, Right), !.

solve_part1(Result) :-
  phrase_from_file(row(Row), "day11_input.txt"),
  solve(25, Row, Result).

solve_part2(Result) :-
  phrase_from_file(row(Row), "day11_input.txt"),
  solve(75, Row, Result).

solve(N, Row, Result) :-
  foldl({N}/[In, Acc, Out]>>(next_number_n(N, In, O), Out is Acc + O), Row, 0, Result).

:- table next_number_n/3.
next_number_n(0, _, 1) :- !.
next_number_n(N, Number, Result) :-
  N > 0,
  N1 is N - 1,
  next_number(Number, NextNumbers),
  foldl({N1}/[In, Acc, Out]>>(next_number_n(N1, In, O), Out is Acc + O),
        NextNumbers, 0, Result), !.
swipl --version
SWI-Prolog version 9.0.4 for x86_64-linux

Also, at advent-of-code-2024/day11.pl at 267c8c33981ac4824876a9f7628363669d8a4141 · kkweon/advent-of-code-2024 · GitHub

Always use_module(library(yall)) if you use lambdas, like you do… they behave differently if not loaded before loading the module

1 Like

Indeed, I made a C solution for that as well, and it ran in 20ms for my input.

Day 10

Here is is. Used asserta/1 to avoid passing big lists around. A bit of tabled dynamic programming. A bit of clpfd for coordinate Maths.

:- use_module(library(clpfd)).

:- table value(_,_,sum), coo/3.

coo(Idx0, Idx, X1-Y1) :-
    d(cols, Cols), d(rows, Rows),
    X0 #= mod(Idx0, Cols), Y0 #= Idx0 // Cols,
    X #= X0 + X1, Y #= Y0 + Y1,
    Rows #> X, Cols #> Y, X #>= 0, Y #>= 0,
    Idx #= X + Y * Cols.

value(Idx, Accept, 1) :- p(Idx, 9), memberchk(Idx, Accept), !.
value(Idx, Accept, Value) :-
    p(Idx, V0), member(Try, [0-1,0-(-1),1-0,-1-0]),
    coo(Idx, Idx1, Try), p(Idx1, V1),
    V1 #= V0 + 1,
    value(Idx1, Accept, Value).

solve(In, Part1, Part2) :-
    read_file_to_string(In, S, []), string_chars(S,Cs0),
    nth0(Cols, Cs0, '\n'), !, exclude(=('\n'), Cs0, Cs),
    maplist(atom_number, Cs, CsNums), length(Cs, N), Rows is N//Cols,
    retractall(d(_,_)), asserta(d(rows, Rows)), asserta(d(cols, Cols)),
    findall(I-V, nth0(I, CsNums, V), CsIdx),
    retractall(p(_,_)), maplist([I-V]>>(asserta(p(I,V))), CsIdx),
    aggregate_all(count, (p(Idx1,0), p(Idx2,9), value(Idx1, [Idx2], _)), Part1),
    aggregate_all(sum(V), (p(Idx1,0), value(Idx1, _, V)), Part2).
1 Like

Day 11

Here it is. Just some tabling.

:- table expand/3.

expand_list(N0, [X|Xs0], Y) :-
    !, maplist(expand(N0), [X|Xs0], Xs1), sumlist(Xs1, Y).

expand(0, Xs, Y) :- is_list(Xs), length(Xs, Y), !.
expand(0, _, 1) :- !.
expand(N0, 0, Ys) :- N is N0-1, !, expand(N, 1, Ys).
expand(N0, X, Ys) :-
    N is N0-1,
    atom_string(X, Xstr), string_length(Xstr, M),
    mod(M, 2) =:= 0, Mnew is M // 2,
    string_concat(S1, S2, Xstr),
    string_length(S1, Mnew), string_length(S2, Mnew),
    atom_number(S1, Snum1), atom_number(S2, Snum2),
    !, expand_list(N, [Snum1, Snum2], Ys).
expand(N0, X0, Ys) :- N is N0-1, X is X0*2024, !, expand(N, X, Ys).

solve(In, Part1, Part2) :-
    read_file_to_string(In, S0, []), re_replace("\n", "", S0, S),
    split_string(S, " ", "", Ss), maplist(atom_number, Ss, Ns),
    expand_list(25, Ns, Part1), expand_list(75, Ns, Part2).

A little note on cuts. A cut should be as early as possible, both for clarity and efficiency. So,

  • The cut in expand_list/3 is useless. There is only one clause.
  • In expand/3
    • 1st clause: cut should be after is_list/1. That is the condition. length(+,-) is det.
    • 3rd clause: cut should be after the head.
    • 4th clause: unclear where the condition ends. Could be atom_number/2, after which it is correct. But possibly it is mod(M, 2) =:= 0?
    • 5th clause: no need. last clause and all subgoals are det.
2 Likes

Finally a day (13) where I felt inspired to revisit Prolog. :smiley:

:- use_module([library(clpq), library(yall)]).

main :-
    read_string(user_input, _, S), re_split("\n\n", S, Sections),
    maplist([S, L]>>(re_foldl([re_match{0:N}, [N|Ns], Ns]>>true, "\\d+"/t, S, L, [], [])), Sections, Inputs),
    maplist({Inputs}/[X, Ans]>>
        aggregate_all(sum(3 * A + B), (
            member([Ax, Ay, Bx, By, Px, Py], Inputs), {
                A >= 0, B >= 0,
                Px + X =:= Ax * A + Bx * B,
                Py + X =:= Ay * A + By * B
            }, integer(A), integer(B)
        ), Ans), [0, 10^13], Ans),
    format("Part 1: ~d~nPart 2: ~d~n", Ans).

2 Likes

Playing catch up. Here is day 12, and here is day 13:

:- use_module(library(clpfd)).

optimise(X0-Y0, X1-Y1, X2-Y2, Offset, Tokens) :-    
    A #>= 0, B #>=0,
    X0+Offset #= A*X1 + B*X2, Y0+Offset #= A*Y1 + B*Y2,
    labeling([min(3*A+B)], [A,B]), Tokens #= 3*A+B.

take([Y0,X0,Y2,X2,Y1,X1|Xs], Offset, Acc0, Total) :-
    optimise(X0-Y0, X1-Y1, X2-Y2, Offset, Tokens), Acc is Acc0 + Tokens,
    !, take(Xs, Offset, Acc, Total).
take([_,_,_,_,_,_|Xs], Offset, Acc, Total) :- take(Xs, Offset, Acc, Total).
take([], _, Acc, Acc).

solve(In, Part1, Part2) :-
    read_file_to_string(In, S, []),
    re_foldl([_{0:N},V0,[N|V0]]>>true, "\\d+"/t, S, [], Ns, []),
    take(Ns, 0, 0, Part1), take(Ns, 10000000000000, 0, Part2).

@BarrensZeppelin , learned many tricks I didn’t know about in your 13 LoC. Please keep posting, you’ve been missed!

Also, a couple bits you might find useful:

  • _{0:N} instead of re_match{0:N}

  • You can abstract lambdas into a variable if you want to avoid long lines. E.g. GetNums = [S, L]>>(re_foldl([re_match{0:N}, [N|Ns], Ns]>>true, "\\d+"/t, S, L, [], [])

Day 14

Here it is. Its straightforward, although part 2 is a bit unusual: I detected a recurrence relationship with clpfd, and then calculated clustering heuristic for all possible states and picked the maximum.

Be careful. If yall expressions are not compiled their semantics is a little different and they are slow. If you use yall, make sure the library is loaded as well as all libraries that provide the meta-predicates calling yall expressions. To make sure, use listing/1 to verify the expression is compiled.

2 Likes