看起来all-print 应该被称为ffn,因为看起来那些应该是递归调用。在这个答案的其余部分,我将使用ffn,因为它更短。
为什么输出是反向的
目前,您的最终cond 子句在进行任何打印之前 进行递归调用,因为您的递归调用是format 的参数:
(format t "~a " (car inlist) (ffn (cdr inlist)))
; ------------ -----------------
; 3rd 4th
format 的所有参数,包括本例中的第 4 个参数,都在调用 format 之前进行评估。这里的第 4 个参数将打印列表的 rest,然后format 将最终打印列表的第一个元素。您的最后一个 cond 子句应该进行打印,然后 然后 进行递归调用:
(cond
…
(t
(format t "~a " (car inlist))
(ffn (cdr inlist))))
为什么你得到 100.999 而不是 9.99
您在输出中得到 100.999 而不是 9.99(或接近它的值),因为 (* 100.999) 的值就是 100.999 的值。我猜你想要(* 10 0.999)(注意10 和0.99 之间的空格)。不过,由于浮点运算,这仍然不会完全是 9.99,但它会很接近。
如何获取打印的元素数量
uselpa's answer 在这里提供了一个很好的解决方案。如果您应该返回打印的元素数量,那么此函数的 every 返回值应该是一个数字。你有四种情况,
- 不是一个列表——返回
nil 不是一个好主意。如果这不能返回一个数字(例如,0),则发出一个真正的错误信号(例如,(error "~A is not a list" inlist)。
- inlist 为空 - 返回 0(你已经这样做了)
-
(car inlist) 是一个列表——在这里你递归调用ffn。由于合同说它会返回一个计数,所以你很好。这是在第一种情况下(不是列表)如此重要以至于您不返回非数字的原因之一;合约取决于每个返回数字的调用。
-
在最后一种情况下,您打印一个项目,然后递归调用ffn。该递归调用返回打印的剩余元素的数量,并且由于您刚刚打印了一个,因此您需要添加一个。因此,最后的cond 子句实际上应该类似于以下内容。 (给某物加一是如此普遍,以至于 Common Lisp 有一个 1+ 函数。)
(cond
…
(t
(format t "~a " (car inlist))
(1+ (ffn (cdr inlist))))) ; equivalent to (+ 1 (ffn (cdr inlist)))
更高效的解决方案
我们已经用您的原始代码解决了这些问题,但我们也可以询问是否有更好的方法来解决这个问题。
不要附加
请注意,当您输入((a b c) d e f) 之类的内容时,您会创建列表(a b c d e f) 并对其进行递归。但是,您可以等效地递归 (a b c) 和 (d e f),并将结果相加。这将避免使用append 创建新列表。
不检查参数类型
您正在检查输入是否是一个列表,但实际上并没有太多需要这样做。如果输入不是列表,则在其上使用列表处理函数将发出类似错误的信号。
新版本
这有点类似于uselpa's answer,但我对如何处理某些事情做出了一些不同的选择。我使用本地函数process-element 来处理每个输入列表中的元素。如果元素是一个列表,那么我们递归地将它传递给print-all,并返回递归调用的结果。否则,我们返回一个并打印该值。 (我用(prog1 1 …)来强调我们正在返回一个,打印只是一个副作用。print-all的主要部分现在是一个典型的递归。
(defun print-all (list)
(flet ((process-element (x)
(if (listp x)
(print-all x)
(prog1 1
(format t "~A " x)))))
(if (endp list)
0
(+ (process-element (first list))
(print-all (rest list))))))
当然,既然我们已经拉出了辅助函数,迭代就清晰了一点,我们看到它实际上是reduce的一个案例。您甚至可以选择取消本地函数,而只使用 lambda 函数:
(defun print-all (list)
(reduce '+ list
:key (lambda (x)
(if (listp x)
(print-all x)
(prog1 1
(format t "~A " x))))))