Related
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!
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.
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 :-)
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. :)
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
I'm trying to solve the following puzzle in Prolog:
Ten cells numbered 0,...,9 inscribe a 10-digit number such that each cell, say i, indicates the total number of occurrences of the digit i in this number. Find this number. The answer is 6210001000.
This is what I wrote in Prolog but I'm stuck, I think there is something wrong with my ten_digit predicate:
%count: used to count number of occurrence of an element in a list
count(_,[],0).
count(X,[X|T],N) :-
count(X,T,N2),
N is 1 + N2.
count(X,[Y|T],Count) :-
X \= Y,
count(X,T,Count).
%check: f.e. position = 1, count how many times 1 occurs in list and check if that equals the value at position 1
check(Pos,List) :-
count(Pos,List,Count),
valueOf(Pos,List,X),
X == Count.
%valueOf: get the value from a list given the index
valueOf(0,[H|_],H).
valueOf(I,[_|T],Z) :-
I2 is I-1,
valueOf(I2,T,Z).
%ten_digit: generate the 10-digit number
ten_digit(X):-
ten_digit([0,1,2,3,4,5,6,7,8,9],X).
ten_digit([],[]).
ten_digit([Nul|Rest],Digits) :-
check(Nul,Digits),
ten_digit(Rest,Digits).
How do I solve this puzzle?
Check out the clpfd constraint global_cardinality/2.
For example, using SICStus Prolog or SWI:
:- use_module(library(clpfd)).
ten_cells(Ls) :-
numlist(0, 9, Nums),
pairs_keys_values(Pairs, Nums, Ls),
global_cardinality(Ls, Pairs).
Sample query and its result:
?- time((ten_cells(Ls), labeling([ff], Ls))).
1,359,367 inferences, 0.124 CPU in 0.124 seconds (100% CPU, 10981304 Lips)
Ls = [6, 2, 1, 0, 0, 0, 1, 0, 0, 0] ;
319,470 inferences, 0.028 CPU in 0.028 seconds (100% CPU, 11394678 Lips)
false.
This gives you one solution, and also shows that it is unique.
CLP(FD) rules... solving this puzzle in plain Prolog is not easy...
ten_digit(Xs):-
length(Xs, 10),
assign(Xs, Xs, 0).
assign([], _, 10).
assign([X|Xs], L, P) :-
member(X, [9,8,7,6,5,4,3,2,1,0]),
count(L, P, X),
Q is P+1,
assign(Xs, L, Q),
count(L, P, X).
count(L, P, 0) :- maplist(\==(P), L).
count([P|Xs], P, C) :-
C > 0,
B is C-1,
count(Xs, P, B).
count([X|Xs], P, C) :-
X \== P,
C > 0,
count(Xs, P, C).
this is far less efficient than #mat solution:
?- time(ten_digit(L)),writeln(L).
% 143,393 inferences, 0.046 CPU in 0.046 seconds (100% CPU, 3101601 Lips)
[6,2,1,0,0,0,1,0,0,0]
L = [6, 2, 1, 0, 0, 0, 1, 0, 0|...] ;
% 11,350,690 inferences, 3.699 CPU in 3.705 seconds (100% CPU, 3068953 Lips)
false.
count/3 acts in a peculiar way... it binds free variables up to the current limit, then check no more are bounded.
edit adding a cut, the snippet becomes really fast:
...
assign(Xs, L, Q),
!, count(L, P, X).
?- time(ten_digit(L)),writeln(L).
% 137,336 inferences, 0.045 CPU in 0.045 seconds (100% CPU, 3075529 Lips)
[6,2,1,0,0,0,1,0,0,0]
L = [6, 2, 1, 0, 0, 0, 1, 0, 0|...] ;
% 3 inferences, 0.000 CPU in 0.000 seconds (86% CPU, 54706 Lips)
false.
Sorry, I could not resist. This problem can also be conveniently expressed as a Mixed Integer Programming (MIP) model. A little bit more mathy than Prolog.
The results are the same:
---- VAR n digit i
LOWER LEVEL UPPER MARGINAL
digit0 -INF 6.0000 +INF .
digit1 -INF 2.0000 +INF .
digit2 -INF 1.0000 +INF .
digit3 -INF . +INF .
digit4 -INF . +INF .
digit5 -INF . +INF .
digit6 -INF 1.0000 +INF .
digit7 -INF . +INF .
digit8 -INF . +INF .
digit9 -INF . +INF .
I'm trying to write langford sequence.
like this:
73 ?- langford4(L).
L = [4, 1, 3, 1, 2, 4, 3, 2] ;
L = [2, 3, 4, 2, 1, 3, 1, 4] ;
This is what i have done:
prefix([H|T],L):-cat([H|T],_,L).
sublist(S,L):-prefix(P,L), posfix(S,P).
posfix([H|T],L):-cat(_,[H|T],L).
langford42(L):-
L = [_,_,_,_,_,_,_,_],
sublist([1,_,1], L),
sublist([2,_,_,2], L),
sublist([3,_,_,_,3], L),
sublist([4,_,_,_,_,4], L).
or this:
langford(L):-
[X,_,_,_,_,X,_,_],
[_,Y,_,Y,_,_,_,_],
[_,_,Z,_,_,_,Z,_],
[_,_,_,_,P,_,_,P].
thanks.
don't get your question, your code seems fine, but anyway the problem, when generalized, is nice: I tried solving with CLP(FD) and the simpler library builtins
% two copies of each number k are k units apart
% constraint solution: would be nice to know how we could speedup this one...
langford_c(N, S) :-
M is N*2,
length(S, M),
S ins 1..N,
distances(S, S),
findall(I-2, between(1,N,I), Cs),
global_cardinality(S, Cs),
label(S).
distances([N|T], S) :-
element(I, S, N),
element(J, S, N),
J #= I + N + 1,
distances(T, S).
distances([], _).
% simple nth1/3 based solution
langford_n(N, S) :-
M is N*2,
length(S, M),
distances(S, 1, N).
distances(S, P, C) :-
P =< C, !,
nth1(I, S, P),
nth1(J, S, P),
J is I + P + 1,
Q is P + 1,
distances(S, Q, C).
distances(_, _, _).
with these result
?- time(langford_n(4, S)).
% 1,102 inferences, 0.000 CPU in 0.000 seconds (98% CPU, 2598909 Lips)
S = [4, 1, 3, 1, 2, 4, 3, 2] ;
% 1,404 inferences, 0.001 CPU in 0.001 seconds (99% CPU, 2679103 Lips)
S = [2, 3, 4, 2, 1, 3, 1, 4] ;
% 1,234 inferences, 0.001 CPU in 0.001 seconds (54% CPU, 2064308 Lips)
false.
?- time(langford_c(4, S)).
% 1,302,863 inferences, 0.489 CPU in 0.491 seconds (100% CPU, 2664067 Lips)
S = [2, 3, 4, 2, 1, 3, 1, 4] ;
% 958,979 inferences, 0.367 CPU in 0.371 seconds (99% CPU, 2611630 Lips)
S = [4, 1, 3, 1, 2, 4, 3, 2] ;
% 359,396 inferences, 0.137 CPU in 0.141 seconds (98% CPU, 2614215 Lips)
false.
CLPFD-systems are not primarily targeted to handle quadratic equations efficiently, nevertheless, are there better ways to formulate problems like the following?
It seems the problem boils down to equations like the following. SWI with library(clpfd) gave:
?- time( ((L+7)^2#=L^2+189, L in 0..10000000) ).
% 252,169,718 inferences, 87208.554 CPU in 87445.038 seconds (100% CPU, 2892 Lips)
ERROR: Out of local stack
But now the latest version in SWI gives
?- time( ((L+7)^2#=L^2+189, L in 0..10000000) ).
% 3,805,545,940 inferences, 868.635 CPU in 870.311 seconds (100% CPU, 4381063 Lips)
L = 10.
and in SICStus 4.3beta7 I get:
| ?- statistics(runtime,_).
yes
| ?- (L+7)*(L+7)#=L*L+189, L in 0..10000000.
L = 10 ? ;
no
| ?- statistics(runtime,[_,T_ms]).
T_ms = 2550 ? ;
no
To solve this quickly, a constraint solver that has already a primitive X #= Y^2 constraint, might also implement the following rules:
Rule #1:
X #= (Y+C)^2 --> H #= Y^2, X #= H + 2*C*Y + C^2
% where C is an integer and X,Y variables
Rule #2:
X #= Z^2, Y #= Z^2 --> X #= Z^2, X #= Y.
% where X,Y,Z are variables
The above rules will reduce the equation to a linear equation, which is anyway directly solved by a decent CLP(FD) system. Here you see the difference between a system with and without rule #2:
Without Rule #2:
?- (L+7)*(L+7) #= L*L+189, stored.
_C #= 140+_B-14*L.
_B #>= 0.
_C #>= 0.
_B #= L*L.
_C #= L*L.
Yes
With Rule #2:
?- (L+7)*(L+7) #= L*L+189, stored.
L = 10
?- statistics(time, S), (L+7)*(L+7) #= L*L+189, statistics(time, T), U is T-S.
U = 3
But rule #2 looks a little ad hoc to me. Not yet sure whether one should keep it.
Bye
clpBNR fares better:
% *** clpBNR v0.9.13alpha ***.
Welcome to SWI-Prolog (threaded, 64 bits, version 8.4.3)
?- time(( { (L+7)**2 == L**2+189 }, L::integer(0, 10_000_000), solve(L) )).
% 1,269,854 inferences, 0.163 CPU in 0.164 seconds (100% CPU, 7770826 Lips)
L = 10 ;
% 271,125,405 inferences, 40.874 CPU in 40.957 seconds (100% CPU, 6633143 Lips)
false.
Comparing clpfd and clpBNR using a range that succeeds in a reasonable timescale:
?- use_module(library(clpfd)).
?- time( ((L+7)^2#=L^2+189, L in 0..90_000) ).
% 13,609,198 inferences, 11.482 CPU in 11.497 seconds (100% CPU, 1185274 Lips)
L = 10.
?- time(( { (L+7)**2 == L**2+189 }, L::integer(0, 90_000), solve(L) )).
% 540,550 inferences, 0.107 CPU in 0.107 seconds (100% CPU, 5058454 Lips)
L = 10 ;
% 2,105,638 inferences, 0.320 CPU in 0.321 seconds (100% CPU, 6578814 Lips)
false.
I already made a working generalized verbal arithmetic solver in Prolog but it's too slow. It takes 8 minutes just to run the simple expression S E N D + M O R E = M O N E Y. Can someone help me make it run faster?
/* verbalArithmetic(List,Word1,Word2,Word3) where List is the list of all
possible letters in the words. The SEND+MORE = MONEY expression would then
be represented as
verbalArithmetic([S,E,N,D,M,O,R,Y],[S,E,N,D],[M,O,R,E],[M,O,N,E,Y]). */
validDigit(X) :- member(X,[0,1,2,3,4,5,6,7,8,9]).
validStart(X) :- member(X,[1,2,3,4,5,6,7,8,9]).
assign([H|[]]) :- validDigit(H).
assign([H|Tail]) :- validDigit(H), assign(Tail), fd_all_different([H|Tail]).
findTail(List,H,T) :- append(H,[T],List).
convert([T],T) :- validDigit(T).
convert(List,Num) :- findTail(List,H,T), convert(H,HDigit), Num is (HDigit*10+T).
verbalArithmetic(WordList,[H1|Tail1],[H2|Tail2],Word3) :-
validStart(H1), validStart(H2), assign(WordList),
convert([H1|Tail1],Num1),convert([H2|Tail2],Num2), convert(Word3,Num3),
Sum is Num1+Num2, Num3 = Sum.
Consider using finite domain constraints, for example, in SWI-Prolog:
:- use_module(library(clpfd)).
puzzle([S,E,N,D] + [M,O,R,E] = [M,O,N,E,Y]) :-
Vars = [S,E,N,D,M,O,R,Y],
Vars ins 0..9,
all_different(Vars),
S*1000 + E*100 + N*10 + D +
M*1000 + O*100 + R*10 + E #=
M*10000 + O*1000 + N*100 + E*10 + Y,
M #\= 0, S #\= 0.
Example query:
?- time((puzzle(As+Bs=Cs), label(As))).
% 5,803 inferences, 0.002 CPU in 0.002 seconds (98% CPU, 3553582 Lips)
As = [9, 5, 6, 7],
Bs = [1, 0, 8, 5],
Cs = [1, 0, 6, 5, 2] ;
% 1,411 inferences, 0.001 CPU in 0.001 seconds (97% CPU, 2093472 Lips)
false.
Poor performance here is due to forming all possible letter assignments before checking if any are feasible.
My advice is "fail early, fail often". That is, push as many checks for failure as early as possible into the assignment steps, thus pruning the search tree.
Klas Lindbäck makes some good suggestions. As a generalization, when adding two numbers the carry is at most one in each place. So the assignment of distinct digits to letters from left to right can be checked with allowance for the possibility of an as-yet-undetermined carry in the rightmost places. (Of course in the final "units" place, there is no carry.)
It's a lot to think about, which is why constraint logic, as mat suggests (and which you've already broached with fd_all_different/1), is such a convenience.
Added: Here's a Prolog solution without constraint logic, using just one auxiliary predicate omit/3:
omit(H,[H|T],T).
omit(X,[H|T],[H|Y]) :- omit(X,T,Y).
which both selects an item from a list and produces the shortened list without that item.
Here then is the code for sendMoreMoney/3 that searches by evaluating the sum from left to right:
sendMoreMoney([S,E,N,D],[M,O,R,E],[M,O,N,E,Y]) :-
M = 1,
omit(S,[2,3,4,5,6,7,8,9],PoolO),
(CarryS = 0 ; CarryS = 1),
%% CarryS + S + M = M*10 + O
O is (CarryS + S + M) - (M*10),
omit(O,[0|PoolO],PoolE),
omit(E,PoolE,PoolN),
(CarryE = 0 ; CarryE = 1),
%% CarryE + E + O = CarryS*10 + N
N is (CarryE + E + O) - (CarryS*10),
omit(N,PoolN,PoolR),
(CarryN = 0 ; CarryN = 1),
%% CarryN + N + R = CarryE*10 + E
R is (CarryE*10 + E) - (CarryN + N),
omit(R,PoolR,PoolD),
omit(D,PoolD,PoolY),
%% D + E = CarryN*10 + Y
Y is (D + E) - (CarryN*10),
omit(Y,PoolY,_).
We get off to a quick start by observing that M must be the nonzero carry from the leftmost digits sum, hence 1, and that S must be some other nonzero digit. The comments show steps where additional letters may be deterministically assigned values based on choices already made.
Added(2): Here is a "general" cryptarithm solver for two summands, which need not have the same length/number of "places". Code for length/2 is omitted as a fairly common built-in predicate, and taking up the suggestion by Will Ness, calls to omit/3 are replaced by select/3 for convenience of SWI-Prolog users.
I've tested this with Amzi! and SWI-Prolog using those alphametics examples from Cryptarithms.com which involve two summands, each of which has a unique solution. I also made up an example with a dozen solutions, I + AM = BEN, to test proper backtracking.
solveCryptarithm([H1|T1],[H2|T2],Sum) :-
operandAlign([H1|T1],[H2|T2],Sum,AddTop,AddPad,Carry,TSum,Pool),
solveCryptarithmAux(H1,H2,AddTop,AddPad,Carry,TSum,Pool).
operandAlign(Add1,Add2,Sum,AddTop,AddPad,Carry,TSum,Pool) :-
operandSwapPad(Add1,Add2,Length,AddTop,AddPad),
length(Sum,Size),
( Size = Length
-> ( Carry = 0, Sum = TSum , Pool = [1|Peel] )
; ( Size is Length+1, Carry = 1, Sum = [Carry|TSum], Pool = Peel )
),
Peel = [2,3,4,5,6,7,8,9,0].
operandSwapPad(List1,List2,Length,Longer,Padded) :-
length(List1,Length1),
length(List2,Length2),
( Length1 >= Length2
-> ( Length = Length1, Longer = List1, Shorter = List2, Pad is Length1 - Length2 )
; ( Length = Length2, Longer = List2, Shorter = List1, Pad is Length2 - Length1 )
),
zeroPad(Shorter,Pad,Padded).
zeroPad(L,0,L).
zeroPad(L,K,P) :-
K > 0,
M is K-1,
zeroPad([0|L],M,P).
solveCryptarithmAux(_,_,[],[],0,[],_).
solveCryptarithmAux(NZ1,NZ2,[H1|T1],[H2|T2],CarryOut,[H3|T3],Pool) :-
( CarryIn = 0 ; CarryIn = 1 ), /* anticipatory carry */
( var(H1)
-> select(H1,Pool,P_ol)
; Pool = P_ol
),
( var(H2)
-> select(H2,P_ol,P__l)
; P_ol = P__l
),
( var(H3)
-> ( H3 is H1 + H2 + CarryIn - 10*CarryOut, select(H3,P__l,P___) )
; ( H3 is H1 + H2 + CarryIn - 10*CarryOut, P__l = P___ )
),
NZ1 \== 0,
NZ2 \== 0,
solveCryptarithmAux(NZ1,NZ2,T1,T2,CarryIn,T3,P___).
I think this illustrates that the advantages of left-to-right search/evaluation can be attained in a "generalized" solver, increasing the number of inferences by roughly a factor of two in comparison with the earlier "tailored" code.
Note: This answer discusses an algorithm for reducing the number of combinations that need to be tried. I don't know Prolog, so I can't provide any code snippets.
The trick to speed up a brute force solution is shortcuts. If you can identify a range of combinations that are invalid you can reduce the number of combinations substantially.
Take the example in hand. When a human solves it, she immediately notices that MONEY has 5 digits while SEND and MORE only have 4, so the M in MONEY must be the digit 1. 90% of the combinations gone!
When constructing an algorithm for a computer, we try to use shortcuts that apply to all possible input first. If they fail to give the required performance we start looking at shortcuts that only apply to specific combinations of input.
So we leave the M=1 shortcut for now.
Instead, I would focus on the last digits.
We know that (D+E) mod 10 = Y.
That's our 90% reduction in the number of combinations to try.
That step should bring exacution to just under a minute.
What can we do if that's not enough?
Next step:
Look at the second to last digit!
We know that (N+R+carry from D+E) mod 10 = E.
Since we are testing through all valid combinations of the last digit, for each test we will know whether the carry is 0 or 1.
A complication (for the code) that further reduces the number of combinations to be tested is that we will encounter duplicates (a letter gets mapped to a number that is already assigned to another letter). When we encounter a duplicate, we can advance to the next combination without going further down the chain.
Good luck with your assignment!
Here's my take on it. I use clpfd, dcg,
and meta-predicate mapfoldl/5:
:- meta_predicate mapfoldl(4,?,?,?,?).
mapfoldl(P_4,Xs,Zs, S0,S) :-
list_mapfoldl_(Xs,Zs, S0,S, P_4).
:- meta_predicate list_mapfoldl_(?,?,?,?,4).
list_mapfoldl_([],[], S,S, _).
list_mapfoldl_([X|Xs],[Y|Ys], S0,S, P_4) :-
call(P_4,X,Y,S0,S1),
list_mapfoldl_(Xs,Ys, S1,S, P_4).
Let's put mapfoldl/5 to good use and do some verbal arithmetic!
:- use_module(library(clpfd)).
:- use_module(library(lambda)).
digits_number(Ds,Z) :-
Ds = [D0|_],
Ds ins 0..9,
D0 #\= 0, % most-significant digit must not equal 0
reverse(Ds,Rs),
length(Ds,N),
numlist(1,N,Es), % exponents (+1)
maplist(\E1^V^(V is 10**(E1-1)),Es,Ps),
scalar_product(Ps,Rs,#=,Z).
list([]) --> [].
list([E|Es]) --> [E], list(Es).
cryptarithexpr_value([V|Vs],X) -->
{ digits_number([V|Vs],X) },
list([V|Vs]).
cryptarithexpr_value(T0,T) -->
{ functor(T0,F,A) },
{ dif(F-A,'.'-2) },
{ T0 =.. [F|Args0] },
mapfoldl(cryptarithexpr_value,Args0,Args),
{ T =.. [F|Args] }.
crypt_arith_(Expr,Zs) :-
phrase(cryptarithexpr_value(Expr,Goal),Zs0),
( member(Z,Zs0), \+var(Z)
-> throw(error(uninstantiation_error(Expr),crypt_arith_/2))
; true
),
sort(Zs0,Zs),
all_different(Zs),
call(Goal).
Quick and dirty hack to dump all solutions found:
solve_n_dump(Opts,Eq) :-
( crypt_arith_(Eq,Zs),
labeling(Opts,Zs),
format('Eq = (~q), Zs = ~q.~n',[Eq,Zs]),
false
; true
).
solve_n_dump(Eq) :- solve_n_dump([],Eq).
Let's try it!
?- solve_n_dump([S,E,N,D]+[M,O,R,E] #= [M,O,N,E,Y]).
Eq = ([9,5,6,7]+[1,0,8,5]#=[1,0,6,5,2]), Zs = [9,5,6,7,1,0,8,2].
true.
?- solve_n_dump([C,R,O,S,S]+[R,O,A,D,S] #= [D,A,N,G,E,R]).
Eq = ([9,6,2,3,3]+[6,2,5,1,3]#=[1,5,8,7,4,6]), Zs = [9,6,2,3,5,1,8,7,4].
true.
?- solve_n_dump([F,O,R,T,Y]+[T,E,N]+[T,E,N] #= [S,I,X,T,Y]).
Eq = ([2,9,7,8,6]+[8,5,0]+[8,5,0]#=[3,1,4,8,6]), Zs = [2,9,7,8,6,5,0,3,1,4].
true.
?- solve_n_dump([E,A,U]*[E,A,U] #= [O,C,E,A,N]).
Eq = ([2,0,3]*[2,0,3]#=[4,1,2,0,9]), Zs = [2,0,3,4,1,9].
true.
?- solve_n_dump([N,U,M,B,E,R] #= 3*[P,R,I,M,E]).
% same as: [N,U,M,B,E,R] #= [P,R,I,M,E]+[P,R,I,M,E]+[P,R,I,M,E]
Eq = (3*[5,4,3,2,8]#=[1,6,2,9,8,4]), Zs = [5,4,3,2,8,1,6,9].
true.
?- solve_n_dump(3*[C,O,F,F,E,E] #= [T,H,E,O,R,E,M]).
Eq = (3*[8,3,1,1,9,9]#=[2,4,9,3,5,9,7]), Zs = [8,3,1,9,2,4,5,7].
true.
Let's do some more and try some different labeling options:
?- time(solve_n_dump([],[D,O,N,A,L,D]+[G,E,R,A,L,D] #= [R,O,B,E,R,T])).
Eq = ([5,2,6,4,8,5]+[1,9,7,4,8,5]#=[7,2,3,9,7,0]), Zs = [5,2,6,4,8,1,9,7,3,0].
% 35,696,801 inferences, 3.929 CPU in 3.928 seconds (100% CPU, 9085480 Lips)
true.
?- time(solve_n_dump([ff],[D,O,N,A,L,D]+[G,E,R,A,L,D] #= [R,O,B,E,R,T])).
Eq = ([5,2,6,4,8,5]+[1,9,7,4,8,5]#=[7,2,3,9,7,0]), Zs = [5,2,6,4,8,1,9,7,3,0].
% 2,902,871 inferences, 0.340 CPU in 0.340 seconds (100% CPU, 8533271 Lips)
true.
Will Ness style, generalized (but assuming length(A) <= length(B)) solver:
money_puzzle(A, B, C) :-
maplist(reverse, [A,B,C], [X,Y,Z]),
numlist(0, 9, Dom),
swc(0, Dom, X,Y,Z),
A \= [0|_], B \= [0|_].
swc(C, D0, [X|Xs], [Y|Ys], [Z|Zs]) :-
peek(D0, X, D1),
peek(D1, Y, D2),
peek(D2, Z, D3),
S is X+Y+C,
( S > 9 -> Z is S - 10, C1 = 1 ; Z = S, C1 = 0 ),
swc(C1, D3, Xs, Ys, Zs).
swc(C, D0, [], [Y|Ys], [Z|Zs]) :-
peek(D0, Y, D1),
peek(D1, Z, D2),
S is Y+C,
( S > 9 -> Z is S - 10, C1 = 1 ; Z = S, C1 = 0 ),
swc(C1, D2, [], Ys, Zs).
swc(0, _, [], [], []).
swc(1, _, [], [], [1]).
peek(D, V, R) :- var(V) -> select(V, D, R) ; R = D.
performance:
?- time(money_puzzle([S,E,N,D],[M,O,R,E],[M,O,N,E,Y])).
% 38,710 inferences, 0.016 CPU in 0.016 seconds (100% CPU, 2356481 Lips)
S = 9,
E = 5,
N = 6,
D = 7,
M = 1,
O = 0,
R = 8,
Y = 2 ;
% 15,287 inferences, 0.009 CPU in 0.009 seconds (99% CPU, 1685686 Lips)
false.
?- time(money_puzzle([D,O,N,A,L,D],[G,E,R,A,L,D],[R,O,B,E,R,T])).
% 14,526 inferences, 0.008 CPU in 0.008 seconds (99% CPU, 1870213 Lips)
D = 5,
O = 2,
N = 6,
A = 4,
L = 8,
G = 1,
E = 9,
R = 7,
B = 3,
T = 0 ;
% 13,788 inferences, 0.009 CPU in 0.009 seconds (99% CPU, 1486159 Lips)
false.
You have
convert([A,B,C,D]) => convert([A,B,C])*10 + D
=> (convert([A,B])*10+C)*10+D => ...
=> ((A*10+B)*10+C)*10+D
So, you can express this with a simple linear recursion.
More importantly, when you pick one possible digit from your domain 0..9, you shouldn't use that digit anymore for subsequent choices:
selectM([A|As],S,Z):- select(A,S,S1),selectM(As,S1,Z).
selectM([],Z,Z).
select/3 is available in SWI Prolog. Armed with this tool, you can select your digits gradually from your thus narrowing domain:
money_puzzle( [[S,E,N,D],[M,O,R,E],[M,O,N,E,Y]]):-
Dom = [0,1,2,3,4,5,6,7,8,9],
selectM([D,E], Dom,Dom1), add(D,E,0, Y,C1), % D+E=Y
selectM([Y,N,R],Dom1,Dom2), add(N,R,C1,E,C2), % N+R=E
select( O, Dom2,Dom3), add(E,O,C2,N,C3), % E+O=N
selectM([S,M], Dom3,_), add(S,M,C3,O,M), % S+M=MO
S \== 0, M \== 0.
We can add two digits with a carry, add produce a resulting digit with new carry (say, 4+8 (0) = 2 (1) i.e. 12):
add(A,B,C1,D,C2):- N is A+B+C1, D is N mod 10, C2 is N // 10 .
Thus implemented, money_puzzle/1 runs instantaneously, thanks to the gradual nature in which the digits are picked and tested right away:
?- time( money_puzzle(X) ).
% 27,653 inferences, 0.02 CPU in 0.02 seconds (100% CPU, 1380662 Lips)
X = [[9, 5, 6, 7], [1, 0, 8, 5], [1, 0, 6, 5, 2]] ;
No
?- time( (money_puzzle(X),fail) ).
% 38,601 inferences, 0.02 CPU in 0.02 seconds (100% CPU, 1927275 Lips)
The challenge becomes now to make it generic.