Внутреннее представление списков. Применяющие функционалы
ЛЕКЦИЯ 8.
ВНУТРЕННЕЕ ПРЕДСТАВЛЕНИЕ СПИСКОВ. ПРИМЕНЯЮЩИЕ ФУНКЦИОНАЛЫ
Содержание
8.1 Внутреннее представление списков
8.1.1 Структура памяти
Каждый атом занимает ячейку.
|
Списки, являются совокупностью атомов, и связываются вместе специальными элементами памяти, называемыми списочными ячейками или
cons-ячейки.
Каждая списочная ячейка состоит из двух частей, полей.
Первое поле - CAR, второе CDR
|
Поля типа списочная ячейка содержат указатели на другие ячейки, или nil.
Если обе ячейки содержат указатели на атомы, то можно записать проще
|
Этому случаю соответствует структура (a.b)
которая называется точечной парой.
8.1.2 Представление списков через списочную ячейку.
Список из одного атома, представляется, как
|
NIL указывает на конец списка.
Вместо nil пишут - \.
Список получается как операция (cons 'a nil)
Список из двух элементов (b a)
|
Правое поле указывает на cdr списка (хвост).
Левое поле, на саr
списка(голову).
Каждому элементу списка соответствует списочная ячейка.
Список (a b c d) будет представлен как :
|
Если список не одного уровня (a (b c) d), то каждому элементу списка соответствует списочная ячейка.
Причем саr поле второй списочной ячейки указывает на вложенный список.
![]() |
8.1.3 Представление списков через точечные пары.
Любой список можно записать в точечной нотации.
(a) (a.nil) | |
(a b c) (a.(b.(c.nil)))
Выражение представленное в точечной нотации можно привести к списочной если cdr поле является списком.
(a.(b c)) <=> (a b c)
(a.(b.c)) <=> (a b.c)
8.1.4 Списочная ячейка и базовые функции.
Результатом действия функции car
будет значение левого поля первой списочной ячейки
* (сar '(a (b c) d)
a
Результатом действия функции cdr
будет значение правого поля первой списочной ячейки.
* (сdr '(a (b c) d)
((b c) d)
CONS создает новую списочную ячейку, car поле которой указывает на первый элемент, а cdr на второй
(cons 'x '(a b c)) |
|
|
* (list 'a '(b c)) (a (b c)) |
|
1. Создается списочная ячейка для каждого аргумента функции
2. В car поле ставится указатель на соответствующий элемент
3. В cdr поле ставится указатель на следующую списочную ячейку

8.1.5 Переменные и списки.
Рассмотрим выражение
(setq y '(a b c))
Переменная Y будет иметь значение '(a b c)
|
(setq x (cons 'd y))
|
Если в функции присвоения список задается явно, то под него отводятся новые списочные ячейки.т.е.
(setq z '(a b c))
Переменная z будет иметь значение '(a b c)
|

8.1.6 EQ и EQUAL.
EQ проверяет физическое равенство списков, и EQUAL -логическое, т.е. для EQ необходимо, чтобы списки имели одинаковую стpуктуpу, а для EQUAL одинаковые элементы. (Структура списка определяется списочными ячейками).
|
* (setq lis2 '(a b))
Другая структура списка
![]() |
* (setq lis3 lis1) * (equal lis1 lis2) t * (equal lis1 lis3) t * (eql lis1 lis2) nil * (eql lis1 lis3) t * (eq lis1 lis2) nil * (eq lis1 lis3) t |
![]() |
* (setq m 'abc) * (setq n 'abc) * (eq m n) t |

8.1.7 Cборка мусора
В результате вычислений в памяти могут возникнуть структуры, на которые нельзя ссылаться. Это происходит, когда вычисляемая структура не сохраняется с помощью setq, или когда теряются ссылки на старое значение.
Например
(setq l1 '((a) b c)) (setq l1 (cdr l1)) |
![]() |
Для повторного использования ставшей мусором памяти в лисп системах предусмотрен специальный сборщик мусора, (garbage collector) GC, который автоматически запускается когда в памяти остается мало места.
|

8.2 Обработка списков без разрушения.
![]() |
(rplaca x y) (setf (car x) y) (rplacd x y) (setf (cdr x) y) |
Например, рассмотрим функцию, replace-item, которая имеет три элемента: первый элемент должен быть списком. Функция разрушающе замещает первое местоположение второго аргумента в списке на третий аргумент.
![]() |
(defun replace-iteme (lis old new) (rplaca (member old lis) new). |

8.3.3 Использование разрушающих функций.
Разрушающие функции необходимо использовать при работе с большими списками, чтобы не увеличивать расход памяти, например использовать nconc вместо append.
Однако использование разрушающих функций приводит к побочным эффектам.
Можно получить бесконечные списки:
![]() |
* (setq v1 '(a b c)) (a b c) * (setq v2 v1) (a b c) * (setq v2 (nconc v1 v2)) (a b c a b c....) |
|

8.4 Применяющие функционалы.
Одним из видов функционалов, используемых в лиспе являются применяющие функционалы. Они применяют функциональный аргумент к его параметрам.
Так как применяющие функционалы вычисляют значение функции, в этом смысле они аналогичны функции EVAL,
вычисляющей значение выражения.

8.4.1 APPLY.
Предположим мы хотим объединить в один список несколько вложенных списков, т.е. из
((a b c) (d e f) (k l)) получить (a b c d e f k l).
Для этой задачи используем функцию apply. APPLY имеет два аргумента: имя функции и список, и применяет названную функцию к элементам списка, как к аргументам функции.
![]() |
![]() |
(defun list-mean (lis) (/ (apply '+ '(lis) (length lis))) * (list-mean '(1 2 3 4)) 2.5 |
Мы хотим найти общее число элемент в списках
![]() |
(countall '((a b c) (d e f) (k l))) (defun countall (lis) (apply '+ (mapcar 'length lis))) |
Например
* (countatom '(a (a (b) c) (d) e (f g)))
8
![]() |
(defun countatom (lis) (cond ((null lis) 0) ((atom lis) 1) (t (apply '+ (mapcar 'countatom lis))))) |

8.4. 2 Cочетание apply, nconc, mapcar - mapcan.
Построить функцию list-last, образующую список из хвостов списков.
Например
* (list-last '((a b) (b c) (c d))) возвращает (b c d)
![]() |
(defun list-last (lis) (apply 'append (mapcar 'last lis))) |
Можно это сделать через nconc:
![]() |
(defun list-last (lis) (apply 'nconc (mapcar 'last lis))) |
![]() |
(mapcan fn x1 x2 ... xN) (apply 'nconc (mapcar fn x1 x2 ... xN)) |
![]() |
(defun list-last (lis) (mapcan 'last lis)) |

8.4.3 Функционал FUNCALL.
Применяющий функционал FUNCALL
аналогичен APPLY, но аргументы он
принимает, не в списке, а по отдельности:
(funcall fn x1 x2 ... xN) (fn x1 x2 ... xN)
Здесь fn - функция с n aргументами.
|
![]() |
Например, * (funcall '+ 1 2) * (+ 1 2) 3 * (funcall (car '(+ - / *)) 1 2) 3 |
Рассмотрим использование funcall для построения функции map2, которая действует аналогично mapcar, но берет в качестве аргументов два элемента из списка, а не один.
Например:
* (map2 'list '(A Christie V Nabokov K Vonnegut))
дает ((A Christie) (V Nabokov) (K Vonnegut))
Эта функция имеет вид:
![]() |
(defun map2 (f2 lst) (if (null lst) nil (cons (funcall f2 (car lst) (cadr lst)) (map2 f2 (cddr lst))))) |
