- •Оглавление
- •Предисловие
- •Предисловие к русскому изданию
- •Глава 1. Введение
- •1.1. Новые инструменты
- •1.2. Новые приемы
- •1.3. Новый подход
- •Глава 2. Добро пожаловать в Лисп
- •2.1. Форма
- •2.2. Вычисление
- •2.3. Данные
- •2.4. Операции со списками
- •2.5. Истинность
- •2.6. Функции
- •2.7. Рекурсия
- •2.8. Чтение Лиспа
- •2.9. Ввод и вывод
- •2.10. Переменные
- •2.11. Присваивание
- •2.12. Функциональное программирование
- •2.13. Итерация
- •2.14. Функции как объекты
- •2.15. Типы
- •2.16. Заглядывая вперед
- •Итоги главы
- •Упражнения
- •Глава 3. Списки
- •3.1. Ячейки
- •3.2. Равенство
- •3.3. Почему в Лиспе нет указателей
- •3.4. Построение списков
- •3.5. Пример: сжатие
- •3.6. Доступ
- •3.7. Отображающие функции
- •3.8. Деревья
- •3.9. Чтобы понять рекурсию, нужно понять рекурсию
- •3.10. Множества
- •3.11. Последовательности
- •3.12. Стопка
- •3.13. Точечные пары
- •3.14. Ассоциативные списки
- •3.15. Пример: поиск кратчайшего пути
- •3.16. Мусор
- •Итоги главы
- •Упражнения
- •Глава 4. Специализированные структуры данных
- •4.1. Массивы
- •4.2. Пример: бинарный поиск
- •4.3. Строки и знаки
- •4.4. Последовательности
- •4.5. Пример: разбор дат
- •4.6. Структуры
- •4.7. Пример: двоичные деревья поиска
- •4.8. Хеш-таблицы
- •Итоги главы
- •Упражнения
- •Глава 5. Управление
- •5.1. Блоки
- •5.2. Контекст
- •5.3. Условные выражения
- •5.4. Итерации
- •5.5. Множественные значения
- •5.6. Прерывание выполнения
- •5.7. Пример: арифметика над датами
- •Итоги главы
- •Упражнения
- •Глава 6. Функции
- •6.1. Глобальные функции
- •6.2. Локальные функции
- •6.3. Списки параметров
- •6.4. Пример: утилиты
- •6.5. Замыкания
- •6.6. Пример: строители функций
- •6.7. Динамический диапазон
- •6.8. Компиляция
- •6.9. Использование рекурсии
- •Итоги главы
- •Упражнения
- •Глава 7. Ввод и вывод
- •7.1. Потоки
- •7.2. Ввод
- •7.3. Вывод
- •7.4. Пример: замена строк
- •7.5. Макрознаки
- •Итоги главы
- •Упражнения
- •Глава 8. Символы
- •8.1. Имена символов
- •8.2. Списки свойств
- •8.3. А символы-то не маленькие
- •8.4. Создание символов
- •8.5. Использование нескольких пакетов
- •8.6. Ключевые слова
- •8.7. Символы и переменные
- •8.8. Пример: генерация случайного текста
- •Итоги главы
- •Упражнения
- •Глава 9. Числа
- •9.1. Типы
- •9.2. Преобразование и извлечение
- •9.3. Сравнение
- •9.4. Арифметика
- •9.5. Возведение в степень
- •9.6. Тригонометрические функции
- •9.7. Представление
- •9.8. Пример: трассировка лучей
- •Итоги главы
- •Упражнения
- •Глава 10. Макросы
- •10.1. Eval
- •10.2. Макросы
- •10.3. Обратная кавычка
- •10.4. Пример: быстрая сортировка
- •10.5. Проектирование макросов
- •10.6. Обобщенные ссылки
- •10.7. Пример: макросы-утилиты
- •10.8. На Лиспе
- •Итоги главы
- •Упражнения
- •Глава 11. CLOS
- •11.1. Объектно-ориентированное программирование
- •11.2. Классы и экземпляры
- •11.3. Свойства слотов
- •11.4. Суперклассы
- •11.5. Предшествование
- •11.6. Обобщенные функции
- •11.7. Вспомогательные методы
- •11.8. Комбинация методов
- •11.9. Инкапсуляция
- •11.10. Две модели
- •Итоги главы
- •Упражнения
- •Глава 12. Структура
- •12.1. Разделяемая структура
- •12.2. Модификация
- •12.3. Пример: очереди
- •12.4. Деструктивные функции
- •12.5. Пример: двоичные деревья поиска
- •12.6. Пример: двусвязные списки
- •12.7. Циклическая структура
- •12.8. Неизменяемая структура
- •Итоги главы
- •Упражнения
- •Глава 13. Скорость
- •13.1. Правило бутылочного горлышка
- •13.2. Компиляция
- •13.3. Декларации типов
- •13.4. Обходимся без мусора
- •13.5. Пример: заранее выделенные наборы
- •13.6. Быстрые операторы
- •13.7. Две фазы разработки
- •Итоги главы
- •Упражнения
- •Глава 14. Более сложные вопросы
- •14.1. Спецификаторы типов
- •14.2. Бинарные потоки
- •14.3. Макросы чтения
- •14.4. Пакеты
- •14.5. Loop
- •14.6. Особые условия
- •Глава 15. Пример: логический вывод
- •15.1. Цель
- •15.2. Сопоставление
- •15.3. Отвечая на запросы
- •15.4. Анализ
- •Глава 16. Пример: генерация HTML
- •16.1. HTML
- •16.2. Утилиты HTML
- •16.3. Утилита для итерации
- •16.4. Генерация страниц
- •Глава 17. Пример: объекты
- •17.1. Наследование
- •17.2. Множественное наследование
- •17.3. Определение объектов
- •17.4. Функциональный синтаксис
- •17.5. Определение методов
- •17.6. Экземпляры
- •17.7. Новая реализация
- •17.8. Анализ
- •Комментарии
- •Алфавитный указатель
3.4. Построение списков |
53 |
3.4. Построение списков
Функция copy-list принимает список и возвращает его копию. Новый список будет иметь те же элементы, но они будут размещены в других cons-ячейках:
> (setf x ’(a b c)
y (copy-list x))
(A B C)
Такая структура ячеек показана на рис. 3.5. Собственную функцию copy-list определим так:
(defun our-copy-list (lst) (if (atom lst)
lst
(cons (car lst) (our-copy-list (cdr lst)))))
Здесь подразумевается, что x и (copy-list x) всегда равны с точки зрения equal, а для eql равны, только если x является nil.
Функция append – еще одна функция для работы со списками, склеи вающая между собой произвольное количество списков:
> (append ’(a b) ’(c d) ’(e)) (A B C D E)
Функция append копирует все аргументы, за исключением последнего.
3.5. Пример: сжатие
В этом разделе приводится пример разработки простого механизма сжатия списков. Рассматриваемый ниже алгоритм принято называть
кодированием повторов (run-length encoding). Представьте ситуацию: в ресторане официант обслуживает столик, за которым сидят четыре посетителя:
«Что вы будете заказывать?» – спрашивает он.
«Принесите фирменное блюдо, пожалуйста», – отвечает первый по сетитель.
«И мне тоже», – говорит второй.
«Ну и я за компанию», – присоединяется третий.
Все смотрят на четвертого клиента. «А я бы предпочел кориандровое суфле», – тихо произносит он.
Официант со вздохом разворачивается и идет к кухне. «Три фирмен ных, – кричит он повару, – и одно кориандровое суфле».
На рис. 3.6 показан подобный алгоритм для списков. Функция compress принимает список из атомов и возвращает его сжатое представление:
> (compress ’(1 1 1 0 1 0 0 0 0 1)) ((3 1) 0 1 (4 0) 1)
54 Глава 3. Списки
(defun compress (x) (if (consp x)
(compr (car x) 1 (cdr x)) x))
(defun compr (elt n lst) (if (null lst)
(list (n-elts elt n)) (let ((next (car lst)))
(if (eql next elt)
(compr elt (+ n 1) (cdr lst)) (cons (n-elts elt n)
(compr next 1 (cdr lst)))))))
(defun n-elts (elt n) (if (> n 1)
(list n elt) elt))
Рис. 3.6. Кодирование повторов: сжатие
Если какой-либо элемент повторяется несколько раз, он заменяется на список, содержащий этот элемент и число его повторений.
Большая часть работы выполняется рекурсивной функцией compr, кото рая принимает три аргумента: elt – последний встреченный элемент; n – число его повторений; lst – остаток списка, подлежащий дальнейшей компрессии. Когда список заканчивается, вызывается функция n-elts, возвращающая сжатое представление n элементов elt. Если первый эле мент lst по-прежнему равен elt, увеличиваем n и идем дальше. В про тивном случае мы получаем сжатое представление предыдущей серии одинаковых элементов и присоединяем это к тому, что получим с помо щью compr от остатка списка.
Чтобы реконструировать сжатый список, воспользуемся uncompress (рис. 3.7):
> (uncompress ’((3 1) 0 1 (4 0) 1)) (1 1 1 0 1 0 0 0 0 1)
Эта функция выполняется рекурсивно, копируя атомы и раскрывая списки с помощью list-of:
> (list-of 3 ’ho) (HO HO HO)
В действительности, нет необходимости использовать list-of. Встроен ная функция make-list выполняет ту же работу, однако использует ар гументы по ключу (keyword), которых мы пока еще не касались.
Функции compress и uncompress, представленные на рис. 3.6 и 3.7, опре делены не так, как это сделал бы опытный программист. Они неэффек
3.6. Доступ |
55 |
тивны, не осуществляют сжатие в достаточной мере и работают только со списками атомов. В следующих нескольких главах мы узнаем, как можно исправить все эти проблемы.
(defun uncompress (lst) (if (null lst)
nil
(let ((elt (car lst))
(rest (uncompress (cdr lst)))) (if (consp elt)
(append (apply #’list-of elt) rest)
(cons elt rest)))))
(defun list-of (n elt) (if (zerop n)
nil
(cons elt (list-of (- n 1) elt))))
Рис. 3.7. Кодирование повторов: декодирование
Загрузка программ
Код в этом разделе впервые претендует на название отдельной программы. Если наша программа имеет достаточно большой размер, удобно сохранять ее текст в файл. Прочитать код из файла можно с помощью load. Если мы сохраним код с рис. 3.6 и 3.7 в файл под названием "compress.lisp", то, набрав в toplevel
(load "compress.lisp")
получим такой же результат, как если бы набрали все выражения непосредственно в toplevel.
Учтите, что некоторые реализации Лиспа могут использовать расширение ".lsp", а не ".lisp".
3.6. Доступ
Для доступа к частям списков в Common Lisp имеются еще несколько функций, которые определяются с помощью car и cdr. Чтобы получить элемент с определенным индексом, вызовем функцию nth:
> (nth 0 ’(a b c)) A
56 |
Глава 3. Списки |
Чтобы получить n-й хвост списка, вызовем nthcdr:
> (nthcdr 2 ’(a b c))
(C)
Функции nth и nthcdr ведут отсчет элементов списка с 0. Вообще говоря, в Common Lisp любая функция, обращающаяся к элементам структур данных, начинает отсчет с нуля.
Эти две функции очень похожи, и вызов nth эквивалентен вызову car от nthcdr. Определим nthcdr без обработки возможных ошибок:
(defun our-nthcdr (n lst) (if (zerop n)
lst
(our-nthcdr (- n 1) (cdr lst))))
Функция zerop всего лишь проверяет, равен ли нулю ее аргумент. Функция last возвращает последнюю cons-ячейку списка:
> (last ’(a b c))
(C)
Это не последний элемент; чтобы получить последний элемент, а не по следнюю ячейку, воспользуйтесь функцией car от last.
В Common Lisp для доступа к элементам с первого по десятый выделены специальные функции, которые получили названия от английских по рядковых числительных (от first до tenth). Обратите внимание, что от счет начинается не с нуля и (second x) эквивалентен (nth 1 x).
Кроме того, в Common Lisp определены функции типа caddr (сокращен ный вызов car от cdr от cdr). Также определены функции вида cxr, где x – набор всех возможных сочетаний a и d длиной до четырех символов. За исключением cadr, которая ссылается на второй элемент, не рекомен дуется использовать подобные функции в коде, так как они затрудняют его чтение.
3.7. Отображающие функции
Common Lisp определяет несколько операций для применения какой- либо функции к каждому элементу списка. Чаще всего для этого ис пользуется mapcar, которая вызывает заданную функцию поэлементно для одного или нескольких списков и возвращает список результатов:
> (mapcar #’(lambda (x) (+ x 10)) ’(1 2 3))
(11 12 13)
> (mapcar #’list ’(a b c)
’(1 2 3 4)) ((A 1) (B 2) (C 3))
