Prolog: append number to a term

420 views Asked by At

Is it possible to append a number to a term directly?

I.e., I can easily do something like this:

?- A = 1 + 2, B = 3, C = A + B.
C = 1+2+3

But is there a way (operator?) to specify something instead of '+' in the C = A + B to get "C = 1+23"?

I feel I'm asking for something strange, so here is the context. I have a list of digits, and I want to generate all expressions that can be obtained by putting '+', '-' or nothing between the digits.

Pluses and minuses are easy part:

possible([X], X) :- !.
possible([A, B | Rest], E) :-
    ( H = A + B ; H = A - B ),
    possible([H | Rest], E).

?- possible([1, 2, 3], E).
E = 1+2+3 ?;
E = 1+2-3 ?;
E = 1-2+3 ?;
E = 1-2-3
yes

But I also want to get "E = 12+3", "E = 1+23" and "E = 123". Is there an easy way to do it?

Update: the solution should be portable or at least work in B-Prolog.

5

There are 5 answers

5
CapelliC On BEST ANSWER

here is my bet

possible([N|Ns], D) :-
    digits_number([N|Ns], D).
possible([N|Ns], X) :-
    append([L|Ls], [R|Rs], [N|Ns]),
    possible([L|Ls], Lx),
    digits_number([R|Rs], Rx),
    (Op = + ; Op = -), X =.. [Op, Lx, Rx].

digits_number(Digits, N) :-
    maplist(digit_code, Digits, Codes),
    number_codes(N, Codes).
digit_code(D, C) :-
    C is D + 0'0.

The purpose of the verbose [N|Ns], etc is to avoid matching empty lists.

edit Here is a variation, that doesn't require maplist/3 and number_codes/2. The code it's quite similar in size...

possible(Ns, D) :-
    digits_number(Ns, _, D).
possible([N|Ns], X) :-
    append([L|Ls], [R|Rs], [N|Ns]),
    possible([L|Ls], Lx),
    digits_number([R|Rs], _, Rx),
    (Op = + ; Op = -), X =.. [Op, Lx,Rx].

digits_number([Digit], 1, Digit).
digits_number([D|Ds], F, N) :-
    digits_number(Ds, G, T),
    F is G * 10,
    N is T + D * F.

It's more efficient tough (at least on inference count), indeed here is a performance test

?- L=[1,2,3,4,5,6,7,8], time(findall(X,possible_1(L,X),L1)), time(findall(X,possible_2(L,X),L2)).
% 31,591 inferences, 0.017 CPU in 0.017 seconds (100% CPU, 1851600 Lips)
% 20,656 inferences, 0.017 CPU in 0.018 seconds (98% CPU, 1192235 Lips)
L = [1, 2, 3, 4, 5, 6, 7, 8],
L1 = L2, L2 = [12345678, 1+2345678, 1-2345678, 12+345678, 12-345678, 1+2+345678, 1+2-345678, ... - ... + 345678, ... - ...|...].

Of course, I've renamed the two versions possible_1, possible_2

2
Shon On

I initially misunderstood your question and just approached it as a list processing exercise using DCGs. I only realized that you were trying to generate Prolog terms after I'd finished with the DCG. But I was able to get a workable solution by converting the list to a string and then the string to a term using SWI-Prolog's string handling predicates. I'd be interested to know of a more straightforward way of accomplishing this.

possible(Digits, AsTerm) :-
    phrase(sequence(Digits), Sequence),
    atomics_to_string(Sequence, AsString),
    term_string(AsTerm, AsString).

sequence(Ds) -->
    digits(Ds).
sequence(Ds) --> {append(First, Last, Ds)},
    digits(First), sign, sequence(Last).

digits([D]) -->
    digit(D).
digits([D|Ds]) -->
    digit(D), digits(Ds).

digit(D) --> {integer(D)},
    [D].

sign --> [+].
sign --> [-].

This version of possible/2 will generate evaluable prolog terms:

?- possible([1,2,3],X), Y is X.
X = Y, Y = 123 ;
X = 1+23,
Y = 24 ;
X = 1+2+3,
Y = 6 ;
X = 1+2-3,
Y = 0 ;
...
5
Shon On

This solution works without string-to-term conversions. It still depends on the SWI-Prolog predicate atom_number/2 (not sure how widely available this is). If ISO compliance is necessary, I believe it should suffice to write a custom atom_number/2 predicate using atom_codes/2 and number_codes/2. digit_appended_to_expression/3 is actually too general, since it will work with any predicate that takes a number as its second argument.

digit_appended_to_expression(Expression, C, ExpressionWithC) :-
    Expression =.. [Operator, A, B],
    digit_concat(B, C, BC),
    ExpressionWithC =.. [Operator, A, BC].

digit_concat(A, B, AB) :-
    number(A),
    number(B),
    atom_number(A_Atom, A),
    atom_number(B_Atom, B),
    atom_concat(A_Atom, B_Atom, AB_Atom),
    atom_number(AB_Atom, AB).

possible([X], X) :- !.
possible([A, B | Rest], E) :-
    ( digit_concat(A, B, H)
    ; H = A + B
    ; H = A - B
    ; digit_appended_to_expression(A, B, H)
    ),
    possible([H | Rest], E).

This still doesn't give an operator, because it needs a 3-place predicate, but one could use term expansion to achieve macro if it were really important.

Is it sufficient?

1
Paulo Moura On

How about this simple and fully portable solution:

possible([Digit], Digit).
possible([Digit| Digits], Digit + RightExpression) :-
    possible(Digits, RightExpression).
possible([Digit| Digits], Digit - RightExpression) :-
    possible(Digits, RightExpression).
possible([Digit1, Digit2| Digits], Expression) :-
    Number0 is Digit1 * 10,
    Number is Number0 + Digit2,
    possible([Number| Digits], Expression).

Using B-Prolog for testing:

$ bp
B-Prolog Version 8.1, All rights reserved, (C) Afany Software 1994-2014.
| ?- [possible].
consulting::possible.pl

yes
| ?- possible([1,2,3], Exp).
Exp = 1+(2+3) ?;
Exp = 1+(2-3) ?;
Exp = 1+23 ?;
Exp = 1-(2+3) ?;
Exp = 1-(2-3) ?;
Exp = 1-23 ?;
Exp = 12+3 ?;
Exp = 12-3 ?;
Exp = 123 ?;
no

Regarding performance, using the same benchmark as in Carlo's answer, I get:

?- L=[1,2,3,4,5,6,7,8], time(findall(X,possible(L,X),L1)).
% 12,037 inferences, 0.003 CPU in 0.003 seconds (93% CPU, 4223509 Lips)
L = [1, 2, 3, 4, 5, 6, 7, 8],
L1 = [1+ (2+ (3+ (4+ (5+ (6+ (7+8)))))), 1+ (2+ (3+ (4+ (5+ (6+ (7-8)))))), 1+ (2+ (3+ (4+ (5+ (6+78))))), 1+ (2+ (3+ (4+ (5+ (... - ...))))), 1+ (2+ (3+ (4+ (... + ...)))), 1+ (2+ (3+ (... + ...))), 1+ (2+ (... + ...)), 1+ (... + ...), ... + ...|...].
2
xxa On

Here is a possible (pun intended) solution using accumulators:

%numberexp( D, N, XN) :- number_chars( D, DL), DL=[ DC| _], number_chars( N, NL), number_chars( XN, [ DC| NL]).
numberexp( D, N, XN) :- XN is integer( exp( log( 10)*(1+integer( log( 10, N))))*D+N).


poss( [ H], H, Z, Z).
poss( [ H| T], H, Z, E) :- poss( T, F, F, XT), E= Z+XT.
poss( [ H| T], H, Z, E) :- poss( T, F, F, XT), E= Z-XT.
poss( [ H| T], A, Z, E) :- poss( T, F, Z,  E), numberexp( H, F, A).

possible( L, E) :- poss( L, F, F, E).

The number expansion part is admittedly ugly either ways, but at least it can be portably ugly.

The output is:

| ?- possible([1,2,3],E).
E = 1+(2+3) ?;
E = 1+(2-3) ?;
E = 1+23 ?;
E = 1-(2+3) ?;
E = 1-(2-3) ?;
E = 1-23 ?;
E = 12+3 ?;
E = 12-3 ?;
E = 123 ?;
no