如果我要求 X,它会生成重复项,但如果我明确询问,它会在重复项上失败

If I ask for X, it generates duplicates, but it fails on duplicates if I ask explicitly

我正在尝试在棋盘上的特定位置生成棋子。一个要求是两个棋子不能占据同一个位置。 所以棋盘作为一个列表,不能包含重复的位置值条目。

我未能删除生成部分中的重复项,否则如果我明确要求,它会在重复项上正确失败。

role(k).
role(r).

color(w).
color(b).

piece(X-Y) :- color(X), role(Y).

piese(X-Y) :- piece(X), pos_(Y).

piese_pos(X, Y) :- X=_-_-Y.

board(Ps) :- maplist(piese_pos, Ps, Ls), is_set(Ls), maplist(piese, Ps).

pos_(a-1).
pos_(a-2).
/*
When I ask board board(X). This is one of the enumerations:
X = [w-k-(a-1), b-k-(a-2), w-r-(a-2)] ;
as you can see a-2 is duplicated.

But if I ask for a duplicate explicitly, it returns false as correct.
[11]  ?- board([w-k-(a-1), b-r-(a-1)]).
false.

[11]  ?- board([w-k-(a-1), b-r-(a-2)]).
true.
*/

% 
is_set(Lst) :-
  setof(X, member(X, Lst), Set),
  length(Lst, N),
  length(Set, N).

board 规则似乎生成了“错误”的解决方案,因为在进行 is_set 检查时尚未绑定任何值。当您传入具有实际值的结果时,is_set 将如预期的那样为 false。要看到这种情况,运行 只有 board/1 的前两个条件:

?- maplist(piese_pos, Ps, Ls), is_set(Ls).
Ps = [_2962-_2964-_2950],
Ls = [_2950] ;
Ps = [_2962-_2964-_2950, _4224-_4226-_4212],
Ls = [_2950, _4212] ;
Ps = [_2962-_2964-_2950, _4224-_4226-_4212, _5550-_5552-_5538],
Ls = [_2950, _4212, _5538] ;
Ps = [_2962-_2964-_2950, _4224-_4226-_4212, _5550-_5552-_5538, _6932-_6934-_6920],
Ls = [_2950, _4212, _5538, _6920] ;
...

虽然这似乎正在生成答案的“结构”,但尚未绑定任何值,is_set 调用将始终在空变量位置上成功。切换谓词的顺序会产生您可能正在寻找的结果,但会突出显示另一个问题:

?- maplist(piese_pos, Ps, Ls), maplist(piese, Ps), is_set(Ls).
Ps = [w-k-(a-1)],
Ls = [a-1] ;
Ps = [w-k-(a-2)],
Ls = [a-2] ;
Ps = [w-r-(a-1)],
...
Ps = [b-r-(a-2), b-r-(a-1)],
Ls = [a-2, a-1] ;
... (no response)

在枚举所有当前定义的两个位置的预期解决方案后,它将无限循环,因为第一个 maplist 将继续无限制,在不断增长的列表中生成重复的位置。添加条件以在该点减少搜索可能是您想要的,或者使用不同的方法首先枚举位置然后在其中放置棋子可能更容易。

tldr: is_set(Ls), maplist(piese, Ps) 在未绑定变量有值之前检查它们。之后,重复的值就可以进来了。这两件事反过来做。


问题显示在生成长度为 2 的板的代码跟踪中:

  • 在顶部的箭头处,面板中必须有两个东西,它们显示为变量,没有人类给定的名称,也没有值,因此它们自动命名为 _840 和 _846。

  • 在箭头对处,maplist 调用已为电路板内容获得正确的 [A-B-C, X-Y-Z] 模式,并为保存每个部分的变量自动命名。

  • 在最后一个箭头处,位置的未知变量已被剥离并交给is_set。仍然没有值,两个未定义的变量是一个集合,所以他们通过了检查。

之后,您将值放入变量中,不再进行检查。

变化:

board(Ps) :- maplist(piese_pos, Ps, Ls), is_set(Ls), maplist(piese, Ps).

至:

board(Ps) :- maplist(piese_pos, Ps, Ls), maplist(piese, Ps), is_set(Ls).

然后 piese 在 is_set 检查重复值之前搜索值。

逻辑上可能没有区别,但实际上 Prolog 系统不能一次做所有事情,必须先做一些事情,然后再做一些事情。

假设棋盘上的每个位置都必须有一个棋子,可以用以下代码生成棋盘:

board(Ps) :-
    setof(P, pos_(P), Ls),     
    maplist(piese_pos, Ps, Ls),
    maplist(piese, Ps).

示例:

?- board(Ps).
Ps = [w-k-(a-1), w-k-(a-2)] ;
Ps = [w-k-(a-1), w-r-(a-2)] ;
Ps = [w-k-(a-1), b-k-(a-2)] ;
Ps = [w-k-(a-1), b-r-(a-2)] ;
Ps = [w-r-(a-1), w-k-(a-2)] ;
Ps = [w-r-(a-1), w-r-(a-2)] ;
Ps = [w-r-(a-1), b-k-(a-2)] ;
Ps = [w-r-(a-1), b-r-(a-2)] ;
Ps = [b-k-(a-1), w-k-(a-2)] ;
Ps = [b-k-(a-1), w-r-(a-2)] ;
Ps = [b-k-(a-1), b-k-(a-2)] ;
Ps = [b-k-(a-1), b-r-(a-2)] ;
Ps = [b-r-(a-1), w-k-(a-2)] ;
Ps = [b-r-(a-1), w-r-(a-2)] ;
Ps = [b-r-(a-1), b-k-(a-2)] ;
Ps = [b-r-(a-1), b-r-(a-2)].

否则,您可以使用其他定义:

board(Ps) :-
    setof(P, pos_(P), S),
    set_subset(S, Ls),
    maplist(piese_pos, Ps, Ls),
    maplist(piese, Ps).

set_subset([], []).
set_subset([X|Xs], S) :-
    set_subset(Xs, T),
    (   S = T
    ;   S = [X|T] ).

示例:

?- board(Ps).
Ps = [] ;
Ps = [w-k-(a-1)] ;
Ps = [w-r-(a-1)] ;
Ps = [b-k-(a-1)] ;
Ps = [b-r-(a-1)] ;
Ps = [w-k-(a-2)] ;
Ps = [w-r-(a-2)] ;
Ps = [b-k-(a-2)] ;
Ps = [b-r-(a-2)] ;
Ps = [w-k-(a-1), w-k-(a-2)] ;
Ps = [w-k-(a-1), w-r-(a-2)] ;
Ps = [w-k-(a-1), b-k-(a-2)] ;
Ps = [w-k-(a-1), b-r-(a-2)] ;
Ps = [w-r-(a-1), w-k-(a-2)] ;
Ps = [w-r-(a-1), w-r-(a-2)] ;
Ps = [w-r-(a-1), b-k-(a-2)] ;
Ps = [w-r-(a-1), b-r-(a-2)] ;
Ps = [b-k-(a-1), w-k-(a-2)] ;
Ps = [b-k-(a-1), w-r-(a-2)] ;
Ps = [b-k-(a-1), b-k-(a-2)] ;
Ps = [b-k-(a-1), b-r-(a-2)] ;
Ps = [b-r-(a-1), w-k-(a-2)] ;
Ps = [b-r-(a-1), w-r-(a-2)] ;
Ps = [b-r-(a-1), b-k-(a-2)] ;
Ps = [b-r-(a-1), b-r-(a-2)].