Prolog中的算术,用2的幂表示数字

Prolog中的算术,用2的幂表示数字,prolog,coin-change,Prolog,Coin Change,我有两个数字,让我们把它们命名为N和K,我想用K的2次幂写出N 例如,如果N=9和K=4,则N可以是N=1+2+2+4(2^0+2^1+2^1+2^2) 我的程序应该输出类似于N=[1,2,2,4]的内容 我习惯于C++。我在Prolog中找不到解决这个问题的方法。任何帮助都将不胜感激 我想这将是一个使用CLP(FD)的几行程序,但没有骰子。能做得简单些吗 这就是完整的解决方案 不要认为我一次就想到了这一点,这里有一些迭代和死胡同 :- use_module(library(debug)).

我有两个数字,让我们把它们命名为
N
K
,我想用
K
的2次幂写出
N

例如,如果
N=9
K=4
,则
N
可以是
N=1+2+2+4
2^0+2^1+2^1+2^2

我的程序应该输出类似于
N=[1,2,2,4]
的内容


我习惯于C++。我在Prolog中找不到解决这个问题的方法。任何帮助都将不胜感激

我想这将是一个使用CLP(FD)的几行程序,但没有骰子。能做得简单些吗

这就是完整的解决方案

不要认为我一次就想到了这一点,这里有一些迭代和死胡同

:- 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).

这里有一个使用CLP(FD)的方案。一般来说,在Prolog中对整数域进行推理时,CLP(FD)是一种很好的方法。这个特殊问题的想法是递归地思考(就像在许多Prolog问题中一样),并使用“分叉”方法

正如大卫在他的回答中所说的那样,像这样的问题的解决方案并不是第一次就可以解决的。在提出问题的解决方案时,有一些初步的概念、试验实施、测试、观察和修订。即使是这个也需要更多的工作。:)


作为练习,您可以了解如何修改它,使其只生成唯一的解决方案。:)

编辑:下面是一个完整、高效的CLP(FD)解决方案,其中包含一些来自的建议性意见:

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).
?- 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
#>=
对指数排序通过排除冗余排列减少了搜索空间。但它也与标签顺序相关(使用
[]
策略)

核心关系
powersum2\u4
对数字进行约束:

?- 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.
然后标记搜索实际解决方案:

2 ?- binary_partition(9,4,L).
L = [1, 2, 2, 4] ;
L = [1, 2, 2, 4] ;
false.
?- powersum2(5, 31, Solution).
Solution = [16, 8, 4, 2, 1] ;
false.
该解决方案比目前为止的其他解决方案效率更高:

?- 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].
原始版本如下。

这里是另一个CLP(FD)解决方案。其思想是将“二的幂”表示为一个“实”约束,也就是说,不是像潜伏者的
power\u of_2/1
那样枚举数字的谓词。它有助于表示实际约束不是真正的“二次幂”,而是“小于或等于已知边界的二次幂”

下面是一些笨拙的代码,用来计算一个上限为2的幂的列表:

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.
。。。然后发布该约束:

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.
(看到这个以这种语法打印出来,我就知道我可以简化计算约束项的代码…)

然后核心关系是:

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).
当我们贴上标签时,会有点快:

?- 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.
这与潜伏者的方法形成对比,后者甚至需要更长的时间才能找到第一个解决方案:

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).
?- 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
但是,该解决方案比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.
与:

?- 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|...]].
可能还有改进我的限制的空间,或者可能有一些神奇的标签策略来改进搜索

编辑:哈!从最大元素到最小元素的标记会极大地改变性能:

?- 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.

这是大卫·托霍夫版本的100倍。我对这一点很满意:-(<)/> P>忘记你所知道的C++的一切。Prolog完全不同。到目前为止你试过什么?您是否已经学习过Prolog教程并了解了列表处理?这是解决方案的关键组成部分。当然,会有对整数的数值运算。总的来说,解决方案不是大量的代码行。很难说清楚你到底被困在哪里。不是每个案例都有解决办法。例如,
N=11
K=2
。我想你希望它在这种情况下失败。是的,如果失败了,我希望它给我一个空的列表。我看过很多教程,我已经解决了C++中的问题。我试图忘记我的C++解决方案,并用Prolog术语来思考,所以我想找到一种递归的方法来解决这个问题,但是我没有尝试过,我也想到了一个列表,其中包含了PoPoSO2E.x[1,2,4,8……],并且试图用不同的方法将它们添加到一个列表中,这样我就可以得到N。但这似乎不正确。我不是被卡住了,我更多的是在寻找方向感。什么都行!是相关的。作为C++(etc)和Prolog之间的桥梁,探索。不要认为我一次就想到了这一点,这里有一些迭代和死胡同。我听到了,兄弟!我也是!:)请参阅我的最新答案,以获得一个实际、高效的带有CLP(FD)的少数班轮,并从@repeat.Great获得帮助!考虑使用<代码> CPLFD:链/ 2 而不是<代码> ListIONTRONICON/2 .BTW,为什么不简单地写“代码> Zy*=2 ^ p,P>>0 < /代码>并标记<代码> P<代码>代码S,而不是<代码> z < /代码>。谢谢提示。我想我已经尝试了
^
,但没有成功,我确信会有类似
clpfd:chain
的东西,但不知道要搜索什么。我会更新我的答案!并非所有CLPFD都有
(^)/2
,但SWI有。看,与其说是约束,不如说是搜索。请看我的观察,链的顺序(或反转要标记的变量)很重要。至于你的代码,我同意它做了一些与我的CLP(FD)版本相当的事情。但是分析表明,代码中有一半的时间花在断言上,删除断言后,另一半时间花在
is/2
上。我认为
is/2
很慢,因为它必须解释其表达式参数。我认为CLP(FD)约束的内部表示更有效。这可能有效,但需要额外的约束。当我去吃零食时,
N=8457894,K=9的查询还没有返回。问题不是约束,而是标签。正如我在其他地方所观察到的,排序问题:将
#==
,在诸如
N=255,K=8
(0.2秒,而第一个解决方案为3.2秒)这样的小查询上,排序速度会显著加快
?- 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|...]].
?- 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.
my_power_of_two_bound(U,P):-
     U #>= 2^P,
     P #=< U,
     P #>=0.

power2(X,Y):-
     Y #= 2^X.
?- 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).
Answer = [1, 2, 2, 4],
K = 4,
N = 9