我正在尝试弄清楚如何从另一个列表中获取 last (非空)列表,或者如果没有这样的列表(递归)则返回nil
。这是一个家庭作业,因此我正在寻找方法的帮助,不一定是它的代码。例如:
(lastele '(1 (2 3) 4 5)) ;=> (2 3)
(lastele '(1 (2 3) (4 5)) ;=> (4 5)
(lastele '(1 2 3 4 5)) ;=> NIL
我试图浏览列表,如果我遇到一个子列表,我会检查列表的其余部分是否包含更多非空子列表,如果是,则继续将列表设置为,并重复直到我们有一个空列表。
(defun lastele2 (L)
(if (null L)
'()
(if (hasMoreLists (rest L))
(lastele2 (rest L))
(first L))))
但是,好像我无法让hasMoreLists
工作。返回t
或f
只是错误。这是解决这个问题的最好方法吗?
答案 0 :(得分:6)
首先,请注意您隐含地假设没有一个子列表是空列表;如果它们可能是空列表,则nil
是一个模棱两可的结果,因为您无法判断您的函数是否返回nil
,因为没有子列表,或者因为存在,最后一个是空。例如,
(fn '(1 2 3 4 5)) ;=> nil because there are no sublists
(fn '(1 2 3 () 5)) ;=> nil because there are sublists, and the last one is nil
因此,假设顶层列表中存在非非空子列表,我们可以继续。
你不需要写这个。您可以将find-if
与谓词listp
一起使用,并指定您希望使用关键字参数:from-end t
从最后搜索:
CL-USER> (find-if 'listp '(1 (2 3) 4 5) :from-end t)
(2 3)
CL-USER> (find-if 'listp '(1 (2 3) (4 5)) :from-end t)
(4 5)
CL-USER> (find-if 'listp '(1 2 3 4 5) :from-end t)
NIL
如果您需要编写类似这样的内容,最好的办法是使用递归函数搜索列表并跟踪您最近看到的列表元素(起始值为{{ 1}})当你最终到达列表的末尾时,你将返回该结果。例如,
nil
这里的局部函数(defun last-list (list)
(labels ((ll (list result) ; ll takes a list and a "current result"
(if (endp list) ; if list is empty
result ; then return the result
(ll (cdr list) ; else continue on the rest of list
(if (listp (car list)) ; but with a "current result" that is
(car list) ; (car list) [if it's a list]
result))))) ; and the same result if it's not
(ll list nil))) ; start with list and nil
是尾递归的,有些实现会将它优化为循环,但使用真正的循环结构会更加惯用。例如,ll
,你会写:
do
如果您不想使用标签,可以将其定义为两个功能:
(defun last-list (list)
(do ((result nil (if (listp (car list)) (car list) result))
(list list (cdr list)))
((endp list) result)))
或者,您可以(defun ll (list result)
(if (endp list)
result
(ll (cdr list)
(if (listp (car list))
(car list)
result))))
(defun last-list (list)
(ll list nil))
将last-list
作为可选参数,使ll
和last-list
具有相同的功能:
result
在所有这些情况下,您实现的算法基本上是迭代的。这是
输入: 列表
结果← nil
while ( list 不为空)
if ( list 的第一个元素是列表)
结果←列表的第一个元素
结束如果
列表←列表的其余部分
结束时
返回 结果
尽管如此,我们仍然可以找到更接近原始方法的东西(它将使用更多的堆栈空间)。首先,你的原始代码有适当的缩进(和一些换行符,但那里的编码风格更灵活):
(defun last-list (list &optional result)
(if (endp list)
result
(last-list (cdr list)
(if (listp (car list))
(car list)
result))))
您尝试使用看起来的方法是将列表(defun lastele2 (L)
(if (null L)
'()
(if (hasMoreLists (rest L))
(lastele2 (rest L))
(first L))))
的最后一个子列表定义为:
L
,如果nil
为空; L
有一些子列表,则无论(rest L)
的最后一个子列表是什么;和(rest L)
没有某些子列表,则为(rest L)
。但最后一行并不完全正确。它必须是
(first L)
没有某些子列表,则(rest L)
如果(first L)
是列表,则为(first L)
。现在,您已经有办法检查nil
是否有任何(非空)子列表;您只需检查(rest L)
是否会返回(lastele2 (rest L))
。如果它返回nil
,则它不包含任何(非空)子列表。否则它会返回其中一个列表。这意味着你可以写:
nil
这是实现一个基本上递归的算法;返回子问题的值,然后 lastList 在检查结果后返回一个值:
功能: lastList(列表)
如果(列表为空)
返回零
的其他强>
结果← lastList(list)
如果(结果不是 nil )
返回 结果
其他如果( list 的第一个元素是列表)
返回 列表的第一个元素
的其他强>
返回零
结束如果
结束
答案 1 :(得分:1)
不,这不是解决这个问题的最好办法。要查找列表的其余部分是否有更多列表,您需要搜索它 - 如果有,则重新开始扫描列表的其余部分。
即。你做了很多来回。
而是just search along,并更新一个辅助变量以指向您沿途找到的任何列表。
(defun lastele (lst &aux a) ; a is NIL initially
(dolist (e lst a) ; return a in the end
(if (consp e) (setq a e))))