- •Оглавление
- •Предисловие
- •Предисловие к русскому изданию
- •Глава 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
Специализированные структуры данных
Впредыдущей главе были рассмотрены списки – наиболее универсаль ные структуры для хранения данных. В этой главе будут рассмотрены другие способы хранения данных в Лиспе: массивы (а также векторы и строки), структуры и хеш-таблицы. Они не настолько гибки, как спи ски, но позволяют осуществлять более быстрый доступ и занимают меньше места.
ВCommon Lisp есть еще один тип структур – экземпляры объектов (instance). О них подробно рассказано в главе 11, описывающей CLOS.
4.1. Массивы
В Common Lisp массивы создаются с помощью функции make-array, пер вым аргументом которой выступает список размерностей. Создадим массив 2×3:
> (setf arr (make-array ’(2 3) :initial-element nil)) #<Simple-Array T (2 3) BFC4FE>
Многомерные массивы в Common Lisp могут иметь по меньшей мере 7 размерностей, а в каждом измерении поддерживается хранение не ме нее 1 023 элементов1.
Аргумент :initial-element не является обязательным. Если он использу ется, то устанавливает начальное значение каждого элемента массива. Поведение системы при попытке получить значение элемента массива, не инициализированного начальным значением, не определено.
1В конкретной реализации ограничение сверху на количество размерностей и элементов может быть и больше. – Прим. перев.
74 |
Глава 4. Специализированные структуры данных |
Чтобы получить элемент массива, воспользуйтесь aref. Как и большин ство других функций доступа в Common Lisp, aref начинает отсчет эле ментов с нуля:
> (aref arr 0 0) NIL
Новое значение элемента массива можно установить, используя setf вме сте с aref:
>(setf (aref arr 0 0) ’b)
B
>(aref arr 0 0)
B
Как и списки, массивы могут быть заданы буквально с помощью син таксиса #na, где n – количество размерностей массива. Например, теку щее состояние массива arr может быть задано так:
#2a((b nil nil) (nil nil nil))
Если глобальная переменная *print-array*1 установлена в t, массивы бу дут печататься в таком виде:
>(setf *print-array* t)
T
>arr
#2A((B NIL NIL) (NIL NIL NIL))
Для создания одномерного массива можно вместо списка размерностей в качестве первого аргумента передать функции make-array целое число:
> (setf vec (make-array 4 :initial-element nil)) #(NIL NIL NIL NIL)
Одномерный массив также называют вектором. Создать и заполнить вектор можно с помощью функции vector:
> (vector "a" ’b 3) #("a" B 3)
Как и массив, который может быть задан буквально с помощью синтак сиса #na, вектор может быть задан буквально с помощью синтаксиса #().
Хотя доступ к элементам вектора может осуществить aref, для работы с векторами есть более быстрая функция svref:
> (svref vec 0) NIL
1В вашей реализации *print-array* может изначально иметь значение t. –
Прим. перев.
4.2. Пример: бинарный поиск |
75 |
Префикс «sv» расшифровывается как «simple vector». По умолчанию все векторы создаются как простые векторы.1
4.2. Пример: бинарный поиск
В этом разделе в качестве примера показано, как написать функцию по иска элемента в отсортированном векторе. Если нам известно, что эле менты вектора расположены в определенном порядке, то поиск нужного элемента может быть выполнен быстрее, чем с помощью функции find (стр.80).Вместотогочтобыпоследовательнопроверятьэлементзаэлемен том, мы сразу перемещаемся в середину вектора. Если средний элемент соответствует искомому, то поиск закончен. В противном случае мы про должаем поиск в правой или левой половине в зависимости от того, боль ше или меньше искомого значения этот средний элемент вектора.
На рис. 4.1 приведена программа, которая работает подобным образом. Она состоит из двух функций: bin-search2 определяет границы поиска и передает управление функции finder, которая ищет соответствующий элемент между позициями start и end вектора vec.
(defun bin-search (obj vec) (let ((len (length vec)))
(and (not (zerop len))
(finder obj vec 0 (- len 1)))))
(defun finder (obj vec start end) (let ((range (- end start)))
(if (zerop range)
(if (eql obj (aref vec start)) obj
nil)
(let ((mid (+ start (round (/ range 2))))) (let ((obj2 (aref vec mid)))
(if (< obj obj2)
(finder obj vec start (- mid 1)) (if (> obj obj2)
(finder obj vec (+ mid 1) end) obj)))))))
Рис. 4.1. Поиск в отсортированном векторе
1Простой массив не является ни расширяемым (adjustable), ни предразме щенным (displaced) и не имеет указатель заполнения (fill-pointer). По умол чанию все массивы простые. Простой вектор – это одномерный простой мас сив, который может содержать элементы любого типа.
2Приведенная версия bin-search не работает, если ей передать объект, мень ший наименьшего из элементов вектора. Оплошность найдена Ричардом Грином. – Прим. перев.
76 |
Глава 4. Специализированные структуры данных |
Когда область поиска сокращается до одного элемента, возвращается сам элемент в случае его соответствия искомому значению obj, в против ном случае – nil. Если область поиска состоит из нескольких элемен тов, определяется ее средний элемент – obj2 (функция round возвращает ближайшее целое число), который сравнивается с искомым элементом obj. Если obj меньше obj2, поиск продолжается рекурсивно в левой поло вине вектора, в противном случае – в правой половине. Остается вари ант obj = obj2, но это значит, что искомый элемент найден и мы просто его возвращаем.
Если вставить следующую строку в начало определения функции finder,
(format t "~A~%" (subseq vec start (+ end 1)))
мы сможем наблюдать за процессом отсечения половин на каждом шаге:
> (bin-search 3 #(0 1 2 3 4 5 6 7 8 9)) #(0 1 2 3 4 5 6 7 8 9)
#(0 1 2 3) #(3)
3
Договоренности о комментировании
В Common Lisp все, что следует за точкой с запятой, считается комментарием. Многие программисты используют последователь но несколько знаков комментирования, разделяя комментарии по уровням: четыре точки с запятой в заглавии файла, три – в опи сании функции или макроса, две – для пояснения последующей строки, одну – в той же строке, что и поясняемый код. Таким об разом, с использованием общепринятых норм комментирования начало кода на рис. 4.1 будет выглядеть так:
;;;; Инструменты для операций с отсортированными векторами
;;; Находит элемент в отсортированном векторе
(defun bin-search (obj vec) (let ((len (length vec)))
;; если это действительно вектор, применяем к нему finder (and (not (zerop len)) ; возвращает nil, если вектор пуст
(finder obj vec 0 (- len 1)))))
Многострочные комментарии удобно делать с помощью макроса чтения #|...|#. Все, что находится между #| и |#, игнорируется счи тывателем.°