我编写了一个谓词
shuffle/3
,它生成两个列表的“洗牌”。当第二个和第三个参数被实例化时,第一个参数变成一个列表,其中包含 Left 和 Right 的所有元素,其顺序与它们在 Left 和 Right 中出现的顺序相同。
例如:
?- shuffle(X, [1, 2], [3, 4]).
X = [1, 3, 2, 4] ;
X = [1, 3, 4, 2] ;
X = [1, 2, 3, 4] ;
X = [3, 4, 1, 2] ;
X = [3, 1, 2, 4] ;
X = [3, 1, 4, 2] ;
false.
这是我想出的实现它的代码:
shuffle([], [], []).
shuffle([H|R], [H|Left], Right) :- shuffle(R, Right, Left).
shuffle([H|R], Left, [H|Right]) :- shuffle(R, Right, Left).
这效果很好,甚至可以为“最通用的查询”生成合理的结果,但它对于任何查询都无法确定,即使是所有参数都完全实例化的查询:
shuffle([1, 2, 3, 4], [1, 2], [3, 4])
。
我真正的问题是:在保持纯度(因此,没有削减)的同时,我能做些什么,这使得当所有参数都完全实例化时,这个谓词具有确定性?
当我在这里时,我是 Prolog 的新手,我想知道是否有人对我为什么关心决定论有建议。它对于真正的 prolog 程序重要吗?
不,没有办法在保持纯代码的同时使这个谓词具有确定性。要看到这一点,请考虑:
?- shuffle([1, 1], [1], [1]).
true
; true.
这个问题有两个答案。为什么?最好的方法是不要使用调试器来理解这一点,而是使用通用查询:
?- shuffle([X1, X2], [Y1], [Y2]).
X1 = Y1, X2 = Y2
; X1 = Y2, X2 = Y1.
所以在这里你可以看到参数之间的“真实”联系!现在我们的特定查询是这个更通用查询的“实例”。因此,无法删除这两个答案。 但是,您可以以纯粹的方式使用 cut,只要它受到保护,以便结果始终是纯粹的。就像测试
ground(shuffe(Xs, Ys, Zs))
但所有这些都是临时的。
再想一想,有一个纯粹的、确定的答案,但前提是shuffle([X1, X2], [Y1], [Y2]).
的答案以某种方式改变。其实答案应该是:
?- shuffledet([X1, X2], [Y1], [Y2]).
X1 = X2, X2 = Y1, Y1 = Y2 % all equal
; dif(X1, X2), X1 = Y1, X2 = Y2
; dif(X1, X2), X1 = Y2, X2 = Y1.
所以这可能是一种可能性......我will
已经为此ASAP下了500的赏金,但没有回应。我再次尝试另一个。
det
版本的
shuffle
的方法是使用库模块if_/3
中的reif
:shuffle_det1( A,B,C):-
if_( B=[], A=C,
if_( C=[], A=B,
( B=[BH|BT], C=[CH|CT], A=[AH|AT], (
AH=BH, shuffle_det1( AT, BT, C)
;
AH=CH, shuffle_det1( AT, B, CT) ) ))).
按位置工作,没问题,并且确实消除了一些(大多数?)虚假选择点:
40 ?- shuffle_det1(X, [1, 2], [3, 4]).
X = [1, 2, 3, 4] ;
X = [1, 3, 2, 4] ;
X = [1, 3, 4, 2] ;
X = [3, 1, 2, 4] ;
X = [3, 1, 4, 2] ;
X = [3, 4, 1, 2].
41 ?- shuffle_det1(X, [11,12], [11,22]).
X = [11, 12, 11, 22] ;
X = [11, 11, 12, 22] ;
X = [11, 11, 22, 12] ;
X = [11, 11, 12, 22] ;
X = [11, 11, 22, 12] ;
X = [11, 22, 11, 12].
81 ?- shuffle_det1([1,2,3,4], [3, 4], [1, 2]).
true.
但是:
82 ?- shuffle_det1([1,2,3,4], [1, 2], [3, 4]).
true ;
false.
此外,正如 [user:false
] 指出的那样,如果两个列表的头元素相等,则答案中存在一些冗余:
11 12 13 .. B
21 22 23 .. C
11 (12.. + 21..) | 21 (11.. + 22..)
12 (13.. + 21..) 11 (12.. + 22..) *
| 21 (12.. + 22..) * | 22 (11.. + 23..)
这里标有
*
的两种情况实际上在
11 == 21
时混为一谈。为了解决这个问题,在这种情况下,我们通过连续执行两次来“展开”拾取:shuffle_det( A,B,C):-
if_( B=[], A=C,
if_( C=[], A=B,
( B=[BH|BT], C=[CH|CT], A=[AH|AT],
if_( \X^(dif(BH,CH),X=true ; BH=CH,X=false),
(
AH=BH, shuffle_det( AT, BT, C)
;
AH=CH, shuffle_det( AT, B, CT) ),
(
AH=BH, AT=[CH|A2], shuffle_det( A2, BT, CT) % **
;
pull_twice( A,B,C)
;
pull_twice( A,C,B)
))))).
pull_twice([BH|AT],[BH|BT],C):- % B,C guaranteed to be non-empty
if_( BT=[], AT=C,
( BT=[BH2|B2], AT=[BH2|A2], shuffle_det(A2,B2,C) )).
测试:
35 ?- shuffle_det(A, [11,12], [11,22]).
A = [11, 11, 12, 22] ;
A = [11, 11, 22, 12] ;
A = [11, 12, 11, 22] ;
A = [11, 22, 11, 12].
这已经比
shuffle_det1
好多了。但还不完全正确:
38 ?- shuffle_det(A, [1], [1]).
A = [1, 1] ;
A = [1, 1] ;
A = [1, 1].
两个
pull_twice
电话可能是罪魁祸首。不知怎的,一定只有一个,这将决定是否做另一个......
reif
包含剪切,因为
->
本质上是剪切,所以:我们使用“纯粹”的定义是什么,难道你真的不想要一个行为明智的程序吗?shuffle([], [], []).
shuffle([H|T], L1, L2) :-
shuffle_l1_(L1, L2, [H|T]).
shuffle_l1_([], L, L).
shuffle_l1_([H1|T1], L2, L) :-
shuffle_l2_(L2, [H1|T1], L).
shuffle_l2_([], L, L).
shuffle_l2_([H2|T2], [H1|T1], [H|T]) :-
% Need to choose side 1 or 2 - using reification
% If both sides are the same, then just choose 1
( (H == H1 ; H1 == H2)
-> Side = 1
; H == H2
-> Side = 2
; true
),
shuffle_side_(Side, [H1|T1], [H2|T2], [H|T]).
shuffle_side_(1, [H|T1], L2, [H|T]) :-
shuffle(T, T1, L2).
shuffle_side_(2, L1, [H|T2], [H|T]) :-
shuffle(T, L1, T2).
这很好地减少了选择点 - 导致 swi-prolog:
?- shuffle([1, 2, 3, 4], [1, 2], [3, 4]).
true.
?- shuffle([1, 2, 3, 4], [3, 4], [1, 2]).
true.
?- shuffle(L, [1], [1]).
L = [1, 1].
?- shuffle(L, [X1, X2], [Y1, Y2]).
L = [X1, X2, Y1, Y2] ;
L = [X1, Y1, X2, Y2] ;
L = [X1, Y1, Y2, X2] ;
L = [Y1, X1, X2, Y2] ;
L = [Y1, X1, Y2, X2] ;
L = [Y1, Y2, X1, X2].
% Improves, but doesn't remove choicepoints completely
?- shuffle(L, [1, 2], [1, 2]).
L = [1, 2, 1, 2] ;
L = [1, 1, 2, 2] ;
false.
为了删除一些不需要的选择点,这种额外的复杂性值得吗?对于大多数用例来说可能不是。