我正处于学习prolog的“早期阶段”,并且遇到了一个易于实现接缝的逻辑谜语:
Link to the riddle | Link to solution
我们正在寻找满足以下条件的10位数字:
...
我想我首先需要对.pl文件实施规则吗? 解决方案的规则是:
我在prolog中阅读了多个规则的介绍,但仍然没有得到它是如何做到的。有人可以帮忙吗?会很棒:))
答案 0 :(得分:3)
由于您已使用clpfd对此进行了标记,因此我想使用约束的其他信息来扩充现有答案。
重要的是,通过约束,您可以通过修剪为您搜索空间来避免生成所有组合。
我们可以从将数字的列表与这些数字所描述的整数相关联来开始:
digits_integer(Ds0, I) :- reverse(Ds0, Ds), Ds0 ins 0..9, foldl(pow, Ds, 0-0, I-_). pow(D, I0-P0, I-P) :- I #= I0 + D*10^P0, P #= P0 + 1.
以下是两个示例查询:
?- digits_integer([1,2,3], I). I = 123. ?- digits_integer(Ds, 302). Ds = [3, 0, 2] .
接下来,让我们描述N
列表Ls
的长度为N
的前缀可分割:
n_divisible(Ls, N) :- length(Prefix, N), append(Prefix, _, Ls), digits_integer(Prefix, I), I mod N #= 0.
整个解决方案因此可以描述为:
solution(Ds) :- length(Ds, 10), Ds ins 0..9, all_distinct(Ds), E in 2..10, findall(E, indomain(E), Es), maplist(n_divisible(Ds), Es).
示例查询:
?- solution(Ds), label(Ds). Ds = [3, 8, 1, 6, 5, 4, 7, 2, 9, 0] ; false.
让我们简要比较两种解决方案的性能:
?- time((puzzle(Vs),false)). % 142,709,119 inferences, 14.865 CPU in 14.867 seconds
VS:
?- time((solution(Ds),label(Ds),false)). % 19,384,173 inferences, 2.166 CPU in 2.166 seconds
因此,在这个具体案例中,基于约束的方法快几倍。这主要是由于约束传播的强大功能,解算器会自动执行 。
答案 1 :(得分:3)
这是与CLP(FD)略有不同的方法。首先让我们考虑一个谓词,它描述一个列表,它的前n个元素和那些n个元素产生的数量之间的关系。这个版本有点类似,但不如@ mat的digits_integer/2
。
:- use_module(library(clpfd)).
digits_firstn_number_(_D,0,Num,Num).
digits_firstn_number_([D|Ds],X1,Num,Acc0) :-
X1 #> 0,
X0 #= X1-1,
Acc1 #= Acc0*10+D,
digits_firstn_number_(Ds,X0,Num,Acc1).
调用谓词num/1
包含一个目标num_/2
,用于描述实际关系,第二个目标label/1
包含数字列表num_/2
,它是{{的第二个参数1}}。与@ mat的版本num/1
的细微差别是实际数字而不是数字列表作为参数:
num(Num) :-
num_(Num,Digits), % <- actual relation
label(Digits). % <- labeling the digits
实际关系num_/2
就此而言是不同的,因为在任何可能的情况下,可分性规则表示为对各个数字的约束(如您所链接的解决方案中所建议的那样)而不是相应的数字:
num_(Num,Digits) :-
Digits=[A,B,C,D,E,F,G,H,I,J],
Digits ins 0..9,
all_distinct(Digits), % divisibility for:
0 #= B mod 2, % <- first 2 digits
0 #= (A+B+C) mod 3, % <- first 3 digits
digits_firstn_number_([C,D],2,Num4,0), % <- first 4 digits
0 #= Num4 mod 4, % <- first 4 digits
0 #= (E) mod 5, % <- first 5 digits
0 #= (A+B+C+D+E+F) mod 3, % <- first 6 digits
0 #= F mod 2, % <- first 6 digits
digits_firstn_number_(Digits,7,Num7,0), % <- first 7 digits
0 #= Num7 mod 7, % <- first 7 digits
digits_firstn_number_([F,G,H],3,Num8,0), % <- first 8 digits
0 #= Num8 mod 8, % <- first 8 digits
0 #= (A+B+C+D+E+F+G+H+I) mod 9, % <- first 9 digits
J #= 0, % <- all 10 digits
digits_firstn_number_(Digits,10,Num,0). % <- the actual number
这种方法的缺点(除了更多的代码)是,它非常适合这个特定的拼图,而@ mat的版本可以更容易地扩展到搜索具有相似约束的不同数字位数(前n位数)被n)整除。从好的方面来看,这种方法更快(与SWI-Prolog(多线程,64位,版本6.6.4)相比):
?- time((num(Num),false)).
% 2,544,064 inferences, 0.486 CPU in 0.486 seconds (100% CPU, 5235403 Lips)
false.
?- time((solution(Ds),label(Ds),false)).
% 19,289,281 inferences, 3.323 CPU in 3.324 seconds (100% CPU, 5805472 Lips)
false.
当然,num/1
会产生相同的解决方案:
?- num(Num).
Num = 3816547290 ;
false.
答案 2 :(得分:1)
在Prolog中解决此类问题的基本方法是生成所有可能性,然后对其进行过滤。在这种情况下,我们需要一个没有重复的十位数列表,每个长度为N的前缀应该可以被N整除。
puzzle([A,B,C,D,E,F,G,H,I,J]) :-
select(A,[0,1,2,3,4,5,6,7,8,9],List1),
select(B,List1,List2), select(C,List2,List3), select(D,List3,List4),
select(E,List4,List5), select(F,List5,List6), select(G,List6,List7),
select(H,List7,List8), select(I,List8,List9), List9 = [J],
divisible([A,B],2),
divisible([A,B,C],3),
divisible([A,B,C,D],4),
divisible([A,B,C,D,E],5),
divisible([A,B,C,D,E,F],6),
divisible([A,B,C,D,E,F,G],7),
divisible([A,B,C,D,E,F,G,H],8),
divisible([A,B,C,D,E,F,G,H,I],9),
divisible([A,B,C,D,E,F,G,H,I,J],10).
甚至可以轻松实现可分割性:
divisible(Is,D) :-
combine(Is,N),
R is N rem D, R == 0.
但是,我们还需要一堆技术性来在整数,字符和原子之间进行转换。
:- use_module(library(lists)).
combine(Is,N) :-
maplist(conv,Is,As), concat_list(As,A),
atom_chars(A,Cs), number_chars(N,Cs).
conv(I,A) :-
number_chars(I,[C]), atom_chars(A,[C]).
concat_list([A1,A2|As],Atom) :-
atom_concat(A1,A2,A3),
concat_list([A3|As],Atom).
concat_list([A],A).
这会产生链接中显示的结果:
| ?- puzzle(X).
X = [3,8,1,6,5,4,7,2,9,0] ? ;
no
| ?-
另外一点:如果你想让它变得更快,而不是像其他人一样买一台更大的电脑,那么你可以将这一代电路交错。测试部分代码:
puzzle2([A,B,C,D,E,F,G,H,I,J]) :-
select(A,[0,1,2,3,4,5,6,7,8,9],List1),
select(B,List1,List2), divisible([A,B],2),
select(C,List2,List3), divisible([A,B,C],3),
select(D,List3,List4), divisible([A,B,C,D],4),
select(E,List4,List5), divisible([A,B,C,D,E],5),
select(F,List5,List6), divisible([A,B,C,D,E,F],6),
select(G,List6,List7), divisible([A,B,C,D,E,F,G],7),
select(H,List7,List8), divisible([A,B,C,D,E,F,G,H],8),
select(I,List8,List9), divisible([A,B,C,D,E,F,G,H,I],9),
List9 = [J], divisible([A,B,C,D,E,F,G,H,I,J],10).
使用SWI Prolog,我得到以下时间:
?- time((puzzle(_),false)).
32m% 142,709,118 inferences, 76.333 CPU in 76.650 seconds (100% CPU, 1869553 Lips)
?- time((puzzle2(_),false)).
32m% 157,172 inferences, 0.142 CPU in 0.144 seconds (98% CPU, 1108945 Lips)
?- time((num(_),false)).
32m% 2,802,204 inferences, 1.008 CPU in 1.028 seconds (98% CPU, 2779208 Lips)
这似乎表明puzzle2
版本比下面给出的num
版本快几倍。