- •Оглавление
- •Предисловие
- •Предисловие к русскому изданию
- •Глава 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. Анализ
- •Комментарии
- •Алфавитный указатель
4.5. Пример: разбор дат |
81 |
будет выглядеть понятнее в виде:
(find ’complete lst :key #’car)
Функции remove (стр. 39) и remove-if работают с последовательностями любого типа. Разница между ними точно такая же, как между find и find-if. Связанная с ними функция remove-duplicates удаляет все по вторяющиеся элементы последовательности, кроме последнего:
> (remove-duplicates "abracadabra") "cdbra"
Эта функция использует все аргументы по ключу, рассмотренные в таб лице выше.
Функция reduce сводит последовательность в одно значение. Она прини мает функцию, по крайней мере, с двумя аргументами и последователь ность. Заданная функция первоначально применяется к первым двум элементам последовательности, а затем последовательно к полученному результату и следующему элементу последовательности. Последнее по лученное значение будет возвращено как результат reduce. Таким обра зом, вызов:
(reduce #’fn ’(a b c d))
будет эквивалентен
(fn (fn (fn ’a ’b) ’c) ’d)
Хорошее применение reduce – расширение набора аргументов для функ ций, которые принимают только два аргумента. Например, чтобы по лучить пересечение трех или более списков, можно написать:
> (reduce #’intersection ’((b r a d ’s) (b a d) (c a t)))
(A)
4.5. Пример: разбор дат
В качестве примера операций с последовательностями в этом разделе приводится программа для разбора дат. Мы напишем программу, кото рая превращает строку типа "16 Aug 1980" в целые числа, соответствую щие дню, месяцу и году.
Программа на рис. 4.2 содержит некоторые функции, которые потребу ются нам в дальнейшем. Первая, tokens, выделяет знаки из строки. Функция tokens принимает строку и предикат, возвращая список под строк, все знаки в которых удовлетворяют этому предикату. Приведем пример. Пусть используется функция alpha-char-p – предикат, справед ливый для буквенных знаков. Тогда получим:
> (tokens "ab12 3cde.f" #’alpha-char-p 0) ("ab" "cde" "f")
Все остальные знаки, не удовлетворяющие данному предикату, рассмат риваются как пробельные.
82 |
|
Глава 4. Специализированные структуры данных |
|
|
|
|
|
(defun tokens (str test start) |
|
|
(let ((p1 (position-if test str :start start))) |
|
|
(if p1 |
|
|
(let ((p2 (position-if #’(lambda (c) |
|
|
(not (funcall test c))) |
|
|
str :start p1))) |
|
|
(cons (subseq str p1 p2) |
|
|
(if p2 |
|
|
(tokens str test p2) |
|
|
nil))) |
|
|
nil))) |
|
|
(defun constituent (c) |
|
|
(and (graphic-char-p c) |
|
|
(not (char= c #\ )))) |
|
|
|
|
Рис. 4.2. Распознавание символов |
|
|
Функция constituent будет использоваться в качестве предиката для |
|
|
tokens. В Common Lisp к печатным знакам (graphic characters) относят |
|
|
ся все знаки, которые видны при печати, а также пробел. Вызов tokens |
|
|
с функцией constituent будет выделять подстроки, состоящие из печат |
|
|
ных знаков: |
|
|
|
> (tokens "ab12 3cde.f |
|
|
gh" #’constituent 0) |
|
|
("ab12" "3cde.f" "gh") |
|
На рис. 4.3 показаны функции, выполняющие разбор дат. |
|
|
|
|
|
|
(defun parse-date (str) |
|
|
(let ((toks (tokens str #’constituent 0))) |
|
|
(list (parse-integer (first toks)) |
|
|
(parse-month (second toks)) |
|
|
(parse-integer (third toks))))) |
|
|
(defconstant month-names |
|
|
#("jan" "feb" "mar" "apr" "may" "jun" |
|
|
"jul" "aug" "sep" "oct" "nov" "dec")) |
|
|
(defun parse-month (str) |
|
|
(let ((p (position str month-names |
|
|
:test #’string-equal))) |
|
|
(if p |
|
|
(+ p 1) |
|
|
nil))) |
|
|
|
Рис. 4.3. Функции для разбора дат
4.6. Структуры |
83 |
Функция parse-date принимает дату, записанную в указанной форме, и возвращает список целых чисел, соответствующих ее компонентам:
> (parse-date "16 Aug 1980") (16 8 1980)
Эта функция делит строку на части и применяет parse-month и parseinteger к полученным частям. Функция parse-month не чувствительна к регистру, так как сравнивает строки с помощью string-equal. Для пре образования строки, содержащей число, в само число, используется встроенная функция parse-integer.
Однако если бы такой функции в Common Lisp изначально не было, нам пришлось бы определить ее самостоятельно:
(defun read-integer (str)
(if (every #’digit-char-p str) (let ((accum 0))
(dotimes (pos (length str)) (setf accum (+ (* accum 10)
(digit-char-p (char str pos)))))
accum)
nil))
Определенная нами функция read-integer показывает, как в Common Lisp преобразовать набор знаков в число. Она использует особенность функции digit-char-p, которая проверяет, является ли аргумент циф рой, и возвращает саму цифру, если это так.
4.6. Структуры
Структура может рассматриваться как более продвинутый вариант вектора. Предположим, что нам нужно написать программу, отслежи вающую положение набора параллелепипедов. Каждое такое тело мож но представить в виде вектора, состоящего из трех элементов: высота, ширина и глубина. Программу будет проще читать, если вместо про стых svref мы будем использовать специальные функции:
(defun block-height (b) (svref b 0))
и так далее. Можете считать структуру таким вектором, у которого все эти функции уже заданы.
Определить структуру можно с помощью defstruct. В простейшем слу чае достаточно задать имена структуры и ее полей:
(defstruct point x
y)
Мы определили структуру point, имеющею два поля, x и y. Кроме того, неявно были заданы функции: make-point, point-p, copy-point, point-x, point-y.
84 |
Глава 4. Специализированные структуры данных |
В разделе 2.3 мы упоминали о способности Лисп-программ писать дру гие Лисп-программы. Это один из наглядных примеров: при вызове defstruct самостоятельно определяет все необходимые функции. На учившись работать с макросами, вы сами сможете делать похожие ве щи. (Вы даже смогли бы написать свою версию defstruct, если бы в этом была необходимость.)
Каждый вызов make-point возвращает вновь созданный экземпляр струк туры point. Значения полей могут быть изначально заданы с помощью соответствующих аргументов по ключу:
> (setf p (make-point :x 0 :y 0)) #S(POINT X 0 Y 0)
Функции доступа к полям структуры определены не только для чтения полей, но и для задания значений с помощью setf:
>(point-x p)
0
>(setf (point-y p) 2)
2
>p
#S(POINT X 0 Y 2)
Определение структуры также приводит к определению одноименного типа. Каждый экземпляр point принадлежит типу point, затем structure, затем atom и t. Таким образом, использование point-p равносильно про верке типа:
>(point-p p)
T
>(typep p ’point)
T
Функция typep проверяет объект на принадлежность к заданному типу.
Также можно задать значения полей по умолчанию, если заключить имя соответствующего поля в список и поместить в него выражение для вычисления этого значения.
(defstruct polemic (type (progn
(format t "What kind of polemic was it? ") (read)))
(effect nil))
Вызов make-polemic без дополнительных аргументов установит исход ные значения полей:
> (make-polemic)
What kind of polemic was it? scathing #S(POLEMIC TYPE SCATHING EFFECT NIL)