用协递归在Prolog中求解动态规划

用协递归在Prolog中求解动态规划,prolog,dynamic-programming,corecursion,Prolog,Dynamic Programming,Corecursion,我想通过Prolog解决以下动态规划问题。但我一直在做广度优先搜索,我想以一种正确的方式实现它: 有一座n层楼的大楼,电梯只能 一次上两层楼,一次下三层楼。使用 动态规划写一个函数来计算 电梯从楼层到达的步数 我到j楼 我已经决定使用惰性列表表示。惰性列表只是一个Prolog闭包C,可以调用它来生成头部和尾部的新闭包 例如,一个流: one(1, one). Haskell take谓词可以简单地编码如下: take(0, _, L) :- !, L = []. take(N, C, [

我想通过Prolog解决以下动态规划问题。但我一直在做广度优先搜索,我想以一种正确的方式实现它:

有一座n层楼的大楼,电梯只能 一次上两层楼,一次下三层楼。使用 动态规划写一个函数来计算 电梯从楼层到达的步数 我到j楼

我已经决定使用惰性列表表示。惰性列表只是一个Prolog闭包C,可以调用它来生成头部和尾部的新闭包

例如,一个流:

 one(1, one).
Haskell take谓词可以简单地编码如下:

 take(0, _, L) :- !, L = [].
 take(N, C, [X|L]) :- N > 0, 
    call(C, X, D), 
    M is N-1, 
    take(M, D, L). 
下面是一个运行示例:

 ?- take(5, one, X).
 X = [1, 1, 1, 1, 1].

 ?- take(10, one, X).
 X = [1, 1, 1, 1, 1, 1, 1, 1, 1|...].
?- take(5, tree, L).
L = [[],[0],[1],[0,0],[1,0]]

?- take(10, tree, L).
L = [[],[0],[1],[0,0],[1,0],[0,1],[1,1],[0,0,0],[1,0,0],[0,1,0]] 

在这个协递归Prolog中,我们需要两个构建块

一个构建块是在Prolog中以递归方式枚举搜索树的方法。我们采用这样的想法,即序言闭包项应该包含一个议程,其中包含应该扩展的路径和节点。然后,我们可以从只包含根的议程开始:

% tree(-Path, -LazyPaths)
tree(H, T) :-
   tree([[]], H, T). 
为了归档广度优先枚举,我们将在议程末尾附加新的扩展路径和节点。这可以通过一个简单的列表附加谓词调用来完成,因此缺少的定义如下所示。在完整的二叉树中,路径和节点总是展开两次:

% tree(+Paths, -Path, -LazyPaths)
tree([X|Y], X, tree(Z)) :-
   append(Y, [[0|X],[1|X]], Z). 
下面是一个运行示例:

 ?- take(5, one, X).
 X = [1, 1, 1, 1, 1].

 ?- take(10, one, X).
 X = [1, 1, 1, 1, 1, 1, 1, 1, 1|...].
?- take(5, tree, L).
L = [[],[0],[1],[0,0],[1,0]]

?- take(10, tree, L).
L = [[],[0],[1],[0,0],[1,0],[0,1],[1,1],[0,0,0],[1,0,0],[0,1,0]] 
在求值器问题的情况下,我们将有一条路径,因此节点扩展不会总是导致两个后续节点。如果我们在k层,电梯可以到达k+2或k-3层,前提是电梯停留在建筑物内。因此,我们很容易得出一个共同递归谓词步骤,它模拟了电梯的所有可能路径:

?- take(5, steps(7,[[2]]), L).
L = [[2],[4,2],[1,4,2],[6,4,2],[3,1,4,2]]

?- take(10, steps(7,[[2]]), L).
L = [[2],[4,2],[1,4,2],[6,4,2],[3,1,4,2],[3,6,4,2],
    [5,3,1,4,2],[5,3,6,4,2],[2,5,3,1,4,2],[7,5,3,1,4,2]]
最后一个障碍和第二个构建块是在Prolog中获得Haskell dropWhile。我们的目标不是为布尔条件使用Prolog闭包项参数的谓词,而是只提供一个枚举惰性列表元素的谓词,并且谓词的用户可以在Prolog continuation中进行过滤

% drop_while(+LazyList, -Element)
drop_while(C, P) :-
   call(C, Q, T),
   (P = Q; drop_while(T, P)).
如果我们把所有的东西放在一起,我们就得到了一个共递归的Prolog解,它甚至可以通过回溯来枚举计算器问题的所有无限解,除了按广度一阶计算结果外:

?- elevator(7,2,6,L), length(L,N).
L = [6,4,2],
N = 3 ;
L = [6,4,2,5,3,1,4,2],
N = 8 ;
L = [6,4,7,5,3,1,4,2],
N = 8 

从正切的角度来看,
take(1,C,L)
不应该强迫C的尾巴。它可能会导致错误,这是不必要的。SRFI-40列出了一个这样的例子,比如(4,map((1/),intsFromBy(4,-1)),L)。没有问题,我得到:?-take(4,map(rezi,ints_from_by(4,-1)),L)。L=[0.25,0.3333,0.5,1.0]。源代码在这里:啊,的确如此。您的实现中没有可用的“当前”头。它离SRFI-41更近。在评论之前,我没有仔细阅读您的代码。谢谢,很有趣。是的,当然。我是说,从概念上讲,它就像是一个实现。另一种可能性是有一对当前元素(已经强制)和一些可以创建下一对的元素(就像我在一些答案中所做的;模仿SICP流;类似于SRFI-40)。在retrosprect中,我想我是在无意识地使用Richard O'Keefes Prolog的工艺,第6章:序列。在那里,你可以找到几乎所有的东西,比如为什么我用编码的方式来编码“向上”和“向下”,用于“步骤”。参见his第6.3.6节:要列出的枚举器。