如何具体化 Prolog 的回溯状态以执行与 Clojure 中的“lazy seq”相同的任务?

Posted

技术标签:

【中文标题】如何具体化 Prolog 的回溯状态以执行与 Clojure 中的“lazy seq”相同的任务?【英文标题】:How to reify Prolog's backtracking state to perform the same task as "lazy seq" from Clojure? 【发布时间】:2019-11-24 23:20:05 【问题描述】:

这是一个用 Clojure 编写的数字快速排序算法。它基本上是在“The Joy of Clojure”,第 2 版,第 133 页中找到的快速排序算法。我稍微修改了它以(希望)更好的可读性,因为原始感觉有点过于紧凑: p>

(defn qsort-inner [work]
   (lazy-seq        
      (loop [loopwork work]
         (let [[ part & partz ] loopwork ]
            (if-let [[pivot & valuez] (seq part)]
                  (let [ smaller? #(< % pivot)
                         smz      (filter smaller? valuez)
                         lgz      (remove smaller? valuez)
                         nxxt     (list* smz pivot lgz partz) ]
                        (recur nxxt))
                  (if-let [[oldpivot & rightpartz] partz]
                          (cons oldpivot (qsort-inner rightpartz))
                          []))))))

(defn qsort [ xs ]
   (qsort-inner (list xs)))

该算法通过调用qsort 启动,它将传递的数字列表封装到另一个列表中(从而创建一个包含单个列表的列表),然后调用qsort-inner

(qsort [10 4 5 88 7 1])     ;; (qsort-inner [[10 4 5 88 7 1]])
;; (1 4 5 7 10 88)

qsort-inner有三个值得注意的点:

它会延迟实际处理。它不是返回对输入列表进行完整排序的结果,而是返回一个“lazy-seq”,它是一个 (object?thing?thunk?) 在查询时发出排序序列的下一个数字,即排序根据需要。计算的状态由(cons oldpivot (qsort-inner rightpartz))的悬尾给出 有一个 loop + recur 尾递归部分,当算法沿着排序树“向左”游荡时使用该部分(有关算法详细信息,请参见下文。) 有一个完全递归调用(qsort-inner rightpartz),当获得下一个最小数字并且可以“重新排列”排序树时使用它(有关算法详细信息,请参见下文。)

借助lazy-seq这个东西,我们可以让算法一一发出数据:

;; the full result is generated on printout
(qsort [10 4 5 88 7 1])
(1 4 5 7 10 88)

;; store the lazy-seq and query it
(def l (qsort [10 4 5 88 7 1]))
(first l)
;; 1
(second l)
;; 4

我正在考虑如何在 Prolog 中执行这种惰性快速排序。事实上,懒惰,至少在这种情况下,在 Prolog 中是通过回溯免费提供的!我们可以要求第一个结果,计算停止,然后通过回溯获得下一个结果。

qsort_inner(X, [[],X|_]).
qsort_inner(X, [[],_|WorkRest]) :- qsort_inner(X, WorkRest).
qsort_inner(X, [[Piv|Ns]|WorkRest]) :- 
    pick_smaller(Piv,Ns,SMs),
    pick_notsmaller(Piv,Ns,NSMs),
    qsort_inner(X,[SMs,Piv,NSMs|WorkRest]).

pick_smaller(Pivot,Ins,Outs) :- include(@>(Pivot),Ins,Outs).
pick_notsmaller(Pivot,Ins,Outs) :- exclude(@>(Pivot),Ins,Outs).

qsort(X,Lin) :- qsort_inner(X,[Lin]).

“懒惰”地对列表进行排序:

qsort(X,[3,2,1]).
X = 1;
X = 2;
X = 3;
false

必须把它们都搞定:

qsort_fully(Lin,Lout) :- bagof(X, qsort(X, Lin), Lout).

不幸的是,跟踪计算状态的数据结构并不明显:它在堆栈上,无法统一为变量。因此只有在Prolog的顶层时才能使用这种“懒惰”。

如何捕获计算状态并在以后调用它?

注意快速排序的工作原理

给定一个数字列表,该算法选择列表的第一个元素作为枢轴值(图像中的浅绿色)。 然后它会构建一棵树,其中那些数字严格小于“左侧”列表中的枢轴值、枢轴本身(深绿色)以及大于或等于“右侧”列表中的枢轴值的数字”。 然后它递归地沿着这棵树“向左”移动。 此过程一直持续到小于枢轴值的数字列表为空。 此时,枢轴值(此处为 28)是最小的数字,可以输出。 这使得排序的列表更小一个元素。现在可以通过简单的重新排列操作将树减少一级:现在无左分支和无枢轴的“最深的树节点,但一个”的右分支成为树节点的左分支“最深的树 -节点但两个”。 现在可以再次“向左”搜索最小元素。

不需要明确保留树结构,因为它不包含任何信息。相反,交替的“叶子列表”和“枢轴编号”的序列保存在一个列表中。这就是我们最初的“数字列表”的原因。

【问题讨论】:

我读了这篇文章,认为你并没有真正提出你需要帮助解决的问题。您已经对可能解决问题的方法进行了猜测并给出了示例,但示例是目前理解您脑海中想法的最佳方式。尽管我多次认为 reify 是答案,但事实证明后者并不是真正需要的。有时答案是使用 BFS 而不是默认的 DFS。有时是使用 DCGS 更容易完成的差异列表。 也许其中一个有您想要的答案:Difference Lists by Frank Pfenning 或 sorting algorithms implemented in Prolog by Markus Triska 感兴趣的:lazy_lists.pl 我推荐Lazy lists in Prolog?,有两种方法——more involved one 带有状态生成器(免责声明,这个答案是我的)和更多direct and simple one,带有freeze/2(还有@ 987654329@)。或者两者的某种组合。 :) @DanielLyons next/3 -- 还需要当前状态。 :)(我认为不是那么混乱。) 【参考方案1】:

Prolog 是一种非常可具体化的语言。只需将您的代码转化为数据:

qsort_gen(Lin, G) :- 
    % G is the initial generator state for Lin's quicksorting
    G = qsort_inner([Lin]).

    %    This_State                   Next_Elt      Next_State
next( qsort_inner([[], X    | WorkRest]), X, qsort_inner(WorkRest) ).
next( qsort_inner([[Piv|Ns] | WorkRest]), X, G ) :-
    pick_smaller(  Piv, Ns, SMs),
    pick_notsmaller(Piv, Ns, NSMs),
    next( qsort_inner([SMs, Piv, NSMs | WorkRest]), X, G).

pick_smaller(  Pivot, Ins, Outs) :- include( @>(Pivot), Ins, Outs).
pick_notsmaller(Pivot, Ins, Outs) :- exclude( @>(Pivot), Ins, Outs).

就是这样。

15 ?- qsort_gen([3,2,5,1,9,4,8], G), next(G,X,G2), next(G2,X2,G3), next(G3,X3,G4).
G = qsort_inner([[3, 2, 5, 1, 9, 4, 8]]),
X = 1,
G2 = qsort_inner([[], 2, [], 3, [5, 9, 4|...]]),
X2 = 2,
G3 = qsort_inner([[], 3, [5, 9, 4, 8]]),
X3 = 3,
G4 = qsort_inner([[5, 9, 4, 8]]).

16 ?- qsort_gen([1,9,4,8], G), next(G,X,G2), next(G2,X2,G3), next(G3,X3,G4).
G = qsort_inner([[1, 9, 4, 8]]),
X = 1,
G2 = qsort_inner([[9, 4, 8]]),
X2 = 4,
G3 = qsort_inner([[8], 9, []]),
X3 = 8,
G4 = qsort_inner([[], 9, []]).

17 ?- qsort_gen([1,9,4], G), next(G,X,G2), next(G2,X2,G3), next(G3,X3,G4).
G = qsort_inner([[1, 9, 4]]),
X = 1,
G2 = qsort_inner([[9, 4]]),
X2 = 4,
G3 = qsort_inner([[], 9, []]),
X3 = 9,
G4 = qsort_inner([[]]).

为了更方便的交互,我们可以使用take/4:

take( 0, Next, Z-Z, Next):- !.
take( N, Next, [A|B]-Z, NextZ):- N>0, !, next( Next, A, Next1),
  N1 is N-1,
  take( N1, Next1, B-Z, NextZ).

那么,

19 ?- qsort_gen([3,2,5,1,9,4,8], G), take(6, G, L-[], _).
G = qsort_inner([[3, 2, 5, 1, 9, 4, 8]]),
L = [1, 2, 3, 4, 5, 8].

20 ?- qsort_gen([3,2,5,1,9,4,8], G), take(7, G, L-[], _).
G = qsort_inner([[3, 2, 5, 1, 9, 4, 8]]),
L = [1, 2, 3, 4, 5, 8, 9].

21 ?- qsort_gen([3,2,5,1,9,4,8], G), take(10, G, L-[], _).
false.

take/4 显然需要调整以在next/3 失败时优雅地关闭输出列表。它最初是在考虑无限列表的情况下编写的。这留给敏锐的探索者练习。

【讨论】:

不错!我在深夜查看 Markus Triska 的 prost.pl,但我没有点击。 我使用尖锐的 语言已经很长时间了,以至于我将代码视为数据的感觉已经萎缩。这是一个阴谋。 Prolog 很特别。我认为在这方面它比 Lisp 更 Lisp。 (有时我喜欢说编译是一个万亿美元的错误。但实际上它本身并不是编译,而是没有源代码的二进制文件的分发。IMO。)【参考方案2】:

这不是标准化的,但现在许多 Prolog 提供了维护和操作多个独立可回溯状态的工具,通常称为引擎

例如,使用ECLiPSe中对应的primitives,你可以这样写

init(Vars, Goal, Engine) :-
    engine_create(Engine, []),
    engine_resume(Engine, (true;Goal,yield(Vars,Cont),Cont), true).

more(Engine, Vars) :-
    engine_resume(Engine, fail, yielded(Vars)).

并按如下方式使用它(qsort/2 由您定义)

?- init(X, qsort(X,[3,2,1]), Eng),
   more(Eng, X1),
   more(Eng, X2),
   more(Eng, X3).

X = X
Eng = $&(engine,"9wwqv3")
X1 = 1
X2 = 2
X3 = 3
Yes (0.00s cpu)

这里,变量Eng 绑定到一个不透明的引擎-句柄。这个引擎执行一个不确定的目标,每次它被恢复并被指示回溯时,它都会为调用者产生一个新的解决方案。

【讨论】:

SWI-Prolog 似乎有 something similar 使用 engine_create/3engine_next/2 (API docs)。

以上是关于如何具体化 Prolog 的回溯状态以执行与 Clojure 中的“lazy seq”相同的任务?的主要内容,如果未能解决你的问题,请参考以下文章

如何在 Prolog 回溯期间获取值列表?

Prolog回溯位置

prolog简介

人工智能语言的lisp和prolog

在Python中实现Prolog统一算法?回溯

Atitit.5gl 第五代语言编程语言 PROLOG教程  人工智能语言的标准 与实现