- •Оглавление
- •Предисловие
- •Предисловие к русскому изданию
- •Глава 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.11. Последовательности |
61 |
(defun our-member-if (fn lst) (and (consp lst)
(if (funcall fn (car lst) lst
(our-member-if fn (cdr lst)))))
Функция adjoin – своего рода условный cons. Она присоединяет задан ный элемент к списку, но только если его еще нет в этом списке (т. е. не member):
>(adjoin ’b ’(a b c)) (A B C)
>(adjoin ’z ’(a b c)) (Z A B C)
Вобщем случае adjoin принимает те же аргументы по ключу, что и member.
Common Lisp определяет основные логические операции с множества ми, такие как объединение, пересечение, дополнение, для которых опре делены соответствующие функции: union, intersection, set-difference. Эти функции работают ровно с двумя списками и имеют те же аргумен ты по ключу, что и member.
>(union ’(a b c) ’(c b s)) (A C B S)
>(intersection ’(a b c) ’(b b c)) (B C)
>(set-difference ’(a b c d e) ’(b e)) (A C D)
Поскольку в множествах нет такого понятия, как упорядочение, эти функции не сохраняют порядок элементов в исходных списках. Напри мер, вызов set-difference из примера может с тем же успехом вернуть
(d c a).
3.11. Последовательности
Списки также можно рассматривать как последовательности элемен тов, следующих друг за другом в фиксированном порядке. В Common Lisp помимо списков к последовательностям также относятся векто ры. В этом разделе вы научитесь работать со списками как с последова тельностями. Более детально операции с последовательностями будут рассмотрены в разделе 4.4.
Длина последовательности определяется с помощью length:
> (length ’(a b c)) 3
Ранее мы писали урезанную версию этой функции, работающую толь ко со списками.
62 |
Глава 3. Списки |
Скопировать часть последовательности можно с помощью subseq. Второй аргумент (обязательный) задает начало подпоследовательности, а тре тий (необязательный) – индекс первого элемента, не подлежащего копи рованию.
>(subseq ’(a b c d) 1 2)
(B)
>(subseq ’(a b c d) 1) (B C D)
Если третий аргумент пропущен, то подпоследовательность заканчива ется вместе с исходной последовательностью.
Функция reverse возвращает последовательность, содержащую исход ные элементы в обратном порядке:
> (reverse ’(a b c)) (C B A)
С помощью reverse можно, например, искать палиндромы, т. е. последо вательности, читаемые одинаково в прямом и обратном порядке (на пример, (a b b a)). Две половины палиндрома с четным количеством аргументов будут зеркальными отражениями друг друга. Используя length, subseq и reverse, определим функцию mirror?1:
(defun mirror? (s)
(let ((len (length s))) (and (evenp len)
(let ((mid (/ len 2))) (equal (subseq s 0 mid)
(reverse (subseq s mid)))))))
Эта функция определяет, является ли список таким палиндромом:
> (mirror? ’(a b b a)) T
Для сортировки последовательностей в Common Lisp есть встроенная функция sort. Она принимает список, подлежащий сортировке, и функ цию сравнения от двух аргументов:
> (sort ’(0 2 1 3 8) #’>) (8 3 2 1 0)
С функцией sort следует быть осторожными, потому что она деструк тивна. Из соображений производительности sort не создает новый спи сок, а модифицирует исходный. Поэтому если вы не хотите изменять исходную последовательность, передайте в функцию ее копию.°
Используя sort и nth, запишем функцию, которая принимает целое чис ло n и возвращает n-й элемент в порядке убывания:
1Ричард Фейтман указал, что есть более простой способ проверки палиндро ма, а именно: (equal x (reverse x)). – Прим. перев.
3.12. Стопка |
63 |
(defun nthmost (n lst) |
|
(nth (- n 1)
(sort (copy-list lst) #’>)))
Мы вынуждены вычитать единицу, потому что nth индексирует элемен ты, начиная с нуля, а наша nthmost – с единицы.
> (nthmost 2 ’(0 2 1 3 8)) 3
Наша реализация nthmost не самая эффективная, но мы пока не будем вдаваться в тонкости ее оптимизации.
Функции every и some применяют предикат к одной или нескольким по следовательностям. Если передана только одна последовательность, они проверяют, удовлетворяет ли каждый ее элемент этому предикату:
>(every #’oddp ’(1 3 5))
T
>(some #’evenp ’(1 2 3))
T
Если задано несколько последовательностей, предикат должен прини мать количество аргументов, равное количеству последовательностей, и из каждой последовательности аргументы берутся по одному:
> (every #’> ’(1 3 5) ’(0 2 4)) T
Если последовательности имеют разную длину, кратчайшая из них огра ничивает диапазон проверки.
3.12. Стопка
Представление списков в виде ячеек позволяет легко использовать их в качестве стопки (stack). В Common Lisp есть два макроса для работы со списком как со стопкой: (push x y) кладет объект x на вершину стоп ки y, (pop x) снимает со стопки верхний элемент. Оба эти макроса можно определить с помощью функции setf. Вызов
(push obj lst)
транслируется в
(setf lst (cons obj lst))
а вызов
(pop lst)
в
(let ((x (car lst)) (setf lst (cdr lst)) x)
64 |
Глава 3. Списки |
Так, например
>(setf x ’(b))
(B)
>(push ’a x) (A B)
>x
(A B)
>(setf y x) (A B)
>(pop x)
A
>x
(B)
>y (A B)
Структура ячеек после выполнения приведенных выражений показана на рис. 3.9.
x = |
|
|
|
|
|
|
|
|
||||
|
|
|
|
|
|
|
|
|
|
|
|
|
y = |
|
|
|
|
|
|
|
|
|
|
nil |
|
|
|
|
|
|
|
|
|
|
|
|||
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
a |
|
|
|
|
b |
||||
|
|
|
|
|
|
|
|
|
|
|
|
|
Рис. 3.9. Эффект от push и pop
С помощью push можно также определить итеративный вариант функ ции reverse для списков:
(defun our-reverse (lst) (let ((acc nil))
(dolist (elt lst) (push elt acc))
acc))
В этом варианте мы начинаем с пустого списка и последовательно кла дем на него, как на стопку, элементы исходного. Последний элемент окажется на вершине стопки, то есть в начале списка.
Макрос pushnew похож на push, однако использует adjoin вместо cons:
> (let ((x ’(a b))) (pushnew ’c x) (pushnew ’a x) x)
(C A B)
Элемент a уже присутствует в списке, поэтому не добавляется.