所以,我有一个定义为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]
与定义如下Ans
map_list
处于(双向)关系中:
map(Head, N)
通常的意思。- 预定义的关系
append
存在于两个列表Ans
、[N]
和NewAns
之间。此时,如果您发出查询map_list([j,k],Ans)
,Ans
尚未受到约束,因此,Prolog只会知道NewAns
是以Ans
开头的列表(我们唯一知道的是它必须是一个列表)并以成员N
结束。事实上,你用错了append
。 NewAns
在map_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]
让我们通过以下方式解决这个问题
- 放弃"任何"匹配
map_list([], _).
- 正确使用
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 虚拟机可能会将其优化为一个循环,修改堆上的结构而不是堆栈,具体取决于)。
你可以编写以下内容来执行"append
on 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).
是关于黑板A
、B
、C
的陈述,其中我们声明黑板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
并更新黑板N
,TailAns
和Ans
,计算就可以继续了。 - 说
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]
。