- •Оглавление
- •Предисловие
- •Предисловие к русскому изданию
- •Глава 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. Анализ
- •Комментарии
- •Алфавитный указатель
90 |
|
Глава 4. Специализированные структуры данных |
|
|
|
|
|
(defun bst-traverse (fn bst) |
|
|
(when bst |
|
|
(bst-traverse fn (node-l bst)) |
|
|
(funcall fn (node-elt bst)) |
|
|
(bst-traverse fn (node-r bst)))) |
|
|
|
|
Рис. 4.8. Двоичные деревья поиска: обход |
|
|
Для этой цели определена функция bst-traverse (рис. 4.8), которая при |
|
|
меняет к каждому элементу дерева функцию fn. |
|
|
|
> (bst-traverse #’princ nums) |
|
13456789 |
|
|
|
NIL |
|
(Функция princ всего лишь отображает отдельный объект.) |
|
|
Код, представленный в этом разделе, служит основой для реализации |
|
|
двоичных деревьев поиска. Вероятно, вы захотите как-то усовершенст |
|
|
вовать его в соответствии с вашими потребностями. Например, каждый |
|
|
узел в текущей реализации имеет лишь одно поле elt, в то время как |
|
|
может оказаться полезным введение двух полей – ключ и значение. |
|
|
Данная версия хотя и не поддерживает такую возможность, но позво |
|
|
ляет ее легко реализовать. |
|
|
Двоичные деревья поиска могут использоваться не только для управле |
|
|
ния отсортированным набором объектов. Их применение оптимально, |
|
|
когда вставки и удаления узлов имеют равномерное распределение. Это |
|
|
значит, что для работы, например, с очередями, BST не лучший выбор. |
|
|
Хотя вставки в очередях вполне могут быть распределены равномерно, |
|
|
удаления будут всегда осуществляться с конца. Это будет приводить |
|
|
к разбалансировке дерева, и вместо ожидаемой оценки O(log n) мы по |
|
|
лучим O(n). Кроме того, для моделирования очередей удобнее использо |
|
|
вать обычный список просто потому, что BST будет вести себя в конеч |
|
|
ном счете так же, как и список.° |
4.8.Хеш-таблицы
Вглаве 3 было показано, что списки могут использоваться для пред ставления множеств и отображений. Для достаточно больших массивов данных (начиная уже с 10 элементов) использование хеш-таблиц суще ственно увеличит производительность. Хеш-таблицу можно создать с помощью функции make-hash-table, которая не требует обязательных аргументов:
> (setf ht (make-hash-table)) #<Hash-Table BF0A96>
Хеш-таблицы, как и функции, при печати отображаются в виде #<...>.
4.8. Хеш-таблицы |
91 |
Хеш-таблица, как и ассоциативный список, – это способ ассоциирова ния пар объектов. Чтобы получить значение, связанное с заданным ключом, достаточно вызвать gethash с этим ключом и таблицей. По умол чанию gethash возвращает nil, если не находит искомого элемента.
> (gethash ’color ht) NIL
NIL
Здесь мы впервые сталкиваемся с важной особенностью Common Lisp: выражение может возвращать несколько значений. Функция gethash возвращает два. Первое значение ассоциировано с ключом. Второе зна чение, если оно nil (как в нашем примере), означает, что искомый эле мент не был найден. Почему мы не можем судить об этом из первого nil? Дело в том, что элементом, связанным с ключом color, может оказаться nil, и gethash вернет его, но в этом случае в качестве второго значения – t.
Большинство реализаций выводит последовательно все возвращаемые значения, но если результат многозначной функции используется дру гой функцией, то ей передается лишь первое значение. В разделе 5.5 объясняется, как получать и использовать сразу несколько значений.
Чтобы сопоставить новое значение какому-либо ключу, используем setf вместе с gethash:
> (setf (gethash ’color ht) ’red) RED
Теперь gethash вернет вновь установленное значение:
> (gethash ’color ht) RED
T
Второе значение подтверждает, что gethash вернул реально имеющийся в таблице объект, а не значение по умолчанию.
Объекты, хранящиеся в хеш-таблицах, могут иметь любой тип. Напри мер, при желании сопоставить каждой функции ее краткое описание можно создать таблицу, в которой ключами будут функции, а значе ниями – строки:
>(setf bugs (make-hash-table)) #<Hash-Table BF4C36>
>(push "Doesn’t take keyword arguments. " (gethash #’our-member bugs))
("Doesn’t take keyword arguments. ")
Так как по умолчанию gethash возвращает nil, вызов push эквивалентен setf, и мы просто кладем нашу строку на пустой список. (Определение функции our-member находится на стр. 39.)
Хеш-таблицы также можно использовать вместо списков для представ ления множеств. Они существенно ускоряют поиск значений и их уда ление в случаях больших объемов данных. Чтобы добавить элемент
92 |
Глава 4. Специализированные структуры данных |
в множество, представленное в виде хеш-таблицы, используйте setf вместе с gethash:
>(setf fruit (make-hash-table)) #<Hash-Table BFDE76>
>(setf (gethash ’apricot fruit) t)
T
Проверка на принадлежность элемента множеству выполняется с помо щью gethash:
> (gethash ’apricot fruit) T
T
По умолчанию gethash возвращает nil, поэтому вновь созданная хештаблица представляет собой пустое множество.
Чтобы удалить элемент из множества, можно воспользоваться remhash:
> (remhash ’apricot fruit) T
Возвращая t, remhash сигнализирует, что искомый элемент был найден и успешно удален.
Для итерации по хеш-таблице существует maphash, которой необходимо передать функцию двух аргументов и саму таблицу. Эта функция будет вызвана с каждой имеющейся в таблице парой ключ-значение в произ вольном порядке.
> (setf (gethash ’shape ht) ’spherical (gethash ’size ht) ’giant)
GIANT
> (maphash #’(lambda (k v)
(format t "~A = ~A~%" k v))
ht) SHAPE = SPHERICAL SIZE = GIANT COLOR = RED
NIL
Функция maphash всегда возвращает nil, однако вы можете сохранить данные, если передадите функцию, которая, например, будет накапли вать результаты в списке.
Хеш-таблицы могут накапливать любое количество вхождений, так как способны расширяться в процессе работы, когда места для хранения элементов перестанет хватать. Задать исходную емкость таблицы мож но с помощью ключа :size функции make-hash-table. Используя этот па раметр, вам следует помнить о двух вещах: большой размер таблицы позволит избежать ее частых расширений (это довольно затратная про цедура), однако создание таблицы большого размера для малого набора данных приведет к необоснованным затратам памяти. Важный момент:
Итоги главы |
93 |
параметр :size определяет не количество хранимых объектов, а коли чество пар ключ-значение. Выражение:
(make-hash-table :size 5)
вернет хеш-таблицу, которая сможет вместить пять вхождений до того, как ей придется расширяться.
Как и любая структура, подразумевающая возможность поиска элемен тов, хеш-таблица может использовать различные предикаты проверки эквивалентности. По умолчанию используется eql, но также можно применить eq, equal или equalp; используемый предикат указывается
спомощью ключа :test:
>(setf writers (make-hash-table :test #’equal) #<Hash-Table C005E6>
>(setf (gethash ’(ralph waldo emerson) writers) t)
T
Это один из компромиссов, с которым нам приходится мириться ради получения эффективных хеш-таблиц. Работая со списками, мы бы вос пользовались функцией member, которой можно каждый раз сообщать различные предикаты проверки. При работе с хеш-таблицами мы долж ны определиться с используемым предикатом заранее, в момент ее соз дания.
С необходимостью жертвовать одним ради другого в Лисп-разработке (да и в жизни в целом) вам придется столкнуться не раз. Это часть фи лософии Лиспа: первоначально вы миритесь с низкой производитель ностью ради удобства разработки, а по мере развития программы може те пожертвовать ее гибкостью ради скорости выполнения.
Итоги главы
1.Common Lisp поддерживает массивы не менее семи размерностей. Одномерные массивы называются векторами.
2.Строки – это векторы знаков. Знаки – это полноценные самостоя тельные объекты.
3.Последовательности включают в себя списки и векторы. Большин ство функций для работы с последовательностями имеют аргументы по ключу.
4.Синтаксический разбор не составляет труда в Common Lisp благода ря наличию множества полезных функций для работы со строками.
5.Вызов defstruct определяет структуру с именованными полями. Это хороший пример программы, которая пишет другие программы.
6.Двоичные деревья поиска удобны для хранения отсортированного набора объектов.
7.Хеш-таблицы – это эффективный способ представления множеств и отображений.