- •Оглавление
- •Предисловие
- •Предисловие к русскому изданию
- •Глава 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.3. Строки и знаки |
77 |
4.3. Строки и знаки1
Строки – это векторы, состоящие из знаков. Строкой принято называть набор знаков, заключенный в двойные кавычки. Одиночный знак, на пример c, задается так: #\c.
Каждый знак соответствует определенному целому числу, как правило, (хотя и не обязательно) в соответствии с ASCII. В большинстве реализа ций есть функция char-code, которая возвращает связанное со знаком число, и функция code-char, выполняющая обратное преобразование.°
Для сравнения знаков используются следующие функции: char< (мень ше), char<= (меньше или равно), char= (равно), char>= (больше или равно), char> (больше) и char/= (не равно) . Они работают так же, как и функции сравнения чисел, которые рассматриваются на стр. 157.
> (sort "elbow" #’char<) "below"
Поскольку строки – это массивы, то к ним применимы все операции с массивами. Например, получить знак, находящийся в конкретной по зиции, можно с помощью aref:
> (aref "abc" 1) #\b
Однако эта операция может быть выполнена быстрее с помощью спе циализированной функции char:
> (char "abc" 1) #\b
Функция char, как и aref, может быть использована вместе с setf для замены элементов:
> (let ((str (copy-seq "Merlin"))) (setf (char str 3) #\k)
str)
"Merkin"
Чтобы сравнить две строки, можно воспользоваться известной вам функ цией equal, но есть также и специализированная string-equal, которая к тому же не учитывает регистр букв:
>(equal "fred" "fred")
T
>(equal "fred" "Fred") NIL
1 Здесь и далее во избежание путаницы между символами в терминах Лиспа и символами-знаками, из которых состоят строки (буквами, цифрами и дру гими типами символов), мы будем называть последние просто «знаками». В случае когда путаница исключена, может использоваться также более привычный термин «символ». – Прим. перев.
78 |
Глава 4. Специализированные структуры данных |
> (string-equal "fred" "Fred") T
В Common Lisp определен большой набор функций для сравнения и про чих манипуляций со строками. Они перечислены в приложении D, на чиная со стр. 378.
Есть несколько способов создания строк. Самый общий – с помощью функции format. При использовании nil в качестве ее первого аргумента format вернет строку, вместо того чтобы ее напечатать:
> (format nil "~A or ~A" "truth" "dare") "truth or dare"
Но если вам нужно просто соединить несколько строк, можно восполь зоваться concatenate, которая принимает тип результата и одну или не сколько последовательностей:
> (concatenate ’string "not " "to worry") "not to worry"
4.4. Последовательности
Тип последовательность (sequence) в Common Lisp включает в себя спи ски и векторы (а значит, и строки) . Многие функции из тех, которые мы ранее использовали для списков, на самом деле определены для любых последовательностей. Это, например, remove, length, subseq, reverse, sort, every, some. Таким образом, функция, определенная нами на стр. 62, бу дет работать и с другими видами последовательностей:
> (mirror? "abba") T
Мы уже знаем некоторые функции для доступа к элементам последова тельностей: nth для списков, aref и svref для векторов, char для строк. Доступ к элементу последовательности любого типа может быть осу ществлен с помощью elt:
> (elt ’(a b c) 1) B
Специализированные функции работают быстрее, и использовать elt рекомендуется только тогда, когда тип последовательности заранее не известен.
С помощью elt функция mirror? может быть оптимизирована для векто ров:
(defun mirror? (s)
(let ((len (length s))) (and (evenp len)
(do ((forward 0 (+ forward 1)) (back (- len 1) (- back 1)))
((or (> forward back)
4.4. Последовательности |
79 |
(not (eql (elt s forward) (elt s back))))
(> forward back))))))
Эта версия по-прежнему будет работать и со списками, однако она более приспособлена для векторов. Регулярное использование последователь ного доступа к элементам списка довольно затратно, а непосредственно го доступа к нужному элементу они не предоставляют. Для векторов же стоимость доступа к любому элементу не зависит от его положения.
Многие функции, работающие с последовательностями, имеют несколь ко аргументов по ключу:
Параметр |
Назначение |
По умол |
|
|
чанию |
|
|
|
:key |
Функция, применяемая к каждому элементу |
identity |
:test |
Предикат для сравнения |
eql |
:from-end |
Если t, работа с конца |
nil |
:start |
Индекс элемента, с которого начинается выполнение |
0 |
:end |
Если задан, то индекс элемента, на котором следует |
nil |
|
остановиться |
|
|
|
|
Одна из функций, которая принимает все эти аргументы, – position. Она возвращает положение определенного элемента в последовательно сти или nil в случае его отсутствия. Посмотрим на роль аргументов по ключу на примере position:
>(position #\a "fantasia")
1
>(position #\a "fantasia" :start 3 :end 5)
4
Во втором случае поиск выполняется между четвертым и шестым эле ментом. Аргумент :start ограничивает подпоследовательность слева, :end ограничивает справа или же не ограничивает вовсе, если этот аргу мент не задан.
Задавая параметр :from-end:
> (position #\a "fantasia" :from-end t) 7
мы получаем позицию элемента, ближайшего к концу последователь ности. Но позиция элемента вычисляется как обычно, то есть от начала списка (однако поиск элемента производится с конца списка) .
Параметр :key определяет функцию, применяемую к каждому элемен ту перед сравнением его с искомым:
> (position ’a ’((c d) (a b)) :key #’car) 1
В этом примере мы поинтересовались, car какого элемента содержит a.
80 |
Глава 4. Специализированные структуры данных |
Параметр :test определяет, с помощью какой функции будут сравни ваться элементы. По умолчанию используется eql. Если вам необходи мо сравнивать списки, придется воспользоваться функцией equal:
>(position ’(a b) ’((a b) (c d))) NIL
>(position ’(a b) ’((a b) (c d)) :test #’equal)
0
Аргумент :test может быть любой функцией от двух элементов. Напри мер, с помощью < можно найти первый элемент, больший заданного:
> (position 3 ’(1 0 7 5) :test #’<) 2
С помощью subseq и position можно разделить последовательность на части. Например, функция
(defun second-word (str)
(let ((p1 (+ (position #\ str) 1)))
(subseq str p1 (position #\ str :start p1))))
возвращает второе слово в предложении:
> (second-word "Form follows function.") "follows"
Поиск элементов, удовлетворяющих заданному предикату, осуществля ется с помощью position-if. Она принимает функцию и последователь ность, возвращая положение первого встреченного элемента, удовлетво ряющего предикату:
> (position-if #’oddp ’(2 3 4 5)) 1
Эта функция принимает все вышеперечисленные аргументы по ключу, за исключением :test.
Также для последовательностей определены функции, аналогичные member и member-if. Это find (принимает все аргументы по ключу) и find-if (принимает все аргументы, кроме :test):
>(find #\a "cat") #\a
>(find-if #’characterp "ham") #\h
Вотличие от member и member-if, они возвращают только сам найденный элемент.
Вместо find-if иногда лучше использовать find с ключом :key. Напри мер, выражение:
(find-if #’(lambda (x)
(eql (car x) ’complete))
lst)