Стандартной функции для этого не существует, возможно, потому что такая функция считалась довольно дорогой, если бы она была правильной, но, на самом деле, мне кажется, что это просто упущение в языке.
Минимальная (не очень производительная) реализация, которая не основывается на обработке ошибок (Python люди думают, что это разумный способ программирования, я не думаю, хотя это - стилистический c выбор), я думаю,
(defun proper-list-p (l)
(typecase l
(null t)
(cons
(loop for tail = l then (cdr tail)
for seen = (list tail) then (push tail seen)
do (cond ((null tail)
(return t))
((not (consp tail))
(return nil))
((member tail (rest seen))
(return nil)))))))
Это занимает квадратичное время c длиной l
и пропорционально длине l
. Очевидно, что вы можете лучше использовать хеш-таблицу для проверки на наличие происшествий, и вы можете использовать алгоритм «черепаха-заяц», чтобы избежать проверки на наличие происшествий (но я не уверен, какая сложность этого лежит на моей голове).
Я уверен, что в библиотеках есть намного лучшие функции, чем эта. В частности Александрия имеет один.
Размышляя над этим вопросом, я также написал эту функцию:
(defun classify-list (l)
"Classify a possible list, returning four values.
The first value is a symbol which is
- NULL if the list is empty;
- LIST if the list is a proper list;
- CYCLIC-LIST if it contains a cycle;
- IMPROPER-LIST if it does not end with nil;
- NIL if it is not a list.
The second value is the total number of conses in the list (following
CDRs only). It will be 0 for an empty list or non-list.
The third value is the cons at which the cycle in the list begins, or
NIL if there is no cycle or the list isn't a list.
The fourth value is the number if conses in the cycle, or 0 if there is no cycle.
Note that you can deduce the length of the leading element of the list
by subtracting the total number of conses from the number of conses in
the cycle: you can then use NTHCDR to pull out the cycle."
;; This is written as a tail recursion, I know people don't like
;; that in CL, but I wrote it for me.
(typecase l
(null (values 'null 0 nil 0 0))
(cons
(let ((table (make-hash-table)))
(labels ((walk (tail previous-tail n)
(typecase tail
(null
(values 'list n nil 0))
(cons
(let ((m (gethash tail table nil)))
(if m
(values 'cyclic-list n tail (- n m))
(progn
(setf (gethash tail table) n)
(walk (cdr tail) tail (1+ n))))))
(t
(values 'improper-list n previous-tail 0)))))
(walk l nil 0))))
(t (values nil 0 nil 0))))
Это можно использовать, чтобы получить кучу информации о списке: как долго это будет, если это правильно, если нет, если это cycli c, и где цикл. Помните, что в случае циклических c списков это вернет круговую структуру в качестве третьего значения. Я полагаю, что для этого вам нужно использовать проверку событий - черепаха и заяц сообщат вам, если список циклический c, но не там, где начинается цикл.