1

I am new to Prolog and I am trying to write a function that finds a list that follows some rules. More specifically, given two numbers, N and K, I want my function to find a list with K powers of two that their sum is N. The list must not contain each power but the total sum of each power. For example if N=13 and K=5, I want my list to be [2,2,1] where the first 2 means two 4, the second 2 means two 2, and the third 1 means one 1 (4+4+2+2+1=13). Consider that beginning from the end of the list each position i represents the 2^i power of 2. So I wrote this code:

sum2(List, SUM, N) :-
   List = []  -> N=SUM;
   List = [H|T],
   length(T, L),
   NewSUM is SUM + (H * 2**L),
   sum2(T, NewSUM, N).

powers2(N,K,X):-
   sum2(X,0,N),
   sum_list(X, L),
   K = L.

The problem is:

?- sum2([2,2,1],0,13).
true.
?- sum2([2,2,1],0,X).
X = 13.
?- sum2(X,0,13).
false.
?- powers2(X,5,[2,2,1]).
X = 13.
?- powers2(13,5,[2,2,1]).
true.
?- powers2(13,X,[2,2,1]).
X = 5.
?- powers2(13,5,X).
false.

In the cases, X represents the list I expected the output to be a list that follows the rules and not false. Could you help me to find how can I solve this and have a list for output in these cases?

false
  • 10,264
  • 13
  • 101
  • 209
Gspoon
  • 81
  • 7
  • 1
    This is almost a duplicate of https://stackoverflow.com/questions/61804572/arithmetics-in-prolog-represent-a-number-using-powers-of-2. But the representation is weird. For example, if you want to represent `N = 16` using `K = 1` power of 2, what is the corresponding list in your positional notation? You could say that it's `[1, 0, 0, 0, 1]` (1 * 16, 0 * 8, 0 * 4, 0 * 2, 0 * 1). But this gives a list whose length is not `K`. Is this a homework project? If yes, did you post the exact specification? – Isabelle Newbie May 27 '20 at 12:20
  • You are right I described it a little bit messy. I didn't want to say that I want a list whose length is K. I wanted to say that the sum of the powers used for the sum must be K. – Gspoon May 27 '20 at 12:51

1 Answers1

0

The immediate reason for the failure of your predicate with an unbound list is due to your use of the -> construct for control flow.

Here is a simplified version of what you are trying to do, a small predicate for checking whether a list is empty or not:

empty_or_not(List, Answer) :-
    (   List = []
    ->  Answer = empty
    ;   List = [H|T],
        Answer = head_tail(H, T) ).

(Side note: The exact layout is a matter of taste, but you should always use parentheses to enclose code if you use the ; operator. I also urge you to never put ; at the end of a line but rather in a position where it really sticks out. Using ; is really an exceptional case in Prolog, and if it's formatted too similarly to ,, it can be hard to see that it's even there, and what parts of the clause it applies to.)

And this seems to work, right?

?- empty_or_not([], Answer).
Answer = empty.

?- empty_or_not([1, 2, 3], Answer).
Answer = head_tail(1, [2, 3]).

OK so far, but what if we call this with an unbound list?

?- empty_or_not(List, Answer).
List = [],
Answer = empty.

Suddenly only the empty list is accepted, although we know from above that non-empty lists are fine as well.

This is because -> cuts away any alternatives once it has found that its condition is satisfied. In the last example, List is a variable, so it is unifiable with []. The condition List = [] will succeed (binding List to []), and the alternative List = [H|T] will not be tried. It seems simple, but -> is really an advanced feature of Prolog. It should only be used by more experienced users who know that they really really will not need to explore alternatives.

The usual, and usually correct, way of implementing a disjunction in Prolog is to use separate clauses for the separate cases:

empty_or_not([], empty).
empty_or_not([H|T], head_tail(H, T)).

This now behaves logically:

?- empty_or_not([], Answer).
Answer = empty.

?- empty_or_not([1, 2, 3], Answer).
Answer = head_tail(1, [2, 3]).

?- empty_or_not(List, Answer).
List = [],
Answer = empty ;
List = [_2040|_2042],
Answer = head_tail(_2040, _2042).

And accordingly, your definition of sum2 should look more like this:

sum2([], SUM, N) :-
    N = SUM.
sum2([H|T], SUM, N) :-
    length(T, L),
    NewSUM is SUM + (H * 2**L),
    sum2(T, NewSUM, N).

This is just a small step, however:

?- sum2(X, 0, 13).
ERROR: Arguments are not sufficiently instantiated
ERROR: In:
ERROR:    [9] _2416 is 0+_2428* ...
ERROR:    [8] sum2([_2462],0,13) at /home/gergo/sum.pl:5
ERROR:    [7] <user>

You are trying to do arithmetic on H, which has no value. If you want to use "plain" Prolog arithmetic, you will need to enumerate appropriate values that H might have before you try to do arithmetic on it. Alternatively, you could use arithmetic constraints. See possible implementations of both at Arithmetics in Prolog, represent a number using powers of 2.

Isabelle Newbie
  • 9,258
  • 1
  • 20
  • 32