如何将某些值映射到Prolog中的列表



所以,我有一个定义为map(Letter,Number)的谓词。例如:

map(j, 0).
map(k, 1).
etc.

我需要创建一个名为 map_list(List, X) 的函数,该函数使用映射谓词中的数字对创建一个列表 X。例如:

map_list([j, j, k, k], X).
X = [0, 0, 1, 1].

这是我到目前为止所拥有的:

map_list([], []).
map_list([], _).
map_list([Head|Tail], Ans) :-
map(Head, N),
append(Ans, [N], NewAns),
map_list(Tail, NewAns).

它所做的只是将随机的"垃圾"附加到新列表中,并且永远不会结束。谁能帮忙?谢谢!

我认为你用append调用使它过于复杂。

就是这么简单:

map_list([], []).
map_list([H1|T1],[H2|T2]) :-
map(H1,H2),
map_list(T1,T2).

您的代码:

map_list([], []).

这意味着[]映射到[],或"字母列表[]与整数列表[]map_list(双向)关系"。好!

map_list([], _).

这意味着[]被映射到任何东西,或者"[]的字母列表与计算机宇宙中的任何事物处于(双向)关系map_list"。坏!

map_list([Head|Tail], Ans) :- map(Head, N),
append(Ans, [N], NewAns),
map_list(Tail, NewAns).

这意味着"非空的字母列表[Head|Tail]与定义如下Ansmap_list处于(双向)关系中:

  1. map(Head, N)通常的意思。
  2. 预定义的关系append存在于两个列表Ans[N]NewAns之间。此时,如果您发出查询map_list([j,k],Ans)Ans尚未受到约束,因此,Prolog只会知道NewAns是以Ans开头的列表(我们唯一知道的是它必须是一个列表)并以成员N结束。事实上,你用错了append
  3. NewAnsmap_list(Tail, NewAns)中递归地变得更加精确,但在这一点上,我们甚至不确定我们在计算什么。

无论如何,您都会得到越来越多的不受约束的变量列表("垃圾"):

?- map_list([j,k],X).
X = [] ;
X = [_7280] ;
X = [_7280, _7292] ;
X = [_7280, _7292, _7304] ;
X = [_7280, _7292, _7304, _7316] ;
X = [_7280, _7292, _7304, _7316, _7328] 

让我们通过以下方式解决这个问题

  1. 放弃"任何"匹配map_list([], _).
  2. 正确使用append构造结果列表。

喜欢这个:

map(j, 0).
map(k, 1).
map_list([], []).
map_list([Head|Tail], Ans) :- map(Head, N),
append([N], TailAns, Ans),
map_list(Tail, TailAns).

这行得通。

?-  map_list([j,k],X).
X = [0, 1].

请注意信息流。与命令式语言不同append当时的信息还不完整。我们知道它是否在查询中传递N,但我们还不知道任何TailAns,尚未构造。它仅由递归调用确定为完全扩展。(编译器或 Prolog 虚拟机可能会将其优化为一个循环,修改堆上的结构而不是堆栈,具体取决于)。

你可以编写以下内容来执行"appendon back from recursive call",这更像是命令式语言中所做的:

map(j, 0).
map(k, 1).
map_list([], []).
map_list([Head|Tail], Ans) :- map(Head, N),
map_list(Tail, TailAns),
append([N], TailAns, Ans).

答复意见问题的附录

我喜欢尝试解释Prolog。它总是令人困惑,但会带来新的挑战。(或者你可以尝试短期但昂贵的The Reasoned Schemer,但你需要先了解Scheme)。

好的,所以:

map_list([Head|Tail], Ans) :-
map(Head, N),
append([N], TailAns, Ans),
map_list(Tail, TailAns).

TailAns突然出现在谓词(或过程)的主体中。这是一个"新鲜"变量(没有必要先在Prolog中声明它)。在TailAns第一次出现的计算状态下,对它一无所知,它是不受约束的。

为了更好地弄清楚发生了什么,请将"变量"一词替换为"黑板"。然后,我们可以"创建一个空的/新的黑板","按名称引用黑板","将黑板传递给另一个谓词(或过程)",通过指定其他细节来"细化黑板上的任何内容"。黑板也可以"在呼叫之间共享",并在同一呼叫中的多个位置通过其名称引用,包括通过其他黑板的内容:

A = g(B,1,C), B = h(a,k,C).

是关于黑板ABC的陈述,其中我们声明黑板A的内容是g(B,1,C)的,黑板B的内容是h(a,k,C)的。此语句在任何时间点都可能是正确的,也可能是不正确的,具体取决于代码的其他部分已经对这些黑板进行了说明。我们对黑板C的内容一无所知。

类似地,在Prolog REPL(顶级)调用谓词append/3

?- append([1,2],[3],C).

创建三个黑板,

  • 一个未命名的,[1,2]作为内容
  • 一个未命名的,[3]作为内容
  • 一个名为C,内容无/未指定("新变量")

append/3的任务现在是细化这三个黑板的内容,以便黑板 3 的内容包含一个列表,该列表是黑板 1 和 2 的内容串联。

append/3实现的不是一个函数,而是一个关系(这是函数的推广),或者至少它试图接近这个理想。与信息必须从输入参数流向输出结果的函数不同,关系参数之间的信息更自由地流动。例如,您可以获取给定输出的输入参数。这里的情况就是这样。

?- append(X,[1,2],[3]).
false.

好吧,上述情况没有解决方案。但:

?- append(X,[1,2],[first,1,2]).
X = [first] ;
false.

使用黑板心理图像,append/3将完善黑板X,以便X[1,2]的串联给出[first,1,2](最后两个写在顶层未命名的不同黑板上)。这是通过将[first]放在黑板上来实现的,黑板上的名称是X.

与确定给定要连接的列表的结果相同:

?- append([first],[1,2],X).
X = [first, 1, 2].

与确定给定第一个参数和连接结果的第二个参数相同:

?- append([first],X,[first,1,2]).
X = [1, 2].

不受约束的黑板导致猜测,凭空拉出的新黑板名为_somenumber

?- append(X,Y,Z).
X = [], Y = Z ;
X = [_5070], Z = [_5070|Y] ;
X = [_5070, _5082], Z = [_5070, _5082|Y] ;
etc.

黑板当然可以通过变量名称引用其他黑板,同一变量可能出现在许多地方:

?- append([1,X,X],[1,X,X],[1,2|R]).
X = 2,
R = [2, 1, 2, 2].

太酷了!

黑板图像有助于解释信息是在呼叫时提供给谓词的,并且谓词可以通过修改所述黑板来返回信息。

请注意,通过填写未指定的变量,黑板的内容会变得更加精确。通过剔除"知道事物"并用变量代替它们,它永远不会变得不那么精确。

但是,如果我们要求在黑板上出现不可能的 thinsg,计算将失败并"回溯"到以前的状态以进行另一次尝试:构建的黑板被丢弃并恢复早期的黑板。

所以回到第二行

map_list([Head|Tail], Ans) :- map(Head, N),
append([N], TailAns, Ans),
map_list(Tail, TailAns).

append/3现在将尝试确定列表[N]和列表TailAns的串联是否提供列表Ans

它将要么

  • true并更新黑板NTailAnsAns,计算就可以继续了。
  • false和计算"回溯"。

在本例中,append/3可以说黑板Ans的内容完全[N|TailAns]但仅此而已

实际上,在计算的这一点上,黑板上有一些具体值N,但TailAns是完全未知的,它必须是某种列表(甚至没有检查,因为 Prolog 没有类型)。

TailAns所代表的只是通过随后的递归调用map_list(Tail, TailAns)变得更加具体。在最深的递归调用之后,它将包含[],因此之前构造的Ans将变得更加精确,事实上,随着现在已知的TailAns[]它从[N|TailAns]变为[N|[]],简单地写成[N]或者,因为此时N已知为 1,[1].这也意味着调用者的[N|TailAns]将从[N|TailAns]细化为[N|[1]],简单地写成[N,1],或者,正如N在调用者的上下文中已知为0,[0,1]

最新更新