Prolog查询无限搜索

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是一个

我是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
是一个字符串列表)

并且假设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中实现