我有一个表示为非线性列表的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-value
和node-children
,make-node
造树。
如果只是t
或nil
结果就足够了,你也可以做:
(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的初学者。