Vincent、Gishu 和 Jerry 暗示您需要在添加之前检查该项目是否已经在结果列表中,而 Derek 暗示您可以在看到某个项目重复时修改原始列表。
在此处阅读函数 adjoin 和 remove 的文档:
http://www.lispworks.com/documentation/HyperSpec/Body/f_adjoin.htm
http://www.lispworks.com/documentation/HyperSpec/Body/f_rm_rm.htm
HyperSpec 是一个非常有用的参考,请将其添加到您的书签中。
这两个函数不会修改它们的参数,而是将结果作为新列表返回。还有其他函数会修改它们的参数,因此可能会更有效,但此时也许你不应该担心它们。
好的,我希望在我写这篇文章的时候你已经明白了。如果没有,请继续尝试,这是您真正学习的唯一方法。
现在我想和你谈谈另一种方法,那就是通过递归调用传递结果。
我们的函数 repeated 只会调用一个辅助函数 repeatedr 来完成实际工作,并将初始空结果 '() 传递给它:
(defun repeated (lst)
(repeatedr lst '()))
现在让我们定义辅助函数,它接收两个参数:搜索重复项的列表,以及我们将累积重复项的结果列表。
(defun repeatedr (lst result)
(if (null lst)
result
(if (member (first lst) (rest lst))
(repeatedr (rest lst) (adjoin (first lst) result))
(repeatedr (rest lst) result))))
当条件(member (first lst) (rest lst)) 成立时,我们将邻接第一项到result,并且邻接的结果将作为第二个参数传递给递归调用;否则我们只需将result 原样传递给递归调用。
当列表最终为空(if (null lst) 时,我们将返回result。
> (repeated '(a a b a b c))
(B A)
请注意,结果是(B A),也许您期望它是(A B)。尝试用笔和纸跟踪递归调用的执行和每次调用的参数值,这将是一个很好的练习,您必须使用adjoin 来了解它的行为。如果你想得到相反的结果,你可以像这样修改函数:
(defun repeatedr (lst result)
(if (null lst)
(reverse result)
(if (member (first lst) (rest lst))
(repeatedr (rest lst) (adjoin (first lst) result))
(repeatedr (rest lst) result))))
这反转是递归结束时的结果。
现在,在继续之前从列表中删除重复元素的建议怎么样?我们可以这样写我们的函数:
(defun repeatedr (lst result)
(if (null lst)
result
(if (member (first lst) (rest lst))
(repeatedr (remove (first lst) lst) (cons (first lst) result))
(repeatedr (rest lst) result))))
尝试在 REPL 中使用remove:
> (remove 'a '(a b c d a e f b a d))
(B C D E F B D)
请注意,我们不再连接结果,而是使用(cons (first lst) result) 创建一个新的“cons cell”。 cons 和 adjoin 做同样的事情,除了 adjoin 只有当它不在列表中时才会添加该值。
希望这能给你带来一些乐趣。