【问题标题】:union in common lisp, preserving order of elements in original listscommon lisp中的联合,保留原始列表中元素的顺序
【发布时间】:2019-03-15 15:19:46
【问题描述】:

我正在研究 Paul Graham 的“ANSI Common Lisp”(1996 年)。 第 3 章,练习,曲。 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)))))

更新:我尝试用cond 替换嵌套的ifs,并在本书的索引中查找了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:我尝试采用更好的缩进。下面是我希望它从非正式测试中做的事情。关于我仍然做错的任何进一步提示? (我意识到我可能应该放弃这个并追求另一条道路,但由于这是一个学习练习,我想尽早改掉尽可能多的潜在坏习惯,然后再继续新的道路)。

这对丑陋赌注的评价如何? :) 现在有经验的 lisper 是否可以阅读它?

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

【问题讨论】:

  • 我会先尝试更好地说明问题,因为格雷厄姆的问题很不清楚。 “保留原始列表中元素的顺序”实际上是什么意思(特别是对于冲突的顺序)? (new-union '(a b) '(b a)) 应该返回什么?书中的示例似乎更喜欢第一个列表的顺序,以防发生冲突。 (new-union '(a b c) '(b a x c)) 呢?这是否需要返回(a b x c) 以保留第二个列表中的 x->c 顺序?性能重要吗?结果可以与参数共享结构吗?
  • 要么你的语法不好,因为if 只能有 3 个部分而不是 5 个部分,或者你的格式很糟糕,无法阅读代码。保留一个包含可见元素的列表并不是最佳选择。它使它成为O(n ^ 2)。将所见元素保存在哈希表中要好得多。我知道这是为了学习,但也许你应该实现remove-duplicates(标准CL功能,但你只需要支持列表)然后它变成(remove-duplicates (append x y))
  • @Sylwester - 你是对的,谢谢。我已经对其进行了调整——我现在有什么不那么糟糕了吗?想知道下一步整理它的步骤是什么——也许以某种方式替换那些嵌套的 if?
  • @SteveLosh - 我已经意识到问题规范是模棱两可的,但由于它只是一个练习并且没有提供任何答案,我想我可以在开发过程中选择一个确切的规范。显然不是你在不同情况下会做的事情:)
  • 当事物不是元素时,MEMBER 将始终返回 NIL。当事物是元素时,它也将始终返回一个 true 值,这与 FIND 不同,FIND 具有不同的目的,因此具有不同的行为。

标签: common-lisp


【解决方案1】:
(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)))

【讨论】:

  • 其余参数后面的位是什么?看起来不像是解构。它的服务有点像让吗?
  • 我还没有完全掌握这一点,以便能够自信地将您的答案标记为正确,(混淆了 re. adjoin vs pushnew,正如书中提到的,似乎做同样的事情) ,但是我将上面的函数粘贴到我的 clisp repl 中并发现它们实际上并没有执行集合联合-它们不会删除重复项...? ...但话虽如此,我正在使用 整数列表 进行测试 - 当我切换到符号列表 '(a b c) 等时,它似乎按预期工作。
  • @mwal:Common Lisp 中的默认比较谓词是 EQL。如果你想比较列表,你需要别的东西。例如(pushnew item place :test #'equal)。请参阅 PUSHNEW 的文档。
【解决方案2】:

Union 将两个列表作为参数,并将返回一个新列表,其中删除了您所知道的重复项。您想保留它出现的原始列表的顺序。如果我记得这本书的具体问题是,如果你有清单:

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

它应该返回:

(A B C D)

为了保持正确的顺序。所以我想你需要一个显然需要两个列表的函数,以及诸如累加器之类的东西,这样你就不会破坏原始列表。 Union 是一个“非解构”函数。由于我们正在处理列表,因此您可以使用 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)

【讨论】:

  • 太好了,谢谢。我最终在许多不同的方向上进行了黑客攻击(刚刚试图了解cond 如何改进if,以及在什么情况下;如上面原始帖子的更新所示)。所以,明天我会以全新的心态来看看你的解决方案。期待。谢谢西蒙! :)
  • 差不多。 (new-union '(t nil) '(nil t)) -&gt; (T NIL NIL)
  • 另见宏pushnew
  • @SimeonIkudabo 当您说“非破坏性”时,您实际上是指“非破坏性”吗?我知道clojure中的解构是什么,还没有在CL中遇到过。此外,显然对于 clojure 和 fp 破坏性操作通常不是一种选择,但我理解 common lisp 是多范式......,需要注意这一点(!)
  • 是的,您认为这是破坏性的(也就是更改列表,而不是简单地返回列表的值)。您会在 lisp 中遇到您在 clojure 中提到的内容,但在宏定义方面更是如此。你所引用的书有一个关于宏的小章节,但当你完成后,我会阅读 graham ('93) 的“on lisp”。当您传递参数列表时,您在 clojure 中提到的内容更多地出现在宏定义中。 “do”宏就是一个例子。
【解决方案3】:

这是一个递归实现。它可以通过一些技巧变得更快。例如,可以使用散列表来保存已经看到的元素。在这种情况下,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))))

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2011-11-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-03-27
    相关资源
    最近更新 更多