【发布时间】: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