binary-tree - 如何在 lisp 中制作引用结构槽的符号?
问题描述
我正在自学 lisp,并认为一个不错的非平凡程序是编写一组标准的树插入和操作例程。我认为它可以用 CONS 来完成,但想用一个结构来尝试它。
我整理了一个有效的版本:
(defstruct treenode data left right)
(defun tree-insert ( value tree )
"Insert data into tree"
(if tree
(if (< value (treenode-data tree))
(setf (treenode-left tree) (tree-insert value (treenode-left tree)))
(setf (treenode-right tree) (tree-insert value (treenode-right tree))))
(setf tree (make-treenode :data value)))
tree)
它在每一步都重建了这棵树,这似乎计算效率低下。低效是指每次进行另一级递归时都必须使用 setf 。所以我想尝试一个通过引用而不是通过值传递树的方案,这样我就可以在插入树的子例程中进行分配。
我将以下内容拼凑在一起,但不起作用(但请感谢我的评论):
(defstruct treenode data left right)
(defun tree-insert ( value tree )
"Insert data value into tree, using pass by reference.
value A datum to insert, in this version has to be a number.
tree The tree passed as a symbol."
(setq tval (symbol-value tree))
(if (eq tval nil)
(set tree (make-treenode :data value)) ; Empty tree. Place data here.
(if (< value (treenode-data tval)) ; Non-empty node. Decide which subtree for insert.
(tree-insert value (treenode-left tval)) ; Left side
(tree-insert value (treenode-right tval)))) ; Right side. This is a stable sort.
nil)
? (setf tr nil)
NIL
? (tree-insert 10 'tr)
NIL
? tr
#S(TREENODE :DATA 10 :LEFT NIL :RIGHT NIL)
?
初始插入工作正常。传递一个符号,(set tree ...) 正确地插入了左右指针为零的结构。
当然,随之而来的问题是在递归调用 tree-insert 时我没有传递符号。
那就是挂机。我还没有找到一种方法将结构槽称为符号,然后我可以将其传递给树插入。
我已经环顾了几天,发现了一个关于 defstruct 宏的有趣评论:“defstruct 不仅为每个插槽定义了一个访问函数,而且还安排 setf 在此类访问函数上正常工作,定义了一个名为name-p,定义了一个名为 make-name 的构造函数,并定义了一个名为 copy-name 的复制器函数。所有自动创建的函数的名称都被保存在处理 defstruct 表单时当前的任何包中(参见包)。 , 所有这些函数都可以根据实现的自由裁量权声明为内联以提高效率;如果您不希望某些函数内联声明,请遵循带有 notinline 声明的 defstruct 形式来覆盖任何自动内联声明。”
那么,我能做些什么来发挥 setf 的魔力呢?我知道我可以使用 setf 对插槽进行分配,但由于词法范围规则,我还没有让 setf 在函数中工作。也许喜欢添加自动功能以允许生成符号,例如 (treenode-data-symbol tr)?
当然,在我的第一个 PDP-8/L 之前,lisp 程序员就已经处理过二叉树了。这样做的方式是什么?
这是一个已编辑的问题。用户 Rainer Joswig 给出了非常快速简洁的回复。我从他举的例子中学到了很多。我对直接修改树而不是使用函数的返回值的问题感兴趣。
从我在这里看到的评论和 Rainer Joswig 的一个答案中,我是否应该得出这样的结论:指针操作的计算成本很低,并且最好的 lisp 方法是使用返回树而不是依赖的函数关于修改论点的方法?
解决方案
为您提供灵感的简单版本:
(defstruct node a b v)
(defun insert-tree (tree value)
(cond ((null tree)
(setf tree (make-node :v value)))
((> (node-v tree)
value)
(setf (node-a tree)
(insert-tree (node-a tree) value)))
(t
(setf (node-b tree)
(insert-tree (node-b tree) value))))
tree)
使用它:
CL-USER 171 > (let ((tree nil))
(loop for i in '(4 7 3 5 9 10 11 8)
do (setf tree (insert-tree tree i)))
(pprint tree)
(values))
#S(NODE :A #S(NODE :A NIL :B NIL :V 3)
:B #S(NODE :A #S(NODE :A NIL :B NIL :V 5)
:B #S(NODE :A #S(NODE :A NIL :B NIL :V 8)
:B #S(NODE :A NIL
:B #S(NODE :A NIL
:B NIL
:V 11)
:V 10)
:V 9)
:V 7)
:V 4)
现在,如果想要做更少的setf
操作,我们可以检查返回的子树是否与我们传递的相同。只有当我们创建一个新节点时才会出现这种情况。
(defun insert-tree (tree value)
(cond ((null tree)
(setf tree (make-node :v value)))
((> (node-v tree)
value)
(let ((new-tree (insert-tree (node-a tree) value)))
(unless (eql new-tree (node-a tree))
(setf (node-a tree) new-tree))))
(t
(setf (node-b tree)
(insert-tree (node-b tree) value))))
tree)
或使用本地宏隐藏部分代码:
(defun insert-tree (tree value)
(macrolet ((insert (place call &aux (new-value-sym (gensym "new-value")))
`(let ((,new-value-sym ,call))
(unless (eql ,place ,new-value-sym)
(setf ,place ,new-value-sym)))))
(cond ((null tree)
(setf tree (make-node :v value)))
((> (node-v tree)
value)
(insert (node-a tree) (insert-tree (node-a tree) value)))
(t
(insert (node-b tree) (insert-tree (node-b tree) value))))
tree))
推荐阅读
- swift - Apple登录委托方法不调用?
- python - 多个堆叠的numpy数组的动画
- eslint - ESLint/Prettier 规则禁止没有占位符的模板文字
- .net-core - SOAP 服务器在从 .NET 框架更新到 .NET 核心后添加随机延迟
- asp.net-core - IIS 配置以在 ASP.NET 核心上获取 Windows 登录用户
- thunderbird - 是否可以通过自定义扩展更新 Thunderbird 中的过滤规则?
- xml - 如何让按钮的颜色改变?
- javascript - 如何根据div位置对对象数组进行排序
- python - 编写基于两个相关字典作为输入的函数
- cypress - 从元素中获取文本并将其用于下一步操作