【问题标题】:Lisp-check for a node in a n-treeLisp 检查 n 树中的节点
【发布时间】:2021-12-20 22:09:13
【问题描述】:

我有一个表示为非线性列表的 n-tree。树表示为(根 list_of_nodes_subtree1 ... list_of_nodes_subtreen)。我需要编写一个函数来测试特定节点是否属于 n 树,并且我必须使用映射函数。我想正确的是mapcar,但我不太确定。试过好几次了,对lambda函数不熟悉,不知道要不要用。

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

【问题讨论】:

    标签: tree common-lisp


    【解决方案1】:

    所以,首先我们不要像 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来制作树。

    【讨论】:

      【解决方案2】:

      如果结果只是 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 中的 lambda 突破。因此,确保该功能在找到节点后立即停止其调查。 (eq t ...) 是必要的,因为mapcar 返回的列表只要不是 '() 就会被解释为 true,尽管它可能仅包含 nils 作为元素。

      好的,我明白了,当使用(map nil ...) 而不是@ignisvolens 所做的(mapcar ...) 时,可以摆脱(eq t ...)

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

      【讨论】:

        【解决方案3】:

        您可能想要一个带有两个参数的函数:一棵树和要查找的符号。它可以调用mapcar,将树和lambda 传递给它。 lambda 检查列表的每个元素,如果它是一个符号,它会评估(eq needle 元素),否则元素必须是一个子树并且它会递归。结果,它调用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_.htmhttp://clhs.lisp.se/Body/f_reduce.htm

        如果这种编程让您感到困惑,我建议您阅读 David Touretzky 的“A Gentle Introduction to Common Lisp”。它从基本原理以简单的方式非常清楚地介绍了这些概念。它并不假定读者完全了解编程,我发现这很有帮助,因为当我学习 Common Lisp 时,我有 30 年的 C 和 C++ 编程经验,可以“忘却”。

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

        【讨论】:

        • 我知道有很多更简单的方法不涉及使用地图功能(这也是我迷路的原因),但我必须解决的问题在“地图功能”一章中",所以我需要找到一种方法来使用它们来解决它:(
        • 好的,我添加了一些代码,希望可以使方法更清晰。
        • 我怀疑#'or 会起作用。它是 SBCL 上的宏。
        • 尖引号适用于 lambda,但在 Common Lisp 中它可能被称为不合时宜。请参阅 stackoverflow.com/questions/14021965/the-in-common-lispstackoverflow.com/questions/29338004/…。我使用它是因为 lambdas 在我用来学习语言的参考手册(Practical Common Lisp 等)中始终使用尖引号。
        • 正如@FrancisKing 所说,(reduce #'or ...) 将不起作用,因为or 不是,也不能是一个函数。
        猜你喜欢
        • 2016-03-21
        • 2015-05-04
        • 1970-01-01
        • 1970-01-01
        • 2015-03-02
        • 1970-01-01
        • 2010-12-08
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多