如何解决关于构建列表直到满足条件
我想使用 Prolog 解决 Dan finkel 的 "the giant cat army riddle"。
基本上,您从 [0]
开始,然后使用以下三种操作之一构建此列表:添加 5
、添加 7
或取 sqrt
。如果您设法建立了一个列表,使 2
、10
和 14
以该顺序出现在列表中,并且它们之间可以有其他数字,那么您就成功地完成了游戏。
规则还要求所有元素都是不同的,它们都是 <=60
并且都只是整数。
例如,从 [0]
开始,您可以应用 (add5,add7,add5)
,这将导致 [0,5,12,17]
,但由于它没有 2,10,14 的顺序,它不满足游戏。
我想我已经成功地编写了所需的事实,但我不知道如何实际构建列表。我认为使用 dcg
是一个不错的选择,但我不知道如何。
这是我的代码:
:- use_module(library(lists)).
:- use_module(library(clpz)).
:- use_module(library(dcgs)).
% integer sqrt
isqrt(X,Y) :- Y #>= 0,X #= Y*Y.
% makes sure X occurs before Y and Y occurs before Z
before(X,Y,Z) --> ...,[X],...,[Y],[Z],... .
... --> [].
... --> [_],... .
% in reverse,since the operations are in reverse too.
order(Ls) :- phrase(before(14,2),Ls).
% rule for all the elements to be less than 60.
lt60_(X) :- X #=< 60.
lt60(Ls) :- maplist(lt60_,Ls).
% available operations
add5([L0|Rs],L) :- X #= L0+5,L = [X,L0|Rs].
add7([L0|Rs],L) :- X #= L0+7,L0|Rs].
root([L0|Rs],L) :- isqrt(L0,X),L0|Rs].
% base case,the game stops when Ls satisfies all the conditions.
step(Ls) --> { all_different(Ls),order(Ls),lt60(Ls) }.
% building the list
step(Ls) --> [add5(Ls,L)],step(L).
step(Ls) --> [add7(Ls,step(L).
step(Ls) --> [root(Ls,step(L).
代码发出以下错误,但我没有试图追踪它或任何东西,因为我确信我使用的 DCG 不正确:
?- phrase(step(L),X).
caught: error(type_error(list,_65),sort/2)
我使用的是 Scryer-Prolog,但我认为 swipl
中的所有模块也都可用,例如 clpfd
而不是 clpz
。
解决方法
鉴于问题似乎已从使用 DCG 转移到解决难题,我想我可能会发布更有效的方法。我在 SICStus 上使用 clp(fd),但我包含了一个修改版本,该版本应该与 Scryer 上的 clpz 一起使用(用 my_simple_table/2 替换 table/2)。
:- use_module(library(clpfd)).
:- use_module(library(lists)).
move(X,Y):-
(
X+5#=Y
;
X+7#=Y
;
X#=Y*Y
).
move_table(Table):-
findall([X,Y],(
X in 0..60,Y in 0..60,move(X,Y),labeling([],[X,Y])
),Table).
% Naive version
%%post_move(X,Y):- move(X,Y).
%%
% SICSTUS clp(fd)
%%post_move(X,Y):-
%% move_table(Table),%% table([[X,Y]],Table).
%%
% clpz is mising table/2
post_move(X,Y):-
move_table(Table),my_simple_table([[X,Table).
my_simple_table([[X,Table):-
transpose(Table,[ListX,ListY]),element(N,ListX,X),ListY,Y).
post_moves([_]):-!.
post_moves([X,Y|Xs]):-
post_move(X,post_moves([Y|Xs]).
state(N,Xs):-
length(Xs,N),domain(Xs,60),all_different(Xs),post_moves(Xs),% ordering: 0 is first,2 comes before 10,and 14 is last.
Xs=[0|_],element(I2,Xs,2),element(I10,10),I2#<I10,last(Xs,14).
try_solve(N,Xs):-
state(N,Xs),labeling([ffc],Xs).
try_solve(N,Xs):-
N1 is N+1,try_solve(N1,Xs).
solve(Xs):-
try_solve(1,Xs).
感兴趣的两个笔记:
- 创建一个包含可能移动的表格并使用 table/2 约束比发布约束的分离要高效得多。请注意,我们每次发布时都会重新创建表格,但我们不妨创建一次并传递它。
- 这是使用 element/3 约束来查找和约束感兴趣的数字的位置(在本例中只有 2 和 10,因为我们可以将 14 固定在最后)。同样,这比在解决约束问题后检查顺序作为过滤更有效。
编辑:
这是一个更新版本以符合赏金约束(谓词名称,-希望- SWI 兼容,只创建一次表):
:- use_module(library(clpfd)).
:- use_module(library(lists)).
generate_move_table(Table):-
X in 0..60,( X+5#=Y
#\/ X+7#=Y
#\/ X#=Y*Y
),findall([X,Y]),Table).
%post_move(X,Y,Table):- table([[X,Table). %SICStus
post_move(X,Table):- tuples_in([[X,Table). %swi-prolog
%post_move(X,Table):- my_simple_table([[X,Table). %scryer
my_simple_table([[X,Table):- % Only used as a fall back for Scryer prolog
transpose(Table,Y).
post_moves([_],_):-!.
post_moves([X,Y|Xs],Table):-
post_move(X,Table),post_moves([Y|Xs],Table).
puzzle_(Xs):-
generate_move_table(Table),N in 4..61,indomain(N),length(Xs,%domain(Xs,%SICStus
Xs ins 0..60,%swi-prolog,scryer
all_different(Xs),post_moves(Xs,14 is last.
Xs=[0|_],14).
label_puzzle(Xs):-
labeling([ffc],Xs).
solve(Xs):-
puzzle_(Xs),label_puzzle(Xs).
我没有安装 SWI-prolog,所以我无法测试效率要求(或者它实际上根本运行)但是在我的机器上和 SICStus 上,solve/1
谓词的新版本需要 16到 31 毫秒,而伊莎贝尔的答案 (https://stackoverflow.com/a/65513470/12100620) 中的 puzzle/1
谓词需要 78 到 94 毫秒。
至于优雅,我想这在旁观者的眼中。我喜欢这个表述,它相对清晰,展示了一些非常通用的约束(element/3
、table/2
、all_different/1
),但它的一个缺点是在问题描述中序列(因此 FD 变量的数量)不是固定的,因此我们需要生成所有大小,直到匹配为止。有趣的是,似乎所有解都具有相同的长度,而且 puzzle_/1
的第一个解会生成一个长度正确的列表。
仅使用 dcg
构建列表的替代方法。在构建列表后检查 2,10,14
约束,因此这不是最佳的。
num(X) :- between(0,60,X).
isqrt(X,Y) :- nth_integer_root_and_remainder(2,X,0). %SWI-Prolog
% list that ends with an element.
list([0],0) --> [0].
list(YX,X) --> list(YL,[X],{ append(YL,YX),num(X),\+member(X,YL),(isqrt(Y,X); plus(Y,5,7,X)) }.
soln(X) :-
list(X,_,_),nth0(I2,nth0(I10,nth0(I14,14),I2 < I10,I10 < I14.
?- time(soln(X)).
% 539,187,719 inferences,53.346 CPU in 53.565 seconds (100% CPU,10107452 Lips)
X = [0,12,17,22,29,36,6,11,16,4,2,9,3,15,20,25,30,35,42,49,14]
,
我尝试了一点magic set。谓词 path/2 确实搜索了一条路径,而没有给我们一条路径。因此,我们可以使用 +5 和 +7 的交换性,减少搜索:
step1(X,Y) :- N is (60-X)//5,between(0,N,K),H is X+K*5,M is (60-H)//7,M,J),Y is H+J*7.
step2(X,0).
:- table path/2.
path(X,Y) :- step1(X,H),(Y = H; step2(H,path(J,Y)).
然后我们使用 path/2 作为 path/4 的魔法集:
step(X,Y) :- Y is X+5,Y =< 60.
step(X,Y) :- Y is X+7,0).
/* without magic set */
path0(X,L,L).
path0(X,R) :- step(X,\+ member(H,L),path0(H,[H|L],R).
/* with magic set */
path(X,L).
path(X,path(H,R).
这是一个时间比较:
SWI-Prolog (threaded,64 bits,version 8.3.16)
/* without magic set */
?- time((path0(0,[0],path0(2,H,path0(10,J,14,L))),reverse(L,R),write(R),nl.
% 13,068,776 inferences,0.832 CPU in 0.839 seconds (99% CPU,15715087 Lips)
[0,14]
/* with magic set */
?- abolish_all_tables.
true.
?- time((path(0,path(2,path(10,nl.
% 2,368,325 inferences,0.150 CPU in 0.152 seconds (99% CPU,15747365 Lips)
[0,14]
注意!
,我设法在没有 DCG 的情况下解决了它,在我的机器上解决长度 N=24 需要大约 50 分钟。我怀疑这是因为 order
检查是从头开始对每个列表进行的。
:- use_module(library(lists)).
:- use_module(library(clpz)).
:- use_module(library(dcgs)).
:- use_module(library(time)).
%% integer sqrt
isqrt(X,Y) :- Y #>= 0,X #= Y*Y.
before(X,Z,L) :-
%% L has a suffix [X|T],and T has a suffix of [Y|_].
append(_,[X|T],append(_,[Y|TT],T),[Z|_],TT).
order(L) :- before(2,L).
game([X],X).
game([H|T],H) :- ((X #= H+5); (X #= H+7); (isqrt(H,X))),X #\= H,H #=< 60,X #=< 60,game(T,X). % H -> X.
searchN(N,L) :- length(L,order(L),game(L,0).
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。