【问题标题】:issues regarding prolog backtracking to find other solution关于序言回溯以找到其他解决方案的问题
【发布时间】:2014-07-31 02:00:35
【问题描述】:

我是 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' .

我使用跟踪来跟踪它,以及我发现的问题,当它试图找到另一个解决方案时,它会回溯并返回失败,直到找到正确的解决方案。与上面的示例一样,显然,它首先会为 V='2.6.5' 返回 true,然后将其返回并运行函数,我们希望它返回 false,然后当它到达开头时运行 package(' 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。有没有办法解决这个问题?

【问题讨论】:

    标签: recursion prolog backtracking


    【解决方案1】:

    要本地化您的问题,首先要减小输入的大小。一个元素就足够了:

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

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

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

    还有另一种方法可以定位此类问题。只需将规则相互比较,然后尝试找出它们都适用的情况。最后一条规则适用于VerS = [],而第一条规则也适用。

    【讨论】:

      【解决方案2】:

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

      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 的 cmets 之后的命名问题:一个非常有用的命名约定是使用具有描述性单字名称的参数名称,由下划线分隔。例如,package/2 变为 package_version/2,因为它的第一个参数是包,第二个参数是版本。

      【讨论】:

      • +1,我还建议将package_version/2 作为比package/2 更具描述性的名称。 check_version/4(和类似的check_all/4)也可以更好地命名,例如:package_relation_version/4
      • @mat 我同意,一致的描述性名称会产生很大的不同。
      • 您的 cmets 关于引号似乎是合适的,但您最好不要说“用 X 做某事”,而应该说“陈述关于 X 的情况”,因为 X 甚至不需要指定。同样,“继续迭代”最好理解为“说明 Xs 的情况”。
      • @mat 引号中的 cmets 无关紧要。我想使用“迭代”和“做”之类的词的唯一原因是让执行模型更加明显,因为对我来说,这似乎是 OP 遇到的问题。
      • 非常感谢,我终于明白为什么会出现这个问题了。
      猜你喜欢
      • 2013-03-29
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2023-04-10
      • 2021-09-07
      • 2015-10-25
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多