Версія з обчисленням за один прохід:
(defvar *carry* 0)
(defun c+ (a b)
(let ((sum (+ a b *carry*)))
(setf *carry* (floor sum 10))
(mod sum 10)))
(defun lst2num (lst)
(if lst (car lst) 0))
(defun addlists (L1 L2)
(let ((*carry* 0))
(loop
for p1 = l1 then (cdr p1)
for p2 = l2 then (cdr p2)
while (or p1 p2 (> *carry* 0))
collect (c+ (lst2num p1) (lst2num p2)))))
Від mapcat відмовився — всі повторювані дії тепер організовано за допомогою більш універсального loop.
У ньому є конструкція for змінна = початкове-значення then наступне-значення , за допомогою якої я здійснюю проходження по списках. Існує і більш спеціалізована конструкція for змінна in список, але вона завершує роботу циклу при досягненні кінця списку (коротшого з них), а мені треба продовжити цикл після кінця списків, поки в *carry* лишається ненульове значення переносу розряду, тому рух по списках організовано за допомогою cdr (який повертає всі елементи, крім першого, якщо список не порожній, або nil, якщо список порожній; nil і порожній список () — у термінах CL, одне й те ж, тому (cdr p1) нескінченно повертатиме nil після кінця списку), а перевірку умови продовження зроблено окремо конструкцією while умова.
Умова продовження циклу: залишок першого чи другого списку має бути непорожнім, або в *carry* має лежати ненульове значення. Оскільки будь-яке значення, крім nil, вважається істиною, то замість (not (eq p1 nil)) можна написати просто p1.
Конструкція collect вираз збирає значення, обчислювані в кожній ітерації, і повертає їх у вигляді списку. Для обчислення значень я використав ту ж функцію c+ та допоміжну функцію lst2num, яка повертає перший елемент непорожнього списку або 0, якщо список порожній.
Нова версія addlists видає такі ж результати, як попередня:
CL-USER(143): (addlists '(1 2 3)'(4 5 6))
(5 7 9)
CL-USER(144): (addlists '(1 2 3)'(7 8 9))
(8 0 3 1)
CL-USER(145): (addlists '(1 0 1 1)'(7 8 9))
(8 8 0 2)
але зроблена більш оптимально, займає менше рядків коду, на довгих списках має показувати вищу швидкодію.