有关prolog回溯的问题,以寻找其他解决方案



我是Prolog的初学者。

我得到的是一个遍历列表的函数,当它满足条件时返回true。

例如,check_version检查包版本是否满足条件(例如:版本满足条件(如大于或小于特定版本),check_all检查接受一个版本和条件列表逐个检查。
package('python', '2.6.5').
package('python', '2.5.4').
package('python', '1.5.2').
package('python', '3.1.0').
check_version(Pac, Ver, Cmp, V):-
   package(Pac, V),
   cmp_version(V, Ver, Cmp).
check_all( Pac, [], [], V):-
   package(Pac, V).
check_all(Pac, [Ver], [Cmp], V):-
   check_version(Pac, Ver, Cmp, V).
check_all(Pac, [Ver|VerS], [Cmp|CmpS], V):-
   check_version(Pac, Ver, Cmp, V),
   check_all(Pac, VerS, CmpS, V).

我的问题是,当我试图找到其他解决方案时,它给了我重复的解决方案。

:

check_all('python', ['3.0','2.4'], [lt,ge], V).
V = '2.6.5' ;
V = '2.6.5' ;
V = '2.5.4' ;
V = '2.5.4' .
预期:

check_all('python', ['3.0','2.4'], [lt,ge], V).
V = '2.6.5' ;
V = '2.5.4' .

我使用trace来跟踪它,我发现的问题,当它试图找到另一个解决方案时,它会返回轨道并返回失败,直到找到正确的解决方案。就像上面的例子一样,显然,它首先会为V='2.6.5'返回true,并将其返回并运行函数,我们期望它返回false,然后当它到达开始时,它运行包('python', V), V将取另一个值。

Exit: (7) check_all(python, ['3.0', '2.4'], [lt, ge], '2.6.5') ? creep
V = '2.6.5' 

 Fail: (9) check_version(python, '2.4', ge, '2.6.5') ? creep
 Redo: (8) check_all(python, ['2.4'], [ge], '2.6.5') ? creep
 Call: (9) check_version(python, '2.4', ge, '2.6.5') ? creep
 Call: (10) package(python, '2.6.5') ? creep
 Exit: (10) package(python, '2.6.5') ? creep

当回溯时,在check_all中,它在check_all失败,正如我们预期的那样,但当它回溯check_version并将package(python, '2.6.5')作为V=2.6.5的新值运行时,它返回true。所以当V=2.6.5时,它再次返回true。有办法解决这个问题吗?

要将问题定位,首先要减小输入的大小。一个元素就足够了:

?- check_all('python', ['3.0'], [lt], V).

现在,哪些规则适用于单个元素?

两个规则都适用!所以删除更专门化的。

还有另一种方法来定位这样的问题。只需将这些规则相互比较,并尝试找出两者都适用的情况。当第一条规则也适用时,最后一条规则适用于VerS = []

对列表的每个元素应用谓词时,最好使用将列表作为其第一个参数的谓词。如果参数是一个列表而不是一个变量(即,当它是一个输入参数时),这使得谓词在迭代完成时成功。您应该有两个子句:一个用于处理空列表,另一个用于一般情况:

foo([]). % succeed
foo([X|Xs]) :-
    /* apply a predicate to X */
    foo(Xs). % apply predicate to the rest of the list
这里重要的一点是,您不需要第三个子句来处理仅包含一个元素的列表,因为包含一个元素的列表实际上是包含一个元素和一个空列表作为其尾部的列表:
?- [a] == [a|[]].
true.
?- [a] = [a|[]].
true.

另一个重要的事情是,在基本情况下,空列表中不应该做任何事情(至少对于您的示例)。

现在的问题是:你的输入是
  • 包名
  • 两个列表,包含您在其他地方(cmp_version/3)定义的谓词的参数对。这是您的条件列表。
实现:

  • 已知包作为事实可用:它们可以通过回溯来枚举。
  • 条件是一个输入参数,以列表的形式提供:您需要将条件应用于列表中的每个元素。
谓词:

check_all([], [], _, _).
check_all([V|Vs], [C|Cs], Name, Version) :-
    package(Name, V), % enumerate all known packages by backtracking
    cmp_version(Version, V, Cmp), % condition
    check_all(Vs, Cs, Name, Version). % apply condition to the rest of the list(s)

你应该阅读maplist的文档。您可以将查询表示为:

?- maplist(check_version(python), ['3.0', '2.4'], [lt, ge], Versions).

,其中您定义了一个谓词check_version/4,看起来像:

check_version(Name, V, Cmp, Version) :-
    package(Name, Version),
    cmp_version(Version, V, Cmp).

作为旁注,maplist将重新排序其参数,使其行为像上面的显式迭代。

编辑

@mat注释之后的命名问题:一个非常有用的命名约定是为参数使用一个描述性的单字名称,用下划线分隔。例如,package/2变成package_version/2,因为它的第一个参数是包,第二个参数是版本。

相关内容

最新更新