Lisp-check for an n-tree 中的节点



我有一个表示为非线性列表的n树。树表示为(根list_of_nodes_subtree1...list_of_nodes_subtreen)。我需要编写一个函数来测试特定节点是否属于 n 树,并且我必须使用 map 函数。我想正确的是mapcar,但我不太确定。我已经尝试了几次,但我不熟悉 lambda 函数,我不确定我是否需要使用它们。

示例:对于表示为 (a (b (c)) (d) (e (f))) 的树,节点 e => true

所以,首先,我们不要像1956年那样编程。 相反,让我们想象一下,不是每个函数都知道树表示的所有血腥细节,这使得它们冗长、难以阅读和脆弱,而是发明了数据抽象,并且有两个函数:

  • node-value返回节点的值;
  • node-children以列表形式返回节点的子节点。

因此,我们的函数将如下所示:

我们正在寻找的值是节点的node-value吗? 如果是这样,我们就完成了,如果没有,则不尝试节点的每个子节点。

所以写这个的明显方法是

(defun value-in-tree-p (value node &optional (test #'eql))
(or (funcall test (node-value node) value)
(dolist (n (node-children node) nil)
(when (value-in-tree-p value n test)
(return-from value-in-tree-p t)))))

但是我们需要使用映射函数,所以,好吧,我们可以这样做:

(defun value-in-tree-p (value node &optional (test #'eql))
(or (funcall test (node-value node) value)
(map nil (lambda (n)
(when (value-in-tree-p value n test)
(return-from value-in-tree-p t)))
(node-children node))))

那只是有点难读。

剩下的就是写node-valuenode-childrenmake-node造树。

如果只是tnil结果就足够了,你也可以做:

(defun node-in-tree (node tree &key (test #'eql))
(eq t (mapcar (lambda (x) (if (listp x) 
(when (node-in-tree node x) (return-from node-in-tree t))
(when (funcall test node x) (return-from node-in-tree t))))
tree)))

这里的诀窍是,一旦发生(funcall test node x)阳性的情况,return-from就可以设法从mapcar中的λ中突破。因此,可以确保此函数在找到节点后立即停止调查。(eq t ...)是必要的,因为只要不'()mapcar返回的列表就会被解释为 true,尽管它可能只包含nil个元素。

好的,我明白了,人们可以在使用(map nil ...)时摆脱(eq t ...),而不是像@ignisvolens那样(mapcar ...)

(defun node-in-tree (node tree &key (test #'eql))
(map nil (lambda (x) (if (listp x) 
(when (node-in-tree node x) (return-from node-in-tree t))
(when (funcall test node x) (return-from node-in-tree t))))
tree)))

稍微优雅一点的是mapcan它给了(nil nil nil ...)由于mapcar(map nil ...)只是一个'().

(defun node-in-tree (node tree &key (test #'eql))
(mapcan (lambda (x) (if (listp x) 
(when (node-in-tree node x) (return-from node-in-tree t))
(when (funcall test node x) (return-from node-in-tree t))))
tree))))

lambda函数可以概括为:

最终解决方案

(defun node-in-tree (node tree &key (test #'eql))
(mapcan (lambda (x) (when (or (and (listp x) (node-in-tree node x)) 
(funcall test node x)) 
(return-from node-in-tree t)))
tree))

或者也很好:

(defun node-in-tree (node tree)
(if (atom tree) 
(eql node tree)
(mapcan #'(lambda (x) (if (node-in-tree node x) (return-from node-in-tree t))) 
tree)))

你可能想要一个接受两个参数的函数:一棵树和要查找的符号。 它可以召唤mapcar,将树和λ传递给它。 lambda 检查列表中的每个元素,如果它是一个符号,它就会计算(eq 针元素),否则该元素必须是子树并且它会递归。 根据此结果,它调用reduce将布尔值列表转换为单个值。 像这样:

;; Look for NEEDLE in TREE.  NEEDLE should be a symbol.  TREE should
;; be a list of symbols or subtrees.  Return t if NEEDLE is in TREE,
;; NIL otherwise.
(defun is-symbol-in-tree (needle tree)
(reduce #'or
(mapcar #'(lambda (element)
(cond
((symbolp element) (eq element needle))
((consp element)
(symbol-in-tree target-sym element))
(t (error "unexpected element in tree"))))
:initial-value nil))

我没有测试该代码,但这就是想法。 :-)

参考资料:http://clhs.lisp.se/Body/f_mapc_.htm 和 http://clhs.lisp.se/Body/f_reduce.htm

如果这种编程让你感到困惑,我推荐你阅读David Touretzky的《A Gentle Introduction to Common Lisp》。 它以简单的方式从第一原则非常清楚地介绍了这些概念。 它并不假设读者完全理解编程,我发现这很有帮助,因为当我学习Common Lisp时,我有30年的C语言编程经验,C++"忘掉"。

我发现这种"应用"的编码风格使代码由内而外流动。 例如,在上面的示例中,reduce出现在mapcar之前,即使它将最后被评估。 顺便说一下,我仍然只是Common Lisp的初学者。

最新更新