15.2 Списки

Следующие функции выполняет различные операции над списками.

Список является одним из первых Lisp’овых типов данных. Имя «Lisp» расшифровывается как «LISt Processing».

[Функция] endp object

Предикат endp используется для проверки конца списка. Возвращает ложь для cons-ячеек, истину для nil, и генерирует ошибку для всех остальных объектов других типов. _______________________________________

Заметка для реализации: Implementations are encouraged to signal an error, especially in the interpreter, for a non-list argument. The endp function is defined so as to allow compiled code to perform simply an atom check or a null check if speed is more important than safety.

___________________________________________________________________________________________________________

[Функция] list-length list

list-length возвращает длину списка list. list-length отличается от length при использовании с циклическим списком. В таком случае length может не вернуть управление, тогда как list-length вернёт nil. Например:

(list-length ’())  0
(list-length ’(a b c d))  4
(list-length ’(a (b c) d))  3
(let ((x (list ’a b c)))
  (rplacd (last x) x)
  (list-length x))  nil

list-length может быть реализован так:

(defun list-length (x)
  (do ((n 0 (+ n 2))            ;Счётчик
       (fast x (cddr fast))     ;Быстрый указатель: на две позиции вперёд
       (slow x (cdr slow)))     ;Медленный указатель: на одну позицию
      (nil)
    ;; Если быстрый указатель дошёл до конца, вернуть длину.
    (when (endp fast) (return n))
    (when (endp (cdr fast)) (return (+ n 1)))
    ;; If fast pointer eventually equals slow pointer,
    ;; then we must be stuck in a circular list.
    ;; (A deeper property is the converse: if we are
    ;; stuck in a circular list, then eventually the
    ;; fast pointer will equal the slow pointer.
    ;; That fact justifies this implementation.)
    (when (and (eq fast slow) (> n 0)) (return nil))))

Смотрите length, которая возвращает длину любой последовательности.


[Функция] nth n list

(nth n list) возвращает n-нный элемент списка list. car элемент списка принимается за «нулевой» элемент. Аргумент n должен быть неотрицательным целым числом. Если длина списка не больше чем n, тогда результат (), или другими словами nil. (Это согласовывается с концепцией того, что car и cdr от () являются ().) Например:

(nth 0 ’(foo bar gack))  foo
(nth 1 ’(foo bar gack))  bar
(nth 3 ’(foo bar gack))  ()

nth может быть использован в связке с setf для изменения элемента списка. В этом случае, аргумент n должен быть меньше чем длина списка list.

Следует отметить, что порядок аргументов в nth обратный в отличие от большинства других функций селекторов для последовательностей, таких ка elt.


[Функция] first list
[Функция] second list
[Функция] third list
[Функция] fourth list
[Функция] fifth list
[Функция] sixth list
[Функция] seventh list
[Функция] eighth list
[Функция] ninth list
[Функция] tenth list

Иногда эти функции удобно использовать для доступа к определёнными элементам списка. first то же, что и car, second то же, что и cadr, third то же, что и caddr, и так далее. Следует отметить, что нумерация начинается с единицы (first) в отличие от нумерации, которая начинается с нуля и используется в nth.

(fifth x)  (nth 4 x)

Каждая из этих функций может быть использована в связке setf для изменения элемента массива.


[Функция] rest list

rest означает то же, что и cdr, но мнемонически согласуется с first. rest может использоваться в связке с setf для изменения элементов массива.


[Функция] nthcdr n list

(nthcdr n list) выполняет для списка lisp операцию cdr n раз, и возвращает результат. Например:

(nthcdr 0 ’(a b c))  (a b c)
(nthcdr 2 ’(a b c))  (c)
(nthcdr 4 ’(a b c))  ()

Другими словами, она возвращает n-нную cdr часть списка.

(car (nthcdr n x))  (nth n x)

Аргумент n должен быть неотрицательным целым числом.


[Функция] last list &optional (n 1)

last возвращает последние n cons-ячеек списка lisp. Список list может быть списком с точкой. Передача зацикленного списка является ошибкой.

Аргумент n должен быть неотрицательным целым числом. Если n равен нулю, тогда возвращается последний атом списка list. Если n не меньше чем количество cons-ячеек, то возвращается весь список.

Например:

(setq x ’(a b c d))
(last x)  (d)
(rplacd (last x) ’(e f))
x  ’(a b c d e f)
(last x 3)  (d e f)
(last ’())  ()
(last ’(a b c . d))  (c . d)
(last ’(a b c . d) 0)  d
(last ’(a b c . d) 2)  (b c . d)
(last ’(a b c . d) 1729)  (a b c . d)


[Функция] list &rest args

list создаёт и возвращает список, составленный из аргументов. Например:

(list 3 4 ’a (car ’(b . c)) (+ 6 -2))  (3 4 a b 4)

(list)  ()
(list (list ’a ’b) (list ’c ’d ’e))  ((a b) (c d e))


[Функция] list* arg &rest others

list* похожа на list за исключением того, что последняя cons-ячейка создаваемого списка будет «с точкой». Последний аргумент используется как последний элемент списка, а именно в последней cons-ячейки в cdr элементе. Данный аргумент необязательно должен быть атомом, и если он не атом, то в результате список будет иметь большую длину чем количество аргументов. Например:

(list* ’a ’b ’c ’d)  (a b c . d)

Это то же, что и

(cons ’a (cons ’b (cons ’c ’d)))

А также:

(list* ’a ’b ’c ’(d e f))  (a b c d e f)
(list* x)  x


[Функция] make-list size &key :initial-element

Функция создаёт и возвращает список содержащий количество size элементов, каждый из которых будет инициализирован значением аргумента :initial-element (который по-умолчанию nil). size должен быть неотрицательным целым числом. Например:

(make-list 5)  (nil nil nil nil nil)
(make-list 3 :initial-element ’rah)  (rah rah rah)


[Функция] append &rest lists

Функция возвращает список содержащий все элементы указанных в аргументах списков. Аргументы не разрушаются. Например:

(append ’(a b c) ’(d e f) ’() ’(g))  (a b c d e f g)

Следует отметить, что append копирует верхний уровень всех переданных списков за исключением последнего. Функция concatenate выполняет похожую операцию, но всегда копирует все аргументы. Смотрите также nconc, которая похожа на append, но разрушает все аргументы кроме последнего.

Последний аргумент может быть любым Lisp объектом, и в этом случае этот объект становится последним элементов итогового списка. Например, (append ’(a b c) ’d)  (a b c . d).

(append x ’()) может быть использовано для копирования списка x, однако для этого больше подходит функция copy-list.


[Функция] copy-list list

Функция возвращает список, который равен equal и в то же время не равен eq списку list, Копируется только верхний уровень списка, то есть copy-list копирует только в направлении cdr элементов, но не в направлении car элементов. Если список «с точкой», то есть (cdr (last list)) является не-nil атомом, тогда итоговый список также будет «с точкой». Смотрите также copy-seq и copy-tree.


[Функция] copy-alist list

copy-alist копирует ассоциативные списки. При этом, также как и в copy-list, копируется только верхний уровень списка lisp. Кроме того, каждый элемент списка list, являющийся в свою очередь cons-ячейкой, заменяется новой cons-ячейкой с теми же car и cons элементами.


[Функция] copy-tree object

copy-tree копирует древовидно организованные cons-ячейки. Аргумент object может быть любым Lisp’овым объектом. Если он не является cons-ячейкой, то ничего не произойдёт и данный объект будет возвращён в качестве результата. В противном случае будет возвращена новая cons-ячейка, в которой car и cons элементы будут результатами рекурсивных вызовов copy-tree. Другими словами, все cons-ячейки будут рекурсивно скопированы, и рекурсия будет останавливаться только на атомах.


[Функция] revappend x y

(revappend x y) похожа на (append (reverse x) y) за исключением того, что она потенциально более производительна. Аргументы x и y должны быть списками. Аргумент x копируется (не разрушается) в отличие от nreconc, которая разрушает первый аргумент.


[Функция] nconc &rest lists

В качестве аргументов nconc принимает списки. Функция соединяет списки и возвращает результат. При этом аргументы изменяются, а не копируются. (В сравнении с append, которая копирует аргументы, а не разрушает их.) Например:

(setq x ’(a b c))
(setq y ’(d e f))
(nconc x y)  (a b c d e f)
x  (a b c d e f)

Следует отметить, что в примере, значение x отличается от первоначального, так как последняя cons-ячейка была изменена с помощью rplacd значением y. Если сейчас выполнить (nconc x y) ещё раз, тогда часть списка зациклится: (a b c d e f d e f d e f ...), и так до бесконечности. Если *print-circle* не равен nil, тогда вывод списка будет таким: (a b c . #1=(d e f . #1#)).

Вызов nconc, совпадающий с наиболее близким шаблоном выражения в левой части приводит к эквивалентным побочным действиям, как в правой части таблицы.

(nconc) nil     ;Нет побочных эффектов
(nconc nil . r)    (nconc . r)
(nconc x) x
(nconc x y) (let ((p x) (q y))
  (rplacd (last p) q)
  p)
(nconc x y . r) (nconc (nconc x y) . r)

[Функция] nreconc x y

(nreconc x y) похожа на (nconc (nreverse x) y) за исключением того, что она потенциально эффективнее. Оба аргумента должны быть списками. Аргумент x разрушается. Сравните с revappend.

(setq planets ’(jupiter mars earth venus mercury))
(setq more-planets ’(saturn uranus pluto neptune))
(nreconc more-planets planets)
 (neptune pluto uranus saturn jupiter mars earth venus mercury)
  теперь значение more-planets точно не определено

Поведение (nreconc x y) совпадает с поведением (nconc (nreverse xy) в части побочных эффектов.


[Макрос] push item place

Форма place должна быть именем обобщённое переменной, содержащей список. item может указывать на любой Lisp’овый объект. item вставляется в начало списка и данный список возвращается в качестве результата. Форма place может быть любой формой, которая подходит для setf. Если рассматривать список как стек, тогда push добавляет элемент на вершину стека. Например:

(setq x ’(a (b c) d))
(push 5 (cadr x))  (5 b c) и теперь x  (a (5 b c) d)

Действие от

(push item place)

эквивалентно действию

(setf place (cons item place))

за исключением того, что push выполняет форму place только один раз, а не три. Более того, в для некоторых форм place push может быть эффективнее чем версия с setf.

Следует отметить, что item вычисляется прежде чем вычисляется place.


[Макрос] pushnew item place &key :test :test-not :key

Форма place должна быть именем обобщённое переменной, содержащей список. item может указывать на любой Lisp’овый объект. Если item не содержится в списке (этот факт устанавливается с помощью предиката переданного в :test, который по-умолчанию eql), тогда item вставляется в начало списка и данный список возвращается в качестве результата. В противном случае возвращается исходный список. Форма place может быть любой формой, которая подходит для setf. Если рассматривать список как множество, тогда pushnew добавляет элемент в множество. Смотрите adjoin.

Именованные параметры pushnew имеют тот же смысл, что и в функциях для последовательностей. Смотрите главу 14. По сути, данные аргументы идентичны аргументам adjoin.

pushnew возвращает модифицированное содержимое переменной place. Например:

(setq x ’(a (b c) d))
(pushnew 5 (cadr x))  (5 b c) и теперь x  (a (5 b c) d)
(pushnew ’b (cadr x))  (5 b c) и x не меняется

Действие от

(pushnew item place :test p)

эквивалентно действию

(setf place (adjoin item place :test p))

за исключением того, что pushnew выполняет форму place только один раз, а не три. Более того, в для некоторых форм place pushnew может быть эффективнее чем версия с setf.

Следует отметить, что item вычисляется прежде чем вычисляется place.


[Макрос] pop place

Форма place должна быть именем обобщённое переменной, содержащей список. Результатом pop является результат car функции для переданного списка, и побочным эффектом является то, что в обобщённую переменную сохраняется результат cdr для списка. Форма place может быть любой формой, которая подходит для setf. Если рассматривать исходный список как стек, то pop достаёт элемент из вершины стека и возвращает его. Например:

(setq stack ’(a b c))
(pop stack)  a and now stack  (b c)

Действия от (pop place) эквивалентно

(prog1 (car place) (setf place (cdr place)))

за исключением того, что pop выполняет форму place только один раз, а не три. Более того, в для некоторых форм place pop может быть эффективнее чем версия с setf.


[Функция] butlast list &optional n

Функция создаёт и возвращает список с такими же элементами кроме n последних, что и в списке list. n по-умолчанию равно 1. Аргумент не разрушается. Если длина списка list меньше чем n, тогда возвращается (). Например:

(butlast ’(a b c d))  (a b c)
(butlast ’((a b) (c d)))  ((a b))
(butlast ’(a))  ()
(butlast nil)  ()

Имя функции образовано от фразы «all elements but the last» («все элементы кроме последних»).


[Функция] nbutlast list &optional n

Это деструктивная версия butlast. Данная функция изменяет cdr элемент cons-ячейки на nil. Искомая cons-ячейка находится на позиции n+1 с конца списка. Если длина списка list меньше чем n, тогда возвращается (), и аргумент не модифицируется. (Таким образом можно написать (setq a (nbutlast a)), а не (nbutlast a).) Например:

(setq foo ’(a b c d))
(nbutlast foo)  (a b c)
foo  (a b c)
(nbutlast ’(a))  ()
(nbutlast ’nil)  ()


[Функция] ldiff list sublist

Аргумент list должен быть списком, и sublist должен быть подсписком list. ldiff (означает «list difference») возвращает новый список, элементы которого содержат все элементы списка list до подсписка sublist. Если sublist не является частью list (или в частности равен nil), тогда возвращается копия всего списка list. Аргумент list не разрушается. Например:

(setq x ’(a b c d e))
(setq y (cdddr x))  (d e)
(ldiff x y)  (a b c)
но (ldiff ’(a b c d) ’(c d))  (a b c d)

так как подсписок не равен eq ни одной части списка.