#common-lisp
#common-lisp
Вопрос:
Итак, у меня есть задание, в котором я должен удалить nil из списка без использования функции remove в common lisp. Я заставил его работать по большей части, за исключением того, что он не распечатывает последний элемент в 1 из списков.
Что у меня есть:
(defun removeNILMost (L)
(cond ((NULL L) NIL)
((listp (car L)) (OR (removeNILMost(car L)) (removeNILMost(cdr L))))
((not (eq nil (car L))) (cons (car L) (removeNILMost(cdr L))))
(T (removeNILMost(cdr L)))
)
)
Вывод:
;;; #2 removeNILMost
(removeNILMost '(NIL X NIL NIL Y NIL Z))
(X Y Z)
(removeNILMost '(X NIL (Y NIL Z) NIL))
(X Y Z)
(removeNILMost '(NIL (NIL) (X NIL Y) (NIL NIL) Z))
(X Y)
(removeNILMost '(NIL ( (((((NIL) NIL)))))))
NIL
Проблема одна — это 2-й по счету вывод, где он просто должен быть by (x Y Z) .
Ответ №1:
Во-первых, давайте немного очистим форматирование, чтобы упростить чтение. Обратите внимание, что оставлять висячие скобки в их собственных строках очень неидиоматично в lisp; в конце концов, это не язык фигурных скобок:
(defun removeNILMost (L)
(cond ((NULL L) NIL)
((listp (car L))
(OR (removeNILMost (car L))
(removeNILMost (cdr L))))
((not (eq nil (car L)))
(cons (car L)
(removeNILMost (cdr L))))
(T
(removeNILMost (cdr L)))))
Теперь, почему or
используется для объединения результатов двух рекурсивных вызовов removeNILMost
? Из or
-за короткого замыкания возвращается только первый результат, когда оба непустых списка в паре вызовов:
(OR (removeNILMost (car L))
(removeNILMost (cdr L))))
Это не желаемый результат, и это причина, по которой z
отсутствует финал (removeNILMost '(nil (nil) (x nil y) (nil nil) z))
.
Теперь, поскольку removeNILMost
возвращает список, append
является подходящей функцией для объединения этих результатов, чтобы получить результат операции (removeNILMost '(X NIL (Y NIL Z) NIL))
—> (X Y Z)
. Здесь целью должно быть как выравнивание входного списка, так и удаление nil
из него всех s. Это изменение «исправит» опубликованный код OP:
(append (removeNILMost (car L))
(removeNILMost (cdr L))))
Если целью является только удаление nil
s из входных данных, то древовидная структура входного списка должна быть сохранена. Примеры этого не показывают, но я подозреваю, что правильная реализация даст:
CL-USER> (removeNILMost '(X NIL (Y NIL Z) NIL))
(X (Y Z))
Чтобы сохранить структуру списка, условие может проверить, соответствует ли первый результат nil
или нет, прежде чем переходить ко второму результату:
(defun removeNILMost (L)
(cond ((NULL L) NIL)
((listp (car L))
(let ((left (removeNILMost (car L)))
(right (removeNILMost (cdr L))))
(if left
(cons left right)
right)))
((not (eq nil (car L)))
(cons (car L)
(removeNILMost (cdr L))))
(T
(removeNILMost (cdr L)))))
Следующий код немного очищает вышеупомянутое; обратите внимание, что kebab-case
это предпочтительнее, чем варианты camelCase
в lisp. Тесты были немного реорганизованы, чтобы сделать логику более понятной.
(defun remove-all-nils (L)
(cond ((null L) '()) ; empty list input remains empty
((null (car L)) ; if the first element is NIL, just move on
(remove-all-nils (cdr L)))
((listp (car L)) ; if the first element is a list...
(let ((left (remove-all-nils (car L)))
(right (remove-all-nils (cdr L))))
(if left ; conditionally CONS the results together
(cons left right)
right)))
(t ; otherwise the first element is not a list
(cons (car L) ; so CONS it to the result of processing the remainder
(remove-all-nils (cdr L))))))
Вот несколько примеров взаимодействия:
CL-USER> (remove-all-nils '(nil x nil nil y nil z))
(X Y Z)
CL-USER> (remove-all-nils '(x nil (y nil z) nil))
(X (Y Z))
CL-USER> (remove-all-nils '(nil (nil) (x nil y) (nil nil) z))
((X Y) Z)
CL-USER> (remove-all-nils '(nil a (b (c nil (nil) d) e (nil f g (h nil i (nil j (nil nil) k) l nil m) n ) o p q) r nil))
(A (B (C D) E (F G (H I (J K) L M) N) O P Q) R)
CL-USER> (remove-all-nils '(nil ((nil (nil)) (nil (nil (nil (nil nil)) nil))) nil))
NIL