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

2023-11-24

这是用 Clojure 编写的数字快速排序算法。它基本上是快速排序算法“Clojure 的乐趣”,第 2 版,第 133 页。我稍微修改了它以(希望)更好的可读性,因为原始版本感觉有点太紧凑:

(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)是总体上最小的数字,可以输出。
  • 这使得对一个元素进行排序的列表更小。现在可以通过简单的重新排列操作将树减少一级:现在无左分支且无枢轴的“最深树节点,但一个”的右分支成为树节点“最深树节点”的左分支节点只有两个”。
  • 现在可以再次“向左”搜索最小元素。

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

Partial example run for quicksort


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).

Then,

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失败了。最初,它是在考虑无限列表的情况下编写的。这是留给敏锐的探索者的练习。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

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

  • 这个版本的trace有什么问题?

    我有这个跟踪元解释器 它是为 swi prolog 编写的 trace Goal trace Goal 0 trace true Depth true trace fail Depth fail trace A gt B Depth A g
  • 为什么这个 Clojure 程序在可变数组上运行如此慢?

    剧透警告 这是 代码降临 第六天的第一部分 我试图解决this http adventofcode com day 6Clojure 和 Scala 中的问题 Scala 程序在我的 Macbook Air 上运行良好 几秒钟内就完成了 然
  • Prolog 列表列表获取所有元素

    我有一个列表列表 decide 1 2 3 2 3 6 4 K 我想按 返回所有可能的解决方案 规则是首先返回其列表大小为 1 的值 然后我想返回其大小大于1的值 size 0 size Xs L size Xs N L is N 1 he
  • Clojure 的分析工具?

    有谁知道 Clojure 有一个好的分析工具或库吗 我更喜欢可以从 REPL 中使用的东西 类似于 with profiling 过去是在 Allegro Common Lisp 中 有什么类似的事情吗 或者您是否有过与 Clojure 配
  • clojure.spec 人类可读的形状?

    使用 clojure spec 有没有办法为嵌套映射定义更 人类可读 的规范 以下内容读起来不太好 s def my domain entity s keys req un a b s def a s keys req un c d s d
  • 如何在 Clojure 中创建循环(且不可变)数据结构而不需要额外的间接?

    我需要在 Clojure 中表示有向图 我想将图中的每个节点表示为一个对象 可能是一条记录 其中包含一个名为 edges这是从当前节点直接可达的节点的集合 希望这是不言而喻的 但我希望这些图表是不可变的 我可以构造有向acyclic只要我进
  • 将“mod”运算符与“or”一起使用时是否强制具体化?

    我使用 CLP FD 和 SWI Prolog 编写了一个 CSP 程序 我认为当我使用时我需要改进我的约束写作mod操作员 和 一起 在我的谓词中 一个简短的例子 use module library clpfd constr X Y Z
  • WAM 中的扁平化形式

    WAM 教程重构指出查询 p Z h Z W f W 需要使用以下原则进行扁平化 话虽这么说 查询扁平化形式是 X3 h X2 X5 X4 f X5 X1 p X2 X3 X4 我对外部变量的定义感到困惑 请考虑以下内容 p Z h Y a
  • 将人员分配到床位 - 自动化方法[关闭]

    Closed 这个问题需要多问focused help closed questions 目前不接受答案 我每年都会帮助举办青年营 将与会者分配到卧室是一项艰巨的任务 有 92 个卧室 活动持续一周 与会者停留的时间长短不一 而且床需要重复
  • findall 的异常行为

    以下看起来很不寻常 findall X member X 1 2 3 X X 1 2 3 痕迹更是如此 trace findall X member X 1 2 3 X Call 11 findall 100058 member 10005
  • 为什么 CouchDB 使用仅追加 B+ 树而不是 HAMT

    我正在阅读数据结构 尤其是不可变的数据结构 例如仅追加 B 树 http guide couchdb org draft btree html用于 CouchDB 和哈希数组映射 trie http en wikipedia org wik
  • 没有这样的命名空间:clojurescript 项目设置中的 clojure.spec.alpha

    我在尝试学习clojure spec 在沿着启动构建工具设置 clojure 项目时 我在需要 clojure spec alpha 时遇到以下错误 Compiling ClojureScript js app js No such nam
  • 替换 prolog 中的部分表达式

    我需要简化序言中的身份 例如x 0 x x x 0 ETC 为此 我需要替换表达式的部分内容 比如x 0 by x 您能帮我更换吗 Prolog 的一个巧妙之处在于您可以非常轻松地解构算术表达式 您的基本模板将如下所示 simplify X
  • 用纯函数式语言保持状态

    我正在尝试弄清楚如何执行以下操作 假设您正在开发直流电机的控制器 您希望让它以用户设置的特定速度旋转 def set point ref sp 90 while true let curr read speed controller set
  • 如何在 Clojure 中转换 Java 类?

    我想将 clojure Java 对象 用 let 分配 转换为另一个 Java 类类型 这可能吗 如果可以的话我该怎么做 更新 自从我发布这个问题以来 我意识到我不需要在 Clojure 中进行强制转换 因为它没有接口的概念 而且更像 R
  • 二叉树和快速排序?

    我有一个家庭作业 内容如下 别生气 担心 我是not请你帮我做作业 编写一个程序 通过使用二分查找的快速排序方法对一组数字进行排序 树 推荐的实现是使用递归算法 这是什么意思 到目前为止 这是我的解释 正如我在下面解释的那样 我认为两者都有
  • 为什么在我添加冗余约束之前此 clpfd 查询不会终止?

    我编写了一些谓词 它们采用列表的长度并附加一些约束 这是要使用的正确词汇吗 clp length 0 clp length Head Rest Length Length gt 0 Length Length1 1 clp length R
  • 协议中的提示返回类型在 Clojure 中是否有任何影响?

    您可以在协议中暗示返回类型 defprotocol Individual Integer age this 并且编译器将使您的方法符合 defrecord person Individual String age this one Comp
  • JavaScript 中 Scala View 的等效项

    在斯卡拉中 view允许防止创建全新的集合 例如在Scala中 视图 有什么作用 https stackoverflow com questions 6799648 in scala what does view do JavaScript
  • 如何根据prolog中的数量来概括程序?

    我使用 swi prolog 我有一个这样的事实库 由数量为 4 的事实组成 attribute a1 a2 a3 a4 data yes no no no data yes no yes no data yes yes yes no da

随机推荐