以通用Lisp合并,保留原始列表中元素的顺序

时间:2019-03-15 15:19:47

标签: common-lisp

我正在研究Paul Graham的“ ANSI Common Lisp”(1996年)。 第三章,练习曲。 2要求该职位的标题中所述的功能。到目前为止,我仅使用书中讲授的内容(显然,有case构造可以清除if的构造,但目前我不介意)。

作为第一次尝试,我最终写了交错记录,保留了重复记录:

(defun interleave (x y)
  (if (and (null x)      
           (null y))
      nil
      (if (null x)
          (cons (car y)
                (interleave (cdr y) x))
          ; where y is null, but also for any other case:
          (cons (car x)
                (interleave y (cdr x))))))

在此之后,我想到了存储一堆已看到的元素并遵从辅助函数的想法,如下所示。 但是,以下内容显然很丑陋且难以理解。 我正在寻求一些有关实现优雅的方向的建议。

关于方法和风格的提示在这一点上可能与提供规范的解决方案一样有用。我下面的第一脉冲给定的代码应该是提取另一个功能吗? (或者也许我在错误的方向上试图首先存储进位?)谢谢其他黑客!

(defun new-union (x y)
  (new-union-helper x y '()))  ; <- idea, add a carry to store what's been seen.

(defun new-union-helper (x y seen)
  (if (and (null x)
           (null y))
      nil
      (if (null x)
          (if (not (member (car y) seen)) ; if first el of y hasn't yet been seen...
              ; cons it to the ultimate result & recur, while adding it to seen:
              (cons (car y) (new-union-helper (cdr y) x (cons (car y) seen)))
              ; if it has been seen, just continue, (skip the duplicate):
              (new-union-helper (cdr y) x seen))
          (if (not (member (car x) seen))
              (cons (car x) (new-union-helper y (cdr x) (cons (car x) seen)))
              (new-union-helper (cdr x) y seen)))))

更新:我在书的索引中查找if后,尝试用cond替换嵌套的cond。抱歉,这太丑了……但是,如果有人可以告诉我我在做什么错,那将不胜感激。这段代码的工作原理与上面相同,但是它打印出nil作为结果列表的最后一个成员(在某些输入上),不确定为什么。

; attempt to use cond instead:
(defun new-union-helper (x y seen)
  (cond ((and (null x) (null y))
         nil)
        ((and (null x) (not (member (car y) seen)))
         (cons (car y) (new-union-helper (cdr y) x (cons (car y) seen))))
        ((null x)
                       (new-union-helper (cdr y) x seen))
        ((not (member (car x) seen))
         (cons (car x) (new-union-helper y (cdr x) (cons (car x) seen))))
        (t
         (new-union-helper (cdr x) y seen))))

更新2:我尝试采用更好的缩进。下面做了我希望它在非正式测试中所做的事情。关于我仍然在做错的任何其他提示? (我意识到我可能应该放弃这条路,而是走另一条路,但是由于这是一次学习练习,因此我想在继续走新路之前,尽早解决尽可能多的潜在不良习惯。)

如何评价丑陋的赌注? :)现在对经验丰富的使用者可读吗?

; better (standard?) formatting
(defun new-union-helper (x y seen)
  (cond ((and (null x) 
              (null y))
         nil)
        ((and (null x) 
              (member (car y) seen)) ; replacing find with member stops duplicate nils
         (new-union-helper (cdr y) x seen))
        ((null x)
         (cons (car y) 
               (new-union-helper (cdr y) x 
                                 (cons (car y) seen))))
        ((member (car x) seen) 
         (new-union-helper (cdr x) y seen))
        (t
         (cons (car x) 
               (new-union-helper y (cdr x) 
                                 (cons (car x) seen))))))

3 个答案:

答案 0 :(得分:2)

(defun new-union (list1 list2 &aux (list3 (reverse list1)))
  (loop for e in list2 do (pushnew e list3))
  (reverse list3))

(defun new-union (list1 list2 &aux (list3 (reverse list1)))
  (dolist (e list2 (reverse list3))
    (pushnew e list3)))

答案 1 :(得分:1)

Union接受两个列表作为参数,并将返回一个新列表,其中已删除重复项。您要保留显示的原始列表的顺序。我想起这本书中的一个具体问题是,如果您有以下列表:

(new-union '(a b c) '(b a d))

它应该返回:

(A B C D)

以保持正确的顺序。因此,我想您需要一个显然带有两个列表的函数,以及一个诸如累加器之类的函数,以便不破坏原始列表。联合是一种“非破坏性”功能。由于我们正在使用列表,因此可以使用dolist宏,以便我们可以遍历两个列表。这将导致我们得出以下结论:下面的函数可能会起作用,因为它将保持两个列表的原始结构,保持两个列表的顺序,并删除重复项:

(defun new-union(lst1 lst2)
   (let((accum nil))
     (dolist(x lst1)
       (push x accum))
     (dolist(y lst2)
       (if(not(find y accum))
      (push y accum)))
     (nreverse accum))

我们可以将第一个列表中的每个元素推到累加器中,然后我们可以遍历第二个列表,并且如果它不是已经推到累加器中的元素,则只能将其推到列表中。这样,我们可以避免重复,保持两个原始列表的结构,并且如果我们使用反向函数返回累加器,则可以保持正确的顺序。让我们在REPL中对其进行测试:

CL-USER> (new-union '(a b c) '(b a d))
(A B C D)

答案 2 :(得分:0)

这是一个递归实现。可以通过一些技巧使其更快。例如,哈希表可以用于保存已经看到的元素。在这种情况下,find将被固定时间的哈希表查找取代。

(defun new-union (lst1 lst2)
  "return xs U ys preserving order in originals"
  (labels ((rec (xs ys acc)
             (let ((x (car xs))
                   (xx (cdr xs))
                   (y (car ys))
                   (yy (cdr ys)))
               (cond ((and (null xs) (null ys))
                      acc)
                     ((null xs)
                      (or (and (find y acc) (rec xx yy acc))
                          (rec xx yy (cons y acc))))
                     ((null ys)
                      (or (and (find x acc) (rec xx yy acc))
                          (rec xx yy (cons x acc))))
                     ((and (find x acc) (find y acc))
                      (rec xx yy acc))
                     ((and (find x acc) (not (find y acc)))
                      (rec xx yy (cons y acc)))
                     ((and (not (find x acc)) (find y acc))
                      (rec xx yy (cons x acc)))
                     (t (rec xx yy (cons y (cons x acc))))))))
    (nreverse (rec lst1 lst2 nil))))