Prolog查询无限搜索
我是Prolog的初学者,我想问一个关于Prolog的问题 我的程序基于非确定性有限状态自动机 开始状态为S0,最终状态为S3 图表是 因此,如果有一个字符串Prolog查询无限搜索,prolog,stack-overflow,nfa,Prolog,Stack Overflow,Nfa,我是Prolog的初学者,我想问一个关于Prolog的问题 我的程序基于非确定性有限状态自动机 开始状态为S0,最终状态为S3 图表是 因此,如果有一个字符串[a,a,b,b,c,c],它应该是 start(s0). edge(a, s0, s0). edge(a, s0, s1). edge(b, s1, s1). edge(b, s1, s2). edge(c, s2, s2). edge(c, s2, s3). final(s3). 有一个谓词accepts(Ls)if(那里Ls是一个
[a,a,b,b,c,c]
,它应该是
start(s0).
edge(a, s0, s0).
edge(a, s0, s1).
edge(b, s1, s1).
edge(b, s1, s2).
edge(c, s2, s2).
edge(c, s2, s3).
final(s3).
有一个谓词accepts(Ls)
if(那里Ls
是一个字符串列表)
并且假设NFA从状态Si到状态Sj,并且在它们之间存在状态Sk,goesTo
谓词定义为
goesTo(Ls, Si, Sj) :- edge (L, Si, Sk), goesTo(Ls, Sk, Sj).
但是如果查询接受(Ls)
(范围从a
到c
的任意字符串列表)
教程中的问题说,它几乎肯定会进入无限搜索,并且会发生堆栈溢出
但是,我不明白为什么查询会进入无限搜索并导致堆栈溢出。如果你能告诉我原因,那就太好了
(编辑:)确切的报价是:
“一个典型的Prolog用户可能希望他/她的goesTo规则是这样的,查询接受(X)将生成被上述NFA接受的连续字符串。几乎可以肯定的是,鉴于给定NFA的上述表示,Prolog系统将进入无限搜索,并会发生堆栈溢出。请说明原因。(如果你想避免这个问题,请说明你是如何设法避免的。)
这是您的NFA的定义:
start(s0).
edge(a, s0, s0).
edge(a, s0, s1).
edge(b, s1, s1).
edge(b, s1, s2).
edge(c, s2, s2).
edge(c, s2, s3).
final(s3).
它与您可能测试是否接受的任何输入字符串都没有连接。请注意每行末尾的点-这些是定义NFA的Prolog谓词
使用任何具体的输入字符串运行accepts/1
,将导致有限的递归。此处的搜索空间是有限的,如果使用长度有限的完全实例化字符串调用accepts/1
,搜索空间肯定会用尽
现在,如果您试图生成所有可能的路径可接受字符串,那么您将有一个无限递归,因为可接受字符串的数量是无限的:
a,b,c
a,a,b,c
a,a,a,b,c
a,a,a,a,b,c
.....
此自动机是否可以接受所有字符串
顺便说一句,您的谓词定义都没有最后一个点。而且goesTo
不太正确。必须将其更改为:
goesTo([], S, S).
goesTo([L1|Ls], S1, Sn) :- edge(L1, S1, S2), goesTo(Ls, S2, Sn).
accepts(Ls) :- start(A), goesTo(Ls, A, B), final(B).
还要注意,谓词名称和左括号之间不能有空格
所以现在OP已经澄清了他们的问题 为什么试图生成所有可能的可接受字符串几乎肯定会进入无效循环 调用
接受(X)
实际上进入无限递归,因为要尝试的新节点的生成从一开始就重新开始,因此在内部,将尝试无限增长的a
s字符串:
a
a,a
a,a,a
a,a,a,a
....
因为edge(a,s0,s0)
是数据库中的第一个edge
事实,对edge/3
的调用位于goesTo/3
谓词定义中的第一个位置。Prolog的搜索策略是从左到右
我们可以通过如下方式重新安排目标,从完全非生产性行为(Prolog只是挂在无限循环中)转变为生产性行为:
start(s0).
edge(a, s0, s1).
edge(b, s1, s2).
edge(c, s2, s3).
edge(a, s0, s0).
edge(b, s1, s1).
edge(c, s2, s2).
final(s3).
现在,
不幸的是,正如我们所看到的,这一代人倾向于
c
。如何使其“公平”…让我们把它留给另一个问题,好吗?这是您的NFA的定义:
start(s0).
edge(a, s0, s0).
edge(a, s0, s1).
edge(b, s1, s1).
edge(b, s1, s2).
edge(c, s2, s2).
edge(c, s2, s3).
final(s3).
它与您可能测试是否接受的任何输入字符串都没有连接。请注意每行末尾的点-这些是定义NFA的Prolog谓词
使用任何具体的输入字符串运行accepts/1
,将导致有限的递归。此处的搜索空间是有限的,如果使用长度有限的完全实例化字符串调用accepts/1
,搜索空间肯定会用尽
现在,如果您试图生成所有可能的路径可接受字符串,那么您将有一个无限递归,因为可接受字符串的数量是无限的:
a,b,c
a,a,b,c
a,a,a,b,c
a,a,a,a,b,c
.....
此自动机是否可以接受所有字符串
顺便说一句,您的谓词定义都没有最后一个点。而且goesTo
不太正确。必须将其更改为:
goesTo([], S, S).
goesTo([L1|Ls], S1, Sn) :- edge(L1, S1, S2), goesTo(Ls, S2, Sn).
accepts(Ls) :- start(A), goesTo(Ls, A, B), final(B).
还要注意,谓词名称和左括号之间不能有空格
所以现在OP已经澄清了他们的问题 为什么试图生成所有可能的可接受字符串几乎肯定会进入无效循环 调用
接受(X)
实际上进入无限递归,因为要尝试的新节点的生成从一开始就重新开始,因此在内部,将尝试无限增长的a
s字符串:
a
a,a
a,a,a
a,a,a,a
....
因为edge(a,s0,s0)
是数据库中的第一个edge
事实,对edge/3
的调用位于goesTo/3
谓词定义中的第一个位置。Prolog的搜索策略是从左到右
我们可以通过如下方式重新安排目标,从完全非生产性行为(Prolog只是挂在无限循环中)转变为生产性行为:
start(s0).
edge(a, s0, s1).
edge(b, s1, s2).
edge(c, s2, s3).
edge(a, s0, s0).
edge(b, s1, s1).
edge(c, s2, s2).
final(s3).
现在,
不幸的是,正如可以看到的那样,生成过程偏向于
c
。如何使其“公平”…让我们把它留给另一个问题,好吗?由于Prolog采用的搜索策略,程序将循环。它试图用一种策略来解决探索解决方案空间的查询,这是一种节省空间但不完整的策略
现在应该清楚,在你的例子中,解空间是无限的,因为图中有这些循环。从初始状态到最终状态有无限的路径
应该是枚举路径的更简单的方法,它很容易在Prolog中实现