4

I have two numbers, let's name them N and K, and I want to write N using K powers of 2.

For example if N = 9 and K = 4, then N could be N = 1 + 2 + 2 + 4 (2^0 + 2^1 + 2^1 + 2^2).

My program should output something like N = [1,2,2,4].

I am used to C++. I can't find a way to solve this problem in Prolog. Any help will be appreciated!

false
  • 10,264
  • 13
  • 101
  • 209
  • Forget everything you know about C++. Prolog is totally different. What have you tried so far? Have you gone through any Prolog tutorials and learned about list processing? That's a key component of the solution. There will, of course, be numeric operations on integers. Overall, the solution is not a large number of lines of code. It's really hard to tell exactly where you're stuck. – lurker May 14 '20 at 19:37
  • Not every case has a solution. For example, `N=11` and `K=2`. I assume you want it to fail in that case. – lurker May 14 '20 at 20:40
  • Yes, if it fails I want it to give me back an empty list. I have seen many tutorials and I have already solved the problem in C++. I tried to forget my C++ solution and think in prolog terms, so i thought of finding a recursive way of solving this, but nothing I tried made sence, I also thought of maybe having a list filled with powersof2 e.x [1,2,4,8....] and with this values trying to add them in differnet ways so that I get my N, but it doesn't seem correct. – CuriousPeet May 14 '20 at 21:00
  • I am not persay stuck, I am looking more for a sense of direction. anything would help! – CuriousPeet May 14 '20 at 21:05
  • [tag:coin-change] is related. as a bridge between C++(etc) and Prolog, explore [tag:recursive-backtracking]. – Will Ness May 16 '20 at 05:01

4 Answers4

4

I thought this would be a few-liner using CLP(FD), but no dice. Can it be done simpler?

So here is the complete solution.

Don't think I came up with this in one attempt, there are a few iterations and dead ends in there.

:- use_module(library(debug)).

% ---
% powersum(+N,+Target,?Solution)
% ---
% Entry point. Relate a list "Solution" of "N" integers to the integer
% "Target", which is the sum of 2^Solution[i].
% This works only in the "functional" direction
% "Compute Solution as powersum(N,Target)"
% or the "verification" direction
% "is Solution a solution of powersum(N,Target)"?
%
% An extension of some interest would be to NOT have a fixed "N".
% Let powersum/2 find appropriate N.
%
% The search is subject to exponential slowdown as the list length
% increases, so one gets bogged down quickly.
% ---

powersum(N,Target,Solution) :- 
   ((integer(N),N>0,integer(Target),Target>=1) -> true ; throw("Bad args!")),   
   length(RS,N),                             % create a list RN of N fresh variables
   MaxPower is floor(log(Target)/log(2)),    % that's the largest power we will find in the solution
   propose(RS,MaxPower,Target,0),            % generate & test a solution into RS
   reverse(RS,Solution),                     % if we are here, we found something! Reverse RS so that it is increasing
   my_write(Solution,String,Value),          % prettyprinting
   format("~s = ~d\n",[String,Value]).

% ---
% propose(ListForSolution,MaxPowerHere,Target,SumSoFar)
% ---
% This is an integrate "generate-and-test". It is integrated
% to "fail fast" during proposal - we don't want to propose a
% complete solution, then compute the value for that solution 
% and find out that we overshot the target. If we overshoot, we
% want to find ozut immediately!
%
% So: Propose a new value for the leftmost position L of the 
% solution list. We are allowed to propose any integer for L 
% from the sequence [MaxPowerHere,...,0]. "Target" is the target
% value we must not overshoot (indeed, we which must meet
% exactly at the end of recursion). "SumSoFar" is the sum of
% powers "to our left" in the solution list, to which we already
% committed.

propose([L|Ls],MaxPowerHere,Target,SumSoFar) :- 
   assertion(SumSoFar=<Target),
   (SumSoFar=Target -> false ; true),          % a slight optimization, no solution if we already reached Target!
   propose_value(L,MaxPowerHere),              % Generate: L is now (backtrackably) some value from [MaxPowerHere,...,0]
   NewSum is (SumSoFar + 2**L),                
   NewSum =< Target,                           % Test; if this fails, we backtrack to propose_value/2 and will be back with a next L
   NewMaxPowerHere = L,                        % Test passed; the next power in the sequence should be no larger than the current, i.e. L
   propose(Ls,NewMaxPowerHere,Target,NewSum).  % Recurse over rest-of-list.

propose([],_,Target,Target).                   % Terminal test: Only succeed if all values set and the Sum is the Target!

% ---
% propose_value(?X,+Max).
% ---
% Give me a new value X between [Max,0].
% Backtracks over monotonically decreasing integers.
% See the test code for examples.
%
% One could also construct a list of integers [Max,...,0], then
% use "member/2" for backtracking. This would "concretize" the predicate's
% behaviour with an explicit list structure.
%
% "between/3" sadly only generates increasing sequences otherwise one
% could use that. Maybe there is a "between/4" taking a step value somewhere?
% ---

propose_value(X,Max) :- 
   assertion((integer(Max),Max>=0)),
   Max=X.
propose_value(X,Max) :- 
   assertion((integer(Max),Max>=0)),
   Max>0, succ(NewMax,Max), 
   propose_value(X,NewMax).

% ---
% I like some nice output, so generate a string representing the solution.
% Also, recompute the value to make doubly sure!
% ---

my_write([L|Ls],String,Value) :-
   my_write(Ls,StringOnTheRight,ValueOnTheRight),
   Value is ValueOnTheRight + 2**L,
   with_output_to(string(String),format("2^~d + ~s",[L,StringOnTheRight])).

my_write([L],String,Value) :-
   with_output_to(string(String),format("2^~d",[L])),
   Value is 2**L.



:- begin_tests(powersum).

% powersum(N,Target,Solution) 

test(pv1)       :- bagof(X,propose_value(X,3),Bag), Bag = [3,2,1,0].
test(pv2)       :- bagof(X,propose_value(X,2),Bag), Bag = [2,1,0].
test(pv2)       :- bagof(X,propose_value(X,1),Bag), Bag = [1,0].
test(pv3)       :- bagof(X,propose_value(X,0),Bag), Bag = [0].

test(one)       :- bagof(S,powersum(1,1,S),Bag), Bag = [[0]].
test(two)       :- bagof(S,powersum(3,10,S),Bag), Bag = [[0,0,3],[1,2,2]].
test(three)     :- bagof(S,powersum(3,145,S),Bag), Bag = [[0,4,7]].
test(four,fail) :- powersum(3,8457894,_).
test(five)      :- bagof(S,powersum(9,8457894,S), Bag), Bag = [[1, 2, 5, 7, 9, 10, 11, 16, 23]]. %% VERY SLOW

:- end_tests(powersum).

rt :- run_tests(powersum).

Running test of 2 minutes due to the last unit testing line...

?- time(rt).
% PL-Unit: powersum ....2^0 = 1
.2^0 + 2^0 + 2^3 = 10
2^1 + 2^2 + 2^2 = 10
.2^0 + 2^4 + 2^7 = 145
..2^1 + 2^2 + 2^5 + 2^7 + 2^9 + 2^10 + 2^11 + 2^16 + 2^23 = 8457894
. done
% All 9 tests passed
% 455,205,628 inferences, 114.614 CPU in 115.470 seconds (99% CPU, 3971641 Lips)
true.
David Tonhofer
  • 14,559
  • 5
  • 55
  • 51
  • 1
    *Don't think I came up with this in one attempt, there are a few iterations and dead ends in there.* I hear you, brother! Me, too! :) – lurker May 15 '20 at 12:21
  • 1
    See my updated answer for an actual, efficient few-liner with CLP(FD), with help from @repeat. – Isabelle Newbie May 15 '20 at 19:19
4

EDIT: With some suggestive comments from repeat, here is a complete, efficient CLP(FD) solution:

powersum2_(N, Target, Exponents, Solution) :-
    length(Exponents, N),
    MaxExponent is floor(log(Target) / log(2)),
    Exponents ins 0..MaxExponent,
    chain(Exponents, #>=),
    maplist(exponent_power, Exponents, Solution),
    sum(Solution, #=, Target).

exponent_power(Exponent, Power) :-
    Power #= 2^Exponent.

powersum2(N, Target, Solution) :-
    powersum2_(N, Target, Exponents, Solution),
    labeling([], Exponents).

Ordering exponents by #>= cuts down the search space by excluding redundant permutations. But it is also relevant for the order of labeling (with the [] strategy).

The core relation powersum2_/4 posts constraints on the numbers:

?- powersum2_(5, 31, Exponents, Solution).
Exponents = [_954, _960, _966, _972, _978],
Solution = [_984, _990, _996, _1002, _1008],
_954 in 0..4,
_954#>=_960,
2^_954#=_984,
_960 in 0..4,
_960#>=_966,
2^_960#=_990,
_966 in 0..4,
_966#>=_972,
2^_966#=_996,
_972 in 0..4,
_972#>=_978,
2^_972#=_1002,
_978 in 0..4,
2^_978#=_1008,
_1008 in 1..16,
_984+_990+_996+_1002+_1008#=31,
_984 in 1..16,
_990 in 1..16,
_996 in 1..16,
_1002 in 1..16.

And then labeling searches for the actual solutions:

?- powersum2(5, 31, Solution).
Solution = [16, 8, 4, 2, 1] ;
false.

This solution is considerably more efficient than the other answers so far:

?- time(powersum2(9, 8457894, Solution)).
% 6,957,285 inferences, 0.589 CPU in 0.603 seconds (98% CPU, 11812656 Lips)
Solution = [8388608, 65536, 2048, 1024, 512, 128, 32, 4, 2].

Original version follows.

Here is another CLP(FD) solution. The idea is to express "power of two" as a "real" constraint, i.e, not as a predicate that enumerates numbers like lurker's power_of_2/1 does. It helps that the actual constraint to be expressed isn't really "power of two", but rather "power of two less than or equal to a known bound".

So here is some clumsy code to compute a list of powers of two up to a limit:

powers_of_two_bound(PowersOfTwo, UpperBound) :-
    powers_of_two_bound(1, PowersOfTwo, UpperBound).

powers_of_two_bound(Power, [Power], UpperBound) :-
    Power =< UpperBound,
    Power * 2 > UpperBound.
powers_of_two_bound(Power, [Power | PowersOfTwo], UpperBound) :-
    Power =< UpperBound,
    NextPower is Power * 2,
    powers_of_two_bound(NextPower, PowersOfTwo, UpperBound).

?- powers_of_two_bound(Powers, 1023).
Powers = [1, 2, 4, 8, 16, 32, 64, 128, 256|...] ;
false.

... and then to compute a constraint term based on this...

power_of_two_constraint(UpperBound, Variable, Constraint) :-
    powers_of_two_bound(PowersOfTwo, UpperBound),
    maplist(fd_equals(Variable), PowersOfTwo, PowerOfTwoConstraints),
    constraints_operator_combined(PowerOfTwoConstraints, #\/, Constraint).

fd_equals(Variable, Value, Variable #= Value).

constraints_operator_combined([Constraint], _Operator, Constraint).
constraints_operator_combined([C | Cs], Operator, Constraint) :-
    Constraint =.. [Operator, C, NextConstraint],
    constraints_operator_combined(Cs, Operator, NextConstraint).

?- power_of_two_constraint(1023, X, Constraint).
Constraint =  (X#=1#\/(X#=2#\/(X#=4#\/(X#=8#\/(X#=16#\/(X#=32#\/(X#=64#\/(X#=128#\/(... #= ... #\/ ... #= ...))))))))) ;
false.

... and then to post that constraint:

power_of_two(Target, Variable) :-
    power_of_two_constraint(Target, Variable, Constraint),
    call(Constraint).

?- power_of_two(1023, X).
X in ... .. ... \/ 4\/8\/16\/32\/64\/128\/256\/512 ;
false.

(Seeing this printed in this syntax shows me that I could simplify the code computing the constraint term...)

And then the core relation is:

powersum_(N, Target, Solution) :-
    length(Solution, N),
    maplist(power_of_two(Target), Solution),
    list_monotonic(Solution, #=<),
    sum(Solution, #=, Target).

list_monotonic([], _Operation).
list_monotonic([_X], _Operation).
list_monotonic([X, Y | Xs], Operation) :-
    call(Operation, X, Y),
    list_monotonic([Y | Xs], Operation).

We can run this without labeling:

?- powersum_(9, 1023, S).
S = [_9158, _9164, _9170, _9176, _9182, _9188, _9194, _9200, _9206],
_9158 in ... .. ... \/ 4\/8\/16\/32\/64\/128\/256\/512,
_9158+_9164+_9170+_9176+_9182+_9188+_9194+_9200+_9206#=1023,
_9164#>=_9158,
_9164 in ... .. ... \/ 4\/8\/16\/32\/64\/128\/256\/512,
_9170#>=_9164,
_9170 in ... .. ... \/ 4\/8\/16\/32\/64\/128\/256\/512,
_9176#>=_9170,
_9176 in ... .. ... \/ 4\/8\/16\/32\/64\/128\/256\/512,
_9182#>=_9176,
_9182 in ... .. ... \/ 4\/8\/16\/32\/64\/128\/256\/512,
_9188#>=_9182,
_9188 in ... .. ... \/ 4\/8\/16\/32\/64\/128\/256\/512,
_9194#>=_9188,
_9194 in ... .. ... \/ 4\/8\/16\/32\/64\/128\/256\/512,
_9200#>=_9194,
_9200 in ... .. ... \/ 4\/8\/16\/32\/64\/128\/256\/512,
_9206#>=_9200,
_9206 in ... .. ... \/ 4\/8\/16\/32\/64\/128\/256\/512 ;
false.

And it's somewhat quick when we label:

?- time(( powersum_(8, 255, S), labeling([], S) )), format('S = ~w~n', [S]), false.
% 561,982 inferences, 0.055 CPU in 0.055 seconds (100% CPU, 10238377 Lips)
S = [1,2,4,8,16,32,64,128]
% 1,091,295 inferences, 0.080 CPU in 0.081 seconds (100% CPU, 13557999 Lips)
false.

Contrast this with lurker's approach, which takes much longer even just to find the first solution:

?- time(binary_partition(255, 8, S)), format('S = ~w~n', [S]), false.
% 402,226,596 inferences, 33.117 CPU in 33.118 seconds (100% CPU, 12145562 Lips)
S = [1,2,4,8,16,32,64,128]
% 1,569,157 inferences, 0.130 CPU in 0.130 seconds (100% CPU, 12035050 Lips)
S = [1,2,4,8,16,32,64,128]
% 14,820,953 inferences, 1.216 CPU in 1.216 seconds (100% CPU, 12190530 Lips)
S = [1,2,4,8,16,32,64,128]
% 159,089,361 inferences, 13.163 CPU in 13.163 seconds (100% CPU, 12086469 Lips)
S = [1,2,4,8,16,32,64,128]
% 1,569,155 inferences, 0.134 CPU in 0.134 seconds (100% CPU, 11730834 Lips)
S = [1,2,4,8,16,32,64,128]
% 56,335,514 inferences, 4.684 CPU in 4.684 seconds (100% CPU, 12027871 Lips)
S = [1,2,4,8,16,32,64,128]
^CAction (h for help) ? abort
% 1,266,275,462 inferences, 107.019 CPU in 107.839 seconds (99% CPU, 11832284 Lips)
% Execution Aborted  % got bored of waiting

However, this solution is slower than the one by David Tonhofer:

?- time(( powersum_(9, 8457894, S), labeling([], S) )), format('S = ~w~n', [S]), false.
% 827,367,193 inferences, 58.396 CPU in 58.398 seconds (100% CPU, 14168325 Lips)
S = [2,4,32,128,512,1024,2048,65536,8388608]
% 1,715,107,811 inferences, 124.528 CPU in 124.532 seconds (100% CPU, 13772907 Lips)
false.

versus:

?- time(bagof(S,powersum(9,8457894,S), Bag)).
2^1 + 2^2 + 2^5 + 2^7 + 2^9 + 2^10 + 2^11 + 2^16 + 2^23 = 8457894
% 386,778,067 inferences, 37.705 CPU in 37.706 seconds (100% CPU, 10258003 Lips)
Bag = [[1, 2, 5, 7, 9, 10, 11, 16|...]].

There's probably room to improve my constraints, or maybe some magic labeling strategy that will improve the search.

EDIT: Ha! Labeling from the largest to the smallest element changes the performance quite dramatically:

?- time(( powersum_(9, 8457894, S), reverse(S, Rev), labeling([], Rev) )), format('S = ~w~n', [S]), false.
% 5,320,573 inferences, 0.367 CPU in 0.367 seconds (100% CPU, 14495124 Lips)
S = [2,4,32,128,512,1024,2048,65536,8388608]
% 67 inferences, 0.000 CPU in 0.000 seconds (100% CPU, 2618313 Lips)
false.

So this is now about 100x as fast as David Tonhofer's version. I'm content with that :-)

Isabelle Newbie
  • 9,258
  • 1
  • 20
  • 32
  • 1
    Great! Consider using `clpfd:chain/2` instead of `list_monotonic/2`. – repeat May 15 '20 at 16:49
  • 1
    Btw, why not simply write `Z #= 2^P, P #>= 0` and label the `P`s, not the `Z`s. – repeat May 15 '20 at 16:56
  • 1
    Thanks for the tips. I think I had tried `^` but it didn't work, and I was sure there would be something like `clpfd:chain` but didn't know what to search for. I'll update my answer! – Isabelle Newbie May 15 '20 at 18:12
  • Not all clpfds have `(^)/2`, but SWI's does. See https://stackoverflow.com/q/34460301/4609915 – repeat May 15 '20 at 18:15
  • Nice! I get it now. So I have tried to [trace the constraints network](https://raw.githubusercontent.com/dtonhofer/prolog_notes/master/prolog_exercises/stackoverflow/powersum/constraints.svg) but come this is so fast. Can it just "fail fast" on solutions better than the non-CLP approach? In the end, there is no magic and labeling has to walk through the desert of the N-dimensional exponent vector no matter what. – David Tonhofer May 15 '20 at 23:12
  • 1
    It's not so much the constraints as the search. See my observation that the ordering of the chain (or reversing the variables to be labeled) matters. As for your code, I agree that it does something equivalent to my CLP(FD) version. But profiling shows that half of the time in your code is spent in assertions, and after removing those, another half is spent in `is/2`. I think `is/2` is slow because it must interpret its expression argument. The internal representation of CLP(FD) constraints is simply more efficient, I believe. – Isabelle Newbie May 16 '20 at 10:36
3

Here's a scheme that uses CLP(FD). In general, when reasoning in the domain of integers in Prolog, CLP(FD) is a good way to go. The idea for this particular problem is to think recursively (as in many Prolog problems) and use a "bifurcation" approach.

As David said in his answer, solutions to problems like this don't just flow out on the first attempt. There are preliminary notions, trial implementations, tests, observations, and revisions that go into coming up with the solution to a problem. Even this one could use more work. :)

:- use_module(library(clpfd)).

% Predicate that succeeds for power of 2
power_of_2(1).
power_of_2(N) :-
    N #> 1,
    NH #= N // 2,
    N #= NH * 2,
    power_of_2(NH).

% Predicate that succeeds for a list that is monotonically ascending
ascending([_]).
ascending([X1,X2|Xs]) :-
    X1 #=< X2,
    ascending([X2|Xs]).

% Predicate that succeeds if Partition is a K-part partition of N
% where the parts are powers of 2
binary_partition(N, K, Partition) :-
    binary_partition_(N, K, Partition),
    ascending(Partition).    % Only allow ascending lists as solutions

binary_partition_(N, 1, [N]) :- % base case
    power_of_2(N).
binary_partition_(N, K, P) :-
    N #> 1,                  % constraints on N, K
    K #> 1,
    length(P, K),            % constraint on P
    append(LL, LR, P),       % conditions on left/right bifurcation
    NL #> 0,
    NR #> 0,
    KL #> 0,
    KR #> 0,
    NL #=< NR,               % don't count symmetrical cases
    KL #=< KR,
    N #= NL + NR,
    K #= KL + KR,
    binary_partition_(NL, KL, LL),
    binary_partition_(NR, KR, LR).

This will provide correct results, but it also generates redundant solutions:

2 ?- binary_partition(9,4,L).
L = [1, 2, 2, 4] ;
L = [1, 2, 2, 4] ;
false.

As an exercise, you can figure out how to modify it so it only generates unique solutions. :)

lurker
  • 56,987
  • 9
  • 69
  • 103
1
my_power_of_two_bound(U,P):-
     U #>= 2^P,
     P #=< U,
     P #>=0.

power2(X,Y):-
     Y #= 2^X.

Query:

?- N=9,K=4,
   length(_List,K),
   maplist(my_power_of_two_bound(N),_List),
   maplist(power2,_List,Answer),
   chain(Answer, #=<), 
   sum(Answer, #=, N), 
   label(Answer).

Then:

Answer = [1, 2, 2, 4],
K = 4,
N = 9
David Tonhofer
  • 14,559
  • 5
  • 55
  • 51
user27815
  • 4,767
  • 14
  • 28
  • This may work but needs additional constraints. The query for `N=8457894,K=9` hasn't returned yet while I went for a snack. – David Tonhofer May 15 '20 at 22:30
  • 1
    The problem isn't the constraints, it's the labeling. As I observed elsewhere, ordering matters: Change `#=<` to `#>=`, and this will be dramatically faster on small queries like `N=255, K=8` (0.2 sec vs. 3.2 sec for the first solution, 0.1 sec vs. 5.5 sec to explore the rest of the search space). That in itself doesn't help with your big query, but if labeling is changed to label the exponents (the horribly named `_List`) instead of the `Answer`, the big query terminates in 3.3 seconds. @user27815 you might want to edit your answer with these changes. – Isabelle Newbie May 16 '20 at 10:28