【问题标题】:Lisp function: unionLisp 函数:联合
【发布时间】:2012-09-30 06:38:27
【问题描述】:

我有一个 lisp 作业,我很难处理它。

我必须编写一个执行联合操作的函数。该函数接受 2 个输入,以原子或列表的形式,并联合每个元素,保持顺序并去除所有级别的括号。

函数的输出:

(my-union 'a 'b)                         ;; (a b)
(my-union 'a '(b))                       ;; (a b)
(my-union '(a b) '(b c))                 ;; (a b c)
(my-union '(((a))) '(b(c((d e))a)))      ;; (a b c d e)

我对 lisp 还很陌生。 这是我到目前为止所写的内容,它仅适用于第三个示例:

(defun new-union (a b)
 (if (not b)
      a
      (if (member (car b) a)
        (new-union a (cdr b))
        (new-union (append a (list (car b))) (cdr b)))))

任何帮助将不胜感激!

【问题讨论】:

    标签: lisp common-lisp clisp


    【解决方案1】:

    由于这是你的第一个作业,而且你是 Lisp 新手,这里有一个非常简单的自上而下的方法,不用担心性能,并充分利用 CL 提供的工具:

    在 Common Lisp 中,已经有一个删除重复项的函数:remove-duplicates。将它与:from-end 关键字参数一起使用将“保留顺序”。现在,假设您有一个函数flatten,它可以展平任意嵌套的列表。那么您的问题的解决方案是:

    (defun new-union (list1 list2)
      (remove-duplicates (flatten (list list1 list2)) :from-end t))
    

    这就是我在没有进一步限制的情况下解决问题的方法,并且没有真正的理由过多担心性能。尽可能多地使用现有的工具箱,除非必要,否则不要重新发明轮子。

    如果你这样处理问题,归结为编写flatten 函数,我将把它留给你作为练习。这并不难,一个简单的选择是编写一个递归函数,这样解决问题:

    如果要展平的列表的第一个元素本身就是一个列表,则将展平的第一个元素附加到展平的其余部分。如果第一个元素不是列表,只需将其添加到列表的展平其余部分。如果输入根本不是列表,则返回它。

    这对你来说应该是一个很好的练习,只需几行代码即可完成。

    (如果您想非常正确,请使用辅助函数来完成这项工作并在包装函数中检查参数是否真的是一个列表。否则,flatten 也适用于原子,这可能会也可能会对你来说不是问题。)

    现在,假设你写了flatten

    > (defun new-union (list1 list2)
        (remove-duplicates (flatten (list list1 list2)) :from-end t))
    NEW-UNION
    > (new-union 'a 'b)
    (A B)
    > (new-union 'a '(b))
    (A B)
    > (new-union '(a b) '(b c))
    (A B C)
    > (new-union '(((a))) '(b (c ((d e)) a)))
    (A B C D E)
    

    【讨论】:

    • 我认为一开始必须重新发明轮子,以感受一门语言; 认识标准函数,而不是在没有真正理解的情况下努力背诵它们的名称和接口。稍后会使用标准功能。如果所有的碎片对我来说都像黑盒子一样,通过黑魔法完成它们的工作,我很难形成一个正确的拼图游戏。 :)
    • Will,重新发明轮子有助于锻炼身体并且有它的位置。然而,从 Lisp 或类似语言开始,重要的一课是更高层次的思考,比如学习有可重用、可组合的通用函数。我回答的重点不仅是有remove-duplicates,而且问题的解决方案可以表示为简单构建块的组合。当然,重新实现 remove-duplicates 或寻求更高性能的解决方案是一种选择,但我认为重新实现不应该总是甚至大部分先于使用。
    • 非常感谢!你能推荐一本好书来了解 lisp 到底发生了什么吗?
    • 不客气。有很多好书。对于绝对初学者,我通常推荐Touretzky。但是,根据您的一般编程经验,它可能有点太慢了。另请参阅older answer,我在其中列出了一些(免费)替代方案。 Winston & Horn 也是一个选项,但它不在线。稍后/ad lib:PAIP.
    • 顺便说一句:即使 Touretzky 对你来说可能有点太慢了,它也有很好的小练习,里面有适合你需要的解决方案。
    【解决方案2】:

    解决此问题的一种方法是将您的关注点分开。一是扁平化;另一个是重复删除;还有一个是结果构建。

    从空列表作为结果开始,继续将第一个列表的元素添加到其中,跳过结果中已经存在的元素。

    然后对第二个列表的元素执行相同的操作,将它们添加到相同的结果列表中。

    (defun my-union (a b &aux (res (list 1)) (p res))
      (nadd-elts p a)
      (nadd-elts p b)
      (cdr res))
    

    nadd-elts 将添加到列表的末尾,破坏性地更新其最后一个单元格(p 指向)使用例如rplacd。一个例子是here

    要添加元素,nadd-eltsemulate flattening 过程,并在检查 res 是否有重复项后将每个叶元素添加到 p


    以函数式工作,没有破坏性更新,一般方法保持不变:从空结果列表开始,将第一个列表添加到其中 - 没有重复 - 然后是第二个。

    (defun my-union (a b &aux res)
      (setq res (add-into res a))
      (setq res (add-into res b))
      res)
    

    现在我们要实现 add-into 函数了。

    (defun add-into (res a &aux r1 r2)
      (cond
         ((atom a) .... )
         (T (setq r1 (add-into res (car a)))
            (setq r2 (............ (cdr a)))
            r2)))
    

    上面的内容可以在没有辅助变量和set 原语的情况下重写。尝试找出如何...好吧,这就是我的意思:

    (defun my-union (a b) (add-into NIL (cons a b)))
    
    (defun add-into (res a)
      (cond
         ((atom a) .... )
         (T (add-into (add-into res (car a)) 
                      (cdr a)))))
    

    【讨论】:

    • 我是 lisp 的新手。两周前开始。作业本应该很简单,但我已经超出了我的想象。 &aux (res (list 1)) (p res)) 是什么意思。
    • 如果您是新手,那么破坏性代码可能不适合您。您是否对采取哪个方向给予任何指导?您是否应该以“功能”样式对此进行编码?但一般建议 - 从空结果开始,将第一个列表添加到其中 - 没有重复 - 然后是第二个。
    • &aux 只是向函数声明内部变量。一个是res,它的初始值为(list 1)。另一个是p,等于res
    • 我们没有得到任何具体的路线。我知道我们的教授喜欢递归,但他没有具体说明什么,因为这是我们的第一次作业。
    【解决方案3】:

    除非您不允许使用哈希表(由于某种原因,我以前遇到过这种要求),否则您可以想出一个排序函数来帮助您以您不使用的方式构建结果集'不必一遍又一遍地重复搜索。

    此外,由于允许嵌套列表,因此您的问题可以缩小到仅删除树中的重复项(因为您可以在开始处理它们之前简单地附加任意数量的列表。

    现在,我将尝试展示一些示例来说明如何做到这一点:

    ;; Large difference between best and worst case.
    ;; Lists containing all the same items will be processed
    ;; in square time
    (defun union-naive (list &rest lists)
      (when lists (setf list (append list lists)))
      (let (result)
        (labels ((%union-naive (tree)
                   (if (consp tree)
                       (progn
                         (%union-naive (car tree))
                         (when (cdr tree) (%union-naive (cdr tree))))
                       (unless (member tree result)
                         (setq result (cons tree result))))))
          (%union-naive list) result)))
    
    ;; Perhaps the best solution, it is practically linear time
    (defun union-hash (list &rest lists)
      (when lists (setf list (append list lists)))
      (let ((hash (make-hash-table)) result)
        (labels ((%union-hash (tree)
                   (if (consp tree)
                       (progn
                         (%union-hash (car tree))
                         (when (cdr tree) (%union-hash (cdr tree))))
                       (setf (gethash tree hash) t))))
          (%union-hash list))
        (maphash
         #'(lambda (a b)
             (declare (ignore b))
             (push a result)) hash)
        result))
    
    ;; This will do the job in more time, then the
    ;; solution with the hash-map, but it requires
    ;; significantly less memory. Memory is, in fact
    ;; a more precious resource some times, but you
    ;; have to decide what algo to use based on the
    ;; data size
    (defun union-flatten (list &rest lists)
      (when lists (setf list (append list lists)))
      (labels ((%flatten (tree)
                 (if (consp tree)
                     (if (cdr tree)
                         (nconc (%flatten (car tree))
                                (%flatten (cdr tree)))
                         (%flatten (car tree)))
                     (list tree))))
        ;; the code below is trying to do something
        ;; that you could've done using 
        ;; (remove-duplicates (%flatten list))
        ;; however sorting and then removing duplicates
        ;; may prove to be more efficient
        (reduce
         #'(lambda (a b)
             (cond
               ((atom a) (list a))
               ((eql (car a) b) b)
               (t (cons b a))))
         (sort (%flatten list)
               #'(lambda (a b)
                   (string< (symbol-name a)
                            (symbol-name b)))))))
    
    
    
    (union-naive '(((a))) '(b(c((d e))a)))
    (union-hash '(((a))) '(b(c((d e))a)))
    (union-flatten '(((a))) '(b(c((d e))a)))
    

    请注意,我用来排序元素的函数不是通用的,但您可能能够为任何类型的数据想出一个替代函数。一般来说,任何快速散列函数都可以,为了简单起见,我使用了这个。

    【讨论】:

    • "允许嵌套列表..." 你的意思是在输入中吗?这可能会令人困惑。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-11-13
    • 2017-10-18
    • 2012-07-20
    • 2017-10-19
    • 2013-12-23
    相关资源
    最近更新 更多