让我们回顾一下您的代码的某些部分。
(if (not (oddp n)) (progn (print ...) (return-from ...)))
当您有一个只有一个分支的if 时,请考虑使用when 或unless,尤其是
如果您在子表单中输入progn。例如,您可以通过切换摆脱progn
给when:
(when (not (oddp n))
(print ...)
(return-from ...))
(when (not ...)) 表达式与(unless ...) 相同:
(unless (oddp n)
...)
在这里,您打印一条错误消息,并从函数返回。
Common Lisp 有针对这些用例的例外。
你通常会打电话给(error "Not an odd integer: ~d" n),
但在这里你可以依赖assert 的默认行为,并且
将整个检查替换为:
(assert (oddp n))
如果你用 2 来尝试,你会得到一个类似这样的错误消息:
The assertion (ODDP N) failed with N = 2.
这对于测试来说已经足够了。
那么,你有以下表达式:
(cons (car (list n)) (triangle (- n 2)))
当你写(list e1 e2 .. en)时,就好像你写了:
(cons e1 (cons e2 (... (cons en nil))))
在您的情况下,这意味着 (list n) 与:
(cons n nil)
但是既然你做了以下事情:
(car (cons n nil))
实际上,您只是为了访问n 而分配一个单元并丢弃它。
整个表达式可以替换为n。
第三,您还在lst 上使用setf,其中lst 是一个未定义的变量。
在大多数实现上(但实际上这种行为是未指定的),这将设置全局绑定
对于lst,从函数内部设置全局变量是一种不好的做法。
您可以改用let:
(let ((lst (cons n (triangle (- n 2)))))
(print lst))
但变量只使用一次,你也可以内联它:
(print (cons n (triangle (- n 2))))
最后,你有:
(defun triangle (n)
(assert (oddp n))
(if (< n 1)
()
(print (cons n (triangle (- n 2))))))
这是风格问题,但请记住,在其一个分支中返回 nil 的 if 通常可以替换
通过when 或unless(此处为unless)。有些人不喜欢这样,宁愿不依赖nil 的返回值when 和unless。
无论如何,让我们测试一下:
(triangle 7)
这给了:
(1)
(3 1)
(5 3 1)
(7 5 3 1)
注意列表是如何倒退的。
您可以尝试解决问题的一种方法是将(print ...) 替换为(print (reverse ...)),但这不起作用。你知道为什么吗?
相反,让我们向后构建列表,这需要从 1 开始计数,直到达到 n:
(defun triangle (n &optional (c 1))
(assert (oddp n))
(when (<= c n)
(print
(cons c (triangle n (+ c 2))))))
由于第二个参数是可选的,我们可以像以前一样调用它:
(triangle 7)
但是现在,输出是:
(7)
(5 7)
(3 5 7)
(1 3 5 7)