Common lisp 公共lisp中的并集,保留原始列表中元素的顺序

Common lisp 公共lisp中的并集,保留原始列表中元素的顺序,common-lisp,Common Lisp,我正在通过Paul Graham的“ANSI Common Lisp”(1996)进行学习。 第三章,练习,第二节要求一个函数,如本文标题所述。到目前为止,我只使用了书中所教的内容(显然有case结构可以清理if,但目前我并不在意) 作为第一次尝试,我最终编写了interleave,它保留了重复项: (defun new-union(lst1 lst2) (let((accum nil)) (dolist(x lst1) (push x accum)) (

我正在通过Paul Graham的“ANSI Common Lisp”(1996)进行学习。 第三章,练习,第二节要求一个函数,如本文标题所述。到目前为止,我只使用了书中所教的内容(显然有
case
结构可以清理
if
,但目前我并不在意)

作为第一次尝试,我最终编写了interleave,它保留了重复项:

(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))
(反联合交织(x-y)
(如果(和(空x)
(空(y))
无
(如果(空x)
(反对党(y车)
(交织(cdr y)x))
;其中y为空,但对于任何其他情况:
(cons(x车)
(交织y(cdr x(()())))
在这之后,我有了存储已看到的元素的进位的想法,并遵从助手函数,如下所示。 然而,下面的内容显然是相当丑陋和难以理解的。 我正在寻求一些关于我可能实现优雅的方向的建议

关于方法和风格的提示在这一点上可能与提供规范解决方案一样有用。下面给出的代码中,我的第一个冲动应该是提取另一个函数吗?(或者可能我在一开始就试图储存随身携带物品时走错了方向?)谢谢各位黑客们

(拆卸新接头(x y)

(新工会助理x y’()) 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)

这是一个递归实现。只需几次破解,它就能变得更快。例如,哈希表可用于保存已看到的元素。在这种情况下,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))))

首先,我会尝试更好地说明这个问题,因为格雷厄姆的问题很不清楚。“保留原始列表中元素的顺序”实际上意味着什么(特别是对于相互冲突的顺序)?
(新联合体(ab);(ba))
应该返回什么?书中的例子似乎更喜欢第一个列表的顺序,以防冲突。那么
(新联合体(abc);(abaxc)呢?
?是否需要返回
(a b x c)
以在第二个列表中保留x->c顺序?性能重要吗?结果能否与参数共享结构?要么语法不好,因为
if
只能有3部分而不是5部分,要么格式太差,无法读取代码。保存一个包含可见元素的列表不是最佳选择。它使它成为O(n^2)。将看到的元素保存在哈希表中要好得多。我知道这是为了学习,但也许你应该实现
删除重复项
(标准CL函数,但你只需要支持列表),然后它变成
(删除重复项(追加x y))
@sylvester-你说得对,谢谢。我已经调整好了--我现在的情况是否不那么糟糕了?想知道整理它的下一步是什么——也许会以某种方式替换那些嵌套的ifs?@SteveLosh-我意识到问题规范是不明确的,但由于这只是一个练习,而且没有提供任何答案,我想我可以在开发过程中选择一个确切的规范。显然不是在不同的上下文中所做的:)当对象不是元素时,MEMBER将始终返回NIL。它还将始终返回一个真实的值,当对象是一个元素时,这与FIND不同,FIND具有不同的目的,因此具有不同的行为。太好了,谢谢。我在这个问题上做了很多不同的尝试(刚才我想了解
cond
是如何比
if
有所改进的,以及在什么情况下;如上面对原始帖子的更新所示)。所以,我明天会以一种全新的思维来看看你的解决方案。很期待。谢谢你,西蒙!:)差不多
(newunion'(t nil)-(t nil t))->(t nil nil)
另请参见宏
pushnew
@SimeonIkudabo当您说“非破坏性”时,您实际上是指“非破坏性”吗?我知道clojure中的解构是什么,但在CL中还没有遇到过。显然,Culjule和FP破坏性操作通常不是一种选择,但我理解普通LISP是多范式的……,需要意识到这一点(是的),这是你认为破坏性的(AKA改变列表,而不是简单地返回一个列表的值)。您将在lisp中遇到clojure中提到的内容,但在宏定义方面更是如此。你参考的那本书有一个关于宏的小章节,但你看完后,我读过格雷厄姆的《关于lisp》(93)。当您传递参数列表时,您在clojure中提到的与宏定义有关的内容比其他任何内容都要多。“do”宏就是一个例子。它是什么
(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)))
(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))))