Давайте рассмотрим некоторые части вашего кода.
(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
.
В-третьих, вы также используете setf
для lst
, где 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))))))
Это вопрос стиля, но помните, что if
, который возвращает nil
в одной из его ветвей, обычно можно заменить на 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)