«НЕПРАКТИЧНЫЙ» PYTHOH
занимательные проекты для тех, кто хочет поумнеть
ЛИ ВОГАН
IMPRACTICAL PYTHON PROJECTS
I M P R A C T I C A L P Y T H O N P R O J E C T S
P l a y f i i l P r o g r a m m i n g A c t i v i t i e A t o M a k e Y o u S m a r t e r
by Lee Vaughan
no starch press
S a n F r a n c is c o
ЛИ ВОГАН
«НЕПРАКТИЧНЫЙ» PYTHON
занимательные проекты для тех, кто хочет поумнеть
Санкт-Петербург
«БХВ-Петербург»
2021
УДК 004.438 Python ББК 32.973.26-018.1
В61
Воган J1.
В61
«Непрактичный» Python: занимательные проекты для тех, кто хочет
поумнеть: Пер. с англ. — СПб.: БХВ-Петербург, 2021. — 464 с.: ил.
ISBN 978-5-9775-6751-0
Книга поможет читателям, самостоятельно осваивающим язык Python, отто чить уже имеющиеся навыки программирования, выработать новые и получить удовольствие от этого процесса. Ее можно рассматривать как свою вторую книгу по языку Python. Книга организована так, чтобы дополнять обычный учебник для начинающих или вводный курс. Для этого применен проектный подход, позво ляющий обойтись без тщательного “пережевывания” материла, который вы уже изучили.
По мере работы над проектами читатели будут использовать анализ марковских цепей для написания хокку, метод Монте-Карло для моделирования финансовых рынков, наложение снимков для улучшения астрофотосъемки и генетические алго ритмы для разведения армии гигантских крыс. В итоге читатели получат практиче ский опыт работы с такими модулями, как pygame, Pylint, pydocstyle, tkinter, python-docx, matplotlib и pillow.
Для программистов на языке Python
УДК 004.438 Python ББК 32.973.26-018.1
Группа подготовки издания:
Руководитель проекта Евгений Рыбаков Зав. редакцией Екатерина Сависте Перевод с английского Андрея Логунова Компьютерная верстка Ольги Сергиенко Оформление обложки
Карины Соловьевой
© 2019 by Lee Vaughan. Title of English-language original Impractical Python Projects: Playful Programming Activities to Make You Smarter, ISBN 978-1-59327-890-8, published by No Starch Press Russian-language edition copyright © 2021 by BHV. All rights reserved.
© 2019 by Lee Vaughan. Название английского оригинала: Impractical Python Projects: Playful Programming Activities to Make You Smarter, ISBN 978-1-59327-890-8, опубликовано No Starch Press. Издание на русском языке © 2021 BHV. Все права защищены.
Подписано в печать 01 12 20 Формат 70x1001/16 Печать офсетная. Уел печ л 37,41 Тираж 1500 экз. Заказ №12630 “БХВ-Петербург”, 191036, Санкт-Петербург, Гончарная ул , 20
Отпечатано с готового оригинал-макета ООО “Принт-М”, 142300, М О , г Чехов, ул Полиграфистов, д. 1
ISBN 978-1-59327-890-8 (англ.) ISBN 978-5-9775-6751-0 (рус.)
© Lee Vaughan, 2019 © Перевод на русский язык, оформление
ООО “БХВ-Петербург”, ООО “БХВ”, 2021
ОГЛАВЛЕНИЕ
Об авторе…………………………………………………………………………………………………………………….16
О техническом рецензенте……………………………………………………………………………………….16
Б лагодарности……………………………………………………………………………………………………………17
В ведение……………………………………………………………………………………………………………………..18 Для кого предназначена эта книга…………………………………………………………………………………..18 Что эта книга содержит………………………………………………………………………………………………….. 19 Версия Python, платформа и интегрированная среда разработчика………………………….. 21 Код………………………………………………………………………………………………………………………… 22 Стиль написания программного кода……………………………………………………………………….22 Где получить помощь……………………………………………………………………………………………………..22 Поехали!……………………………………………………………………………………………………………………….. 23
Г лава 1. Генератор дурацких и м е н ……………………………………………………………………….. 24 ПРОЕКТ 1: генерирование псевдонимов…………………………………………………………………………24 Планирование и конструирование проекта………………………………………………………………………25 Стратегия……………………………………………………………………………………………………………….. 25 Псевдокод……………………………………………………………………………………………………………….26 Код………………………………………………………………………………………………………………………………..27 Использование руководства сообщества Python по стилю написания кода………………. 30 Проверка кода с помощью программы Pylint……………………………………………………………30 Установка программы Pylint…………………………………………………………………………….31 Выполнение программы Pylint…………………………………………………………………………31 Обработка ложных ошибок константных имен……………………………………………….. 32 Конфигурирование программы Pylint……………………………………………………………….33 Описание своего кода с помощью литералов документирования……………………………… 34 Проверка стиля программного кода………………………………………………………………………….36 Резюме…………………………………………………………………………………………………………………………..38 Дальнейшее чтение…………………………………………………………………………………………………………39 Псевдокод……………………………………………………………………………………………………………….39 Стилевые руководства……………………………………………………………………………………………..39 Сторонние модули…………………………………………………………………………………………………..40
6 Оглавление
Практические проекты………………………………………………………………………………………………….. 40 “Поросячья” латынь……………………………………………………………………………………………….. 40 Столбчатый график бедняка…………………………………………………………………………………… 40 Сложные проекты…………………………………………………………………………………………………………. 41 Столбчатый график бедного чужака……………………………………………………………………….. 41 Среднее имя…………………………………………………… ………………………………………………………41 Что-то совершенно другое……………………………………………………………………………………… 42
Г л ав а 2. П оиск палииграм м иы х за к л и н а н и й ………………………………………………………43 Поиски и открытие словаря…………………………………………………………………………………………… 44 Обработка исключений при открытии файлов………………………………………………………… 45 Загрузка файла словаря………………………………………………………………………………………….. 46 ПРОЕКТ 2: поиск палиндромов…………………………………………………………………………………….. 47 Стратегия и псевдокод…………………………………………………………………………………………… 47 Код отыскания палиндромов………………………………………………………………………………….. 48 ПРОЕКТ 3: поиск палинграмм………………………………………………………………………………………. 50 Стратегия и псевдокод…………………………………………………………………………………………… 50 Код отыскания палинграмм……………………………………………………………………………………. 53 Профилирование палинграмм………………………………………………………………………………… 55 Профилирование с помощью cProfile……………………………………………………………… 55 Профилирование со временем………………………………………………………………………… 56 Оптимизация палинграмм………………………………………………………………………………………. 57 Резюме…………………………………………………………………………………………………………………………..59 Дальнейшее чтение……………………………………………………………………………………………………….. 59 Практический проект: очистка словаря………………………………………………………………………….. 59 Сложный проект: рекурсивный подход………………………………………………………………………….. 60
Г л ав а 3. Реш ение ан аграм м ……………………………………………………………………………………. 61 ПРОЕКТ 4: поиск однословных анаграмм……………………………………………………………………… 61 Стратегия и псевдокод…………………………………………………………………………………………… 62 Идентификация анаграммы……………………………………………………………………………. 62 Использование псевдокода…………………………………………………………………………….. 63 Код отыскания анаграмм……………………………………………………………………………………….. 63 ПРОЕКТ 5: поиск фразовых анаграмм…………………………………………………………………………… 65 Стратегия и псевдокод…………………………………………………………………………………………… 66 Использование контейнерного типа Counter для подсчета букв……………………….. 67 Псевдокод……………………………………………………………………………………………………….68 Дележ обязанностей………………………………………………………………………………………. 68 Код генерирования анаграммных словосочетаний…………………………………………………… 70 Настройка и отыскание анаграмм…………………………………………………………………… 70 Обработка выбранного варианта…………………………………………………………………….. 72 Определение функции mainQ…………………………………………………………………………. 74 Выполнение демонстрационного сеанса…………………………………………………………. 76 ПРОЕКТ 6: поиски Волдеморта, или галльский гамбит………………………………………………….. 77 ПРОЕКТ 7: поиски Волдеморта, или британская грубая сила…………………………………………. 79 Стратегия………………………………………………………………………………………………………………..79 Фильтрация с помощью проекции в гласные-согласные………………………………….. 80 Фильтрация с помощью триграмм………………………………………………………………….. 80 Фильтрация с помощью диграмм………………………………………………………… ………… 80
Оглавление 7
Код с использованием британской грубой силы……………………………………………………… 82 Определение функции mainQ…………………………………………………………………………..82 Подготовка списка слов…………………………………………………………………………………..83 Генерирование проекции в гласные-согласные……………………………………………….. 84 Определение фильтра проекций в гласные-согласные…………………………………….. 85 Определение триграммного фильтра………………………………………………………………..87 Определение диграммного фильтра………………………………………………………………….87 Предоставление возможности выбирать начальную букву………………………………. 89 Выполнение функции mainQ……………………………………………………………………………90 Резюме…………………………………………………………………………………………………………………………..90 Дальнейшее чтение…………………………………………………………………………………………………………91 Практический проект: поиск диграмм……………………………………………………………………………..91 Сложный проект: автоматический генератор анаграмм……………………………………………………91
Глава 4. Декодирование шифров времен Гражданской войны в США………… 92 ПРОЕКТ 8: маршрутный шифр……………………………………………………………………………………….92 Стратегия……………………………………………………………………………………………………………….. 95 Создание управляющего сообщения………………………………………………………………..96 Проектирование, заполнение и обнуление матрицы………………………………………… 97 Псевдокод………………………………………………………………………………………………………………. 98 Код расшифровки маршрутного шифра……………………………………………………………………98 Взламывание маршрутного шифра…………………………………………………………………………101 Добавление пользовательского интерфейса…………………………………………………………….104 Инструктаж пользователя и получение входных данных……………………………….. 104 Определение функции mainQ…………………………………………………………………………107 Верификация данных…………………………………………………………………………………….107 Построение и декодирование переводной матрицы……………………………………….. 109 ПРОЕКТ 9: зигзагообразный шифр……………………………………………………………………………….111 Стратегия………………………………………………………………………………………………………………112 Код шифровки зигзагообразным шифром……………………………………………………………….113 Инструктаж пользователя и получение входных данных……………………………….. 113 Шифрование сообщения………………………………………………………………………………..114 Код расшифровки зигзагообразного шифра……………………………………………………………115 Импорт модулей, инструктаж пользователя и получение входных данных…….. 116 Расшифровка сообщения……………………………………………………………………………….117 Резюме…………………………………………………………………………………………………………………………119 Дальнейшее чтение……………………………………………………………………………………………………….119 Практические проекты………………………………………………………………………………………………….119 Взламывание сообщения Линкольна………………………………………………………………………119 Идентификация типов шифров……………………………………………………………………………….120 Хранение ключа в форме словаря…………………………………………………………………………..120 Автоматическое генерирование возможных ключей……………………………………………… 120 Маршрутный перестановочный шифр: атака с применением грубой силы……………… 121 Сложные проекты…………………………………………………………………………………………………………122 Кодировщик маршрутного шифра………………………………………………………………………….122 Зигзагообразный многорядный шифр…………………………………………………………………….123
Глава 5. Кодирование шифров времен Гражданской войны в Англии………. 124 ПРОЕКТ 10: шифр Треваниона……………………………………………………………………………………..125 Стратегия и псевдокод…………………………………………………………………………………………..126
8 Оглавление
Код шифра Треваниона………………………………………………………………………………………… 128 Загрузка текста…………………………………………………………………………………………….. 128 Отыскание скрытого сообщения…………………………………………………………………… 129 Определение функции main()……………………………………………………………………….. 130 Выполнение функции mainQ………………………………………………………………………… 131 ПРОЕКТ 11: написание нулевого шифра……………………………………………………………………… 132 Код спискового шифра…………………………………………………………………………………………. 133 Вывод спискового шифра…………………………………………………………………………………….. 134 Резюме…………………………………………………………………………………………………………………………135 Дальнейшее чтение……………………………………………………………………………………………………… 136 Практические проекты………………………………………………………………………………………………… 136 Спасение королевы Марии…………………………………………………………………………………… 136 Колчестерская поимка………………………………………………………………………………………….. 137
Глава 6. Написание текста невидимыми чернилами…………………………………… 138 ПРОЕКТ 12: сокрытие шифра Виженера……………………………………………………………………… 138 Платформа………………………………………………………………………………………………………………….. 140 Стратегия……………………………………………………………………………………………………………………..140 Создание невидимых чернил………………………………………………………………………………… 141 Учет типов шрифтов, кернинга и межзнакового интервала…………………………….142 Как избежать проблем………………………………………………………………………………….. 143 Управление документами Word с помощью python-docx……………………………………….. 144 Скачивание ресурсных файлов……………………………………………………………………………… 146 Псевдокод…………………………………………………………………………………………………………………….148 Код………………………………………………………………………………………………………………………………148 Импорт модуля python-docx, создание списков и добавление фирменного бланка…….. 148 Форматирование и чередование сообщений………………………………………………………….. 150 Добавление шифра Виженера……………………………………………………………………………….. 153 Обнаружение скрытого сообщения……………………………………………………………………………… 154 Резюме…………………………………………………………………………………………………………………………157 Дальнейшее чтение……………………………………………………………………………………………………… 158 Практический проект: проверка числа пустых строк…………………………………………………….. 158 Сложный проект: использование моноширинного шрифта…………………………………………… 158
Глава 7. Разведение гигантских крыс с помощью генетических алгоритмов…………………………………………………………………………….. 159 Поиск наилучших решений из всех возможных……………………………………………………………. 159 ПРОЕКТ 13: разведение армии суперкрыс…………………………………………………………………… 160 Стратегия…………………………………………………………………………………………………………….. 161 Код размножения суперкрыс………………………………………………………………………………… 164 Ввод данных и допущений…………………………………………………………………………… 164 Инициализация популяции…………………………………………………………………………… 165 Измерение приспособленности популяции……………………………………………………. 166 Разведение нового поколения……………………………………………………………………….. 167 Мутирование популяции………………………………………………………………………………. 168 Определение функции mainQ……………………………………………………………………….. 169 Выполнение функции mainQ………………………………………………………………………… 171 Резюме………………………………………………………………………………………………………………….171 ПРОЕКТ 14: взламывание высокотехнологичного сейфа……………………………………………… 172 Стратегия………………………………………………………………………………………………………………175
Оглавление 9
Код взломщика сейфов…………………………………………………………………………………………..176 Настройка и определение функции fitnessQ…………………………………………………….176 Определение и запуск функции mainQ……………………………………………………………177 Резюме………………………………………………………………………………………………………………….179 Дальнейшее чтение………………………………………………………………………………………………………. 180 Сложные проекты…………………………………………………………………………………………………………180 Формирование крысиного гарема…………………………………………………………………………..180 Создание более эффективного взломщика сейфов…………………………………………………..180
Глава 8. Подсчет слогов в стихотворениях хокку………………………………………… 181 Японская поэзия хокку………………………………………………………………………………………………….182 ПРОЕКТ 15: подсчет слогов………………………………………………………………………………………….183 Стратегия……………………………………………………………………………………………………………………..183 Использование корпуса………………………………………………………………………………………….184 Установка NLTK……………………………………………………………………………………………………184 Скачивание словаря произношения CMUdict………………………………………………………….185 Подсчет звуков вместо слогов………………………………………………………………………………..186 Обработка слов с несколькими произношениями……………………………………………………186 Управление отсутствующими словами………………………………………………………………………….186 Тренировочный корпус………………………………………………………………………………………….187 Код отыскания отсутствующих слов………………………………………………………………………187 Импорт модулей, загрузка корпуса CMUdict и определение функции mainQ….. 187 Загрузка тренировочного корпуса и отыскание отсутствующих слов…………….. 189 Построение словаря отсутствующих слов………………………………………………………190 Сохранение словаря отсутствующих слов………………………………………………………192 Код подсчета слогов……………………………………………………………………………………………………..193 Подготовка, загрузка и подсчет………………………………………………………………………………194 Определение функции mainQ…………………………………………………………………………………195 Программа проверки вашей программы………………………………………………………………………..196 Резюме…………………………………………………………………………………………………………………………197 Дальнейшее чтение……………………………………………………………………………………………………….197 Практический проект: счетчик слогов против файла словаря………………………………………… 198
Глава 9. Написание стихотворений хокку с помощью анализа марковских цепей…………………………………………………………………………………………..199 ПРОЕКТ 16: анализ марковских цепей………………………………………………………………………….200 Стратегия……………………………………………………………………………………………………………………..203 Выбор и отбрасывание слов…………………………………………………………………………………..203 Переход от одной строки к другой………………………………………………………………………….205 Псевдокод…………………………………………………………………………………………………………………….206 Тренировочный корпус…………………………………………………………………………………………………207 Отладка………………………………………………………………………………………………………………………..208 Сооружение строительных лесов……………………………………………………………………………208 Использование модуля журналирования…………………………………………………………………209 Код………………………………………………………………………………………………………………………………210 Настройка……………………………………………………………………………………………………………..210 Построение марковских моделей……………………………………………………………………………211 Выбор случайного слова………………………………………………………………………………………..213 Применение марковских моделей…………………………………………………………………………..214
10 Оглавление
Генерирование строк хокку………………………………………………………………………………….. 215 Сборка первой строки………………………………………………………………………………….. 215 Сборка остальных строк………………………………………………………………………………. 217 Написание пользовательского интерфейса……………………………………………………………. 219 Результаты……………………………………………………………………………………………………………………222 Хорошее стихотворение хокку……………………………………………………………………………… 223 Инициирующее хокку………………………………………………………………………………………….. 225 Резюме…………………………………………………………………………………………………………………………225 Дальнейшее чтение……………………………………………………………………………………………………… 226 Сложные проекты……………………………………………………………………………………………………….. 226 Генератор новых слов………………………………………………………………………………………….. 226 Тест Тьюринга……………………………………………………………………………………………………… 227 Потрясающе! Просто потрясающе!………………………………………………………………………. 228 Писать хокку или не писать………………………………………………………………………………….. 228 Марковская музыка……………………………………………………………………………………………….228
Глава 10. Мы одни? Разведывание парадокса Ферми…………………………………. 229 ПРОЕКТ 17: моделирование Млечного Пути……………………………………………………………….. 230 Стратегия……………………………………………………………………………………………………………………..231 Оценивание числа цивилизаций…………………………………………………………………………………… 231 Выбор размеров радиопузыря……………………………………………………………………………………… 232 Генерирование формулы вероятности обнаружения…………………………………………………….. 233 Код вероятности обнаружения…………………………………………………………………………………….. 236 Вычисление вероятности обнаружения для диапазона цивилизаций………………………. 237 Генерирование предсказательной формулы и проверка результатов……………………….240 Построение графической модели…………………………………………………………………………………. 241 Шкалирование графической модели……………………………………………………………………… 244 Код симулятора галактики……………………………………………………………………………………. 245 Ввод данных и ключевых параметров…………………………………………………………… 245 Настройка холста tkinter и задание значений константам………………………………. 245 Шкалирование галактики и вычисление вероятности обнаружения………………..247 Использование полярных координат…………………………………………………………….. 248 Построение спиральных рукавов…………………… ……………………………………………..249 Рассеяние звездной дымки……………………………………………………………………………. 252 Определение функции mainQ……………………………………………………………………….. 253 Результаты……………………………………………………………………………………………………………………256 Резюме…………………………………………………………………………………………………………………………257 Дальнейшее чтение……………………………………………………………………………………………………… 257 Практические проекты………………………………………………………………………………………………… 257 Далекая-предалекая галактика………………………………………………………………………………. 258 Построение галактической империи……………………………………………………………………… 258 Окольный путь предсказания обнаруживаемости………………………………………………….. 260 Сложные проекты…………………………………………………………………………………………………………260 Создание спиральной галактики с перемычкой……………………………………………………… 260 Добавление в галактику обитаемых зон………………………………………………………………… 260
Глава 11. Задача Монти Холла…………………………………………………………………….. 262 Симуляция Монте-Карло…………………………………………………………………………………………….. 263 ПРОЕКТ 18: верификация утверждения вое Савант……………………………………………………… 265 Стратегия………………………………………………………………………………………………………………265
Оглавление 11
Код верификации утверждения Мэрилин вое Савант………………………………………………266 Получение входного числа прогонов………………………………………………………………266 Выполнение симуляции Монте-Карло и вывод результатов на экран…………….. 267 ПРОЕКТ 19: игра в Монти Холла………………………………………………………………………………….268 Краткое введение в объектно-ориентированное программирование……………………….. 269 Стратегия и псевдокод………………………………………………………………………………………….. 272 Игровые активы…………………………………………………………………………………………………….274 Код игры в Монти Холла………………………………………………………………………………………. 274 Импорт модулей и определение класса игры…………………………………………………..274 Создание виджетов для изображений и инструкций по игре………………………….. 276 Создание радиокнопок и текстовых виджетов………………………………………………..278 Расстановка виджетов……………………………………………………………………………………280 Обновление изображения двери……………………………………………………………………..281 Выбор выигрышной двери и показ козы…………………………………………………………281 Раскрытие выбранного игроком окончательного варианта…………………………….. 283 Вывод статистики на экран…………………………………………………………………………….284 Настройка корневого окна и запуск событийного цикла………………………………… 285 Резюме………………………………………………………………………………………………………………………… 286 Дальнейшее чтение………………………………………………………………………………………………………. 286 Практический проект: парадокс дня рождения………………………………………………………………286
Глава 12. Обеспечение сохранности пенсионных сбережений…………………….. 287 ПРОЕКТ 20: симулирование продолжительности жизни на пенсии………………………………. 288 Стратегия…………………………………………………………………………………………………………………….. 288 Исторические финансовые возвраты имеют значение……………………………………………. 291 Самая большая неопределенность………………………………………………………………………….294 Качественный способ представления результатов…………………………………………………..295 Псевдокод…………………………………………………………………………………………………………………….297 Поиск исторических данных…………………………………………………………………………………………298 Код………………………………………………………………………………………………………………………………299
Импортирование модулей и определение функций загрузки внешних данных и получения данных от пользователя……………………………………………………………………..299 Получение входных данных от пользователя………………………………………………………….300 Проверка на наличие других ошибок ввода данных………………………………………………. 303 Определение механизма Монте-Карло……………………………………………………………………304 Симулирование каждого года в заданном случае…………………………………………………… 307 Расчет вероятности разорения………………………………………………………………………………..308 Определение и вызов функции mainQ…………………………………………………………….309 Использование симулятора……………………………………………………………………………………………311 Резюме…………………………………………………………………………………………………………………………315 Дальнейшее чтение……………………………………………………………………………………………………….315 Сложные проекты…………………………………………………………………………………………………………316 Ясная картинка стоит тысячи слов………………………………………………………………………….316 Перемешать и увязать……………………………………………………………………………………………317 Масть пошла, а деньги кончились…………………………………………………………………………..317 Все что есть…………………………………………………………………………………………………………..317
Глава 13. Симуляция инопланетного вулкана…………………………………………….. 318 ПРОЕКТ 21: шлейфы на Ио…………………………………………………………………………………………..319 Кусочек пакета pygame…………………………………………………………………………………………………319
12 Оглавление
Стратегия……………………………………………………………………………………………………………………..320 Использование эскиза игры в целях планирования………………………………………………… 321 Планирование класса частиц………………………………………………………………………………… 322 Код………………………………………………………………………………………………………………………………324
Импортирование модулей, инициализирование модуля pygame и определение цветов…………………………………………………………………………………………….324 Определение класса частиц Particle………………………………………………………………………. 325 Выброс частицы…………………………………………………………………………………………………… 328 Обновление частиц и обработка граничных условий…………………………………………….. 330 Определение функции mainQ……………………………………………………………………………….. 331 Завершение функции mainQ…………………………………………………………………………………. 333 Выполнение симуляции………………………………………………………………………………………………. 335 Резюме…………………………………………………………………………………………………………………………336 Дальнейшее чтение……………………………………………………………………………………………………….336 Практический проект: весь путь до конца……………………………………………………………………. 337 Сложные проекты…………………………………………………………………………………………………………337 Ударный купол………………………………………………………………………………………………………337 Источник выбросов……………………………………………………………………………………………….338 Полет пули…………………………………………………………………………………………………………….338
Глава 14. Картографирование Марса с помощью орбитального спутника ….340 Астродинамика для геймеров………………………………………………………………………………………. 341 Закон универсальной гравитации (или всемирного тяготения)………………………………. 341 Законы движения планет Кеплера…………………………………………………………………………. 342 Орбитальная механика…………………………………………………………………………………………..342 Полет назад…………………………………………………………………………………………………..343 Поднятие и опускание орбит………………………………………………………………………… 343 Занятие внутренней дорожки……………………………………………………………………….. 344 Округлость эллиптической орбиты……………………………………………………………….. 345 Поднятие и опускание орбит с помощью гомановской траектории перехода…….346 Поднятие и опускание орбит методом однотангенциального сжигания…………..346 Исполнение спиральной орбиты со спиральным переносом…………………………… 347 Исполнение синхронных орбит…………………………………………………………………….. 347 ПРОЕКТ 22: игра “Орбитальный спутник Марса”………………………………………………………… 348 Стратегия……………………………………………………………………………………………………………………..348 Активы игры………………………………………………………………………………………………………………..351 Код………………………………………………………………………………………………………………………………352 Импорт и построение цветовой таблицы……………………………………………………………….. 353 Определение метода инициализации класса Satellite……………………………………………… 354 Задание первоначального положения спутника, его скорости, топлива и звука……….355 Запуск двигателей и проверка вводимых игроком значений………………………………….. 357 Локализация спутника……………………………………………………………………………………………358 Вращение спутника и вычерчивание его орбиты…………………………………………………… 359 Обновление объекта Satellite………………………………………………………………………………… 360 Определение метода инициализации класса Planet………………………………………………… 361 Вращение планеты…………………………………………………………………………………………………363 Определение методов gravityQ и updateQ……………………………………………………………… 364 Расчет эксцентриситета………………………………………………………………………………………….366 Определение функций для создания надписей………………………………………………………. 367 Картографирование влажности грунта………………………………………………………………….. 369
Оглавление 13
Отбрасывание тени………………………………………………………………………………………………..370 Определение функции mainQ…………………………………………………………………………………371 Инстанцирование объектов, настройка верификации орбиты, картографирования и хронометража……………………………………………………………………………………………………..372 Запуск игрового цикла и воспроизведение звуков…………………………………………………. 374 Применение гравитации, расчет эксцентриситета и обработка аварий……………………. 375 Вознаграждение за успех и обновление и отрисовка спрайтов……………………………….. 377 Вывод на экран инструкций и телеметрии и отбрасывание тени…………………………….. 378 Резюме…………………………………………………………………………………………………………………………380 Сложные проекты…………………………………………………………………………………………………………380 Титульный экран игры…………………………………………………………………………………………..380 Умные датчики……………………………………………………………………………………………………..381 Временное прекращение радиосвязи………………………………………………………………………381 Подсчет баллов……………………………………………………………………………………………………..381 Руководство по стратегии игры………………………………………………………………………………381 Аэродинамическое торможение……………………………………………………………………………..381 Сигнал о вторжении!……………………………………………………………………………………………..383 Скольжение по верхам…………………………………………………………………………………………..383
Глава 15. Улучшение астрофотосъемки с помощью наложения снимков планеты……………………………………………………………………………………………385 ПРОЕКТ 23: наложение фотоснимков Юпитера…………………………………………………………….386 Модуль pillow………………………………………………………………………………………………………………. 387 Работа с файлами и папками…………………………………………………………………………………………387 Каталожные пути…………………………………………………………………………………………………..387 Модуль утилит командной оболочки………………………………………………………………………390 Видеоролик………………………………………………………………………………………………………………….390 Стратегия…………………………………………………………………………………………………………………….. 392 Код………………………………………………………………………………………………………………………………392 Код обрезки и масштабирования снимков………………………………………………………………392 Импорт модулей и определение функции mainQ…………………………………………… 392 Удаление и очистка папок……………………………………………………………………………..394 Обрезка, масштабирование и сохранение снимков………………………………………… 395 Код наложения снимков…………………………………………………………………………………………399 Код улучшения качества снимков…………………………………………………………………………..401 Резюме…………………………………………………………………………………………………………………………405 Дальнейшее чтение……………………………………………………………………………………………………….405 Сложный проект: акт исчезновения……………………………………………………………………………….406
Глава 16. Выявление мошенничества с помощью закона Бенфорда…………… 408 ПРОЕКТ 24: закон первых цифр Бенфорда……………………………………………………………………409 Применение закона Бенфорда………………………………………………………………………………..411 Проверка по статистическому показателю хи-квадрат…………………………………………… 413 Набор данных……………………………………………………………………………………………………………….414 Стратегия……………………………………………………………………………………………………………………..415 Код………………………………………………………………………………………………………………………………416 Импортирование модулей и загрузка данных………………………………………………………… 416 Подсчет первых цифр…………………………………………………………………………………………….417 Получение ожидаемых количеств…………………………………………………………………………..419 Определение степени соответствия………………………………………………………………………..420
14 Оглавление
Определение функции построения столбчатого графика……………………………………….. 421 Завершение функции построения столбчатого графика…………………………………………. 422 Определение и выполнение функции mainQ………………………………………………………….. 424 Резюме……………………………………………………………………………………………………………………….. 427 Дальнейшее чтение……………………………………………………………………………………………………… 427 Практический проект: победа над Бенфордом……………………………………………………………… 428 Сложные проекты……………………………………………………………………………………………………….. 429 Бенфордирование колеблющихся штатов……………………………………………………………… 430 Пока никто не заметил…………………………………………………………………………………………. 430
П рилож ение. Реш ения п рактических п р о ек то в ……………………………………………….. 431 Глава 1. Генератор дурацких имен………………………………………………………………………………. 431 “Поросячья” латынь……………………………………………………………………………………………… 431 Столбчатый график бедняка…………………………………………………………………………………. 432 Глава 2. Поиск палинграммных заклинаний…………………………………………………………………. 432 Очистка словаря…………………………………………………………………………………………………… 432 Глава 3. Решение анаграмм…………………………………………………………………………………………. 433 Поиск диграмм…………………………………………………………………………………………………….. 433 Глава 4. Декодирование шифров времен Гражданской войны в США……………………………434 Взламывание сообщения Линкольна…………………………………………………………………….. 434 Идентификация типов шифров……………………………………………………………………………… 434 Хранение ключа в виде словаря……………………………………………………………………………. 435 Автоматическое генерирование возможных ключей……………………………………………… 435 Маршрутный перестановочный шифр: атака с применением грубой силы………………436 Глава 5. Кодирование шифров времен Гражданской войны в Англии……………………………439 Спасение королевы Марии…………………………………………………………………………………… 439 Колчестерская поимка………………………………………………………………………………………….. 441 Глава 6. Написание текста невидимыми чернилами……………………………………………………… 442 Проверка числа пустых строк……………………………………………………………………………….. 442 Глава 8. Подсчет слогов в стихотворениях хокку…………………………………………………………. 444 Счетчик слогов против файла словаря…………………………………………………………………… 444 Глава 10. Мы одни? Разведывание парадокса Ферми……………………………………………………. 445 Далекая-предалекая галактика………………………………………………………………………………. 445 Построение галактической империи……………………………………………………………………… 446 Окольный путь предсказания обнаруживаемости………………………………………………….. 449 Глава 11. Задача Монти Холла…………………………………………………………………………………….. 451 Парадокс дня рождения…………………………………………………………………………………………451 Глава 13. Симуляция инопланетного вулкана………………………………………………………………. 452 Полет пули…………………………………………………………………………………………………………….452 Глава 16. Выявление мошенничества с помощью закона Бенфорда……………………………….454 Победа над Бенфордом…………………………………………………………………………………………..454
П редм етны й у к азател ь…………………………………………………………………………………………..457
Для случайных программистов, преданных своему делу неспециалистов, копателей и дилетантов, как то: всех непрофессионалов, которые сами пишут свой код каждый день. Да пусть она поможет вам на вашем пути
ОБ АВТОРЕ
Ли Воган (Lee Vaughan)— геолог с более чем 30-летним опытом работы в нефтя ной промышленности. Будучи старшим техническим специалистом по геологиче скому моделированию в крупной международной нефтяной компании, он прини мал участие в создании и обзоре компьютерных моделей, разработке, тестировании и коммерциализации программного обеспечения, а также в подготовке геологов и инженеров. Защитник непрограммистов, которым приходится использовать про граммирование в своей карьере, он написал эту книгу для того, чтобы помочь тем, кто самостоятельно оттачивает свои навыки в языке Python.
О ТЕХНИЧЕСКОМ РЕЦЕНЗЕНТЕ
Джереми Кун (Jeremy Кип) окончил Университет штата Иллинойс в Чикаго с док торской степенью по математике. Он ведет блог “Математика П Программирова ние” (Math П Programming, https://jeremykun.com/) и в настоящее время работает над оптимизацией центра обработки данных в Google.
БЛАГОДАРНОСТИ
Написание книги — семейное дело, и я не смог бы добиться успеха без поддержки со стороны моей настоящей семьи, так и со стороны моей ’’второй” семьи — еди номышленников из издательства No Starch Press. Прежде всего, спасибо моей жене Ханне и дочерям Саре и Лоре за их понимание, терпение и бесконечную помощь в редактировании.
Спасибо Биллу Поллоку и Тайлеру Ортману за то, что они приняли мое предложе ние; Заку Лебовски за понимание того, что я пытался сказать; Джанель Людовиз за высокопрофессиональную работу по производственному редактированию; Рэйчел Монаган и Пауле Флеминг за то, что взяли на себя трудную работу по копирова нию и технической проверке книги; Дэвиду Ван Нессу за композицию и Серене Янг и Джошу Эллингсону за потрясающий дизайн обложки. Спасибо также моим техническим рецензентам Джереми Куну, Майклу Контравеосу и Микеле Пратусе- вичу за значительное улучшение книги с бесценными предложениями и исправле ниями.
Сара Воган, Эрик Эвенчик, Сяо-Хуэй By, Брукс Кларк, Брайан Проетт, Брент Фрэнсис и Гленн Крум оказали значительную техническую поддержку.
Наконец, спасибо Марку Натерну за то, что познакомил меня с языком программи рования Python, и в первую очередь Гвидо ван Россуму за то, что он изобрел эту замечательную вещь!
ВВЕДЕНИЕ
Добро пожаловать в книгу ““Непрактичный” Python: занимательные проекты для тех, кто хочет поумнеть”! Здесь вы будете использовать язык программирования Python для изучения Марса, Юпитера и самых дальних уголков галактики, сердец поэтов, мира высоких финансов, преступного мира шпионажа и фальсификации голосов на выборах, надувательства игровых шоу и многого другого. Вы будете применять такие методы, как анализ марковских цепей для написания хокку, симу лирование Монте-Карло для моделирования финансовых рынков, наложение сним ков для улучшения вашей астрофотосъемки и генетические алгоритмы для разве дения армии гигантских крыс — все это при получении опыта работы с такими мо дулями, как pygame, Pylint, pydocstyle, tkinter, python-docx, matplotlib И pillow. И самое главное, вы будете получать удовольствие.
Для кого предназначена эта книга
Эту книгу можно рассматривать как свою вторую книгу по языку Python. Она орга низована так, чтобы следовать и дополнять либо полноценную книгу для начи нающих, либо вводный курс. Вы сможете продолжить самоподготовку, используя проектный подход, не тратя свои деньги (или не занимая место на полке) на тща тельное ’’пережевывание” материла, который вы уже изучили. Однако не волнуй тесь, я не оставлю вас один на один с программами; весь исходный код аннотиро ван и объяснен. Приведенные в книге проекты предназначены для тех, кто хочет применять про граммирование для проведения экспериментов, проверки теорий, симулирования природных явлений или просто для того, чтобы весело провести время. Это отно сится ко всем тем, кто использует программирование как часть своей работы (на пример, исследователям и инженерам), но которые не являются программистами как таковыми, а также тем, кого я называю ’’непреклонными неспециалистами”, — дилетантам и любителям, которым нравятся программистские задачи как забавное времяпрепровождение. Если вы хотели бы поиграть с представленными здесь идеями, но вас обескураживает запуск потенциально сложных проектов с нуля либо он требует много времени, то эта книга — для вас.
Введение 19
Что эта книга содержит
По мере работы над проектами вы будете расширять свои знания о полезных биб лиотеках и модулях языка Python; узнаете больше кратких форм, встроенных функций и полезных способов и попрактикуетесь в проектировании, тестировании и оптимизации программ. Кроме того, вы сможете связать то, что вы делаете, с ре альными приложениями, наборами данных и задачами.
Процитирую Ральфа Уолдо Эмерсона: ’’Ничто великое никогда не достигалось без энтузиазма”. Сюда входит и опыт самостоятельного освоения. Конечная цель этой книги — зажечь ваше воображение и подвести вас к разработке собственных инте ресных проектов. Не волнуйтесь, если на первый взгляд они покажутся слишком амбициозными; чуть-чуть усердия и много гугления способны сотворить чудеса — и быстрее, чем вы думаете.
Ниже приведен обзор глав этой книги. Вам не обязательно нужно работать с ними последовательно, но самые простые проекты находятся в начале, и я объясняю но вые концепции, модули и методы подробнее, когда они вводятся впервые. Глава 1 “Генератор дурацких имен”. В этом разминочном проекте представлены рекомендации РЕР 8 и РЕР 257 по написанию программного кода на языке Python, а также модули pylint и pydocstyle, которые анализируют соответствие вашего ко да этим рекомендациям. Конечный продукт— это генератор глупых имен, вдох новленный сериалом ’’Ясновидец” (Psych) американского кабельного телеканала USA Network. Глава 2 “Поиск палинграммных заклинаний”. Вы научитесь профилировать свой код и спасете волшебницу Затанну из комиксов DC Comics от мучительной смерти. Вы отыщете волшебные палинграммы в онлайновых словарях, которые нужны Затанне для того, чтобы победить злодея, обращающего время вспять. Глава 3 “Решение анаграмм”. Вы напишете программу, которая поможет пользо вателю создавать анаграмму фразы из своего имени; например, имя Клинт Иствуд (Clint Eastwood) дает ’’действие старого Запада” (old west action). Затем, применив лингвистическое решето, вы поможете Тому Марволо Реддлу (Tom Marvolo Riddle) вывести свою анаграмму ”Я — Лорд Волдеморт” (I am Lord Voldemort). Глава 4 “Декодирование шифров времен Гражданской войны в США”. Вы разведаете и взломаете один из самых успешных военных шифров в истории, мар шрутный перестановочный шифр союзников. Затем поможете шпионам с обеих сторон отправлять и расшифровывать секретные сообщения, используя зигзагооб разный шифр.
Глава 5 “Кодирование шифров времен Гражданской войны в Англии”. Вы прочитаете сообщение, скрытое у всех на виду, расшифровав потайной шифр вре мен Гражданской войны в Англии. Затем спасете голову Марии, королевы Шот ландии, разработав и реализовав код для выполнения более сложной задачи напи сания нулевого шифра.
Глава 6 “Написание текста невидимыми чернилами”. Вы поможете корпора тивному ’’кроту” предать отца Шерлока Холмса и избежать обнаружения с помо
20 Введение
щью невидимых электронных чернил. Эта глава основана на эпизоде телевизион ного сериала ’’Элементарно” (Elementary) американского телеканала CBS.
Глава 7 “Разведение гигантских крыс с помощью генетических алгоритмов”. Вы примените генетические алгоритмы, обусловленные эволюцией Дарвина, для того, чтобы развести расу суперкрыс размером с самку бульмастифа. Затем помо жете Джеймсу Бонду в мгновение ока взломать сейф с шифром на 10 млрд возмож ных комбинаций.
Глава 8 “Подсчет слогов в стихотворениях хокку”. Вы научите свой компьютер подсчитывать слоги на английском языке в качестве прелюдии к написанию япон ских стихотворений в стиле хокку в следующей главе.
Глава 9 “Написание стихотворений хокку с помощью анализа марковских цепей”. Вы научите свой компьютер писать хокку, объединив модуль подсчета слогов из главы 8 с анализом цепи Маркова и тренировочным корпусом из не скольких сотен древних и современных хокку.
Глава 10 “Мы одни? Разведывание парадокса Ферми”. Вы изучите вопрос, как выяснить наличие или отсутствие инопланетных радиосигналов, используя уравне ние Дрейка, размеры галактики Млечного Пути и допущения о размере обнаружи ваемых ’’пузырей излучения”. Вы изучите и примените популярный модуль tkinter для создания графического изображения галактики и собственного радиопузыря Земли.
Глава 11 “Задача Монти Холла”. Вы поможете умнейшей в мире женщине выиг рать спор о задаче Монти Холла. Затем вы примените объектно-ориентированное программирование (ООП) для того, чтобы построить версию знаменитой игры в Монти с забавным графическим интерфейсом.
Глава 12 “Обеспечение сохранности пенсионных сбережений”. Вы спланируете свою (или ваших родителей) обеспеченную жизнь на пенсии с использованием фи нансовой симуляции Монте-Карло.
Глава 13 “Симуляция инопланетного вулкана”. Вы примените библиотеку pygame для симуляции извержения вулкана на Ио, одной из лун Юпитера.
Глава 14 “Картографирование Марса с помощью орбитального спутника”. Вы построите аркадную игру на основе гравитации и подтолкнете спутник на круго вую орбиту картографирования без исчерпания топлива или сгорания в атмосфере. Вы выведите на экран показания ключевых параметров, отследите орбитальные траектории, добавите тени планеты и медленное вращение Марса вокруг своей оси, все это время изучая орбитальную механику!
Глава 15 “Улучшение астрофотосъемки с помощью наложения снимков пла неты”. Вы откроете облачные полосы Юпитера и Большое красное пятно, оптиче ски совместив некачественные видеоснимки с помощью библиотеки обработки изображений Python. Вы узнаете, как работать с файлами, папками и каталожными ПуТЯМ И С ПОМОЩЬЮ В С Т роеН Н Ы Х М ОД улеЙ os И shutil.
Глава 16 “Выявление мошенничества с помощью закона Бенфорда”. Вы при мените закон Бенфорда для расследования случаев фальсификации во время голо
Введение 21
сования на президентских выборах 2016 года в США. Вы задействуете библиотеку matpiotlib для подытоживания результатов на графике.
Каждая глава заканчивается по крайней мере одним практическим или одним сложным проектом. Каждый практический проект сопровождается решением. Это вовсе не означает, что указанное решение является лучшим, — вы можете приду мать оптимальное решение самостоятельно, так что не забегайте вперед!
Однако со сложными проектами вы действительно предоставлены сами себе. Когда в 1519 г. Кортес вторгся в Мексику, он сжег свои каравеллы. Это было сделано для того, чтобы его конкистадоры поняли, что пути назад нет и им придется столкнуть ся с ацтеками, имея лишь мрачную и непоколебимую решимость. Таким образом, английское выражение ’’сжечь свои корабли” стало означать безоглядность или полную приверженность делу. Именно так вы должны решать сложные проекты — как если бы ваш корабль был сожжен, — и если вы его выполните, то из этих уп ражнений вы, скорее всего, узнаете больше, чем из любой другой части книги!
Версия Python, платформа и интегрированная среда разработчика
Каждый проект в этой книге построен с помощью языка Python версии 3.5 в среде операционной системы Microsoft Windows 10. Нет никаких проблем, если вы ис пользуете другую операционную систему: где это уместно, для других платформ предлагаются совместимые модули.
Используемые в этой книге примеры кода и снимки экрана взяты либо из текстово го редактора Python IDLE, либо из интерактивной оболочки. Среда IDLE (integrated development and learning environment) — это интегрированная среда разработки и самообучения. Она представляет собой обычную интегрированную среду разработ ки (IDE) с добавлением буквы L, в результате давая аббревиатуру, которая таким образом косвенно ссылается на Эрика Айдла (Eric Idle) из знаменитого комик-шоу Монти Пайтон (Monty Python). Интерактивная оболочка, также именуемая интер претатором, представляет собой окно, которое позволяет немедленно выполнять команды и тестировать программный код без необходимости создания файла.
Среда IDLE имеет ряд недостатков, таких как отсутствие столбца с номером строки в текстовом редакторе, но она является бесплатной и поставляется в комплекте с языком Python, поэтому каждый имеет к ней доступ. Вы можете использовать любую интегрированную среду разработки, которую вы захотите. В Интернете есть много вариантов, таких как Geany (произносится ’’джини”), PyCharm и PyScripter. Geany работает с широким спектром операционных систем, включая UNIX, macOS и Windows. PyCharm работает с Linux, Windows и macOS. PyScripter работает с Windows. Для получения подробного списка доступных инструментов разработки на Python и совместимых платформ посетите веб-страницу https://wiki.python.org/ moin/DevelopmentT ools/.
22 Введение
Код
В книге приведены все строки кода для каждого проекта, и я рекомендую вам на бирать их вручную всякий раз, когда это возможно. Мне запомнилось выражение моего школьного учителя, который однажды сказал, что мы “учимся своими рука ми”, и должен согласиться, что ввод кода заставляет нас уделять максимальное внимание тому, что происходит.
Но если вы хотите быстро завершить проект или же если вы случайно удалили всю свою работу, то вы можете скачать весь код, включая решения практических про ектов, с веб-сайта книги по адресу https://www.nostarch.com/impracticaIpython/.
Стиль написания программного кода
Эта книга посвящена решению занимательных задач и предназначена для нович ков, поэтому исходный код может иногда отклоняться от передовых практических решений и максимальной эффективности. Иногда вы, возможно, будете использо вать операцию включения в список или специальный оператор, но по большей час ти вы сосредоточитесь на простом, доступном для понимания коде, который легко усвоить.
Программирующим непрограммистам, читающим эту книгу, важно придерживать ся простоты. Значительная часть их кода, возможно, будет “кодом Kleenex” — его используют один-два раза для определенной цели, а затем выбрасывают. Это тот тип кода, которым можно делиться с коллегами или который можно навязать им во время кадровых изменений, поэтому он должен быть легким в понимании и усвое нии.
Весь главный код проекта аннотируется и объясняется автономно, и он обычно следует рекомендациям РЕР 8 (Python Enhancement Proposal) по написанию про граммного кода на языке Python. Подробная информация о рекомендациях РЕР 8 и программном обеспечении, которое поможет вам соблюдать эти рекомендации, приведена в главе 1.
Где получить помощь
Берясь за решение сложной программистской задачи, вы можете столкнуться с трудностями. Написание программного кода — это не всегда то, что можно по нять интуитивно, даже с помощью такого дружественного языка, как Python. В по следующих главах я буду приводить ссылки на полезные источники информации, но в случае проектов, которые вы формулируете самостоятельно, ничто не может превзойти поиск в Интернете.
Ключ к успешному поиску — знать, о чем спрашивать. Поначалу он может ока заться безуспешным, вызывая досаду, но подумайте о нем как об игре в двадцать вопросов. Продолжайте с каждым последующим поисковым запросом оттачивать свои ключевые слова до тех пор, пока не найдете ответ или не достигнете точки уменьшения отдачи.
Введение 23
Если книги и онлайновый поиск оказываются безуспешными, то следующий шаг — спросить у кого-то еще. Вы можете сделать это онлайн, за отдельную плату либо на бесплатных форумах, таких как Stack Overflow (https://stackoverflow.com), но имейте в виду: члены этих веб-сайтов не в восторге от человеческой глупости. Перед опубликованием обязательно прочитайте их страницы с рекомендациями о том, как задать хороший вопрос (которые так и называются “How do I ask a good question?”); например, подобную страницу можно найти на веб-сайте Stack Overflow по адресу http://stackoverflow.com/heIp/how-to-ask/.
Поехали!
Спасибо, что нашли время прочитать данное введение! Вы явно хотите получить от этой книги как можно больше, и вы на правильном пути. Когда вы достигнете по следней страницы, вы будете более искусны в языке Python и лучше подготовлены к решению сложных реальных задач. Давайте же приступим к работе.
1
ГЕНЕРАТОР ДУРАЦКИХ ИМЕН
Американский кабельный телеканал USA Network однажды транслировал детективный сериал под названием ’’Яснови- дец" (Psych), в котором гипернаблюдательный сыщик-лю битель Шон Спенсер решал дела, притворяясь, что пользуется экстрасенсорными способностями. Фирменным знаком данного сериала было то, как Шон представлял своего напарника Г аса дурацкими именами, выдумываемыми им на лету, такими как Галилео Хампкинс, Лавандер Гумз и Пло хие Новости Марвин Барнс. Это произвело на меня сильное впечатление, ибо много лет назад один мой приятель, который работал в Бюро переписи населения, дал мне список настоящих имен, таких же странных, как те, которые придумывал Шон.
Проект 1: генерирование псевдонимов
В этом разминочном проекте вы напишете простую программу на языке Python, которая генерирует чудаковатые имена, случайным образом комбинируя имена и фамилии. Если повезет, то вы создадите обилие псевдонимов, которые наполнят гордостью любого закадычного друга. Вы также познакомитесь с рекомендациями по написанию программного кода и примените внешние программы, которые по могут вам писать код, соответствующий этим рекомендациям.
Сериал ‘’Ясновидец” не в вашем стиле? Замените имена в моем списке в коде соб ственными шутками или темой. Вы можете так же легко превратить этот проект в генератор имен ’’Игры престолов”, или, возможно, вы захотите изобрести свое
Генератор дурацких имен 25
имя типа Бенедикт Камбербэтч; мое любимое— Бендилик Крикетбэт (Bendylick Cricketbat, что на русском будет примерно звучать как Похотлиз Дубомоль).
Ц е л ь
Случайно сгенерировать смешные имена закадычных друзей, используя код на Python, который соответствует установленным стилевым рекомендациям.
Планирование и конструирование проекта
Время на планирование никогда не тратится впустую. Не имеет значения, програм мируете ли вы для получения удовольствия или заработка, в какой-то момент вам нужно будет оценить — довольно точно, — сколько времени займет проект, с ка кими препятствиями вы можете столкнуться и какие инструменты и ресурсы вам понадобятся для выполнения работы. И для этого вам в первую очередь нужно по нять, что конкретно вы пытаетесь создать!
Помню слова одного успешного менеджера, который как-то сказал мне, что секрет его успеха заключается в том, чтобы просто задавать много вопросов. Что ты пыта ешься сделать? Зачем ты это делаешь? Почему ты делаешь это таким образом? Сколько у тебя времени? Сколько денег? Ответы на эти вопросы чрезвычайно по лезны для процесса проектирования и дают вам четкий ракурс.
В своей книге ’’Думай по-питоновски” (Downey A. Think Python. O’Reilly, 2015) Аллен Дауни описывает два типа планов разработки программного обеспечения: “прототип и заплатка” и “проектная разработка”. В случае прототипа и заплатки вы начинаете с простой программы, а затем накладываете заплатки или отредактиро ванный код для решения проблем, возникающих при тестировании. Этот подход может быть неплохим, когда вы работаете над сложной задачей, которую вы не очень хорошо понимаете. Но он также может создавать усложненный и ненадеж ный код. Если у вас есть четкое представление о задаче и о том, как вы хотите ее решить, то во избежание будущих проблем и их последующих исправлений вы должны применять план проектной разработки. Этот подход еще больше может упростить и поднять эффективность написания программного кода, и он обычно приводит к более прочному и надежному программному коду.
В этой книге вы будете начинать все проекты с четко определенной задачи или це ли, которые лягут в основу ваших проектных решений. Затем мы будем обсуждать стратегию, которая поможет лучше понять возникающие вопросы и создавать план проектной разработки.
Стратегия
Здесь вы начнете с двух списков смешных имен и фамилий. Списки будут относи тельно короткими, поэтому они не будут потреблять много памяти, не будут нуж даться в динамическом обновлении и не должны представлять никаких сложностей во время выполнения. Поскольку единственное, что от вас потребуется, — это про
26
Глава 1
читать имена из списка, вы задействуете кортеж, который будет использован в ка честве контейнера.
С помощью двух кортежей имен вы создадите новые имена — попарно соединяя имена с фамилиями — одним нажатием кнопки. Благодаря этому пользователь сможет легко повторять этот процесс до тех пор, пока не появится достаточно смешное имя. Вы также должны каким-то образом выделить имя в окне интерпретатора так, что бы оно отличалось в командной строке. Интерактивная оболочка IDLE дает не слишком много вариантов шрифтов, но вы, вероятно, — как никто другой — знае те, что ошибки выводятся на экран красным цветом. По умолчанию функция print () выводит печать в стандартный канал вывода, но, загрузив модуль sys, мож но перенаправлять вывод в канал ошибок с его фирменным красным цветом, ис пользуя параметр file:
print(печатаемое_сообщение, file=sys.stderr)
Наконец, вы выясните, какие, собственно, существуют стилевые рекомендации по написанию программного кода на языке Python. Эти рекомендации должны касать ся не только кода, но и размещаемой в коде документации.
Псевдокод “Можно всегда рассчитывать на то, что американцы сделают все правильно после того, как они испробовали все остальное”. Эта цитата, приписываемая Уинстону Черчиллю, подводит итог тому, как многие люди подходят к написанию псевдо кода. Псевдокод— это высокоуровневый, неформальный способ описания компьютер ных программ с использованием структурированного английского или любого дру гого человеческого языка. Он должен напоминать упрощенный язык программиро вания и содержать ключевые слова и соответствующие отступы. Разработчики ис пользуют его для того, чтобы игнорировать весь тайный синтаксис истинных языков программирования и сосредоточиться на базовой логике. Невзирая на свое широкое применение, псевдокод не имеет официальных стандартов — только ру ководящие принципы. Если вы обнаружили, что широким шагом движетесь в сторону разочарования, то это может быть только потому, что вы не нашли времени написать псевдокод. Я искренне верю в псевдокод, т. к. он безошибочно приводил меня к решениям тогда, когда я оказывался блуждающим по лесу. Следовательно, в большинстве проектов этой книги вы будете использовать некоторую форму псевдокода. По крайней мере, надеюсь, что вы увидите его полезность, но я также надеюсь, что вы дисциплинируете себя и будете писать его в собственных проектах. Очень высокоуровневый псевдокод нашего генератора забавных имен может вы глядеть так:
Загрузить список имен Загрузить список фамилий Выбрать имя наугад
Генератор дурацких имен 27
Назначить имя переменной Выбрать фамилию наугад Назначить имя переменной Напечатать имена на экране по порядку и красным шрифтом Запросить у пользователя, что делать дальше: выйти из программы или сыграть еще раз Если пользователь решит сыграть еще раз:
Повторить
Если пользователь решит выйти:
Завершить и выйти из программы
Если только вы не пытаетесь воплотить здесь программный класс либо предоста вить четкие инструкции другим, то сосредоточьтесь на цели псевдокода; не старай тесь рабски соблюдать (нестандартные) рекомендации по его написанию. И не за цикливайтесь только на программировании — процесс написания псевдокода мож но применить к гораздо большему кругу задач. Как только вы с ним освоитесь, вы обнаружите, что он поможет вам и в других вещах, таких как уплата налогов, пла нирование инвестиций, строительство дома или подготовка к походу. Это отлич ный способ сосредоточиться и перенести успехи в программировании в реальную жизнь. О, если бы только его применяли в конгрессе!
Код
В листинге 1. 1 приведен код для генератора смешных имен (файл pseudonyms.ру), который составляет и печатает список псевдонимов из двух кортежей имен. Если вы не хотите набирать все имена на клавиатуре, то можете набрать их подмножест во или же скачать код с веб-сайта книги по адресу https://nostarch.com/ impracticalpython/.
Листинг 1.1. Генерирует глупые псевдонимы из кортежей имен. Файл pseudonyms.py
О import sys, random Q print(“Добро пожаловать в ‘Подбор имен для напарника.’ как в сериале ‘Ясновидец’\п”)
print(“Имя, наподобие того, которое Шин подбирал для Гаса:\п\п”)
first = (‘Baby Oil1, ‘Bad News’, ‘Big Burps’, “Bill ‘Beenie-Weenie’”, “Bob ‘Stinkbug’”, ‘Bowel Noises’, ‘Boxelder’, “Bud ‘Lite’ “, ‘Butterbean’, ‘Buttermilk’, ‘Buttocks’, ‘Chad’, ‘Chesterfield’, ‘Chewy’, ‘Chigger”, “Cinnabuns’, ‘Cleet’, ‘Cornbread’, ‘Crab Meat’, ‘Crapps’, ‘Dark Skies’, ‘Dennis Clawhammer’, ‘Dicman’, ‘Elphonso’, ‘Fancypants’, ‘Figgs’, ‘Foncy’, ‘Gootsy’, ‘Greasy Jim’, ‘Huckleberry’, ‘Huggy’, ‘Ignatious’, ‘Jimbo’, “Joe ‘Pottin Soil’”, ‘Johnny’, ‘Lemongrass’, ‘Lil Debil’, ‘Longbranch’, ‘“Lunch Money”’, ‘Mergatroid’, ‘“Mr Peabody”’, ‘Oil-Сап’, ‘Oinks’, ‘Old Scratch’, ‘Ovaltine’, ‘Pennywhistle’, ‘Pitchfork Ben’, ‘Potato Bug’, ‘Pushmeet’,‘Rock Candy’, ‘Schlomo’,
28
Глава 1
’Scratchensniff’, ‘Scut’, “Sid ‘The Squirts’”, ‘Skidmark’, ’Slaps’, ’Snakes’, ’Snoobs’, ’Snorki’, ’Soupcan Sam’, ’Spitzitout’, ’Squids’, ’Stinky’, ‘Storyboard’, ’Sweet Tea’, ’TeeTee’, ’Wheezy Joe’, “Winston ’Jazz Hands’”, ’Worms ’)
last = (’Appleyard’, ’Bigmeat’, ’Bloominshine’, ’Boogerbottom’,
’Breedslovetrout’, ‘Butterbaugh’, ‘Clovenhoof’, ‘Clutterbuck’,
’Cocktoasten’, ’Endicott’, ‘Fewhairs’, ’Gooberdapple’, ’Goodensmith’, ‘Goodpasture’, ’Guster’, ’Henderson’, ’Hooperbag’, ’Hoosenater’, ’Hootkins’, ’Jefferson’, ’Jenkins’, ’Jingley-Schmidt’, ’Johnson’, ’Kingfish’, ‘Listenbee’, “M’Bernbo”, ’McFadden’, ’Moonshine’, ‘Nettles’, ‘Noseworthy’, ’Olivetti’, ’Outerbridge’, ‘Overpeck’, ‘Overturf’, ‘Oxhandler’, ’Pealike’, ’Pennywhistle’, ’Peterson’, ’Pieplow’, ’Pinkerton’, ’Porkins’, ’Putney’, ’Quakenbush’, ’Rainwater’, ’Rosenthal’,
’Rubbins’, ’Sackrider’, ’Snuggleshine’, ‘Splern’, ‘Stevens’,
’Stroganoff’, ‘Sugar-Gold’, ’Swackhamer’, ’Tippins’, ’Turnipseed’, ‘Vinaigrette’, ‘Walkingstick’, ‘Wallbanger’, ‘Weewax’, ‘Weiners’, ’Whipkey’, ‘Wigglesworth’, ’Wimplesnatch’, ‘Winterkorn’, ‘Woolysocks’)
О while True:
О firstName = random.choice(first)
ф lastName = random.choice(last)
print("\n\n”)
О print(”{} {}".format(firstName, lastName), file=sys.stderr)
print("\n\n”)
ф try_again = input("\п\пПопробуете еще? (Нажмите Enter либо n, чтобы
выйти)\n “)
if try_again.lower() == “n”:
break
ф input("\пНажмите Enter для завершения работы.”)
Сначала импортируйте модули sys и random ф. Модуль sys вы будете использовать для доступа к системным функциям сообщений об ошибках с целью окраски своего результата в привлекательный красный цвет в окне интерпретатора IDLE. А модуль random позволяет вам выбирать наугад элементы из списков имен.
Инструкции print В о знакомят пользователя с программой. Команда новой стро
ки \п заставляет появиться новую строку, а одинарные кавычки " позволяют ис пользовать кавычки в распечатке без необходимости прибегать к управляющему символу обратной косой черты, который уменьшает читаемость кода.
Генератор дурацких имен 29
Далее определите свои кортежи имен. Затем инициируйте цикл while 0 . Выраже ние while - True, в сущности, означает ’’продолжать выполнять до тех пор, пока я не скажу вам остановиться”. Впоследствии для завершения цикла вы примените инструкцию break.
Цикл начинает со случайного выбора имени из первого кортежа, а затем назначает ЭТО ИМЯ переменной firstName 0 . Он ИСПОЛЬЗует метод choice модуля random, КОТО РЫЙ возвращает случайный элемент из непустой последовательности — в данном случае кортежа имен.
Далее случайно выберите фамилию из кортежа last и назначьте ее переменной lastName 0 . Теперь, когда у вас есть оба имени, напечатайте их и хитростью за ставьте интерактивную оболочку IDLE использовать красный шрифт ’’ошибки”, указав в инструкции print необязательный аргумент fiie=sys.stderr 0 . Для пре образования переменных с именами в символьную цепочку1 используйте более но вый метод форматирования символьных цепочек, а не старый оператор формати рования (%). Дополнительные сведения о новом методе см. на веб-странице https://d0cs.pyth0n.0rg/3.7/library/string.html. После того как имя будет выведено на экран, попросите пользователя выбрать дальнейшее действие: сыграть еще раз либо выйти из программы, применив для этого инструкцию input с описанием в кавычках. В этом случае задействуйте не сколько пустых строк для того, чтобы сделать смешное имя в окне интерпретатора IDLE очевиднее. Если пользователь отвечает нажатием клавиши , то в пе ременную try again 0 ничего не возвращается. Если ничего не возвращается, то условие в инструкции if не удовлетворяется, цикл while продолжается, и печатает ся новое имя. Если вместо этого пользователь нажимает клавишу , то инструк ция if приводит к команде break, и цикл завершается, потому что инструкция while больше не оценивается как True. Используйте метод lower о перевода символьной цепочки в нижний регистр для того, чтобы учесть ситуацию, когда у игрока задей ствована клавиша . Другими словами, не имеет значения, в нижнем или верхнем регистре пользователь вводит символ N, потому что программа всегда будет читать его в нижнем регистре. Наконец, попросите пользователя выйти из программы, нажав клавишу 0 . Нажатие клавиши не задает переменной значение, возвращаемое из функ ции input (), программа завершается, и окно консоли закрывается. Нажатие клави ши в окне текстового редактора IDLE приводит к исполнению законченной программы.
Этот код работает, но только работы недостаточно — программы на языке Python должны работать в принятом стиле.
1 В настоящем переводе термин string намеренно переводится как символьная цепочка, реже — как символьная последовательность, а не как “строка”, что нередко можно встретить в подобной перевод ной литературе. Это сделано намеренно с целью избежать путаницы при переводе таких терминов, как line o f code (строка кода) или row (строка таблицы), которые часто встречаются вместе в одном абзаце. — Прим. перев.
30
Глава 1
Использование руководства сообщества Python по стилю написания кода
Согласно Дзен языка Python (https://www.python.org/dev/peps/pep-0020/), “должен существовать один — и желательно только один — очевидный способ сделать это”. В духе предоставления единственного очевидного “правильного способа” делать вещи и в целях достижения консенсуса вокруг этих практических приемов сообще ство Python выпускает рекомендации по улучшению языка Python, которые пред ставляют собой правила написания программного кода на Python, в состав которых входит стандартная библиотека главного дистрибутива Python. Наиболее важными из них являются рекомендации РЕР 8, руководство по написанию программного кода на языке Python. Время идет, и РЕР 8 регулярно эволюционирует, поскольку выявляются новые правила, а прошлые устаревают из-за изменений в языке.
Рекомендации РЕР 8 (https://www.python.org/dev/peps/pep-0008/) устанавливают стандарты для правил именования, использования пустых строк, отступов и пробе лов, максимальной длины строки, комментариев и т. д. Цель состоит в том, чтобы улучшить читаемость кода и сделать его единообразным между широким спектром программ на Python. Когда вы только начинаете программировать, то должны стремиться научиться и следовать принятым правилам до того, как укоренятся вредные привычки. Программный код в этой книге будет точно соответствовать рекомендациям РЕР 8, но из уважения к издательской индустрии я переопределил некоторые правила (например, за счет меньшего объема комментированного кода, меньшего числа пустых строк и более коротких литералов документирования).
Стандартизованные имена и процедуры особенно важны, когда вы работаете в кросс-функциональных группах. При переводе с языка ученых на язык инженеров многое может потеряться, как в 1999 г., когда инженеры потеряли климатический орбитальный спутник Марса, потому что разные группы разработчиков использо вали разные единицы измерения. В течение почти двух десятилетий я строил ком пьютерные модели Земли, которые трансформировались в инженерную функцию. Инженеры использовали мои скрипты для загрузки этих моделей в собственные программы. От проекта к проекту они делились этими скриптами между собой, тем самым повышая эффективность и помогая неопытным. Поскольку эти “командные файлы” были специально настроены под каждый проект, то по понятным причинам инженеры были не в восторге, когда во время обновлений моделей имена атрибу тов менялись. По сути дела, одним из их внутренних принципов было “Упрашивай, подкупай или запугивай— лишь бы твой разработчик моделей применял единооб разные имена свойств!”.
Проверка кода с помощью программы Pylint
Непременно следует ознакомиться с правилами РЕР 8, но вы все равно будете де лать ошибки, а сверка своего кода с рекомендацией будет серьезным препятствием. К счастью, такие программы, как Pylint, pycodestyle и Flake8 способны помочь вам
Генератор дурацких имен 31
легко следовать рекомендациям РЕР 8. В этом проекте вы будете использовать про грамму Pylint.
Установка программы Pylint
Pylint— это средство проверки исходного кода, наличия ошибок и качества для языка программирования Python. Для скачивания его бесплатной копии перейдите по ссылке https://www.pylint.Org/#install в разделе Install (Установить) найдите вашу платформу. Там будет отображена команда для установки программы Pylint. Так, в Windows перейдите в папку, содержащую вашу копию Python (например, C:\Python35), при нажатой клавише щелкните правой кнопкой мыши для того, чтобы открыть контекстное меню, и в нем выберите команду Открыть командное окно здесь или Открыть окно PowerShell здесь, в зависимости от ис пользуемой версии Windows. Выполните pip install pylint (pip3, если установле ны Python 2 и 3).
Выполнение программы Pylint
В Windows программа Pylint запускается из окна интерпретатора или же, в случае более новых систем, из интерактивной оболочки PowerShell (оба открываются при нажатой клавише и щелчке правой кнопкой мыши в папке, содержащей мо дуль Python, который вы хотите проверить). Для запуска программы наберите pylint имя_файла (рис. l.l). Расширение р у является необязательным, и ваш путь к каталогу будет отличаться от показанного. В macOS или другой системе на базе UNIX используйте эмулятор терминала.
C:\Python35\Python 3 Stuff>pylint pseudonyms.ру
Р ис. 1.1. Окно командной строки W indow s с командой для запуска программы Pylint
В окне интерпретатора будут показаны результаты работы программы Pylint. Ниже приведен пример полезного результата:
C:\Python35\Python 3 Stuff\Psych>pylint pseudonyms.ру No config file found, using default configuration ★ * ** * * ** * **** Module pseudonyms
C: 45, 0: No space allowed around keyword argument assignment
print(firstName, lastName, file = sys.stderr)
A (bad-whitespace) C: 1,0: Missing module docstring (missing-docstring) C: 2, 0: Multiple imports on one line (sys, random) (multiple-imports) C: 7, 0: Invalid constant name “first” (invalid-name) C: 23, 0: Invalid constant name “last” (invalid-name) C: 40, 4: Invalid constant name “firstName” (invalid-name)
32
Глава 1
С: 42, 4: Invalid constant паше “lastName” (invalid-name) С: 48, 4: Invalid constant name “try_again” (invalid-name)
Заглавная буква в начале каждой строки — это код сообщения. Например, с: 15, о относится к нарушению стандарта написания программного кода в строке 15 и столбце 0. В случае разнообразных кодов сообщений программы Pylint можно ссы латься на следующие ключи:
♦ r — реорганизация программного кода (рефакторинг); соответствует наруше
нию показателя ’’общепринятого на практике приема”;
♦ с — правило (конвенция); соответствует нарушению стандарта написания про
граммного кода;
♦ w— предупреждение; соответствует стилистическим проблемам или незначи
тельным проблемам в написании программного кода;
♦ е — ошибка; соответствует важным проблемам в написании программного кода
(т. е., скорее всего, дефектам);
♦ f — фатальная ошибка; соответствует ошибкам, которые препятствуют даль
нейшей обработке.
Pylint завершит свой отчет, оценив соответствие вашей программы рекомендациям РЕР 8. В данном конкретном случае ваш код получил отметку 4 из 10 возможных:
Global evaluation
Your code has been rated at 4.00/10 (previous run: 4.00/10, +0.00)
Обработка ложных ошибок константных имен
Возможно, вы заметили, что программа Pylint неправильно предполагает, что все имена переменных в глобальном пространстве относятся к константам и поэтому должны начинаться с заглавных букв. Этот недостаток можно обойти несколькими способами. Первый из них — разместить свой программный код в функции main () (листинг 1.2); благодаря этому он будет находиться вне глобального пространства.
Листинг 1.2. Определяет и вызывает функцию main о
def main():
некий вмененный код некий вмененный код некий вмененный код
О if name == “main”:
ф main ()
И м я _name_является специальной встроенной переменной, которую можно ис пользовать для оценивания того, как выполняется программа: в автономном режи ме либо как импортированный модуль; помните, что модуль— это просто про грамма на Python, используемая внутри другой программы на Python. Если вы
Генератор дурацких имен 33
выполняете программу напрямую, то переменная_name_имеет значение__ main__. В листинге 1.2 переменная_паше_используется для обеспечения того, чтобы при импортировании программы функция main () не выполнялась до тех пор, пока вы не вызовете ее намеренно, однако, когда вы выполняете программу напрямую, усло вие в инструкции if удовлетворено не будет ф и функция main о будет вызвана автоматически ф. Это правило не всегда вам потребуется. Например, если ваш программный код всего лишь определяет функцию, то вы можете загрузить его как модуль и вызвать ее без необходимости в переменной_паше__.
Давайте встроим все это в программу pseudonyms.ру под функцией maino, за ис ключением инструкции import, а затем вставим вызов функции main() под инст рукцией if, как в листинге 1.2. Соответствующие изменения можно внести само стоятельно либо скачать программу pseudonyms main.py с веб-сайта. Повторно вы полните программу Pylint. В окне интерпретатора вы должны получить следующие ниже результаты.
C:\Python35\Python 3 Stuff\Psych>pylint pseudonyms_main No config file found, using default configuration ************* Module pseudonyms_main
C: 47, 0: No space allowed around keyword argument assignment
print(firstName, lastName, file = sys.stderr)
A (bad-whitespace)
С: 1, 0: Missing module docstring (missing-docstring)
C: 2, 0: Multiple imports on one line (sys, random) (multiple-imports)
C: 4, 0: Missing function docstring (missing-docstring)
C: 42, 8: Invalid variable name “firstName” (invalid-name)
C: 44, 8: Invalid variable name “lastName” (invalid-name)
Теперь раздражающие комментарии о недопустимых именах констант исчезли, но вы еще не вышли из леса. Как бы мне это ни нравилось, но правила языка Python не ДОПуСКаЮТ “горбатых” имен, TaKJfX как firstName.
Конфигурирование программы Pylint
При вычислении небольших скриптов я предпочитаю использовать настройки по умолчанию программы Pylint и игнорировать ложные ошибки “имен констант”. Я также люблю запускать опцию -гп (аббревиатура для -reports=n) для подавления большого объема посторонней статистики, которую программа Pylint возвращает:
C:\Python35\Python 3 Stuff\Psych>pylint - m pseudonyms_main.ру
Обратите внимание, что использование -гп отключит параметр оценивания про граммного кода. Еще одна проблема с программой Pylint заключается в том, что ее максимальная длина строки по умолчанию составляет 100 символов, но РЕР 8 рекомендует 79 символов. В целях соблюдения рекомендаций РЕР 8 программу Pylint можно выполнить со следующей настройкой:
C:\Python35\Python 3 Stuff\Psych>pylint — xnax-line-length=79 pseudonyms_main
34
Глава 1
Теперь вы увидите, что выделение имен функции main () отступами привело к тому, что некоторые строки превысили пределы, установленные рекомендациями:
С: 12, 0: Line too long (80/79) (line-too-long)
С: 14, 0: Line too long (83/79) (line-too-long) –обрезано–
Наверняка вы не захотите конфигурировать программу Pylint всякий раз при ее вы полнении, и, к счастью, вам это не нужно делать. Вместо этого с помощью команды -generate-rcfile можно создать собственный конфигурационный файл. Например, для того чтобы подавить создание отчетов и установить максимальную длину стро ки, равной 79, в командной строке введите следующее:
имя вашего пути>pylint - m — max-line-length=79 — generate-rcfile >
name.pylintrc
Поместите необходимые изменения перед инструкцией -generate-rcfile > name, pylintrc и укажите свое имя перед расширением .pylintrc. Конфигурационный файл можно создать автономно, как только что было показано, либо одновременно с оцениванием программы Python. Файл .pylintrc автоматически сохраняется в те кущем рабочем каталоге, хотя есть возможность добавить каталожный путь (более подробную информацию см. на веб-страницах https://pylint.org и https://pylint. readthedocs.io/en/latest/user_guide/run.html).
Индивидуально настроенный конфигурационный файл можно применить повторно с помощью параметра —rcfile, за которым должны находиться имя вашего лично го файла конфигурации и имя оцениваемой программы. Например, для того чтобы выполнить конфигурацию myconfig.pylintrc на программе pseudonymsmain.py, введите следующее:
C:\Python35\Python 3 Stuff\Psych>pylint — rcfile myconfig.pylintrc
pseudonyms_main
Описание своего кода с помощью литералов документирования
Программа Pylint обнаруживает, что в программе pseudonyms main.py отсутствует литерал документирования. Согласно рекомендациям по написанию программного кода на языке Python РЕР 257 (https://www.python.org/dev/peps/pep-0257/), литерал документирования docstring — это символьный литерал, который появляется в ка честве первой инструкции в определении модуля, функции, класса или метода. Ли терал документирования представляет собой в основном краткое описание того, что делает ваш код, и может включать в себя конкретные аспекты программного кода, такие как необходимые входные данные. Здесь в тройных кавычках приведен пример однострочного литерала документирования для функции:
def circ(г):
“““Вернуть длину окружности радиусом г.”””
с = 2 * г * math.pi
return с
Генератор дурацких имен 35
Приведенный выше литерал документирования просто описывает то, что конкрет но функция делает, но литералы документирования могут быть длиннее и содер жать намного больше информации. Например, ниже приведен многострочный литерал документирования для той же функции, который показывает информацию о входе и выходе функции:
def circ(г):
“““Вернуть длину окружности радиусом г.
Аргументы:
г - радиус круга
Возвращает:
float: окружность круга
к п п
с = 2 * г * math.pi
return с
К сожалению, литералы документирования специфичны тем, что прочно привязаны к человеку, проекту и компании, и можно найти целый ряд конфликтующих реко мендаций. Компания Google имеет свой формат и превосходное стилевое руково дство. Некоторые члены научного сообщества используют стандарт литералов до кументирования библиотеки NumPy. А текстовый формат облегченной разметки RST (restructured Text) используется в основном в сочетании с инструментом Sphinx, который применяет литералы документирования с целью создания документации для проектов на Python в файлах таких форматов, как HTML и PDF. Если вы когда- нибудь читали документацию (https://readthedocs.org/) по какому-нибудь модулю Python, то видели Sphinx в действии. Ссылки на руководства для некоторых из этих стилей можно найти в разд. ‘‘Дальнейшее чтение ” далее в этой главе.
С помощью бесплатного инструмента pydocstyle можно проверить, насколько хо рошо ваша документация соответствует рекомендациям РЕР 257. Для того чтобы установить его в Windows или любой другой операционной системе, откройте окно интерпретатора и выполните команду pip install pydocstyle (используйте pip3, если установлены Python 2 и 3).
Для выполнения pydocstyle откройте окно интерпретатора в папке, содержащей код, который вы хотите проверить. Если не указать имя файла, то pydocstyle будет работать на всех находящихся в папке программах Python и предоставит отклик:
C:\Python35\Python 3 Stuff\Psych>pydocstyle .\OLD_pseudonyms_main.py:1 at module level:
D100: Missing docstring in public module
.\OLD_pseudonyms_main.py:4 in public function ‘main’: D103: Missing docstring in public function
.\ pseudonyms.p y :1 at module level:
D100: Missing docstring in public module
Л pseudonyms_main_broken.py:1 at module level:
D200: One-line docstring should fit on one line with quotes (found 2)
36
Глава 1
Л pseudonyms_main_broken.py:6 in public function ‘main’:
D205: 1 blank line required between summary line and description
(found 0)
Если же указать файл, в котором нет проблем с литералом документирования, то pydocstyle ничего не вернет:
C:\Python35\Python 3 Stuff’\Psych>pydocstyle pseudonyms_main_f ixed. ру
C:\Python35\Python 3 Stuff\Psych>
Во всех проектах этой книги я буду использовать довольно простые литералы документирования, с тем чтобы уменьшить визуальный шум в аннотированном программном коде. Если вы захотите попрактиковаться, можете свободно их рас ширить. Свои результаты всегда можно проверить с помощью инструмента pydocstyle.
Проверка стиля программного кода
Когда я был еще подростком, мой дядя ездил в большой город из нашего сельского городка делать себе ’‘стильную” стрижку. Я никогда не понимал, чем она отличает ся от обычной стрижки, но зато я знаю, как “стилизовать” наш программный код генератора забавных имен так, чтобы он соответствовал рекомендациям РЕР 8 и РЕР 257.
Сделайте копию программы pseudonyms main.py под названием pseudonyms_ main fixed.py и сразу же оцените ее с помощью программы Pylint, используя вот эту команду:
ваш_путь>pylint — max-line-length=79 pseudonyms_main_fixed
He подавляйте отчет с помощью опции -гп. В нижней части окна интерпретатора вы должны увидеть вот этот результат:
Global evaluation
Your code has been rated at 3.33/10
Теперь исправьте программный код на основе результата работы программы Pylint. В следующем ниже примере исправления выделены жирным шрифтом. Я внес измене ния в кортежи с именами, исправив проблемы с длиной строк. Исправленный про граммный код pseudonyms main fixed.py можно также скачать с веб-страницы кни ги по адресу https://www.nostarch.com/impracticalpython/.
Файл pseudonym s_m ain_fixed.py
“““Сгенерировать забавные имена, случайно комбинируя имена
из 2 отдельных списков.”””
import sys import random
Генератор дурацких имен 37
def mai n():
“““Случайно выбрать имена из 2 кортежей имен и напечатать на экране.”””
print(“Добро пожаловать в ’Подбор имен для напарника.’ как в сериале
’Ясновидец’\п”)
print(“Имя, наподобие того, которое Шин подбирал для Гаса:\п\п”)
first = (‘Baby Oil’, ‘Bad News’, ‘Big Burps’, ’’Bill ’Beenie-Weenie’”,
“Bob f Stinkbug’”, ‘Bowel Noises1, ‘Boxelder’, “Bud ‘Lite’”, ’Butterbean’, ‘Buttermilk’, ’Buttocks’, ’Chad’, ‘Chesterfield’, ‘Chewy’, ’Chigger’, *Cinnabuns’, ‘Cleet’, ’ Combread ’, ’ Crab Meat1 , ’ Crapps ’ , ’ Dark Skies ’ , ‘Dennis Clawhammer’, ‘Dicman’, ‘Elphonso’, ’Fancypants’, ‘Figgs’, ‘Foncy’, ‘Gootsy’, ’Greasy Jim’, ‘Huckleberry’, ‘Huggy’, ‘Ignatious’, ‘Jinibo’, “Joe ’Pottin Soil’”, ’Johnny’, ‘Lemongrass’, 1Lil Debil’, ‘Longbranch’, ‘“Lunch Money”’,
‘Mergatroid’, ’ “Mr Peabody” ‘, ’Oil-Сап’, ’Oinks’, ’Old Scratch’, ’Ovaltine’, ‘Pennywhistle’, ‘Pitchfork Ben’, ’Potato Bug’, ’Pushmeet’, ’Rock Candyf, ‘Schlomo1, ‘Scratchensniff’, ‘Scut’, “Sid ‘The Squirts’”, ‘Skidmark’, ‘Slaps’, ’Snakes’, ’Snoobs’, ’Snorki’, ’Soupcan Sam’, ‘Spitzitout’, ‘Squids’, ’Stinky’, ‘Storyboard’, ‘Sweet Tea’, ’TeeTee’, ’Wheezy Joe’, “Winston ‘Jazz Hands’”, ‘Worms’)
last= (‘Appleyard’, ‘Bigmeat’ , ’ Bloominshine’ , ’ Boogerbottom’ ,
‘Breedslovetrout’, ‘Butterbaugh’, ’Clovenhoof’, ’Clutterbuck’, ‘Cocktoasten’, ’Endicott’, ‘Fewhairs’, ‘Gooberdapple’, ‘Goodensmith’, ‘Goodpasture’, ’Guster’, ‘Henderson’, ’Hooperbag’, ‘Hoosenater’, ’Hootkins’, ‘Jefferson’, ‘Jenkins’, ’Jingley-Schmidt’, ‘Johnson’, ‘Kingfish’, ‘Listenbee’, “M’Bembo”, ‘McFadden’, ’Moonshine’, ‘Nettles’, ‘Noseworthy’, ‘Olivetti’, ’Outerbridge’, ‘Overpeck’, ‘Overturf’, ‘Oxhandler’, ’Pealike’, ’Pennywhistle’, ‘Peterson’, ’Pieplow’, ’Pinkerton’, ’Porkins’, ‘Putney’, ’Quakenbush’, ‘Rainwater’, ’Rosenthal’, ’ Rubbins’, ‘Sackrider’, ‘Snuggleshine’, ‘S p l e m ‘, ‘Stevens’, ‘Stroganoff’, ’Sugar-Gold’, ‘Swackhamer’, ’Tippins’, ’Tumipseed’, ‘Vinaigrette’, ’Walkingstick’, ‘Wallbanger ’, ‘Weewax’, ’Weiners’, ‘Whipkey’, ’Wigglesworth’, ’ Wimple sna tch’ , ’Winterkom’, ’Woolysocks ’)
while True:
first_name = random.choice(first)
last_name = random.choice(last)
print("\n\n”)
хитростью заставить IDLE использовать конфигурацию #
для “фатальной ошибки” с целью распечатки имени в красном цвете, #
print(”{> {>” .format(first_name, last_name), file=sys.stderr)
print("\n\n”)
38
Глава 1
try_again = input("\п\пПопробуете еще? (Нажмите Enter либо п,
чтобы выйти)\п “)
if try_again.lower() == “n”:
break
input(11 ХпНажмите Enter для завершения работы.”)
if name == “main” :
m a i n ()
Пересмотренный программный код получит от программы Pylint оценку 10 из 10:
Global evaluation
Your code has been rated at 10.00/10 (previous run: 3.33/10, +6.67)
Как вы видели в предыдущем разделе, выполнение инструмента pydocstyle на про грамме pseudonyms main flxed.py не дает никаких ошибок, но не обманывайте се бя: это не означает, что программа является хорошей или даже адекватной. Напри мер, вот этот литерал документирования также проходит:
“““ksjkdls lskjds kjs jdi wllk sijkljs dsdw noiu sss.”””
Писать разрозненные, краткие и действительно полезные литералы документиро вания и комментарии довольно трудно. Рекомендации РЕР 257 помогут с литера лами документирования, но комментарии имеют более свободный стиль и “откры тый диапазон”. Слишком много комментариев создают визуальный шум, они могут отталкивать пользователя своим видом, в них нет необходимости, и так же, как хорошо написанный программный код, в значительной степени являются самодо- кументирующимися. Вескими причинами для добавления комментариев может быть уточнение намерений и устранение потенциальных ошибок пользователей, например когда требуются конкретные единицы измерения или форматы входных данных. При поиске правильного баланса в комментариях обратите внимание на хорошие примеры, когда вы с ними сталкиваетесь. Кроме того, подумайте о том, что конкретно вы хотели бы увидеть, доведись вам открыть собственный код после пятилетнего перерыва!
Инструменты Pylint и pydocstyle просты в установке, легки в запуске и помогут вам усвоить и соблюдать стандарты написания программного кода, принятые в сообще стве Python. Просеивание программного кода через программу Pylint до его публи кации на веб-форумах также является хорошим практическим приемом, в осо бенности когда вы ищете помощь и ожидаете получить “более добрые и мягкие” ответы!
Резюме
Теперь вы должны знать, как писать код и документировать его, чтобы его коммен тарии соответствовали ожиданиям сообщества языка Python. А еще (и это важнее) вы создали несколько по-серьезному смешных имен для напарника, гангстера, ин форматора и кого угодно.
Генератор дурацких имен 39
Вот несколько моих любимых:
Pitchfork Ben Pennywhistle Chewy Stroganoff Spitzitout Winterkorn
‘Bad News’ Bloominshine ’Sweet Tea’ Tippins Wheezy Joe Jenkins
‘Big Burps’ Rosenthal
Soupcan Sam Putney
Bill ‘Beenie-Weenie’ Clutterbuck Greasy Jim Wigglesworth Dark Skies Jingley-Schmidt
Chesterfield Walkingstick Jimbo Woolysocks
Potato Bug Quakenbush Worms Endicott Cleet Weiners
Ignatious Outerbridge
Дальнейшее чтение
Fancypants Pinkerton Dicman Overpeck
Buttocks Rubbins
Для получения интерактивной версии этих ресурсов посетите веб-сайт книги по адресу https://www.nostarch.com/impracticaIpython/.
Псевдокод
Описания некоторых довольно формальных стандартов псевдокода можно найти по адресам http://users.csc.calpoly.edu/~jdalbey/SWE/pdl_std.html и http://www. slideshare.net/sabiksabz/pseudo-code-basics/.
Стилевые руководства
Вот список стилевых руководств, к которым вы можете обращаться при создании программ на языке Python.
♦ Рекомендации РЕР 8 по написанию программного кода на языке Python можно
найти по адресу https://www.python.org/dev/peps/pep-0008/.
♦ Рекомендации РЕР 257 по написанию программного кода на языке Python мож
но найти по адресу https://www.python.org/dev/peps/pep-0257/.
♦ Google имеет собственное руководство по форматированию и стилю, которое
размещено по адресу https://google.github.io/styleguide/pyguide.html.
♦ Примеры стиля Google можно найти по адресу
https://sphinxcontrib-napoleon.readthedocs.io/en/latest/example_google.html.
♦ Стандарты библиотеки NumPy по написанию литералов документирования нахо
дятся по адресу https://numpydoc.readthedocs.io/en/latest/.
♦ Примеры литералов документирования библиотеки NumPy можно найти по адре су https://sphinxcontrib-napoleon.readthedocs.io/en/latest/example_numpy.html.
♦ О формате RST (restructured Text) можно узнать по адресам
https://docs.python.org/devguide/documenting.html, https://d0cs.pyth0n.0rg/3.l/d 0cumenting/rest.html и https://wiki.python.org/moin/reStructuredText/.
40
Глава 1
♦ Руководство автостопщика по Python (The Hitchhiker’s Guide
to Python. http://docs.python-guide.org/en/latest/writing/style/) содержит раздел о стилях программного кода и инструменте autopep8, который автоматически переформа тирует код согласно рекомендаций РЕР 8 (в какой-то степени).
♦ Книга Бретта Слаткина “Эффективный Python” (Slatkin В. Effective Python. Addison-Wesley, 2015) содержит полезный раздел по документированию про грамм.
Сторонние модули
Ниже приведено несколько ресурсов для использования сторонних модулей.
♦ Подробная информация о программе Pylint находится по адресу
https://docs.pylint.Org/en/l.8/tutorial.html.
♦ Подробную информацию об инструменте pydocstyle можно найти по адресу
http://www.pydocstyle.org/en/latest/.
Практические проекты
Попробуйте приведенные ниже проекты для работы с символьными цепочками. Мои собственные решения доступны в приложении к книге.
“Поросячья” латынь
Для того чтобы сформировать ’’поросячью” латынь, берут английское слово, кото рое начинается с согласной, эту согласную перемещают в конец, а затем в конец слова добавляют ”ау”. Если слово начинается с гласной, то в конец слова добавля ют ’’way”. Одной из самых известных фраз на ’’поросячьей” латыни всех времен является ’’ixnay on the ottenray”2, произнесенная Марти Фельдманом (Marty Feldman) в комедийном шедевре Мела Брукса (Mel Brooks) “Молодой Франкен штейн” (Young Frankenstein).
Напишите программу, которая на входе принимает слово и использует индексацию и операцию среза для возврата своего эквивалента ’’поросячьей” латыни. Примени те инструменты Pylint и pydocstyle к своему коду и исправьте все стилевые ошибки. Решение задачи можно найти в приложении к книге либо скачать программу pig_latin_practice.py с веб-сайта https://www.nostarch.com/impracticalpython/.
Столбчатый график бедняка
Шесть наиболее часто используемых в английском языке букв можно запомнить с помощью мнемоники ’’etaoin” (произносится eh-tay-oh-in). Напишите скрипт на Python, который на входе принимает предложение (символьную цепочку) и воз
2 Соответствует словосочетанию nix on the rotten. — Прим. перев.
Генератор дурацких имен 41
вращает результат в виде простого столбчатого графика, как показано на рис. 1.2. Подсказка: я использовал структуру данных “словарь” и два модуля, которые я еще не рассматривал: pprint И collections/defaultdict.
■A Python 3.5.2 Shell File Edit Shell Debug Options Window Help
You may need t o s t r e t c h c o n s o l e window i f t e x t wra ppi ng o c c u r s .
□
X
—
t e x t * Like the castle in its coiner in a medieval game, 1 foresee terrible trouble and I stay here just the same.
d e f a u l t d i c t ( #
‘a’ J/
’ e 1,
’ e ’ , * e ’ ,
’ e ’ ,
’ e ’ ,
’ e
,
’ b ’ ] , ’ c ’ ] , ’ d ’ ] , ’ e ‘,
‘Ь’ : [ ‘b* , ’ с ’ : [ ‘c* , • d ’ : I ’ d* , [ ’ e ’ » ’ е ’ : • f ’ : Г’ f • ] , • g ’ : ( ’ g ’ ] ,
’ e ’ ,
lr
• j *: С1 j • ), [ ’ k ’ 1, • k • :
’ m ’ :
{ ’m’ ,
’ m ’ ,
’m’ u
),
[ • s ’ • [ ’ t ’ »
’ r ’ : ’ s ’ : ’ t ‘: ‘u 1 : »у» ; • yt . I’y’ 1 )>
’ s ’ / ’ t
- s ’ , • t ’ ,
’ s ’ , ’ t ’ ,
’ s ’ , ’ t ’ ,
’ s ’ ] , ’ t ’ ,
’ t ’ ,
’ t ’ ] ,
,
[ ’ v ’ ] ,
________________________________________________________________________________________________________________________________________________ Ln: 23 Cot: 34
Р и с. 1.2. Р езультат программы E TA O IN _p ractice.p y из приложения к книге в виде столбчатого граф ика
Сложные проекты
Для сложных проектов не предусмотрено никаких решений. Ожидается, что вы справитесь с ними самостоятельно!
Столбчатый график бедного чужака
Примените онлайновый переводчик для того, чтобы изменить свой текст на другую систему латинского письма (например, испанскую или французскую), повторно выполните свой код из проекта о столбчатом графике бедняка и сравните результа ты. Например, испанская версия текста из рис. 1.2 дает результаты на рис. 1.3.
В испанском предложении в два раза больше букв L и в три раза больше букв U. Для того чтобы сделать столбчатые графики прямо сопоставимыми для различных входных данных, измените код так, чтобы каждая буква алфавита имела ключ и выводилась на экран, даже если нет значений.
Среднее имя
Перепишите код генератора забавных имен так, чтобы включить средние имена (отчества). Сначала создайте новый кортеж middie name, затем разделите сущест-
42
Глава 1
i
Python 3.5.2 Shell
File Edit Shell Debug Options Window Help You may need t o s t r e t c h c o n s o l e window i f
t e x t wrapping о с с и г з .
□
X
t e x t = A1 aq ui
l o mismo.
i g u a l que e l
c a s t i l l o «n l a
e q u in a en un j u e g o m e d i e v a l , p t e v e o t e r r i b l e s p r o b l e m s у me qu*»do
d e f a u l t d i c t ( C c l a s s
l i s t *>,
’ a *t ’ a t • ь ч ,
I ’ a ’ : I ’ a ’ , [ ’ b ’ , [ • c ’ l ,
*b’ : ’ c * :
’ a ’ ,
’ a ’ •
’ a ’ ,
’ a ’ ,
’ a ’ 1,
’ e ‘’t
! * e ’ ,
’ e ’ f
’ e ’ ,
’ e ’ ,
’ e ’ ,
’ e ’ ,
’ e ’ ,
’ e ’ ,
’ e ’ ,
’ e ’ r
’ e * J ,
- e’, ’ g ’ 1 #
[ ’ e •, t ‘g ’ »
- e *: ’g ’ : ’ i 1 : I t • j ’ l ,
- j *:
( ’ i
• l * i ’ m ’ : [ ’m ’ , ’ n 1 : I ’ n ’ , ’ o ’ : [ ’ p ’ : I ‘P’ * ’ P ’ b ‘q ’ : I ’ 4 ’ r ’ r ’ : [ [ ’ s ’ : ’ t • : [ ’ t ‘, ’ t ’ ] , ’ u ’ : { ’ u ’ , ’ u ’ , ’ v ’ : I ’ V ’ , V ) , I * у * 1 } ) ■у’:
’ l ’ r •1'1, * 1 ’ , ’m’’i ‘ra’, ’ m ‘, ’ n ’’ f ’ n ’ , ’ n * ] /
’ 1 * ,
• 1 » ,
’ I ’ , *1»,
’ I ’ ] #
‘o ’ ] f
•1» , ’m’ b
],
’ u ’’ /
’ u ’ ,
‘u ’ #
‘U’ !
’ u 4 ,
» 1
Ln: 31 Col 4
Р и с. 1.3. Результаты выполнения программы E A TO IN _challenge.py перевода текста из рис. 1.2 на испанский язык
вующие пары “имя— отчество” (например, " Joe ’Pottin Soil ’” или " Sid The Squirts’”) и добавьте их в кортеж. Вы также должны переместить в кортеж middie name некоторые очевидные прозвища (например, ’’Oil Сап”). Наконец, до бавьте несколько новых средних имен (таких, как ’’The Big News”, или ’’Grunts”, или ’’Tinkie Winkle”). Примените модуль random языка Python для того, чтобы среднее имя выбиралось только в половине или трети случаев.
Что-то совершенно другое
Создайте собственный список смешных имен и добавьте его в генератор смешных имен. Подсказка: титры к кинофильмам являются богатым полем для поиска!
2
ПОИСК ПАЛИНГРАММНЫХ ЗАКЛИНАНИЙ
Радар. Заказ. Ротатор. Комок. Kayak. Sexes. Что общего у всех этих слов? Это палиндромы (перевертыши) — слова, которые читаются одинаково слева направо и справа налево. Палииграммы еще лучше1. Это целые фразы, которые ведут себя таким же образом. Наполеон является автором самой известной палинграммы. Когда он впервые увидел Эльбу, остров своего изгнания, он изрек: “Able was I ere I saw Elba”, что примерно можно перевести на русский, как “Я был в состоянии, прежде чем увидел Эльбу”.
В 2011 г. издательство DC Comics опубликовало интересную историю, которая подняла использование палинграмм на новый интеллектуальный уровень. Суперге роиня, волшебница Затанна, была проклята и в результате могла произносить за клинания, только говоря палиндромами. Ей удалось придумать ровно столько сло восочетаний из двух слов, таких как ’’nurses run, stack cats и puff up” (медсестры бе гают, складывают кошек и раздуваются), что этого хватило для того, чтобы победить своего меченосного противника. Это заставило меня задаться вопросом: а сколько всего существует ’’боевых” палинграмм? И есть ли среди них для Затанны получше?
В этой главе вы скачаете файлы словарей из Интернета и примените Python для об наружения в них сначала палиндромов, а затем более сложных палинграмм. Затем
1 Палинграмма (palingram), или сложный палиндром, — это предложение, в котором буквы, слоги или слова читаются одинакового как слева направо, так и справа налево. Например, “a dog, a plan, a canal: pagoda”. См. https://www.sandipn.com .np/2017/05/collection-of-palingram -sentence.htm l.— Прим. перев.
44
Глава 2
вы примените инструмент под названием cProfiie для анализа программного кода генерирования палинграмм с целью сделать его производительнее. Наконец, вы просеете палинграммы через решето для того, чтобы посмотреть, сколько из них имеют “агрессивную” природу.
Поиски и открытие словаря
Все проекты этой главы требуют списков слов в формате текстового файла, обычно именуемого файлом словаря, поэтому давайте начнем с того, что узнаем, как загру зить один из них.
Несмотря на свое название, файлы словарей содержат только слова — без произ ношения, числа слогов, без определений и т. д. Эта новость радует, т. к. на нашем пути возникали бы всяческие сложности. Но что еще лучше, файлы словарей дос тупны онлайн бесплатно.
Подходящие файлы словарей можно найти в местах, перечисленных в табл. 2.1. Скачайте один из файлов или, если он открывается напрямую, скопируйте и вставьте содержимое в текстовый редактор, например Notepad или WordPad (TextEdit в macOS), и запишите его в файл с расширением .txt. Сохраните словарь в той же папке, что и код Python. Для подготовки этого проекта я использовал файл 2of4brif.txt. Его можно найти в скачиваемом архиве 12dicts-6.0.2.zip на веб-сайте, приведенном первым в табл. 2.1.
Таблица 2.1. Скачиваемые файлы словарей
Файл
http://wordlist.aspell.net/12dicts/
https://inventwithpython.com/dictionary.txt
http://www-personal.umich.edu/~jlawler/wordlist.html
http://greenteapress.com/thinkpython2/code/words.txt
Число слов
60 388
45 ООО
69 903
113 809
В дополнение к файлам в табл. 2.1 операционные системы UNIX и UNIX-подобные системы поставляются в комплекте с большим файлом слов с разделением строк символом новой строки. В нем более чем 200 тыс. слов. Он обычно хранится в пап ке /usr/dict/words. В операционной системе Debian GNU/Linux списки слов находятся в папке /usr/share/opendict/dictionaries.
/usr/share/dict/words или
Словари macOS обычно находятся в папке /Library/Dictionaries, и туда включены неанглийские словари. В случае, если вы захотите использовать один из этих фай лов, то вам, возможно, придется выполнить поиск в Интернете с запросом по вашей операционной системе и соответствующей версии для того, чтобы найти точный путь к каталогу.
Некоторые файлы словарей исключают буквы а и I как слова. Другие могут вклю чать каждую букву в словаре как однословный “заголовок” (например, d в начале
слов, начинающихся с d). В следующих ниже проектах однобуквенные палиндромы будут проигнорированы, поэтому подобного рода проблемы не должны вызывать затруднений.
Поиск палинграммных заклинаний 45
Обработка исключений при открытии файлов
Всякий раз, когда вы загружаете внешний файл, ваша программа должна автомати чески выполнять проверку на наличие проблем ввода-вывода, таких как отсутст вующие файлы или неправильные имена файлов, и сообщить вам об этих про блемах.
Используйте следующие ниже инструкции try и except для перехвата и обработки исключений, т. е. ошибок, обнаруживаемых во время исполнения: О try:
0 with open(file) as in_file: сделать что - то
except IOError® as e:
О print (11 {} ХпОшибка при открытии {}. Завершение программы.11.
format(е, file), file=sys.stderr)
0 sys.exit(1)
Сначала исполняется раздел try ф . Инструкция with автоматически закроет файл после вложенного блока кода, независимо от того, как блок завершает работу 0 . Закрытие файлов перед завершением процесса является хорошим практическим решением. Если вы не закроете эти файлы, то можете исчерпать файловые деск рипторы (в основном эта проблема характерна для крупных скриптов, которые ра ботают в течение длительного времени), заблокировать файл от дальнейшего дос тупа в Windows, повредить файлы либо потерять данные, если вы пишете в файл.
Если что-то идет не так и тип ошибки соответствует имени исключения после клю чевого слова except 0, то остальная часть раздела try пропускается, и исполняется раздел except о Если все проходит нормально, то исполняется раздел try, а пред ложение except пропускается. Инструкция print в разделе except позволяет узнать о наличии проблемы, а аргумент fiie=sys. stderr окрашивает инструкцию error в красный цвет в окне интерпретатора IDLE.
Инструкция sys. exit (1) е используется для завершения работы программы. Еди ница в sys.exit (1) указывает на то, что программа испытала ошибку и не закры лась успешно.
Если возникает исключение, которое не соответствует именованному исключе нию в разделе except, то оно передается наверх любым внешним разделам try или исполнению главной программы. Если обработчик не найден, то необработанное исключение вызывает остановку программы со стандартным сообщением об ошиб ке в отчете об обратной трассировке (“traceback”).
46
Глава 2
Загрузка файла словаря
Листинг 2.1 загружает файл словаря в виде списка. Введите этот скрипт вручную либо скачайте его в форме файла loaddictionary.py с веб-сайта https://nostarch.com/impracticaIpython/. Этот файл можно импортировать в другие программы в виде модуля и запустить его с помощью однострочной инструкции. Помните, что модуль — это просто про грамма Python, которая может быть использована в другой программе Python. Как вы, вероятно, знаете, модули представляют собой форму абстракции. Абстракция означает, что вам не нужно беспокоиться обо всех деталях написания программно го кода. Принцип абстракции состоит в инкапсуляции, акте сокрытия деталей. Мы инкапсулируем программный код загрузки файлов в модуль, поэтому в другой про грамме вам не нужно видеть или беспокоиться о деталях его программного кода.
Листинг 2.1. Модуль загрузки файла словаря в виде списка. Файл load_dictionary.py
“““Загрузить текстовый файл как список.
Аргументы:
имя текстового файла (и каталожный путь, если необходимо) Исключения:
IOError, если имя файла не найдено. Возвращает:
список всех слов в текстовом файле в нижнем регистре.
Требует - import sys
О import sys
ф def load(file):
“““Открыть текстовый файл и вернуть список цепочек символов в нижнем регистре.“1111 try:
with open(file) as in_file: 6 loaded_txt = in_file.read().strip( ) . split( ’ \n’) О loaded_txt = [x.lower() for x in loaded_txt]
return loaded_txt
except IOError as e:
ф print(11 {}ХпОшибка при открытии {}. Завершение программы.”,
format(е, file), file=sys.stderr)
sys.exit(1)
После литерала документирования мы импортируем системные функции с по мощью модуля sys для того, чтобы заработал ваш программный код обработки ошибок ф. Следующий блок кода определяет функцию, опираясь на предыдущее обсуждение темы открытия файлов ф. Указанная функция в качестве аргумента принимает имя файла.
Поиск палинграммных заклинаний 47
Если исключения не возникают, то в текстовом файле удаляются пробелы, а его элементы разбиваются на отдельные строки и добавляются в список 0 . Вы хотите, чтобы каждое слово стало отдельным элементом в списке перед тем, как этот спи сок будет возвращен. И поскольку для языка Python регистр букв имеет значение, слова в списке конвертируются в нижний регистр посредством операции включе ния в список 0 Включение в список — это сокращенный способ преобразования списка или другой итерируемой последовательности в другой список. В этом слу чае указанная операция заменяет цикл for.
При обнаружении ошибки ввода-вывода программа выводит на экран стандартное сообщение об ошибке, обозначаемое спецификатором е, а также сообщение, опи сывающее событие и информирующее пользователя об окончании работы 0. Команда sys.exit (1) завершает работу программы.
Данный пример кода предназначен для иллюстративных целей, с тем что показать, как все эти шаги работают вместе. Как правило, инструкция sys.exit о не будет вызываться из модуля, т. к. вы можете захотеть, чтобы ваша программа сделала что-то еще до завершения, например написала в журнальный файл. В последующих главах для ясности и контроля мы переместим оба раздела try-except и команду sys . exit () В функцию main ().
Проект 2: поиск палиндромов
Вы начнете с отыскания однословных палиндромов в словаре, а затем перейдете к более сложным палиндромным словосочетаниям.
Ц е л ь
Применить язык Python для отыскания палиндромов в файле словаря английского языка.
тшжттть * ■
Стратегия и псевдокод
Прежде чем заняться программным кодом, сделайте шаг назад и подумайте о том, что вы хотите сделать концептуально. Идентифицировать палиндромы легко: надо лишь сравнить слово с ним сами, только пройденном в обратном порядке. Ниже приведен пример прохождения по слову, начиная с первой буквы до последней и затем начиная с последней буквы до первой:
» > word = ‘NURSES’
word[:] ‘NURSES’ » > word[: : -1] ‘SESRUN’
Если во время прохождения по символьной цепочке (или любого другого индекси руемого типа) значения не указаны, то по умолчанию используется начало цепочки, конец цепочки и положительный шаг, равный 1.
48
Глава 2
На рис. 2.1 показан процесс обратного прохождения. Я предоставил начальную по зицию 2 и шаг -1. Поскольку конечный индекс не предоставлен (между двоеточия ми нет индекса или пробела), то подразумевается, что нужно возвращаться назад (т. к. шаг индекса равен -1) до тех пор, пока больше не останется символов.
Конец
Начало — ч слова s — Шаг
( word [2 :: -1]
♦
R U N ^ —————————- 3 0
2
1
4
5
6 Конечные точки
0
1
2
3
4
5
Значения позиций
N и
R S
Е
S
-6
-5
-4
-3
-2
-1
Значения позиций
-6
-5
-4
-3
-2
-1
Конечные точки
Р и с. 2.1. Пример прохождения в обратном порядке для w o rd =
’ N U R S E S ’
Прохождение в обратном порядке не ведет себя точно так же, как прямое прохож дение, а положительные и отрицательные значения позиций и конечные точки являются асимметричными. Это может привести к путанице, поэтому давайте огра ничимся нашим прохождением в обратном порядке в простом формате [: :-1].
Отыскание палиндромов в словаре займет меньше строк кода, чем загрузка файла словаря! Вот псевдокод:
Загрузить файл цифрового словаря в виде списка слов Создать пустой список для хранения палиндромов Перебрать каждое слово в списке слов:
Если слово, пройденное прямо, совпадает со словом, пройденным обратно:
Добавить слово в список палиндромов
Напечатать список палиндромов
Код отыскания палиндромов
Листинг 2.2 считывает файл словаря английского языка, определяет, какие слова являются палиндромами, сохраняет их в списке и распечатывает список одним махом как стопку элементов. Этот код можно скачать с веб-страницы книги по ад ресу https://www.nostarch.com/impracticaIpython/. Вам также понадобится файл loaddictionary.py и файл словаря; сохраните все три файла в одной папке.
Поиск палинграммных заклинаний 49
Листинг 2.2. Отыскание палиндромов в загруженном файле словаря. Файл palindrom es.ру
“““Отыскивает палиндромы (буквенные палинграммы) в файле словаря.”””
О import load_dictionary ф word_list = load_dictionary.load(’2of4brif.txt’) ф pali_list = []
О for word in word_list:
if len(word) > 1 and word == word[::-l]:
pali_list.append(word)
print("\пЧисло обнаруженных палиндромов = {}\n”.
format(len(pali_list)))
ф print(*pali_list, sep=’\n’)
Начните с импорта файла load dictionary.py как модуля ф. Обратите внимание, что расширение .ру при импорте не используется. Кроме того, указанный модуль нахо дится в той же папке, что и этот скрипт, поэтому указывать путь к каталогу модуля не нужно. И поскольку модуль содержит необходимую строку import sys, вам не нужно ее здесь повторять.
Для того чтобы заполнить ваш список словами из словаря, вызовите функцию load о из модуля load dictionary с помощью точечной формы записи ф. Передай те ей имя внешнего файла словаря. Опять же, вам не нужно указывать путь, если файл словаря находится в той же папке, что и скрипт Python. Имя используемого вами файла может отличаться в зависимости от скачанного словаря. Далее создайте пустой список для хранения палиндромов ф и начните перебирать слова в списке word iist ф, сравнивая прямой фрагмент с обратным фрагментом. Если два фрагмента идентичны, то добавьте слово в список paii iist. Обратите внимание, что допускаются только те слова, которые имеют более одной буквы (len (word) > 1), что соответствует самому строгому определению палиндрома. Наконец, напечатайте палиндромы привлекательным способом— стопкой и без кавычек или запятых ф. Это можно сделать, циклически перебирая каждое слово в списке, но для этого существует способ поэффективнее. Вы можете применить унарный оператор, обозначаемый звездочкой *2, который на входе принимает спи сок и в вызове функции разворачивает его в позиционные аргументы. Последний аргумент является разделителем, используемым между несколькими значениями списка для печати. Разделителем по умолчанию является пробел (sep=’ ‘), но вме сто этого мы печатаем каждый элемент в новой строке (sep=’ \n’).
Однословные палиндромы встречаются редко, по крайней мере, в английском язы ке. Используя файл словаря из 60 тыс. слов, вам повезет найти около 60, или всего
2 В английской терминологии такой оператор именуется splat (шлеп, плюх) operator, т. е. такой, кото рый выполняет сложную операцию одним махом. — Прим. перев.
50
Глава 2
0,1% всех слов. Однако, несмотря на их редкость, с помощью языка Python их дос таточно легко отыскать. Итак, давайте перейдем к более интересным и более слож ным вещам — палинграммам.
Проект 3: поиск палинграмм
Отыскание палинграмм требует немного больше усилий, чем поиск однословных палиндромов. В этом разделе мы спланируем и напишем программный код для по иска палинграмм словарных пар.
Ц е л ь
Применить язык Python для отыскания двухсловных палинграмм в словаре английско го языка. Проанализировать и оптимизировать программный код генерирования па линграмм с помощью инструмента cProfile.
Стратегия и псевдокод
Примером палинграммы словарных пар является nurses run (медсестры бегут) и stir grits (мешают крупу)3. (Если вам интересно, то g rits— это блюдо для завтрака из молотой кукурузы, похожее на итальянскую поленту.)
Как и палиндромы, палинграммы читаются одинаково слева направо, и наоборот. Мне нравится думать о них как о стержневых словах, таких как nurses, из которых выводятся палиндромная последовательность и перевернутое слово (рис. 2.2).
“Стержневое” слово г
NURSES RUN 1 L- r - J 1
|
Вторая часть — это Первая часть — это палиндромная перевернутое слово последовательность
“Стержневое” слово 1
STIR GRITS ,1 4 1
Первая часть - это палиндромная
Вторая часть - это перевернутое слово
последовательность
Р и с. 2.2. Р ассечение палинграмм словарных пар
Наша программа будет экзаменовать стержневое слово. Основываясь на рис. 2.2, о стержневом слове можно сделать следующие выводы:
Оно может иметь четное либо нечетное число букв.
Одна смежная часть слова буквально содержит реальное слово, когда читается
назад.
3 Примеры палинграмм на русском языке: ежу хуже и лев осовел. — Прим. перев.
Поиск палинграммных заклинаний 51
Эта смежная часть может занимать часть или все стержневое слово целиком.
Другая смежная часть содержит палиндромную последовательность букв.
Палиндромная последовательность может занимать часть или все стержневое
слово целиком.
- Палиндромная последовательность не обязательно должна быть реальным сло
вом (если только она не занимает все слово целиком).
Эти две части не могут перекрываться либо использовать буквы совместно.
Последовательность обратима.
ПРИМЕЧАНИЕ Если перевернутое слово занимает все стержневое слово и не является палиндро мом, то оно называется семорднилапом (semordnilap). Семорднилап похож на палин дром, за исключением одного ключевого отличия: вместо того, чтобы содержать бук вально одинаковое слово при чтении назад, он содержит другое слово. Примеры на английском языке— bats и stab (летучие мыши и колоть), wolf и flow (волк и поток). Кстати, семорднилап — это слово palindromes, написанное задом наперед.
На рис. 2.3 показано произвольное слово из шести букв. Крестики представляют собой часть слова, которая может образовывать реальное слово при чтении назад (например, run в слове nurses). Нолики представляют собой возможную палин дромную последовательность (например, ses в слове nurses). Слово, представленное в левой колонке на рис. 2.3, ведет себя как nurses на рис. 2.2, где перевернутое сло во находится в начале. Слово, представленное правой колонкой, ведет себя как grits, где перевернутое слово находится в конце. Обратите внимание, что число комбинаций в каждом столбце — это суммарное число букв в слове плюс единица; заметьте также, что верхняя и нижняя строки представляют одинаковые ситуации.
хххххх хххххо ххххоо хххооо ххоооо хооооо 000000
хххххх ооооох оооохх оооххх оохххх оххххх 000000
Р и с. 2.3. Возможные позиции для букв перевернутого слова (X) и палиндромной последовательности (0) в шестибуквенном стержневом слове
Верхняя строка в каждом столбце представляет собой семорднилап. Нижняя строка в каждом из н и х — палиндром. Они оба являются перевернутыми словами, просто разными типами перевернутых слов. Следовательно, они считаются одной сущно стью и оба могут быть выявлены с помощью одной строки кода в одной итерации цикла.
52
Глава 2
Для того чтобы увидеть эту диаграмму в действии, рассмотрим рис. 2.4, на котором изображены палинграммы devils lived (дьяволы жили) и retro porter (ретро-портье). Оба слова, devils и porter, являются стержневыми словами и зеркальными отраже ниями друг друга по отношению к палиндромным последовательностям и перевер нутым словам. Сравните это с семорднилапом evil (зло) и палиндромом kayak (каяк).
DEVILS PORTER ХХХХХО ОХХХХХ EVIL ХХХХ
LIVE ХХХХ
KAYAK ХХХХХ 00000
KAYAK ХХХХХ 00000
Р и с. 2.4. Перевернуты е слова (X) и палиндромны е последовательности (0) в словах, семорднилапах и палиндромах
Палиндромы— это и перевернутые слова, и палиндромные последовательности. Поскольку они имеют тот же шаблон из крестиков, что и семорднилапы, их можно обрабатывать тем же кодом, который используется для семорднилапов. С точки зрения стратегии вам нужно будет перебрать каждое слово в словаре и оценить его на предмет всех комбинаций на рис. 2.3. Исходя из того, что словарь имеет 60 тыс. слов, программа должна будет предпринять около 500 тыс. проходов по словарю. Для понимания работы циклов взгляните на стержневое слово для палинграммы stack cats на рис. 2.5. Ваша программа должна перебрать буквы в слове, начиная с конечной буквы, и каждую итерацию добавлять букву. Для того чтобы отыскать такие палинграммы, как stack cats, она одновременно будет оценивать слово на на личие палиндромной последовательности в конце стержневого слова, stack, и пере-
Палиндромная?
Четвертый 1
’ Третий’
r r J — 1 Второй
Первый
Г
1
STACK " 1 „ i : Первый 1 | 1 Второй L”TJ Третий
_ Четвертый
Перевернутое слово?
Р и с. 2.5. Пример прокручивает стержневое слово, одноврем енно ищ а палиндромы и перевернуты е слова
Поиск палинграммных заклинаний S3
вернутого слова в начале. Обратите внимание, что первый цикл на рис. 2.5 будет успешным, т. к. в данной ситуации одиночная буква (к) может служить палиндро мом.
Но вы еще не закончили. Для того чтобы уловить поведение ’’зеркального отраже ния” на рис. 2.3, вы должны выполнить циклы в обратном порядке, ища палин дромные последовательности в начале слова и перевернутые слова в конце. Это позволит найти такие палинграммы, как stir grits. Вот псевдокод алгоритма отыскания палинграмм:
Загрузить цифровой словарь в виде списка слов Создать пустой список, который будет содержать палинграммы Для слова в списке слов: Получить длину слова, Если длина > 1:
Перебрать буквы в слове:
Если перевернутый фрагмент слова в начале слова находится в списке слов и последующие буквы образуют палиндромную последовательность:
Добавить слово и перевернутое слово в список палинграмм
Если фрагмент перевернутого слова в конце слова находится в списке слов и предыдущие буквы формируют палиндромную последовательность:
Добавить перевернутое слово и слово в список палинграмм
Отсортировать список палинграмм в алфавитном порядке Напечатать палинграммы словарных пар из списка палинграмм
Код отыскания палинграмм
Листинг 2.3 (файл palingrams.py) перебирает список слов, выявляет, какие слова образуют палинграммы словарных пар, сохраняет эти пары в списке и печатает список как стопку элементов. Код программы можно скачать с веб-страницы https://www.nostarch.com/impracticaIpython/. Для начала предлагаю вам исполь зовать файл словаря 2of4brif.txt, благодаря чему ваши результаты будут соответст вовать моим. Сохраните словарь и файл load_dictionary.py в той же папке, что и скрипт palingrams.py.
Листинг 2.3. Отыскивает и печатает палинграммы словарны х пар загруженного словаря. Файл palingrams.py
“““Отыскать все палинграммы словарных пар в файле словаря, import load_dictionary
word_list = load_dictionary.load(‘2of4brif.txt’)
#
отыскать палинграммы словарных пар
О def find_palingrams():
“““Отыскать палинграммы в словаре.”””
54
Глава 2
pali_list = []
for word in word_list: 0 end = len(word) 0 rev_word = word[::-l] О if end > 1:
0 for i in range(end):
0 if word[i:] == rev_word[:end-i]and rev_word[end-i:] in
word_list:
word_list:
pali_list.append((word, rev_word[end-i:])) 0 if word[:i] == rev_word[end-i:]and rev_word[:end-i] in
pali_list.append((rev_word[:end-i], word))
0 return pali_list
О palingrams = find_palingrams()
отсортировать палинграммы по первому слову #
palingrams_sorted = sorted(palingrams)
показать список палинграмм #
0 print(“ХпЧисло палинграмм = {}\n”.format(len(palingrams_sorted)))
for first, second in palingrams_sorted:
print(”{} {}".format(first, second))
После повторения шагов, которые вы использовали в программном коде palindromes.ру для загрузки файла словаря, вы определяете функцию, которая будет выполнять поиск палинграмм ф . Использование указанной функции позже позво лит вам изолировать код и засечь время, требуемое для обработки всех слов в сло варе.
Сразу же создайте список под названием paii iist для хранения всех палинграмм, обнаруженных программой. Затем начните цикл for для оценивания слов в списке word list. Для каждого слова найдите его длину и назначьте ее переменной end 0. Длина слова определяет индексы, которые программа использует для прохождения по слову в поисках каждой возможной комбинации ’’перевернутое слово — палин дромная последовательность”, как на рис. 2.3.
Далее пройдите слово в обратном порядке и назначьте результаты переменной rev word 0 . Альтернативой выражению word[::-i] является выражение ‘’. join (reversed (word)), его некоторые считают более читаемым.
Поскольку вы ищете палинграммы словарных пар, исключите однобуквенные сло ва 0 Затем вложите еще одну инструкцию for для того, чтобы перебрать буквы в текущем слове 0. Теперь выполните условное выражение, требующее, чтобы конец слова был палин- дромным, а начало слова — перевернутым словом в списке слов (другими словами,
Поиск палинграммных заклинаний 55
“реальным” словом) О- Если слово проверку проходит, то оно добавляется в спи сок палинграмм, за которым сразу следует перевернутое слово.
Основываясь на рис. 2.3, вы знаете, что вам нужно повторить условие, но изменить направление прохождения и порядок слов для того, чтобы перевернуть результат. Другими словами, вы должны улавливать палиндромные последовательности в на чале слова, а не в конце Q . Завершая функцию, вы возвращаете список палинграмм о Определив функцию, вы ее вызываете ф . Поскольку порядок, в котором слова ело- варя добавляются в список палинграмм, переключается во время цикла, палин граммы не будут в алфавитном порядке. Поэтому вы сортируете список так, чтобы первые слова в словарной паре были в алфавитном порядке. Далее печатаете длину а затем показываете каждую словарную пару в отдельной строке. списка
В том виде, как она написана, программе palingrams.py потребуется около трех ми нут на то, чтобы выполнить работу на файле словаря из порядка 60 тыс. слов. В следующих разделах мы разведаем причину этого длительного времени выпол нения и посмотрим, что можно сделать для того, чтобы его сократить.
Профилирование палинграмм
Профилирование — это аналитический процесс, который собирает статистические данные о поведении программы (например, о числе и продолжительности вызовов функций) в ходе ее исполнения. Профилирование является ключевой частью про цесса оптимизации. Оно точно говорит вам о том, какие части программы занима ют большую часть времени или памяти. Благодаря ему вы будете знать, на каких местах следует сосредоточить усилия с целью повышения производительности.
Профилирование с помощью cProfile
Профиль — это результат измерения — регистрация того, как долго и как часто выполняются части программы. Стандартная библиотека Python предоставляет удобный интерфейс профилирования, cProfile, являющийся расширением языка С, который подходит для профилирования длительных программ.
В функции find_pa ling rams (), вероятно, есть что-то, что объясняет относительно длительное время работы программы palingrams.py. Для подтверждения давайте ЗапуСТИМ cProfile.
Скопируйте следующий ниже фрагмент кода в новый файл с именем cprofile test.py и сохраните его в той же папке, что и palingrams.py и файл словаря. Указанный программный код импортирует cProfile и программу palingrams и запускает про филировщик cProfile на функции find palingrams о, вызываемой при П О М О Щ И ТО- чечной формы записи. Еще раз обратите внимание на то, что указывать расширение .ру не нужно.
import cProfile import palingrams
cProfile.run(‘palingrams.find_palingrams()’)
56
Глава 2
Выполните скрипт cprofiletest.py, и после его завершения (в окне интерпретатора вы увидите »> ) вы должны увидеть что-то похожее на следующее:
62622 function calls in 199.452 seconds
Ordered by: standard name
ncalls tottime percall cumtime percall filename:lineno(function)
1
0.000
0.000 199.451 199.451 :1 ()
1 199.433 199.433 199.451 199.451 palingrams.py:7 (find_palingrams)
1
0.000
0.000 199.452 199.452 {built-in method builtins.exec}
60388
0.018
0.000
0.018
0.000 {built-in method builtins.len}
2230
0.001
0.000
0.001
0.000 {method ’append’ of ’list’ objects}
На моей машине вся циклическая обработка, прохождение слов и поиск заняли 199 452 секунды, но, разумеется, ваше время может отличаться от моего. Вы также получите дополнительную информацию о некоторых встроенных функциях, и по скольку каждая палинграмма вызывала встроенную функцию appendo, вы даже увидите число найденных палинграмм (2230).
ПРИМЕЧАНИЕ Общепринятым способом запуска профилировщика cProfile является его запуск не посредственно в интерпретаторе. Это позволяет сбрасывать результат в текстовый файл и просматривать его с помощью веб-браузера. Для получения дополнительной информации посетите веб-страницу https://docs.python.org/37library/profile.htm l.
Профилирование со временем
Еще один способ засечь время выполнения программы — использовать метод time.time о, который возвращает временную метку эпохи — число секунд с 12 часов 1 января 1970 года UTC (временную метку эпохи UNIX). Скопируйте файл palingrams.py в новый файл, сохраните его как palingrams timed.py и вставьте следующий ниже фрагмент кода в самом верху:
import time
start_time = time.time ()
Теперь перейдите в конец файла и добавьте следующий фрагмент кода:
end_time = time.time ()
print(“Время выполнения этой программы составило {} секунд.
“. format (end_time - start_time) )
Сохраните и запустите файл. Внизу окна интерпретатора вы должны получить сле дующий ниже отклик — плюс-минус несколько секунд:
Время выполнения этой программы составило 222.73954 5583724 98 секунд.
Время выполнения будет больше, чем раньше, т. к. теперь вы оцениваете всю про грамму, ВКЛЮЧаЯ Печать, а Не ТОЛЬКО фунКЦИЮ find palingrams ().
В отличие от профилировщика cProfile, функция time о не предоставляет подроб ную статистику, но, как и cProfile, она может выполняться на отдельных компо
Поиск палинграммных заклинаний 57
нентах программного кода. Отредактируйте файл, который вы только что выпол нили, переместив инструкции начального и конечного времени (как показано ниже жирным шрифтом) так, чтобы они обрамляли нашу длительную функцию find palingrams о . Оставьте инструкции импорта и печати без изменений соответ ственно вверху и внизу файла.
start_time = time. time () palingrams = find_palingrams() end_time = time, time ()
Сохраните и запустите файл. Внизу окна интерпретатора вы должны получить сле дующий ниже отклик:
Время выполнения этой программы составило 199.42786622047424 секунды.
Теперь он соответствует первоначальным результатам, полученным с помощью профилировщика cProfile. Если повторно выполнить программу или применить другой таймер, то получить в точности такое же время не удастся, но не зацикли вайтесь на этом. Указанное время является относительным, и оно важно для регу лирования процесса оптимизации кода.
Оптимизация палинграмм
Простите, но три минуты моей жизни — это слишком долго, чтобы ждать, когда появятся палинграммы. Вооружившись нашими результатами профилирования, мы знаем, что функция find paiingrams о занимает большую часть времени обработки. Вероятно, это имеет какое-то отношение к чтению списков и записи в них, нарезке списков или поиску в списках. Использование альтернативной спискам структуры данных — к примеру, кортежей, множеств или словарей — могло бы ускорить ра боту указанной функции. Множества, в частности, значительно быстрее списков при использовании ключевого слова in. Для выполнения очень быстрого поиска в множествах используется хэш-таблица. При хешировании символьные цепочки с текстом преобразуются в уникальные числа, которые намного меньше, чем адре суемый текст, и гораздо эффективнее для поиска. С другой стороны, в списке необ ходимо выполнять линейный поиск, перебирая каждый элемент.
Подумайте об этом так: если дома вы ищете свой потерянный мобильный телефон, то список можно эмулировать, просматривая каждую комнату, прежде чем вы его найдете (по закону подлости в пресловутом самом последнем месте, куда вы загля нете). Но, эмулируя множество, вы, в сущности, можете набрать номер своего мо бильного телефона с другого телефона, услышать мелодию звонка и перейти прямо в нужную комнату.
Недостатком использования множеств является то, что порядок следования элемен тов в множестве не контролируется и дублирующие значения не допускаются. В списках порядок сохраняется, и дубликаты разрешены, но поиск занимает боль ше времени. К счастью для нас, порядок следования или дубликаты нас не интере суют, поэтому множества — это как раз то, что нам нужно!
58
Глава 2
Листинг 2.4 представляет собой функцию find paiingrams о из оригинальной про граммы palingrams.py, отредактированную для использования множества слов вместо списка слов. Указанную функцию можно найти в. новой программе palingramsoptimized.py, которую можно скачать с веб-сайта https://w w w . nostarch.com /im practicaIpython/, либо просто внесите следующие ниже изменения в свою копию программы palingrams timed.py, в случае если хотите проверить но вое время выполнения самостоятельно.
Листинге 2.4. Функция find_palingrams (), оптимизированная для работы с множествами
def find_palingrams():
“““Отыскать палинграммы в словаре.”””
pali_list = []
О words = set(word_list)
ф for word in words:
end = len(word)
rev_word = word[::-l]
if end > 1:
for i in range(end):
ф if word[i:] == rev_word[:end-i] and rev_word[end-i:] in words:
pali_list.append((word, rev_word[end-i:]))
О if word[:i] == rev_word[end-i:] and rev_word[:end-i] in words:
pali_list.append((rev_word[:end-i], word))
return pali_list
Изменены всего четыре строки. Определите новую переменную words, которая представляет множество word iist ф . Затем выполните обход множества ф , а не списка, как раньше, в поисках членства фрагментов слова в этом множестве ф ф .
Вот новое время выполнения функции find palingrams () в программе palingrams_ optimized.ру:
Время выполнения этой программы составило 0.48582673072814 94 секунды.
Ух ты! С более 3 минут до менее 1 секунды! Вот это оптимизация! И разница лишь в структуре данных. Проверка принадлежности слова в списке была бы просто убийственной.
Почему я с самого сначала показал вам ’’неправильный” способ? Потому что имен но так все происходит в реальном мире. Вы доводите программный код до рабочего состояния, а затем его оптимизируете. Опытный программист реализовал бы этот простой пример с пол-оборота, но он символизирует общую концепцию оптимиза ции: заставить его работать как можно лучше, а затем сделать его еще лучше.
Поиск палинграммных заклинаний 59
Резюме
Вы написали программный код для поиска палиндромов и палинграмм, профили рованный код с применением инструмента cProfile и оптимизированный код с ис пользованием структуры данных, лучшим образом соответствующей задаче. Ну и как же мы поступим с Затанной? Есть ли у нее шанс побороться?
Здесь я перечислил несколько наиболее ’’агрессивных” палинграмм, найденных в файле словаря 2of4brif — тут все, начиная с неожиданной sameness enemas (клиз мы одинаковости) и суровой torsos rot (торсы гниют) до близкой для меня как гео лога: eroded ore (эродированная руда).
dump mud
legs gel
sleet eels
eroded ore
strafe farts
wolfs flow
slaps pals
drowsy sword
denims mined
dairy raid
rise sir
torsos rot partner entrap
flack calf
sameness enemas
lepers repel
slam mammals
pots nonstop
swan gnaws
nuts stun
knobs bonk
Дальнейшее чтение
Книга “Думай по-питоновски” (Downey A. Think Python. O’Reilly, 2015) Аллена Дауни содержит краткое и ясное описание хэш-таблиц и причин, почему они явля ются такими эффективными. Она также является отличным справочником по языку Python.
Практический проект: очистка словаря
Файлы данных, доступные в Интернете, не всегда находятся в формате plug and play (подключи и играй). Прежде чем применять их в вашем проекте, вы вполне можете обнаружить, что вам необходимо немного отрегулировать данные. Как упоминалось ранее, некоторые файлы онлайновых словарей содержат каждую бук ву алфавита в качестве слова. Это вызовет проблемы, если в палинграммах вы за хотите разрешить использовать однобуквенные слова, как, например, в ’’acidic а”. Их всегда можно удалить из словаря, непосредственно отредактировав текстовый файл, но такой способ утомителен и для неудачников. Вместо него напишите ко роткий скрипт, который удаляет их после загрузки словаря в Python. Для проверки того, что все работает, отредактируйте файл словаря так, чтобы он включал не сколько однобуквенных слов, таких как Ъ и с. Решение см. в приложении к книге либо найдите копию (dictionary_cleanup_practice.py) онлайн по адресу https:// www.nostarch.com/impracticaIpython/.
60
Глава 2
Сложный проект: рекурсивный подход
В языке Python, как правило, существует более одного способа расколоть орех. Взгляните на обсуждение и псевдокод на веб-сайте Khan Academy:
https://www.khanacademy.org/computing/computer-cience/algorithms/recursive- algorithms/a/using-recursion-to-determine-whether-a-word-is-a-palindrome/.
Затем перепишите программу palindrome.py так, чтобы для выявления палиндромов в ней использовалась рекурсия.
3
РЕШЕНИЕ АНАГРАММ
Анаграмма— это слово, образованное путем перестановки букв другого слова. Например, Elvis дает жуткое трио из evils (зло), lives (жизни) и veils (завесы). Означает ли это, что Элвис по-прежнему жив, но скрывает свое злое существование? В книге “Гарри Поттер и Тайная комната” предложение ”1 am Lord Voldemort” (Я — Лорд Волдеморт) является анаграммой настоящего имени злого волшебника, Тома Марволо Реддла (Tom Marvolo Riddle). Имя ”Lord Earldom Vomit” также является анаграммой имени Тома Марволо Реддла, но у автора Джоан Роулинг хватило здравого смысла ее переделать (vomit означает рвота). В этой главе вы сначала найдете все анаграммы для заданного слова или имени. Затем вы напишете программу, которая позволит пользователю интерактивно стро ить анаграммное словосочетание из собственного имени. Наконец, вы поиграете в компьютерного мастера слова и увидите, что нужно для того, чтобы извлечь ”1 am Lord Voldemort” из “Tom Marvolo Riddle”.
Проект 4: поиск однословных анаграмм
Вы начнете с анализа простых однословных анаграмм и выяснения того, как выяв лять их программно. Выполнив это, вы будете готовы взяться за анаграммы слово сочетаний в следующем разделе.
Ц е л ь
Применить язык Python и файл словаря для отыскания всех однословных анаграмм для заданного английского слова или одного имени. Инструкции по отысканию и за грузке файлов словарей можно прочитать в начале главы 2.
62
Глава 3
Стратегия и псевдокод
Более 600 газет и 100 интернет-сайтов содержат анаграмму под названием Jumble (Беспорядок). Созданная в 1954 г., она теперь стала самой признанной в мире игрой для беспорядочного перемешивания букв в словах (скремблирования слов). Игра Jumble может по-настоящему доводить до исступления, однако отыскивать ана граммы почти так же легко, как отыскивать палиндромы — просто нужно знать общее свойство всех анаграмм: они должны иметь одинаковое число одинаковых букв.
Идентификация анаграммы
Python не содержит встроенного оператора анаграмм, но вы можете легко его напи сать. В проектах этой главы вы загрузите файл словаря из главы 2 в виде списка символьных цепочек. Так что программе необходимо лишь удостовериться, что две символьные цепочки являются анаграммами друг друга.
Давайте рассмотрим пример. Слово pots является анаграммой слова stop, и с помо щью функции len () вы можете проверить, что stop и pots имеют одинаковое число букв. Но Python не знает, имеют или нет две символьные цепочки одинаковое чис ло любого символа в слове — по крайней мере, без преобразования цепочек в дру гую структуру данных либо с помощью функции подсчета. Поэтому вместо того, чтобы рассматривать эти два слова просто как символьные цепочки, вы можете представить их как два списка, содержащих односимвольные цепочки. Создайте эти списки в интерактивной оболочке, к примеру в окне интерпретатора IDLE, и назовите их word и anagram, как я сделал здесь:
word = list(‘stop’) word [’S
‘O’, anagram = list(‘pots’) » > anagram
’ t ’ ,
‘p’]
[‘p’, ‘o’, ’t’, ’s’]
Эти два списка соответствуют нашему описанию пары анаграмм, т. е. они содержат одинаковое число одинаковых букв. Но если вы попытаетесь сравнить их с по мощью оператора сравнения ==, то результат будет ложным.
anagram = word False
Проблема заключается в том, что оператор == рассматривает два списка как эквива лентные только в том случае, если они имеют одинаковое число одинаковых эле ментов и эти элементы выстроены в одинаковом порядке. Эту проблему можно легко решить с помощью встроенной функции sortedO, которая в качестве аргу мента может принимать список и переупорядочивать его содержимое в алфавитном порядке. Итак, если вызвать sorted () дважды — по одному разу для каждого спи ска, а затем сравнить отсортированные списки, то они будут эквивалентными. Дру гими словами, оператор == вернет True.
Решение анаграмм 63
» > word = sorted (word)
word
[‘o’, ‘p\ ’s’, ’t’] » > anagram = sorted (anagram) » > anagram
[‘o’, ‘p’, ’s’, • t B ]
» anagram = word
True
Кроме того, в функцию sorted () можно передать символьную цепочку, в результа те создав отсортированный список, как в предыдущем фрагменте кода. Это окажет ся полезным для преобразования слов из файла словаря в отсортированные списки односимвольных цепочек.
Теперь, когда вы знаете, как проверить, что анаграмма найдена, давайте создадим скрипт в полном объеме — от загрузки словаря и запроса слова (или имени) у поль зователя до отыскания и печати всех анаграмм.
Использование псевдокода
Напомним, что планирование с помощью псевдокода поможет вам определить по тенциальные проблемы, а раннее выявление этих проблем сэкономит вам время. Следующий ниже псевдокод должен помочь вам лучше понять скрипт anagrams.ру, который мы напишем в следующем разделе.
Загрузить файл цифрового словаря в виде списка слов Принять слово от пользователя Создать пустой список для хранения анаграмм Отсортировать буквы введенного пользователем слова Перебрать каждое слово в списке слов:
Отсортировать буквы слова Если отсортированное слово тождественно отсортированному слову пользователя:
Добавить слово в список анаграмм
Напечатать список анаграмм
Указанный скрипт начнется с загрузки слов из файла словаря в список в виде сим вольных цепочек. Прежде чем обойти словарь в цикле в поисках анаграмм, вам нужно знать, анаграммы какого слова вам нужны, и вам требуется место для хране ния анаграмм, когда вы их найдете. Поэтому сначала попросим пользователя вве сти слово, а затем создадим пустой список для хранения анаграмм. После того как программа переберет каждое слово в словаре, она напечатает этот список анаграмм.
Код отыскания анаграмм
Листинг 3.1 загружает файл словаря, принимает указанное в программе слово или имя и отыскивает для этого слова или имени все анаграммы в файле словаря. Вам также понадобится программный код загрузки словаря из главы 2. Указанный код можно скачать с веб-сайта https://www.nostarch.com/impracticaIpython/ соответ ственно в виде файлов anagrams.ру и load dictionary.py. Сохраните оба файла в од
64 Глава 3
ной папке. Можно использовать тот же файл словаря, который вы использовали в главе 2, либо загрузить другой (рекомендации см. в табл. 2.1).
Листинг 3.1. С учетом слова (либо имени) и файла словаря эта программа оты скивает и распечатывает список анаграмм. Файл anagram s.ру
ф import load_dictionary
ф word_list = load_dictionary.load(’2of4brif.txt’)
ф anagram_list = []
ниже ввести ОДНО слово либо ОДНО имя для отыскания анаграмм(ы): #
ф паше = ’Foster’
print(“Входное имя = {}".format (name))
ф name = name.lower()
print(“Используя имя = {}".format(name))
отсортировать имя и отыскать анаграммы #
ф name_sorted = sorted(name)
ф for word in word_list:
word = word.lower()
if word != name:
if sorted(word) == name_sorted:
anagram_list.append(word)
распечатать список анаграмм #
print()
ф if len(anagram_list) == 0:
print(“Вам нужны более крупный словарь либо новое имя!”)
else:
ф print(“Анаграммы =”, *anagram_list, sep=’\n’)
Начните с импорта модуля load dictionary, созданного в главе 2 ф . Указанный модуль откроет текстовый файл словаря и с помощью функции load () загрузит все слова в список ф . Используемый вами txt-файл может отличаться в зависимости от того, какой файл словаря вы загрузили (см. разд. “Отыскание и открытие словаря” в главе 2).
Далее создайте пустой список под названием anagram iist для хранения любых анаграмм, которые вы найдете ф . Попросите пользователя добавить одно слово, например свое имя ф . Указанное слово не обязательно должно быть именем собст венным, но в программном коде мы будем ссылаться на него как на имя для того, чтобы отличать его от слова словаря. Напечатайте это имя для того, чтобы пользо ватель смог увидеть, какое имя было введено.
Следующая строка кода предвосхищает проблемное действие пользователя. Люди, как правило, вводят свое имя с заглавной буквы, но файлы словарей могут не со
Решение анаграмм 65
держать заглавные буквы, а для Python это имеет значение. Поэтому с помощью метода символьных цепочек lower () сначала переведите все буквы в нижний ре гистр 0 .
Теперь отсортируйте буквы имени О- Как упоминалось ранее, в функцию sorted о можно передавать символьную цепочку или список.
Имея список отсортированных по алфавиту данных, самое время отыскать ана граммы. Начните цикл с обходом каждого слова в списке слов словаря ф. Для под страховки конвертируйте слово в нижний регистр, т. к. операции сравнения чувст вительны к регистру. После конвертирования сравните слово с неотсортированным именем, т. к. слово не может быть анаграммой самого себя. Затем отсортируйте слово словаря и сравните его с отсортированным именем. Если сравнение прохо дит, то добавьте это слово словаря в список анаграмм anagram list.
Теперь покажите результаты. Сначала проверьте, является ли список анаграмм пус тым. Если это так, то распечатайте причудливый ответ, для того чтобы не оставить пользователя сидеть перед пустым экраном ф. Если программа нашла хотя бы од ну анаграмму, то напечатайте список с помощью унарного оператора “звездочка” (*). Как вы помните из главы 2, указанный оператор позволяет печатать каждый элемент списка на отдельной строке ф.
Ниже приведен пример результата работы этой программы с использованием вход ного имени Foster.
Входное имя = Foster Используя имя = foster
Анаграммы = forest fortes softer
Если вы хотите использовать другое входное значение, то в исходном коде измени те значение переменной name. В качестве упражнения попробуйте настроить про граммный код так, чтобы пользователю предлагалось вводить имя (или слово); это можно сделать с помощью функции input ().
Проект 5: поиск фразовых анаграмм
В предыдущем проекте вы брали одно-единственное имя или слово, переставляли все буквы и отыскивали однословные анаграммы. Теперь вы будете выводить из имени несколько слов. Слова в этих фразовых анаграммах образуют только часть входного имени, и для того, чтобы исчерпать доступные буквы, вам понадобится несколько слов.
Ц е л ь
Написать программу на языке Python, которая позволяет пользователю в интерактив ном режиме строить анаграммное словосочетание из букв в его имени.
66 Глава 3
Стратегия и псевдокод
Лучшие фразовые анаграммы — это те, что описывают какую-то известную осо бенность или действие, которые ассоциированы с носителем имени. Например, бу квы в имени Clint Eastwood могут быть перестановлены так, чтобы сформировать словосочетание old west action (меры старого Запада), имя Alec Guinness даст сло восочетание genuine class (подлинный класс), Madam Curie произведет предложе ние radium came (радий пришел), George Bush даст he bugs Gore (он прослушивает Гора), a Statue of Liberty (статуя Свободы) будет содержать словосочетание built to stay free (построенная для того, чтобы мы оставались свободными). Мое собствен ное имя дает a huge navel (огромный пупок), который на самом деле не является ни одной из моих особенностей.
На этом этапе вы, возможно, увидите, что перед нами возникает одна стратегиче ская трудность: каким образом компьютер сможет обрабатывать контекстное со держимое? Сотрудники компании IBM, которые изобрели суперкомпьютер Watson, похоже, это знают, но для остальных из нас этот валун будет поднять трудновато.
Метод грубой силы (или исчерпывающего перебора всех вариантов) — это широко применяемый подход, который используется в онлайновых генераторах анаграмм. Их алгоритмы принимают имя и возвращают большое число случайных анаграмм- ных словосочетаний (как правило, от 100 до 10 000-ь). Большинство возвращаемых словосочетаний является тарабарщиной, и пролистывание сотен из них может ока заться муторным делом.
Альтернативный подход — признать факт, что люди лучше всего справляются с контекстуальными задачами, и написать программу, которая помогает человеку работать над решением этой задачи. Компьютер берет начальное имя и предостав ляет слова, которые будут собраны из нескольких (или всех) его букв; пользователь выбирает слово, которое для него “имеет смысл”. Затем программа пересчитывает варианты выбора слов из остальных букв в имени, повторяя процесс до тех пор, пока не будет использована каждая буква или не будут исчерпаны возможные варианты слов. Такая конструкция подыгрывает сильным сторонам обоих участ ников.
Вам понадобится простой интерфейс, который предложит пользователю ввести на чальное имя, покажет потенциальные варианты слов и покажет все остальные бук вы. Программа также должна отслеживать растущее анаграммное словосочетание и сообщать пользователю о том, когда каждая буква была использована. Вероятно, по ходу возникнет много неудачных попыток, поэтому интерфейс должен позво лить пользователю перезапускать процесс в любое время.
Поскольку анаграммы имеют одинаковое число одинаковых букв, другой способ их выявления состоит в подсчете индивидуальных букв. Если подумать о своем имени как о коллекции букв, то из вашего имени можно построить слово, если, во-первых, все его буквы встречаются в вашем имени и, во-вторых, они встречаются с одина ковой частотой или меньше. Очевидно, что если буква е встречается в слове три раза, а в вашем имени — дважды, то из вашего имени слово получить невозможно.
Решение анаграмм 67
i 1 оэтому если коллекция букв, составляющих слово, не является подмножеством коллекции букв вашего имени, то это слово не может быть частью анаграммы вашего имени.
Использование контейнерного типа Counter для подсчета букв
К счастью для нас, Python поставляется с модулем под названием collections, ко торый включает в себя несколько контейнерных типов данных. Один из таких ти пов, counter, подсчитывает вхождения элемента. Python хранит элементы как клю чи словаря, а количества— как его значения. Например, следующий ниже фраг мент кода подсчитывает количество находящихся в списке видов деревьев бонсай.
» from collections import Counter
О »> my_bonsai_trees = [‘maple’, ‘oak’, ’elm’, ‘maple’ , ’elm’, ’elm’, ’elm’,
’elm’ ]
0 » > count = Counter (nry_bonsai_trees)
» > print (count)
0 Counter({’elm’: 5, ‘maple’: 2, ’oak’: 1})
Список my bonsai trees содержит многократные значения одного и того же типа деревьев ф . Контейнер counter подсчитывает деревья о и создает простой в об ращении словарь 0. Обратите внимание, что функция print о является необяза тельной и используется здесь для ясности. Если ввести просто count, то содержи мое словаря будет показано тоже.
Контейнерный тип counter можно использовать для отыскания однословных ана грамм вместо метода sorted (). Вместо двух отсортированных списков на выходе будут два словаря, которые также можно напрямую сравнить с помощью оператора ==. Ниже приведен пример:
» > name = ‘foster’ » > word = ’forest’ » > name_count = Counter (name) » > print (name_count)
О Counter({‘f’: 1, ’t’: 1, ’e’: 1, ‘o’: 1, ‘г’: 1, ’s’: 1})
» > word_count = Counter (word) » > print (word__count)
0 Counter({’f’: 1, ’t ’: 1, ‘o’: 1, ’e’: 1, ‘r’: 1, ‘s’: 1})
Контейнерный тип counter для каждого слова создает словарь, который увязывает каждую букву в слове с количеством ее появлений О0. Словари не отсортирова ны, но несмотря на отсутствие сортировки, Python правильно идентифицирует каж дый словарь как тождественный другому, если словари содержат одни и те же бук вы и одинаковые их количества:
» > if word_count = name__count:
print(“Совпадение!”)
Совпадение!
68
Глава 3
Контейнерный тип counter дает вам прекрасный способ отыскивать слова, которые ’’укладываются” в имя. Если количество каждой буквы в слове меньше или равно количеству той же буквы в имени, то слово можно получить из имени!
Псевдокод
Теперь мы приняли два важных проектных решения: 1) дать пользователю возмож ность интерактивно строить свою анаграмму по одному слову за раз; 2) ис пользовать контейнерный тип counter для отыскания анаграмм. Этого достаточно для того, чтобы начать думать о высокоуровневом псевдокоде:
Загрузить файл словаря Принять имя от пользователя Установить предел равным длине имени Создать пустой список для хранения анаграммного словосочетания До тех пор, пока длина словосочетания для того, чтобы начать все сначала, либо нажать клавишу для того, чтобы выйти из программы. Исполь зуйте символ #, а не слово или букву, для того чтобы не спутать его с действитель ным вариантом выбора. Если пользователь делает выбор, то введенная символьная цепочка назначается пе ременной candidate, очищается от пробелов и вся переводится в нижний регистр ф . Это значит, что ее можно напрямую сравнить с переменной name. После этого из переменной name строится список, содержащий все остальные буквы ф . Теперь начните цикл, для того чтобы вычесть буквы, используемые в переменной candidate ф . Если выбранная буква в списке присутствует, то она удаляется. Если пользователь ввел слово, которого в показываемом списке нет, либо он ввел несколько слов, то буква может отсутствовать в списке. Для того чтобы это прове рить, вычтите остальные буквы из имени, и если результатом будет количество букв в candidate, то входные данные считаются допустимыми и можно выйти из цикла while ф . В противном случае покажите предупреждение и для тех, кто ис пользует окно интерактивной оболочки IDLE, выведите это предупреждение шрифтом красного цвета. Цикл while будет продолжать опрашивать пользователя до тех пор, пока не будет сделан приемлемый выбор. Если проверку проходят все буквы в выбранном пользователем варианте, то список оставшихся букв конвертируется обратно в символьную цепочку и используется для обновления переменной name ф . Конвертирование списка в символьную це почку не является строго необходимым, но оно поддерживает единообразным тип переменной name и позволяет показывать остальные буквы в четко читаемом фор мате без необходимости дополнительных аргументов инструкции print.
74 Глава 3
Завершите действия, вернув выбранный пользователем вариант и цепочку осталь ных букв (name) В функцию main () ф .
Определение функции mainQ
Листинг 3.4 определяет функцию main о в программе phraseanagrams.py. Указан ная функция обертывает предыдущие функции, запускает цикл while и определяет момент успешного создания пользователем анаграммного словосочетания.
Листинг 3.4. Определяет и вызывает функцию m ain ( ) . Файл phrase_anagrams.py, часть 3
def m a i n ():
,,H"Помочь пользователю построить анаграммное словосочетание
из своего имени.”””
ф name = ’ join(ini_name.lower().split())
name = name.replace(’ - ’, ’’)
ф limit = len(name)
phrase = ’’
running = True
ф while running:
ф temp_phrase = phrase.replace(’ ‘, ’’)
ф if len(temp_phrase) , то функция main () вызывается снова о Как было сказано ранее, пользо ватель может изменить начальное имя единственным способом — выйти из про граммы и ее перезапустить.
Вне функции main о завершите стандартными двумя строками кода с вызовом функции main о , когда программа не импортирована в виде модуля ф .
Выполнение демонстрационного сеанса
В этот раздел я включил пример интерактивного сеанса, используя программу phraseanagrams.py и имя Bill Во. Полужирный шрифт указывает на ввод данных пользователем, полужирный курсив — на то, где на экране используется красный шрифт.
Введите имя: Bill Во Длина анаграммного словосочетания = О bib bill blob bob
boil
boll i ill
lib lilo 10 lob
oi 011
Остальные буквы = billbo Число остальных букв = 6 Число остальных анаграмм (реальных слов) = 14
Текущее анаграммное словосочетание =
Выберите либо введите Enter, чтобы начать сначала, либо # для выхода: ill
Длина анаграммного словосочетания = 3
bob
Остальные буквы = bbo
Число остальных букв = 3 Число остальных анаграмм (реальных слов) = 1 Текущее анаграммное словосочетание = ±11
Выберите либо введите Enter, чтобы начать сначала, либо # для выхода: Bob
Решение анаграмм 77
***** ЗАКОНЧЕНО!!! *****
Анаграмма имени = 111 Bob
Попробуете еще раз? (Нажмите Enter либо “п” для выхода)
Число найденных анаграмм зависит от используемого файла словаря. Если у вас возникли трудности с построением анаграммных словосочетаний, то попробуйте применить более крупный словарь.
Проект 6: поиски Волдеморта, или галльский гамбит
Вы когда-нибудь задумывались о том, как Том Реддл придумал анаграмму “Я — Лорд Волдеморт”? Приложил ли он перо к пергаменту или же просто взмах нул палочкой? А может, ему помогло волшебство языка Python?
Давайте на мгновение представим, что вы — профессор компьютерной школы ча родейства и волшебства в Хогвартсе и Том Реддл, школьный префект и образцовый ученик, пришел к вам за помощью. Используя волшебство вашей программы phrase anagrams.py из предыдущего раздела, он мог бы, к своему огромному удо вольствию, уже в самом первом списке анаграмм отыскать фразу ”Я — Господь”. Но остальные буквы, tmvoordle, дают только тривиальные слова, такие как dolt, drool, looter и lover (болван, слюни, мародер и любовник). И это Реддла не обрадо вало бы.
Если мы оглянемся назад, то нам покажется, что проблема выглядит очевидной: слово Voldemort— французского происхождения и не будет найдено ни в одном файле словаря английского языка. Vol de la mort на французском языке означает ’’полет смерти”, а английское Voldemort в свободном толковании будет означать “смертельный полет”. Но Реддл — стопроцентный англичанин, и до сих пор вы ра ботали с английским языком. Без реконструкции, причин внезапно переключиться с родного английского словаря на французский не больше, чем на голландский, немецкий, итальянский или испанский.
Можно попробовать случайно перетасовать оставшиеся буквы и посмотреть, что выпадет. К сожалению, число возможных комбинаций является факториалом числа букв, деленным на факториал числа повторов (буква о появляется дважды): 9!/2! = 181 440. Если бы вам пришлось прокручивать все эти перестановки, тратя на просмотр каждой из них всего одну секунду, то на то, чтобы закончить просмотр списка, вам потребовалось бы более двух дней! И если бы вы попросили об этом Тома Реддла, то он, вероятно, сделал бы из вас крестраж!1
1 В книгах о Гарри Поттере крестраж — это предмет, в котором заключена часть души темного мага. — Прим. перев.
78 Глава 3
На этом этапе я хотел бы наперед разведать два логических пути. Один я называю “галльским гамбитом”, а другой— ’’британской грубой силой”. Здесь мы рассмот рим первый, а второй — в следующем разделе.
ПРИМЕЧАНИЕ Марволо — это явно сфабрикованное слово, используемое для того, чтобы анаграмма Волдеморта сработала. Джоан Роулинг могла бы получить дополнительные обороты, используя Thomas вместо Т о т либо отбросив такие части, как “Lord” или “I am”. Такие трюки используются, когда книга переводится на неанглийские языки. В некоторых языках может потребоваться изменить одно или оба имени. По-французски анаграмма звучит как “I am Voldemort” (я — Волдеморт). По-норвежски — Voldemort the Great (Вол деморт Великий). По-голландски — “My name is Voldemort” (Меня зовут Волдеморт). В других языках, таких как китайский, использовать анаграмму вообще невозможно!
Том Реддл был одержим победой над смертью, и если вы отправитесь на поиски смерти в tmvoordle, то найдете и старофранцузское слово morte (как в знаменитой книге ”Le Morte d’Arthur” сэра Томаса Мэлори — ’’Смерть Артура”), и современное французское mort. После удаления букв mort остается vodle, пять букв с очень управляемым числом перестановок. На самом деле, можно легко отыскать volde прямо в окне интерпретатора:
О > » from itertools import permutations
» > name = ‘vodle*
О > » perms = [’’.join(i) for i in permutations (name) ] ф » > print (len (perms))
120
О > » print (perms)
‘vdleo’, ’vdeol’
’vledo’, ’veodl’ ‘ovdel’, ‘ovlde’ ’odlev’, ’odevl’ ’oledv’, ’oevdl’ ’dvoel’, ’dvloe’ ‘dolev’, ’doevl’ ’dleov’, ‘devol’ ’lvoed’, ’lvdoe’ ’lodev’, ’loevd’ ’ldeov’, ’levod’ ’evold’, ’evdol’ ’eodlv’, ’eolvd’ ’edlov’, ’elvod’
[’ vodle , ’vodel1, ’volde , ‘voled’, ’voedl’, ‘voeld’,, ’vdole’ , ‘vdoel’, ’vdelo’, ’vlode’, ’vloed’, ‘vldoe’, ’video’, ’vdloe’ ‘veold’, f vedolf, ’vedlo’, ’velod’, ‘veldo’, ’vleod’ ’ovled’, ’ovedl’, ’oveld’, ’odvle’, ‘odvel’, ’ovdle’ ‘odelv’, ‘olvde’, ‘olved’, ‘oldve’, ‘oldev’, ’odlve’ ‘oevld’, ‘oedvl’, ’oedlv’, ‘oelvd’, ‘oeldv’, ’olevd’ ‘dvleo1, ‘dveol’, ‘dvelo*, ’dovle’, ‘dovel’, ’dvole’ ‘doelv*, ’dlvoe’, ‘dlveo’, ‘dlove’, ‘dloev’, ‘dolve’ ’devlo’, ’deovl’, ‘deolv’, ‘delvo*, ‘delov’, ‘dlevo’ ’lvdeo’, ’lveod’, ’lvedo’, ’lovde’, ’loved’, ’lvode’ 1loedv’, ’ldvoe’, 1ldveo’, ’ldove’, ’ldoev’, ’lodve’ ’levdo*, ’leovd’, ’leodv’, ’ledvo’, ’ledov’, ’ldevo’ ’evdlo’, ’evlod’, ’evldo’, ’eovdl’, ’eovld’, ’evodl’ ’eoldv’, ’edvol’, ’edvlo’, ’edovl’, ’edolv’, ’eodvl’ ’elvdo’, ’elovd’, ’elodv’, ’eldvo’, ’eldov’] ’edlvo’ » > » > print (*perms, sep=’\n ) vodle vodel volde vo led voedl –обрезано–
Решение анаграмм 79
Начните с импорта итератора перестановок permutations из модуля itertoois ф . Модуль itertoois — это группа функций в стандартной библиотеке Python, кото рые создают итераторы с целью эффективной циклической обработки. Обычно, рассуждая о перестановках, имеют в виду числа, но версия itertoois работает с итерируемыми типами, которые включают и буквы.
После ввода имени или, в данном случае, остальных букв имени примените опера цию включения в список с целью создания списка перестановок имени ф. Объеди ните каждый элемент в перестановке методом join, так чтобы каждый элемент в окончательном списке был уникальной перестановкой букв vodle. Использование указанного метода дает новое имя как один элемент, ‘vodle’ вместо трудночитае мого кортежа из односимвольных элементов (V, ’o’, ’d’, Т, ’е’).
В качестве проверки получите длину перестановок; благодаря этому вы можете подтвердить, что речь действительно идет о факториале 5 ф. В конце концов неза висимо от того, как вы его напечатаете фф, volde легко найти.
Проект 7: поиски Волдеморта, или британская грубая сила
Теперь давайте предположим, что Том Реддл плохо разбирается в анаграммах (ли бо французском языке). Он не отличает mort от morte, и вы снова тысячи и тысячи раз тасуете оставшиеся девять букв в поисках комбинации букв, которая ему по нравилась бы.
С другой стороны, в программном отношении это задача интереснее, чем интерак тивное решение, которое вы только что увидели. Нужно просто сократить все пере становки, используя некую форму фильтрации.
Ц е л ь
Уменьшить число анаграмм цепочки tmvoordle до управляемого числа, которое по- прежнему будет содержать Voldemort.
Стратегия
Во 2-м издании Оксфордского словаря английского языка в настоящее время ис пользуется 171 476 английских слов, что меньше, чем общее число перестановок в tmvoordle! Независимо от языка можно предположить, что большинство генери руемых функцией permutations () анаграмм являются бессмыслицей.
С помощью криптографии, науки о кодах и шифрах, можно без вреда устранить многие бесполезные, непроизносимые комбинации, такие как Idtmvroeo, и вам даже не придется проверять их визуально. Криптографы уже давно изучают языки и со ставляют статистику по повторяющимся шаблонам слов и букв. Для имплемента ции этого проекта мы могли бы применить огромное число крипто-аналитических приемов, но давайте сосредоточимся на трех: проецировании букв в согласные и гласные, частоте триграмм и частоте диграмм.
80 Глава 3
Фильтрация с помощью проекции в гласные-согласные
Проецирование букв в гласные и согласные (consonant-vowel map, c-v map) в зави симости от обстоятельств просто заменяет буквы в слове на с (consonant — соглас ная) или v (vowel — гласная). Слово Riddle, например, становится cvcccv. Можно написать программу, которая сканирует файл словаря и создает проекцию в глас- ные-согласные для каждого слова. По умолчанию невозможные комбинации, такие как ccccccvvv, будут исключены. Можно также исключить членство, удалив слова с проекциями в гласные-согласные, которые в принципе возможны, но имеют низ кую частоту встречаемости. Проецирование букв в согласные и гласные имеет довольно всеохватывающий ха рактер, но это хорошо. На данный момент вариант для слова Riddle состоит в том, чтобы составить новое имя собственное, а имена собственные не обязательно должны быть как раз теми словами, которые встречаются в словаре. И поэтому вы не хотите слишком ограничивать себя в самом начале процесса.
Фильтрация с помощью триграмм
Поскольку начальный фильтр требует относительно широкой апертуры (проходи мого отверстия), вам придется снова выполнять фильтрацию на более низком уров не для того, чтобы без вреда удалить из перестановок еще больше анаграмм. Три граммы — это триплеты, в состав которых входят три идущие подряд буквы. Неудивительно, что самой распространенной триграммой в английском языке яв ляется слово the, за которым следуют and и ing. На другом конце шкалы находятся такие триграммы, как zvq. Статистику по частоте появления триграмм можно найти в Интернете, в частности по адресу http://norvig.com/ngrams/count_3I.txt. Для любой группы букв, напри мер tmvoordle, можно сгенерировать и применить список наименее распространен ных триграмм, еще больше сократив число перестановок. В этом проекте можно использовать файл least-likely_trigrams.txt, который можно скачать с веб-сайта https://www.nostarch.com/impracticaIpython/, с наименее вероятными триграмма ми. Этот текстовый файл содержит триграммы в tmvoordle, которые встречаются в нижних 10% триграмм английского языка, на основе частоты встречаемости.
Фильтрация с помощью диграмм
Диграммы (также именуемые биграммами) — это пары букв. Широко встречаю щиеся диграммы английского языка включают ап, st и ег. С другой стороны, редко встречаются такие пары, как kg, vl или oq. Статистику по частоте появления ди грамм можно найти на таких веб-сайтах, как https://www.math.corneII.edu/ ~mec/2003-2004/cryptography/subs/digraphs.html и http://practicaIcryptography.com/. Таблица 3.1 была построена из коллекции букв слова tmvoordle и файла словаря английского языка на 60 тыс. слов. Буквы в левой части диаграммы являются на чальными буквами диграмм; расположенные вверху представляют конечную букву. Например, для отыскания vo начните с v слева и читайте поперек до столбца под о. В случае диграмм в слове tmvoordle сочетание vo встречается только в 0,8% слу чаев.
Решение анаграмм 81
Таблица 3.1. Относительная частота диграмм из букв слова tmvoordle в словаре на 60 ООО слов (черные квадраты указывают на отсутствие вхождений)
d
6,6%
0,4%
0,0%
1,5%
0,9%
0.0%
0,0%
d
е
I
m
о
г
t
V
е
3,5%
4,4%
2,2%
0,5%
6,0%
6,2%
2,5%
I
0,5%
2,3%
0,0%
3,7%
0,4%
0,6%
0,0%
m
0,1%
1,4%
0,1%
3,2%
0,7%
0,1%
0,0%
о
1,7%
0,7%
4,2%
2,8%
5,3%
5,7%
3,6%
0,8%
г
0,5%
8,9%
0.0%
0,0%
7,1%
2,3%
0,0%
t
0,0%
2,0%
0,4%
0.0%
2,4%
1,3%
0,0%
v
0,1%
0,6%
0,1%
0,0%
1,4%
0,3%
0,0%
Если исходить из того, что вы ищете комбинации букв, которые являются “англо- подобными”, то подобного рода частотные проекции можно использовать с целью исключить пары букв, которые вряд ли встретятся. Думайте об этом как о “ди- граммном решете”, которое пропускает только незатененные квадраты.
Для подстраховки исключите диграммы, которые встречаются просто менее 0,1% случаев. Я затенил их черным цветом. Обратите внимание, что если вы будете ре зать слишком близко к кости, то можно очень легко устранить нужное сочетание букв vo в слове Voldemort! Свой фильтр можно сделать еще более избирательным, пометив диграммы, кото рые вряд ли встретятся в начале слова. Например, хотя нет ничего необычного в том, что диграмма 1т появится внутри слова (как в словах almanac и balmy, на пример), вам понадобится немало удачи для того, чтобы отыскать слово, которое будет начинаться с 1т. Для того чтобы отыскать эти диграммы, вовсе не нужна криптография; просто попробуйте произнести их! В табл. 3.2 некоторые отправные варианты для них затенены серым цветом.
Таблица 3.2. Обновление табл. 3 .1, где серые затененные квадраты указывают на то, что диграммы вряд ли появятся в начале слова
d
6,6%
0,4%
0,0%
1,5%
0,9%
0,0%
0,0%
d
е
1
m
о
г
t
V
е
3,5%
4,4%
2,2%
0,5%
6,0%
6,2%
2,5%
I
0,5%
2,3%
0,0%
3,7%
0,4%
0,6%
0,0%
m
0,1%
1,4%
0,1%
0,1%
3,2%
0,7%
0,1%
0,0%
о
1,7%
0,7%
4,2%
2,8%
5,3%
5,7%
3,6%
0,8%
г
0,5%
8,9%
0,0%
0,0%
7,1%
2,3%
0.0%
t
0,0%
2,0%
0,4%
0.0%
2,4%
1,3%
0,0%
V
0,1%
0,6%
0,1%
0,0%
1,4%
0,3%
0,0%
82 Глава 3
Теперь у вас есть три фильтра, которые можно применить к 181 440 перестановкам слова tmvoordle: проекции в гласные-согласные, триграммы и диграммы. В качест ве последнего фильтра вы должны предоставить пользователю возможность про смотра только тех анаграмм, которые начинаются с заданной буквы. Это позволит пользователю разделить оставшиеся анаграммы на более управляемые “куски” ли бо сосредоточиться на тех анаграммах, которые звучат более пугающе, таких, кото рые начинаются с v!
Код с использованием британской грубой силы
Приведенный ниже программный код генерирует перестановки слова tmvoordle и пропускает их через только что описанные фильтры. Затем он дает пользователю возможность просмотреть либо все перестановки, либо только те из них, которые начинаются с заданной буквы.
Все программы, которые вам понадобятся, можно скачать с https://www. nostarch.com/impracticaIpython/. Программный код этого раздела представлен од ним скриптом с именем voldemort british.py. Вам также понадобится программа load dictionary.py в той же папке вместе с тем же файлом словаря, который вы ис пользовали в предыдущих проектах этой главы. Наконец, вам потребуется новый файл с именем least-likely_trigrams.txt — текстовый файл триграмм с низкой часто той встречаемости в английском языке. Скачайте все эти файлы в одну папку.
Определение функции mainQ
Листинг 3.5 импортирует модули, необходимые для программы voldemort_ british.py, и определяет свою функцию main (). В программе phrase anagrams.py вы определили функцию main () в конце кода. Здесь мы поместили ее в начало. Пре имущество заключается в том, что вы с самого начала видите, что конкретно функ ция делает — как она выполняет программу. Недостатком является то, что вы еще не знаете, что делают все вспомогательные функции.
Листинг 3.5. Импортирует модули и определяет функцию m a in (). Файл voldemort_british.py, часть 1
О import sys
from itertoois import permutations from collections import Counter
import load_dictionary
Q def m a i n ():
“““Загрузить файлы, выполнить фильтры, показать пользователю
анаграммы по 1-й букве.”””
ф name = ’tmvoordle’ name = name.lower()
О word_list_ini = load_dictionary.load(‘2of4brif.txt’)
trigrams_filtered = load_dictionary.load(’least-likely_trigrams.txt’)
Решение анаграмм 83
ф word_list = prep_words(name, word_list_ini)
filtered_cv_map = cv_map_words(word_list)
filter_l = cv_map_fiIter(name, filtered_cv_map)
filter_2 = trigram_filter(filter_l, trigrams_filtered)
filter_3 = letter_pair_filter(filter_2)
view_by_letter(name, filter_3)
Начните с импорта модулей, которые вы использовали в предыдущих проектах ф . Теперь определите функцию main о ф . Переменная name представляет собой це почку из остальных букв слова tmvoordle ф . Переведите ее в нижний регистр, для того чтобы защититься от ошибки пользователя при вводе. Затем с помощью моду ля load dictionary загрузите файл словаря и файл триграмм в виде списков ф . Имя файла вашего словаря может отличаться от показанного.
Наконец, вызовите все функции по порядку ф . Вскоре я опишу каждую из этих функций, но, в сущности, вам нужно подготовить список слов, подготовить проек ции в гласные-согласные, применить три фильтра и дать пользователю увидеть все анаграммы целиком либо просмотреть их подмножество на основе первой буквы анаграммы.
Подготовка списка слов
Листинг 3.6 подготавливает список слов, включая только те слова, которые содер жат столько же букв, сколько и в переменной name (в данном случае девять). В це лях поддержки единообразия слов вы также должны обеспечить, чтобы все слова находились в нижнем регистре.
Листинг 3.6. Создание списков слов, длина которых равна переменной паше. Файл voldemort_.british.py, часть 2
ф def prep_words(name, word_list_ini):
“““Подготовить список слов для отыскания анаграмм.”””
ф print(“длина первоначального списка word_list = {}”.
format(len(word_list_ini)))
1en_name = 1e n (name)
ф word_list = [word.lower() for word in word_list_ini
if len(word) == len_name]
ф print(“длина нового списка word_list = {}".format(len(word_list)))
ф return word_list
Определите функцию prep words (), которая в качестве аргументов принимает сим вольную цепочку с именем и список слов словаря ф . Я предлагаю вам печатать длины ваших списков слов до и после того, как они прошли через фильтр; благода ря этому вы сможете отслеживать степень воздействия фильтров. Поэтому напеча тайте длину словаря ф . Задайте переменную для хранения длины имени, а затем
84 Глава 3
примените операцию включения в список для создания нового списка путем пере бора слов в word list ini, сохраняя те из них, длина которых совпадает с числом букв в имени, и их конвертации в нижний регистр 0 . Затем напечатайте длину это го нового списка слов О и> наконец, верните этот новый список для использования в следующей функции 0.
Генерирование проекции в гласные-согласные
Вам нужно конвертировать подготовленный список слов в проекцию в гласные- согласные (c-v map). Напомню, что вы больше не заинтересованы в реальных сло вах из словаря; они были рассмотрены и отклонены. Ваша цель состоит в том, что бы тасовать оставшиеся буквы до тех пор, пока они не образуют нечто напоми нающее имя собственное.
Листинг 3.7 определяет функцию, которая для каждого слова в word iist создает проекции в гласные-согласные. Программа voldemortbritish.py будет использовать проекцию в гласные-согласные для вынесения решения о том, является ли перета сованная комбинация букв разумной, на основе шаблонов следования согласных и гласных в английском языке.
Листинг 3.7. Создает проекции в гласны е-согласны е из слов в word_list. Файл voldemort_british.py, часть 3
0 de f сv_map_wo гd s (wo гd_li s t ):
“““Спроецировать буквы слов в согласные и гласные.”””
0 vowels = ‘aeiouy’ 0 cv_mapped_words = [ ] 0 for word in word_list: temp = ’’
for letter in word:
if letter in vowels:
temp += ’v '
else:
temp += ‘с '
cv_mapped_words.append(temp)
определить число УНИКАЛЬНЫХ шаблонов согласная-гласная #
0 total = len(set(cv_mapped_words))
целевая доля устраняемых #
0 target = 0.05
получить число элементов в целевой доле #
0 n = int(total * target) 0 count_pruned = Counter(cv_mapped_words).most_common(total - n) 0 filtered_cv_map = set()
for pattern, count in count_pruned:
filtered_cv_map.add(pattern)
Решение анаграмм 85
print(“длина множества filtered_cv_map = {}”.
format(len(filtered_cv_map)))
0 return filtered_cv_map
Определите функцию cv map words (), которая в качестве аргумента принимает го товый список слов ф . Поскольку согласные и гласные образуют двоичную систе му, вы можете определить гласные с помощью символьной цепочки 0. Создайте пустой список для хранения проекций 0. Затем в цикле переберите слова из списка и буквы в каждом слове, конвертируя буквы в с или v 0. Используйте переменную temp для накопления проекции; затем добавьте ее в список. Обратите внимание, что temp инициализируется повторно всякий раз, когда цикл повторяется. Вы хотите знать частоту появления заданного шаблона проекции (например, cvcv) с целью устранения тех из них, которые имеют низкую вероятность появления. Пе ред вычислением частоты вам нужно свести свой список к уникальным проекци ям — как и сейчас, цепочка cvcv может повторяться очень много раз. Поэтому пре образуйте список cv mapped words в множество, удалив дубликаты, и получите его длину 0. Теперь вы можете определить целевой процент устраняемых элементов, используя дробные значения 0. Начните с низкого числа, такого как 0,05 — экви валента 5%, — благодаря такому значению вы с меньшей вероятностью исключите анаграммы, которые могут образовывать полезные имена собственные. Умножьте это целевое значение на суммарную длину множества cv mapped words и назначьте результат переменной п 0. Обязательно сконвертируйте п в целое число; оно не может быть вещественным, т. к. оно будет представлять значение количества. Модуль count имеет удобный метод most common (), который возвращает наиболее распространенные элементы в списке на основе значения количества, которое вы предоставите; в данном случае это значение будет длиной списка проекций в глас- ные-согласные, total, минус п. Передаваемое вами в функцию most common () зна чение должно быть целым числом. Если передать в функцию most common () длину списка, то она вернет все элементы списка. Если вычесть количество наименее ве роятных 5%, то вы практически устраните эти проекции из списка 0. Напомню, что объект count возвращает словарь, но вам нужны лишь окончатель ные проекции в гласные-согласные, а не ассоциированные с ними частотности (частотные количества). Поэтому инициализируйте пустое множество с именем fiitered cv map 0 и в цикле переберите каждую пару ’’ключ— значение” в count pruned (), добавляя в новое множество только ключ. Напечатав длину этого множества, вы увидите влияние фильтра. Затем завершите действия, вернув от фильтрованную проекцию для использования в следующей функции 0.
Определение фильтра проекций в гласные-согласные
В листинге 3.8 применяется фильтр проекций в гласные-согласные: на основе каж дой перестановки букв в переменной name генерируются анаграммы, а затем про грамма конвертирует их в проекции в гласные-согласные и сравнивает эти ана граммы с отфильтрованными проекциями, построенными с помощью функции cv map words (). Если проекция анаграммы находится в fiitered cv map, то про грамма сохраняет анаграмму для следующего фильтра.
8 6
Глава 3
Листинг 3.8. Определяет функцию cv_map_filter (). Файл voldemort_british.py, часть 4
О def cv_map_filter(name, filtered_cv_map):
“““Удалить перестановки слов на основе маловероятных
комбинаций гласные-согласные.”””
0 perms = {’’.join(i) for i in permutations(name)}
print(“длина первоначального множества перестановок = {}”.
format(len(perms)))
vowels = ’aeiouy’
6 filter_l = s e t ()
О for candidate in perms:
temp = ‘’
for letter in candidate: if letter in vowels:
temp += ’v '
else:
temp += ‘с ’
О if temp in filtered_cv_map:
filter_l.add(candidate)
print(”# вариантов после фильтра filter_l = {}”.
format(len(filter_l)))
0 return filter_l
Определите функцию cv map f ilter (), принимающую два аргумента: имя, за которым следует набор проекций в гласные-согласные, возвращаемых из cv map words о 0. Примените операцию включения в множество и итератор permutations для генери рования множества перестановок о Этот процесс описан в разд. “Проект 6: поис ки Волдеморта, или галльский гамбит”ранее в этой главе. Используйте здесь мно жество, для того чтобы позже иметь возможность применять операции над множе ствами, например взятие разницы между двумя отфильтрованными множествами. Указанная структура данных также удаляет дубликаты, т. к. перестановки рассмат ривают каждую букву о как отдельный элемент и вместо 9!/2! возвращают 9!. Обратите внимание, что итератор permutations рассматривает слова tmvoordle и tmvoordle как разные символьные цепочки.
Теперь инициализируйте пустое множество для хранения содержимого первого фильтра 0 и начните перебирать перестановки в цикле О - Используйте термин “кандидат”, т. к. большинство из них являются не словами, а просто символьными цепочками из случайных букв. Пройдите буквы каждого кандидата в цикле и спроецируйте их в с или v, как это было сделано в функции cv map words (). Про верьте каждую проекцию temp на членство В fiitered cv map. Это одна из причин использовать множества: проверка вхождения в множества выполняется молние носно. Если кандидат соответствует условию, то добавьте его в filter 1 0 . Завер шите обработку, вернув свое новое множество анаграмм 0.
Решение анаграмм 87
Определение триграммного фильтра
Листинг 3.9 определяет триграммный фильтр, который удаляет перестановки с ма ловероятными трехбуквенными триплетами. Он использует текстовый файл, полу ченный из различных криптографических веб-сайтов, который был адаптирован к буквам в слове tmvoordle. Эта функция будет возвращать только те перестановки, которые содержат одну из этих триграмм; функция main () передаст новое множест во следующей фильтровочной функции.
Листинг 3.9. Определяет функцию trigram_filter (). Файл voldemort_british.py, часть 5
О def trigram_filter(filter_l, trigrams_filtered):
“““Удалить маловероятные триграммы из перестановок.”””
0 filtered = set() 0 for candidate in filter_l:
О f°r triplet in trigrams_filtered:
triplet = triplet.lower()
if triplet in candidate:
filtered.add(candidate) 0 filter_2 = filter_l - filtered
print(”# вариантов после фильтра filter_2 = {}”.
format(len(filter_2)))
О return filter_2
Параметры для триграммного фильтра включают выход из фильтра проекций в гласные-согласные и внешний список маловероятных триграмм, trigrams filtered О *
Инициализируйте пустое множество для хранения перестановок, содержащих одну из запрещенных триграмм 0 . Затем начните еще один цикл for, который просмат ривает кандидатов, переживших предыдущий фильтр 0 . Вложенный цикл for про сматривает каждый триплет в списке триграмм 0 . Если триплет находится в кан дидате, то он добавляется в фильтр.
Теперь можно применить операции над множествами с целью вычесть новый фильтр из fiiter i 0, а затем вернуть разницу для использования в следующем фильтре 0.
Определение диграммного фильтра
В листинге 3.10 определен диграммный фильтр, который удаляет маловероятные буквенный пары. Некоторые будут активировать фильтр, если они появляются в любом месте в пределах перестановки; другие будут делать это только в том слу чае, если они появляются в начале перестановки. Запрещенные диграммы основаны на затененных ячейках в табл. 3.2. Указанная функция возвращает результаты этого фильтра для использования в конечной фильтровочной функции.
8 8
Глава 3
Листинг 3.10. Определяет функцию letter_pair_filter (). Файл voldemort_british.py, часть б
О clef letter_pair_f ilter (filter_2) :
"”” Удалить маловероятные буквенные пары из перестановок.11"”
0 filtered = set()
Q rejects = [‘dt’, ’lr’, ‘md’, ‘ml1, ‘mr’, ‘mt’, ‘mv’,
’ td’, 1 tv1f ’ vd’, ‘vl’, ‘vm’, ‘vr’, ‘vt’]
О first_pair_rejects = [‘Id’, ’lm’, ’It’, ’lv’, ‘rd’,
’ rl’, ‘rm’, ‘rt’, ‘rv’, ’ tl’, ’tm’]
О for candidate in filter_2:
Q for r in rejects:
if r in candidate:
filtered.add(candidate)
Q for fp in first_pair_rejects: if candidate.startswith(fp):
filtered.add(candidate)
О filter_3 = filter_2 - filtered
print(”# вариантов после фильтра filter_3 = {}”.
format(len(filter_3)))
О if ‘voldemort’ in filter_3:
print(“Волдеморт найден!”, file=sys.stderr)
ф return filter_3
Указанный фильтр в качестве аргумента принимает результаты предыдущего фильтра ф . Пустое множество инициализируется для хранения любых отброшен ных перестановок О . Затем два списка отклоненных пар назначаются переменным rejects О и first pair rejects О - Оба списка были введены вручную. Первый представляет ячейки табл. 3.2, затененные черным цветом; второй ссылается на ячейки, затененные серым цветом. Любая перестановка, содержащая член первого списка — в любом месте, — будет отброшена; перестановки, начинающиеся с чле на второго списка, не будут допущены. Эти списки можно изменить, добавив либо удалив диграммы, тем самым изменив поведение фильтра.
Начните перебирать перестановки — продолжайте называть их ’’кандидатами”, по скольку они не обязательно являются словами 0 . Вложенный цикл for проходит пары В rejects, Определяет, находятся ЛИ ОНИ В candidate, и добавляет их в множе ство filtered О - Второй вложенный цикл for повторяет этот процесс для first pair rejects О . Вычтите filtered ИЗ множества filter 2, возвращенного предыдущей функцией ф .
Ради удовольствия и в целях обеспечения того, чтобы вы не переусердствовали с фильтрованием, проверьте наличие слова voldemort в filter з 0 и распечатайте объявление с выделенным найденным словом, используя привлекательный крас ный шрифт для пользователей интерпретатора IDLE. Затем завершите, вернув окончательное МНОЖеСТВО filtered
Решение анаграмм 89
Предоставление возможности выбирать начальную букву
Вы не можете сказать заранее, будет ли ваша фильтрация успешной или нет. Вы по-прежнему можете получить тысячи перестановок. Возможность наблюдать только подмножество результатов не уменьшит совокупное их число, но зато пси хологически облегчит их восприятие. Листинг 3.11 добавляет в программу voldemort british.py возможность просмотра списка анаграмм, которые начинаются с определенной входной буквы.
Листинг 3.11. Определяет функцию view_by_letter (). Файл voldemort_british.py, часть 7
О clef view_by_letter(паше, filter_3) :
“““Отфильтровать анаграммы, начинающиеся с входной буквы.”””
ф print(“Остальные буквы = {}".format(паше))
О
first = input(“выберите стратовую букву либо нажмите Enter,
чтобы увидеть все: “)
О subset = []
ф for candidate in filter_3:
if candidate.startswith(first):
subset.append(candidate)
ф print(*sorted(subset), sep=’\n’)
print(“Число вариантов, начинающихся с {} = {}”.
format(first, len(subset)))
msg = “Попробуете еще раз? (Нажмите Enter либо любую другую клавишу
для выхода):”
О try_again = input(msg)
if try_again.lower() == '
ф view_by_letter(name, filter_3)
else:
ф sys.exit()
Определите функцию view by ietter (), которая в качестве аргументов принимает переменную name и фильтр filter з ф . Вам нужно имя, поэтому вы можете пока зать пользователю имеющиеся варианты букв, по которым производится фильтра ция ф . Получите от пользователя ответ относительно того, хочет ли он видеть все остальные перестановки целиком либо только те, которые начинаются с опреде ленной буквы ф . Затем создайте пустой список, который будет хранить подмноже ство второго варианта ф .
Цикл for с условием проверяет, что кандидат начинается с выбранной буквы, и добавляет те буквы, которые передаются в подмножество subset ф . Этот список печатается с помощью унарного оператора ’’звездочка” ф . Затем программа спра шивает пользователя, хочет ли он повторить попытку либо выйти из программы ф . Если он нажимает клавишу , то view by letter () вызывается рекурсивно и
90
Глава 3
запускается снова с самого начала ф. В противном случае программа завершает работу ф. Обратите внимание, что в Python по умолчанию глубина рекурсии огра ничена 1000 вызовами, и данное ограничение в этом проекте мы проигнорируем.
Выполнение функции mainQ
Вернувшись в глобальное пространство, листинг 3.12 завершает код, вызывая функцию maino, если пользователь запускает программу в автономном режиме в отличие от импорта в другую программу.
Листинг 3.12. Вызов функции main ()
if паше == ‘main’ :
main ()
Пример результата, полученного из завершенной программы, показан ниже. После того как программа применила третий фильтр, осталось 248 перестановок, из кото рых очень управляемые 73 начинаются с v. Для краткости распечатка перестановок опущена. Как видно в полученном результате, слово voldemort после фильтрации остается.
длина первоначального списка word_list = 60388
длина нового списка word_list = 8687
длина множества filtered_cv_map = 234
длина первоначального множества перестановок = 1814 40
вариантов после фильтра filter_l = 123120 #
вариантов после фильтра filter_2 = 6 7 4 #
вариантов после фильтра filter_3 = 248 #
Волдеморт найден!
Остальные буквы = tmvoordle
выберите стратовую букву либо нажмите Enter, чтобы увидеть все: v
Интересно, что еще одной сохранившейся перестановкой является lovedmort (любящий смерть). С учетом того, сколько людей Волдеморт погубил (или мог бы погубить), это прозвище, возможно, будет самым подходящим из всех.
Резюме
В этой главе вы сначала написали код, который отыскал анаграммы для заданного слова или имени. Затем вы его расширили с целью отыскания анаграмм именных словосочетаний, работая в интерактивном режиме с пользователем. Наконец, вы применили крипто-аналитические методы для выуживания Волдеморта почти из 200 тыс. возможных анаграмм. По пути вы применили полезный функционал из модулей collections И itertoois.
Решение анаграмм 91
Дальнейшее чтение
Веб-сайт игры Jumble находится по адресу http://www.jumble.com/.
Несколько представительных онлайновых генераторов анаграмм можно найти на веб-сайтах по следующим адресам:
♦ http://wordsmith.org/anagram/;
♦ https://www.dcode.fr/anagram-generator;
♦ http://www.wordplays.com/anagrammer/.
Другие анаграммные программы представлены в книге Аллена Дауни “Думай по- питоновски” (Downey A. Think Python. O’Reilly, 2015).
Книга Эла Свейгарта “Взламывание кодов с помощью Python” (Sweigart A. Cracking codes with Python. No Cramp Press, 2018) предоставляет еще несколько примеров исходного кода для вычисления шаблонов слов, таких как те, которые используют ся для фильтрации в программе voldemort british.py.
Практический проект: поиск диграмм
В поисках частотной статистики можно прочесать криптографические веб-сайты либо вывести ее самим. Напишите программу Python, которая находит все диграм мы в слове tmvoordle, а затем подсчитывает их частоту появления в файле словаря. Обязательно протестируйте свой программный код на таких словах, как volvo, с тем чтобы не упустить из виду повторяющиеся диграммы в одном и том же слове. Решение данного проекта можно найти в приложении к книге либо скачать про грамму count_digrams_practice.py с веб-сайта
https://www.nostarch.com/impracticalpython/.
Сложный проект: автоматический генератор анаграмм
Взгляните на онлайновые генераторы анаграмм, на которые я только что сослался в разд. “Дальнейшее чтение “, и напишите программу на языке Python, которая ими тирует один из них. Ваша программа должна автоматически генерировать фразо вые анаграммы из входного имени и выводить на экран их подмножество (напри мер, первые 500) для просмотра пользователем.
4
ДЕКОДИРОВАНИЕ ШИФРОВ ВРЕМЕН ГРАЖДАНСКОЙ ВОЙНЫ В США
Криптография — это наука о безопасном общении посред ством использования кодов и шифров. Код заменяет целые слова другими словами; шифр беспорядочно перемешивает (скремблирует) или заменяет буквы в словах (поэтому техниче ски азбука Морзе, или морзянка, — это в действительности шифр Морзе). Одной из целей криптографии является использование ключа для шифров ки читаемого открытого текста в нечитаемый шифротекст, а затем расшифровки его обратно в открытый текст. Цель криптоанализа — расшифровать шифры и ко ды, не зная их ключа или алгоритма шифрования.
В этой главе мы рассмотрим два шифра, известных во времена Гражданской войны в США: маршрутный шифр, использовавшийся Севером, и зигзагообразный шифр (шифр жердевой изгороди), использовавшийся обеими сторонами. Мы также выяс ним, что сделало один из них таким успешным и как мы можем задействовать уро ки, извлеченные из его применения, для того чтобы более качественно писать про граммы для неопытных пользователей и тех, кто не знаком с вашим программным кодом на Python.
Проект 8: маршрутны й шифр
Во время Гражданской войны в Америке Союзники имели почти все преимущества перед Конфедератами, в том числе в области криптографии. У Союза были лучшие коды, лучшие шифры и лучше обученный персонал. Но, пожалуй, самое большое его преимущество заключалось в лидерстве и организации.
Декодирование шифров времен Гражданской войны в США 93
Главой военного телеграфного управления США был Энсон Стейджер (рис. 4.1). Как соучредитель телеграфной компании Western Union, Стейджер знал по опыту, что телеграфисты делают меньше ошибок при отправке целых слов, в отличие от цепочек случайных букв и цифр, общепринятых для большинства шифротекстов. Он также знал, что для того чтобы приказы были выполнены, военные депеши должны оставаться в секрете достаточно долгое время. Его безопасным решением была гибридная криптосистема, именуемая маршрутным перестановочным шиф ром (route transposition cipher — дословно шифр перестановки маршрута), т. е. ком бинация переставленных реальных слов и кодовых слов, который стал одним из самых успешных военных шифров всех времен.
Рис. 4.1. Генерал Энсон Стейджер, телеграфный корпус США, 1865 г.
В отличие от подстановочных шифров, которые заменяют буквы в открытом тек сте различными знаками или символами, перестановочные шифры беспорядочно перемешивают (скремблируют) расстановку букв или слов. На рис. 4.2 показан пример маршрутного перестановочного шифра. Сообщение записывается слева на право по ряду предопределенных столбцов и строк, важные слова открытого текста заменяются кодовыми словами, а последняя строка заполняется фиктивными сло- вами-заполнителями. Читатель определяет порядок переупорядоченных слов, про ходя вверх и вниз по этим столбцам, как показано на рисунке. Начальным словом является REST (ОТДЫХ), а дальнейший маршрут шифрования показан стрелками.
Для того чтобы полностью расшифровать это сообщение, вам нужно знать отправ ную точку и маршрут, используемый для прохождения по сообщению и создания окончательного шифротекста, и смысл кодовых слов.
В начале XX в. выдающийся военный криптоаналитик Уильям Фридман отзывался о маршрутном шифре Стейджера пренебрежительно. Он считал его слишком бес хитростным и находил крайне маловероятным, что конфедераты его так и не взло мали. Но факт остается фактом: сотни тысяч маршрутных шифров, отправленных
Code Words
Кодовые слова
VILLAGE = Enemy
ROANOKE = Cavalry
GODWIN = Tennessee
SNOW = Rebels
ДЕРЕВНЯ = Вражеский
РОАНОК = Кавалерия
ГОДВИН = Теннесси
СНОУ = Мятежники
Original Message in Encryption Matrix
Исходное сообщение в матрице шифрования
Enemy
Tennessee
you
transport
cavalry
With
are
your
heading
Rebels
free
supplies
to
gone
to
south
Вражеская
Теннесси
Вы
кавалерия
направляется
в
Со
Мятежниками
ушли
свободно
можете
транспортировать
свое
снаряжение
на юг
Encryption Route + Code & Dummy Words
Маршрут шифрования + код и фиктивные слова
VILLAGE
GODWIN
you
transport
REST
ROANOKE
With
are
your
IS
heading
SNOW
free
supplies
JUST
to -
gone
to
south
FILLER
ДЕРЕВНЯ
ГОДВИН
вы
РОАНОК
направляется
Со
СНОУ
в ушли
свободно
можете
транспортировать
ваше
снаряжение
на юг
ОТДЫХ
ЭТО
ПРОСТО
ЗАПОЛНИТЕЛЬ
Cyphertext
Шифротекст
REST TRANSPORT YOU GODWIN VILLAGE ROANOKE WITH ARE YOUR IS JUST SUPPLIES FREE SNOW HEADING TO GONE TO SOUTH FILLER
ОТДЫХ ТРАНСПОРТИРОВАТЬ ВЫ ГОДВИН ДЕРЕВНЯ РОАНОК СО МОЖЕТЕ ВАШИ ПРОСТО СНАРЯЖЕНИЯ СВОБОДНО СНОУ НАПРАВЛЯЕТСЯ К УШЛИ НА ЮГ ЗАПОЛНИТЕЛЬ
Рис. 4.2. Марш рутны й шиф р, в котором используются ф актические кодовые слова союзников
Декодирование шифров времен Гражданской войны в США 95
во время войны, по-видимому, так и не были расшифрованы, и не от недостатка попыток. В одном из ранних примеров краудсорсинга конфедераты опубликовали закодированные сообщения в газетах, надеясь хоть на какую-то помощь в расшиф ровке, но эта попытка оказалась безрезультатной. Хотя историки предполагают, что данный шифр иногда удавалось взламывать, схема Стейджера преподает несколько важных уроков.
♦ Проектирование с учетом человеческой ошибки. Военные шифры должны быть простыми, т. к. в день могут отправляться сотни. Используемые в мар шрутном шифре настоящие слова делали гораздо менее вероятным то, что он будет искажен телеграфистами. Стейджер знал своих клиентов и на них рассчи тывал. Он осознавал ограниченности своей рабочей силы и соответствующе подстраивал свое изделие. Конфедераты, напротив, расшифровывали свои сложные сообщения с большим трудом, иногда бросая их и объезжая вокруг вражеских линий, для того чтобы поговорить лицом к лицу!
♦ Инновация имеет преимущество перед изобретением. Иногда не нужно изо бретать что-то новое, просто нужно заново открыть что-то старое. Короткие шифры перестановки слов, пригодные для телеграфной передачи, были слиш ком слабы, для того чтобы использовать их самостоятельно, но в сочетании с кодовыми именами и дезориентирующими фиктивными словами они сбивали с толку конфедератов.
♦ Обмен опытом. Поскольку в телеграфном корпусе все использовали одну и ту же методологию, было легко опираться на существующие решения и делиться извлеченными уроками. Это позволило маршрутному шифру с течением време ни эволюционировать за счет введения сленга и преднамеренных опечаток, а также растущего числа кодовых слов для топографических мест, людей и дат.
Практичный шифр Стейджера, возможно, не понравился более поздним “перфек ционистам”, но для того времени это схема была идеальной. Идеи, лежащие в его основе, являются вечными и легко переносятся в современные приложения.
ш ш т> l i s t _ o f _ l i s t s = [ [ ’ 1 6 ’ , ’ 12 ’ , ’ 8 ’ , ’ 4 ’ , ’O’ ] , [ ’ I ’ , ’5 ’ , ’ 9 ’ , ’ 1 3 ’ , ’ 1 7 ’ ] , [ ’1 8 4
’ 7 \ 4 1 ’ ,
’ 1 9 ’ ] ]
’ 1 5 ’ ,
’ 1 0 ’ ,
’ 2 ’ ] ,
[ ’ 3 ’ ,
’ 14 ’ ,
’ б ’ ,
Элементы в этом новом списке теперь представляют списки— причем каждый список представляет столбец, — и пять элементов в каждом списке представляют строки, составляющие этот столбец. Это немного трудно представить, поэтому да вайте напечатаем каждый из этих вложенных списков на отдельной строке:
» > for i in range(len(list_of_lists)): print(list_of_lists[i])
(16, 12, 8, 4, 0] [1, 5, 9, 13, 17]
[18, 14, 10, 6, 2]
[3, 7, 11, 15, 19]
Если вы читаете каждый список слева направо, начиная сверху, то следуете по пе рестановочному маршруту, который был вверх и вниз по чередующимся столбцам (см. рис. 4.3). С точки зрения Python первый столбец обозначается как lis t-o f - lists [0] И отправной ТОЧКОЙ является list-of-lists [0] [0].
Теперь нормализуем маршрут, считывая все столбцы в том же направлении, что и начальный столбец (вверх). Это требует изменения порядка следования элементов в каждом втором списке, как показано здесь жирным шрифтом:
[16, 12, 8, 4, 0] [17, 13, 9, 5 , 1] [18, 14, 10, 6, 2] [19, 15, 11, 7 , 3]
98
Глава 4
Возникает шаблон. Если вы начинаете справа вверху и читаете каждый столбец вниз, заканчивающийся слева внизу, то числа находятся в числовом порядке; вы восстановили открытый текст! Для того чтобы это повторить, скрипт может прокрутить в цикле каждый вложен ный список, удаляя в этом списке последний элемент и добавляя его в новую це почку до тех пор, пока переводная матрица не будет опустошена. Скрипт будет знать из ключа, какие вложенные списки ему нужно обратить, и порядок, в котором нужно опустошать матрицу. Результатом будет цепочка с восстановленным откры тым текстом:
’О 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19’
Теперь у вас должно быть очень общее представление о стратегии. Давайте опишем ее подробнее, а затем напишем псевдокод.
Псевдокод
Скрипт можно разбить на три главные части: ввод данных пользователем, заполне ние переводной матрицы и расшифровка в открытый текст. Вы должны увидеть эти части в следующем ниже псевдокоде:
Загрузить символьную цепочку с шифротбкстом. Конвертировать шифротекст в шифросписок, разбив его на отдельные слова. Получить входные данные с числом столбцов и строк. Получить входные данные с ключом. Конвертировать ключ в список, разбив на отдельные числа. Создать новый список для переводной матрицы. Для каждого числа в ключе:
Создать новый список и добавить п элементов (п = число строк) из шифросписка. Использовать знак числа ключа, для того чтобы решить, как следует читать строку матрицы: вперед либо назад. Используя выбранное направление, добавить в матрицу новый список. Индекс каждого нового списка зависит от номера столбца, используемого в ключе.
Создать новую символьную цепочку для хранения результатов перевода. Для диапазона строк матрицы:
Для вложенного списка в переводной матрице:
Удалить последнее слово во вложенном списке Добавить слово в цепочку с переводом. Напечатать символьную цепочку с переводом.
Все, что предшествует первому циклу, по сути просто собирает и переформатирует шифровальные данные. Первый цикл отвечает за построение и заполнение матри цы, а второй создает из этой матрицы символьную цепочку с переводом. Наконец, цепочка с переводом печатается.
Код расшифровки маршрутного шифра
Листинг 4.1 принимает сообщение, закодированное маршрутным шифром, число столбцов и строк в перестановочной матрице и ключ, а затем показывает переве
Декодирование шифров времен Гражданской войны в США 99
денный открытый текст. Он будет расшифровывать все “распространенные” мар шрутные шифры, где маршрут начинается в верхней или нижней части столбца и продолжается вверх и/или вниз столбцов. Эта версия является прототипной; после того как вы убедитесь, что она работает, вы упакуете ее для других пользователей. Указанный код можно скачать с веб сайта https://www.nostarch.com/impracticaIpython/.
Листинг 4.1. Код расшифровки маршрутного шифра route_cipher_decrypt_prototype.py
ф ciphertext = “16 12 8 4 О 1 5 9 13 17 18 14 10 6 2 3 7 11 15 19”
разбить элементы на слова, не на буквы #
0 cipherlist = list(ciphertext.split()) 0 # инициализировать переменные
COLS = 4 ROWS = 5 key = ’ - 1 2 - 3 4 ’ # отрицательное число означает чтение ВВЕРХ столбца,
а не ВНИЗ #
translation_matrix = [None] * COLS plaintext = ’’ start = 0 stop = ROWS
превратить key_int в список целых чисел: #
О key_int = [int(i) for i in key.split()]
превратить столбцы в элементы списка списков: #
0 for k in key_int:
0 if к 0: # читать в столбце сверху вниз
col_items = list((reversed(cipherlist[start:stop])))
translation_matrix[abs(k) - 1] = col_items start += ROWS stop += ROWS
print(“Хпшифротекст = {}".format(ciphertext)) print("\ппереводная матрица =”, *translation_matrix, sep=M\n”)
print("\пдлина ключа = {}".format(len(key_int)))
обойти в цикле вложенные списки, передавая последний элемент #
в новый список: #
0 for i in range(ROWS):
for col_items in translation_matrix:
0 word = str(col_items.p o p ())
0 plaintext += word + ’ '
print("\поткрытый текст = {}".format(plaintext))
100
Глава 4
Начните с загрузки шифротекста ф в виде цепочки символов. Вы хотите работать со словами, а не с буквами, поэтому разбейте символьную цепочку по пробелам, используя метод обработки символьных цепочек split () для создания нового спи ска с именем cipherlist О Метод split о является обратным методу join(), кото рый вы встречали раньше. Разбиение может производиться на любую символьную цепочку; указанный метод по умолчанию выполняется на отрезках пробелов, рас положенных один за другим, удаляя каждый пробел, прежде чем перейти к сле дующему. Теперь самое время ввести то, что вы знаете о шифре 0: столбцы и строки, кото рые образуют матрицу, и ключ, который содержит маршрут. Инициализируйте числа столбцов и строк как константы. Затем создайте пустой список с именем transiation matrix для хранения содержимого каждого столбца в виде (вложен ного) списка. Задайте значения заполнителей, умножив значения None на число столбцов. Индексы этих пустых элементов можно использовать с целью возврата столбцов в их правильный порядок для ключей, которые не находятся в числовом порядке.
Расшифрованное сообщение будет содержаться в пустой строке с именем plaintext.
Далее идут несколько параметров среза. Обратите внимание, что некоторые из них являются производными от числа строк, которое тождественно числу элементов в каждом столбце.
Теперь конвертируйте переменную key, являющуюся символьной цепочкой, в спи сок целых чисел, используя операцию включения в список, т. е. сокращенный спо соб выполнения операций на списках О- Позже вы будете использовать числа в ключе в качестве индексов, поэтому они должны иметь целочисленный тип.
Следующим блоком кода является цикл for, который заполняет матрицу transiation matrix, являющуюся просто списком списков 0. Поскольку каждый столбец становится вложенным списком, а длина списка key int равна числу столбцов, диапазоном цикла является ключ, который также описывает маршрут.
Внутри цикла примените условное выражение для проверки того, является ли ключ положительным или отрицательным 0; если ключ положительный, то направление среза меняется на противоположное. Назначьте срезу правильную позицию в transiation matrix на основе абсолютного значения ключа и вычтите 1 (т. к. ключи не содержат 0, а индексы списка содержат). Завершите цикл, продвинув конечные точки среза вперед на число строк и напечатав немного полезной информации. Заключительный блок 0 в цикле перебирает число строк (что эквивалентно числу слов в одном из вложенных списков) и каждый вложенный список. Первые два цикла показаны на рис. 4.4. Когда вы останавливаетесь в каждом вложенном спи ске, вы задействуете одну из моих любимых функций Python — списковый метод pop () 0. Метод pop () выталкивает из списка и возвращает его последний элемент, если не указан определенный индекс. Он разрушает вложенный список, но он вам все равно больше не понадобится.
Декодирование шифров времен Гражданской войны в США 101
Первый цикл [16,12,8,4, 0]
[17,13,9,5,1]
[18.14.10.6.2]
[19.15.11.7.3]
Второй цикл
[16.12.8.4]
[17.13.9.5
[18.14.10.6
]
]
ГО’.
Т ,
‘2\
‘3’,…]
[19.15.11.7]
— |——————————–
[O’,
Т ,
‘2’,
‘3’,
‘4’,
‘5’,
‘6’,
‘7’,…]
Р ис. 4.4. Первый и второй циклы по вложенным спискам, удаление каждого конечного элем ента и его добавление в символьную цепочку с переводом
После того как вы вытолкнули слово, присоедините его к символьной цепочке plaintext и добавьте пробел О - Осталось лишь показать расшифрованный шифро- текст. Результат для тестового числового набора выглядят следующим образом:
открытый текст = 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
Похоже, все прошло успешно!
Взламывание маршрутного шифра
Приведенный выше код исходит из допущений, что вы знаете маршрут по матрице шифрования либо правильно угадали ключ. Если эти допущения являются ложны ми, то ваш единственный выход — попробовать все возможные расстановки ключа и матрицы. Возможность автоматизировать процесс выбора ключа — для заданно го числа столбцов — вы получите в разд. “Маршрутный перестановочный шифр: атака с применением грубой силы” далее в этой главе. Но, как вы увидите, мар шрутный шифр союзников хорошо укреплен против атак с применением грубой силы. Вы можете его взломать, но в итоге у вас получится так много данных, что вы будете чувствовать себя как собака, которая все преследовала машину и наконец ее догнала.
По мере того как сообщения становятся все длиннее, число возможных путей шиф рования в перестановочном шифре становится слишком большим для того, чтобы его решить применением грубой силы, даже с использованием современных ком
102 Глава 4
пьютеров. Например, если в матрице восемь столбцов и вы разрешаете маршруту переходить к любому столбцу, то число способов объединить столбцы является факториалом восьми: 8 x 7 x 6 x 5 x 4 x 3 x 2 x 1 = 40 320. А это 40 320 путей. Учтите это, прежде чем начнете выбирать альтернативные маршруты по столбцам. Если маршрут может изменять направление вверх или вниз по столбцу, то число комби наций увеличивается до 10 321 920. И если вы решите начать с любого места в столбце — а не в самом верху или внизу — и разрешите, чтобы через матрицу проходил любой маршрут (например, спираль), то все начнет действительно выхо дить из-под контроля!
По этой причине даже короткие перестановочные шифры могут иметь от тысяч до миллионов возможных путей. Даже если число путей поддается решению на ком пьютере и атака с применением грубой силы может восторжествовать, то вам все равно понадобится способ просеять несметное число результатов и либо выбрать победителя вычислительно, либо отобрать небольшое подмножество кандидатов для визуального изучения.
Для более распространенных буквенных перестановочных шифров легко написать функцию, которая обнаруживает английский язык, сравнивая каждую попытку расшифровки с файлом словаря. Если число расшифрованных слов, принадлежа щих словарю, превышает определенный пороговый процент, то вы, возможно, взломали шифр. Схожим образом, если имеется высокая частота распространенных буквенный пар (диграмм), например er, th, on или ап, то вы, возможно, нашли ре шение. К сожалению, этот подход не будет работать для словарного перестановоч ного шифра, подобного тому, который вы используете здесь.
Словари не способны помочь вам выяснить правильность расстановки слов. Для расстановки слов можно попробовать использовать такие подходы, как граммати ческие правила и вероятностные языковые модели, в частности и-граммы, проче сывая тысячи расшифровок и программно выбирая кандидатные результаты, но мудрое использование Стейджером кодовых имен и фиктивных слов в его мар шрутном шифре значительно усложнит процесс.
Криптоаналитики считают, что, невзирая на только что описанные проблемы, ко роткие, прямолинейные перестановочные шифры довольно легко взломать без компьютера. Можно отыскать распространенные пары слов или букв, которые имеют смысл, и использовать их для того, чтобы угадать число строк в перестано вочной матрице.
Для иллюстрации воспользуемся нашим управляющим сообщением, состоящим из чисел. На рис. 4.5 вы видите результаты шифротекста для матрицы 4x5, каждый из которых порождается, начиная с одного из четырех углов решетки и следуя че редующимся последовательным маршрутом. Все случаи включают повторение со седних чисел (на рис. 4.5 закрашены). Они указывают на то, где по решетке вы пе ремещаетесь в боковом направлении, и они дают наводки о схеме матрицы и прой денном по ней маршруте. Сразу видно, что строк было пять, потому что первым из каждой общей пары является пятое слово. Кроме того, зная, что в сообщении име ется 20 слов, вы узнаете, что число столбцов составило четыре (20/5 = 4). Исполь
Декодирование шифров времен Гражданской войны в США 103
зуя разумное допущение о том, что текстовое сообщение было написано слева на право, можно даже угадать маршрут. Например, если вы начнете в правом нижнем углу, то вы пойдете вверх до 3, далее влево до 2, затем вниз до 18, затем влево до 17, затем вверх до 1 и влево до 0. Разумеется, со словами было бы сложнее, т. к. связь между ними не такая явная, но использование чисел действительно имеет смысл.
5 чисел соответствует 5 строкам!
Справа внизу = 19 15 11 7 3 2 6 10 14 18 17 13 9 5 1 0 4 8 12 16
Слева внизу = 16 12 8 4 0 1 5 9 13 1 Щ 14 10 6 2 3 7 11 15 19
Справа вверху = 3 7 11 15 19 18 14 10 6 2 1 5 9 13 17 16 12 8 4 0
Слева вверху = 0 4 8 12 16 17 13 9 5 Ц 6 10 14 18 19 15 11 7 3
Р и с. 4 .5 . Символы или слова в логическом порядке (затонированны е) могут использоваться для угадывания м арш рута ш иф рования
Взгляните на рис. 4.6 (на нем приведено сообщение из рис. 4.2). Конечные слова и возможные связанные слова, такие как “is just” (просто) или “heading to” (направ ляются), затенены.
REST TRANSPORT YOU GOOWIN VILLAGE ROANOKE WITH ARE YOUR
IS 3UST SUPPLIES FREE SNOW HEADING TO GONE TO SOUTH FILLER
Р и с. 4.6 . Взламы вание маршрутного ш иф ра из рис. 4 .2 человеком. У казан а пятистрочная матрица
Всего имеется 20 слов, для которых может быть 4, 5 или 10 строк матрицы. Сомни тельно, что будет использоваться двухстолбцовая матрица, поэтому мы реально имеем дело с расстановкой 4x5 или 5x4. Если путь маршрутного шифра подобен тому, который показан на рис. 4.5, то мы ожидаем увидеть два незатененных слова между затененными словами для четырехстрочной матрицы и три незатененных слова для пятистрочной матрицы. Труднее придумать осмысленные словарные па ры, которые соблюдают четырехстолбцовый шаблон, независимо от того, в каком направлении вы читаете шифротекст. Поэтому мы, вероятно, имеем дело с пяти столбцовым решением, которое начинается с левой стороны матрицы, поскольку связанные слова имеют смысл, если читать слева направо.
Обратите внимание, как затененные слова на рис. 4.6 заполняют верхнюю и ниж нюю строки в перестановочной матрице из рис. 4.7. Это то, что мы и ожидали, т. к. путь “разворачивается” вверху и внизу каждого столбца. Графические решения — Божий дар для непонятливых!
Это выглядело просто, но опять же, мы знаем, как работает маршрутный шифр. Шифровальщики конфедерации в конце концов тоже его раскрыли, но использова ние кодовых слов лишило их полного входа в систему. Для того чтобы взломать
104 Глава 4
коды, им нужна была захваченная кодовая книга или крупная организация, способ ная собирать и анализировать большие данные, что в XIX в. было вне досягаемости конфедерации.
VILLA G E
RO A N O K E
heading
GODW IN
you
transport
REST
With
are
your
IS
SN O W
free
supplies
JU ST
to
gone
to
south
FILLE R
Р и с. 4 .7 . Затененны е слова из рис. 4.6, помещ енны е в перестановочную матрицу
Добавление пользовательского интерфейса
Вторая цель этого проекта— написать программный код таким образом, чтобы уменьшить ошибки людей, в особенности тех, у кого меньше опыта (включая тех ников, стажеров, коллег и телеграфных служащих в 1864 г.). Разумеется, лучший способ сделать программу удобной для пользователя — это внедрить графический интерфейс пользователя (graphical user interface, GUI), но иногда это непрактично или невозможно. Например, программы для взламывания кода автоматически пе ребирают тысячи возможных ключей, и выполнить автогенерацию этих ключей проще, чем получить их непосредственно от пользователя.
В этом примере вы будете исходить из допущения о том, что пользователь взлома ет файл программы и введет в программу данные или даже внесет в код незначи тельные изменения. Вот некоторые рекомендации:
Начать с полезного литерала документирования (см. главу 1).
Поместить вверху все необходимые данные, вводимые пользователем.
Использовать комментарии для уточнения требований к входным данным.
Четко отделить вводимые пользователем данные от остального кода.
Инкапсулировать большинство процессов в функциях.
Задействовать функции для улавливания предсказуемых ошибок пользователя.
Самое приятное в этом подходе то, что не оскорбляется ничей интеллект. Если пользователь хочет прокрутить вниз и посмотреть на код или даже изменить его, его ничто не остановит. Если он хочет лишь ввести несколько значений и получить черноящичное решение, то он тоже будет доволен. И мы отдали дань духу Энсона Стейджера, делая все проще и уменьшая вероятность ошибки.
Инструктаж пользователя и получение входных данных
В листинге 4.2 показан прототипный программный код, переупакованный для ис пользования совместно с другими пользователями. Указанный код можно найти на веб-сайте https://www.nostarch.com/impracticalpython/.
Декодирование шифров времен Гражданской войны в США
1 0 5
Листинг 4.2. Литерал документирования, импорт и входные данные, вводимые пользователем для программы route_cipher_decrypt.py, часть 1 0 “““расшифровать путь посредством маршрутного шифра союзников.
О Предназначено для полнословных перестановочных шифров с переменными строками и столбцами. Исходит из допущения, что
шифрование началось вверху или внизу столбца. Ключ указывает на порядок чтения столбцов и направление прохождения.
Отрицательные числа столбцов означают начало снизу и чтение вверх.
Положительные числа столбцов означают начало сверху и чтение вниз.
Приведенный ниже пример предназначен для матрицы 4x4 с ключом - 1 2 - 3 4 . Примечание: “0” не допускается.
Стрелки показывают маршрут шифрования; для отрицательных значений ключа
читать ВВЕРХ.
СООБЩЕНИЕ ПИШЕТСЯ
ПОПЕРЕК КАЖДОЙ СТРОКИ
ТАКИМ ОБРАЗОМ
ПОСЛЕДНЯЯ СТРОКА ЗАПОЛНЯЕТСЯ ФИКТИВНЫМИ СЛОВАМИ
НАЧАЛО
КОНЕЦ
Необходимые входные данные - текстовое сообщение, число столбцов,
число строк, символьная цепочка с ключом
Печатает переведенный текст
0 import sys
#================================================================
О # ВХОДНЫЕ ДАННЫЕ ПОЛЬЗОВАТЕЛЯ:
0 # дешифруемая символьная цепочка (введите или вставьте между
тройными кавычками) : #
ciphertext = “““16 12 8 4 0 1 5 9 13 17 18 14 10 6 2 3 7 11 15 19
II II II
0 # число столбцов в перестановочной матрице:
COLS = 4
106
Глава 4
число строк в перестановочной матрице: #
ROWS = 5
Q # ключ с пробелами между числами; отрицательный
для чтения ВВЕРХ столбца #
(например, -1 2 -3 4) :
key = "”” - 1 2 - 3 4 ..
О # КОНЕЦ ВХОДНЫХ ДАННЫХ ПОЛЬЗОВАТЕЛЯ - НЕ РЕДАКТИРОВАТЬ НИЖЕ
ЭТОЙ СТРОКИ! #
= = = = = = = = = = = = = = = = = = = = = = = = „ #
=
=
=
=
=
О
Начните с многострочного литерала документирования в тройных кавычках О- Литерал документирования сообщает пользователю о том, что программа расшиф ровывает только типичный маршрутный шифр— тот, который начинается либо сверху, либо снизу столбца, — и о том, как вводить информацию о ключе Q . Таб лица включена для того, чтобы помочь уяснить смысл.
Затем импортируйте модуль sys для доступа к системным шрифтам и функциям Q . Вы собираетесь проверить введенные пользователем данные по критерию прием лемости, поэтому вам потребуется возможность показывать сообщения в оболочке интерпретатора в привлекательном красном цвете. Размещение этой инструкции import здесь является примером взаимоисключающей ситуации. Поскольку страте гическая цель состоит в сокрытии рабочего кода от пользователя, вы, по сути дела, должны применить ее в программе гораздо позже. Однако правило языка Python по размещению всех инструкций импорта в верхней части программы является черес чур строгим, чтобы его игнорировать.
Теперь что касается раздела ввода данных. Как часто вы видели программный код или имели с ним дело, где результаты должны показываться или изменения долж ны вноситься на протяжении всего кода программы? Это может сбить с толку са мого автора и, что еще хуже, другого пользователя. Поэтому ради удобства, пущей вежливости и предотвращения ошибок переместите все эти важные переменные наверх.
Сначала отделите раздел входных данных линией, а затем с помощью комментария в верхнем регистре сообщите пользователю о том, что он находится в указанном разделе О - Необходимые входные данные четко определены комментариями. Для текстовых входных данных можно использовать тройные кавычки. Это позволит лучше разместить длинные фрагменты текста. Обратите внимание, что я ввел сим вольную цепочку чисел из рис. 4.3 © . Далее пользователю необходимо добавить число столбцов и строк перестановочной матрицы Q , а затем предложенный (или известный) ключ О .
Завершите раздел вводимых пользователем данных комментарием с объявлением на этот счет и предостережением не редактировать ничего ниже следующей стро
Декодирование шифров времен Гражданской войны в США 107
ки О Затем добавьте несколько дополнительных пустых строк для того, чтобы четче отделить раздел ввода данных от остальной части программы О-
Определение функции main()
В листинге 4.3 определена функция main (), которая выполняет программу и печа тает открытый текст после декодирования шифра. Функция main о может быть определена до или после вызываемых функций, если она является последней вызы ваемой функцией.
Листинг 4.3. Определяет функцию main (). Файл route_cipher_decrypt.py, часть 2
def m a in():
“““Выполнить программу и напечатать расшифрованный открытый текст.”””
О print(“ХпШифротекст = {}".format(ciphertext))
print(“Тестирование {} столбцов”.format(COLS)) print(“Тестирование {} строк”.format(ROWS))
print(“Тестирование ключа = {}".format(key))
#
разбить элементы на слова, не на буквы
0 cipherlist = list(ciphertext.split())
0 validate_col_row(cipherlist)
0 key_int = key_to_int(key)
0 translation_matrix = build_matrix(key_int, cipherlist)
0 plaintext = decrypt(translation_matrix)
0 print(“Открытый текст = {}".format(plaintext))
Начните функцию maino, напечатав введенные пользователем данные в интерак тивной оболочке О- Затем превратите шифротекст в список, разбив его на пробе лы, как это было сделано в прототипном программном коде 0 .
Следующая серия инструкций вызывает функции, которые вы определите в бли жайшее время. Первая проверяет, являются ли входные строки и столбцы приемле мыми для длины сообщения О- Вторая конвертирует переменную key из символь ной цепочки в список целых чисел О* Третья строит переводную матрицу О, а четвертая выполняет дешифровальный алгоритм на матрице и возвращает сим вольную цепочку с открытым текстом 0 . Завершите ma i n o печатью открытого текста 0 .
Верификация данных
Когда вы продолжите упаковывать route cipher decrypt.py для конечного пользова теля, вам необходимо проверить допустимость входных данных. Листинг 4.4 пред восхищает распространенные ошибки пользователя и предоставляет пользователю полезные отклики и рекомендации.
108
Глава 4
Листинг 4.4. Определяет функции для проверки и подготовки введенных пользователем данных. Файл route_cipher_decrypt.py, часть 3
О def validate_col_row(cipherlist):
“““Проверить, что входные столбцы и строки приемлемы
по отношению к длине сообщения.”””
factors = []
len_cipher = len(cipherlist)
О for i in range(2, len_cipher): # диапазон исключает
1 столбец шифров #
if len_cipher % i == 0:
factors.append(i)
0 print(“ХпДлина шифра = {}".format(len_cipher))
print(“Приемлемые значения столбцов/строк включают: {}”.
format(factors))
print ()
О if ROWS * COLS != len_cipher:
print(“ХпОшибка - входные столбцы и строки не являются”
“кратными длины шифра. Завершение программы.”,
file=sys.stderr)
sys.exit(1)
ф def key_to_int(key):
“““Превратить ключ в список целых чисел и проверить допустимость.”””
О key_int = [int(i) for i in key.splitO]
key_int_lo = min(key_int)
key_int_hi = max(key_int)
О if len(key_int) != COLS or key_int_lo COLS \
or 0 in key_int:
ф print(“ХпОшибка - проблема с ключом. Завершение.”,
file=sys.stderr)
sys.exit(1)
else:
О return key_int
Функция validate coi rowO проверяет, что входные номера столбцов и строк соответствуют длине шифросписка, который передается в качестве аргумента О- Перестановочная матрица всегда имеет тот же размер, что и число слов в сообще нии, поэтому число столбцов и число строк должны быть кратными размеру сооб щения. Для того чтобы определить все допустимые кратные, сначала составьте пустой список для хранения кратных, а затем получите длину шифросписка. Ис пользуйте шифросписок, а не входной шифротекст, т. к. элементы в шифротексте являются буквами, а не словами. Для получения кратных чисел обычно использовался бы диапазон (1, число + 1), но вы не хотите, чтобы эти конечные точки были в списке factors, т. к. переводная
Декодирование шифров времен Гражданской войны в США 109
матрица с этими размерностями была бы просто открытым текстом. Поэтому ис ключите эти значения из диапазона ф. Так как кратное число делится без остатка на это число, для отыскания кратных используйте оператор деления по модулю (%) и затем добавьте их в список factors.
Затем покажите пользователю полезную информацию: длину шифросписка и до пустимые варианты строк и столбцов О- Наконец, перемножьте два выбранных пользователем варианта между собой и сравните произведение с длиной шифро списка. Если они не совпадают, то покажите в оболочке интерпретатора красное предупреждающее сообщение (используя наш старый трюк fiie=sys. stderr) и за вершите программу О* Примените sys.exit (1), т. к. 1 указывает на аварийный вы ход из программы.
Теперь определите функцию для проверки ключа и конвертируйте его из символь ной цепочки в список О- Передайте ей в качестве аргумента переменную key. Вы делите каждый элемент в key и конвертируйте его в целое число; назовите список key int, для того чтобы отличить его от введенной пользователем переменной key О . Далее определите минимальное и максимальное значения в списке key int. За тем примените инструкцию if, с целью обеспечить, чтобы список содержал то же само число элементов, что и столбцы, и ни один из элементов в key не был слиш ком большим, слишком маленьким либо равным 0 ф. Завершите работу програм мы сообщением об ошибке, если какой-либо из этих критериев не проходит ф. В противном случае верните список key int ф.
Построение и декодирование переводной матрицы
Листинг 4.5 определяет две функции: одну для построения переводной матрицы, другую для ее декодирования,— и вызывает функцию m aino как модуль либо в автономном режиме.
Листинг 4.5. Определяет функции для построения и декодирования переводной матрицы. Файл route_cipherJJecrypt.py, часть 4
О def build_matrix(key_int, cipherlist):
“““Превратить каждые п элементов в списке в новый элемент
в списке списков.”””
translation_matrix = [None] * COLS start = 0
stop = ROWS for k in key_int:
if k 0: # читать в столбце сверху-вних
col_items = list((reversed(cipherlist[start:stop])))
translation_matrix[abs(k) - 1] = col_items start += ROWS stop += ROWS
return translation matrix
110
Глава 4
О def decrypt(translation_matrix):
“““Перебрать вложенные списки в цикле, выталкивая последний элемент
в символьную цепочку.”””
plaintext = 11
for i in range(ROWS):
for matrix_col in translation_matrix:
word = str(matrix_col.pop()) plaintext += word + 1 1
return plaintext
0 if name == 1__main__1 :
m a i n ()
Эти две функции представляют собой инкапсуляцию кода из программы route_cipher_decrypt_prototype.py. Подробное описание см. в листинге 4.1.
Сначала определите функцию для построения переводной матрицы; передайте ей в качестве аргументов переменные key int и cipherlist О- Указанная функция возвращает список списков.
Затем упакуйте дешифровальный код, где выталкивается конец каждого вложенно го списка, как функция, которая в качестве аргумента использует список translation matrix 0 . Верните открытый текст, который будет напечатан функци ей main ().
Завершите программу с помощью условной инструкции, которая позволяет про грамме работать как модуль либо в автономном режиме Q .
Если вы являетесь случайным или разовым пользователем этого кода, то вы оцените, насколько он прост и доступен. Если вы планируете изменить указанный код для своих собственных нужд, то вы также оцените то, что ключевые перемен ные легко доступны, а главные задачи модуляризованы. Вам не придется копаться в программе, чтобы вычленить важные части или понять разницу между потайны ми переменными, такими как listi и iist2.
Вот результат работы программы с учетом шифротекста из рис. 4.3:
Шифротекст = 16 12 8 4 0 1 5 9 13 17 18 14 10 6 2 3 7 11 15 19
Тестирование 4 столбцов
Тестирование 5 строк Тестирование ключа = - 1 2 - 3 4
Длина шифра = 2 0
Приемлемые значения столбцов/строк включают: [2, 4, 5, 10]
Открытый текст = 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
Теперь вы можете легко расшифровать маршрутный перестановочный шифр с из вестным ключом либо протестировать подозреваемые маршруты, используя понят
Декодирование шифров времен Гражданской войны в США
1 1 1
ный и доступный интерфейс скрипта по настройке ключа. Вы получите шанс по-настоящему взломать один из этих шифров, автоматически тестируя все воз можные ключи, в разд. “Маршрутный перестановочный шифр: атака с примене нием грубой силы " далее в этой главе.
Проект 9: зигзагообразны й шифр
Офицеры конфедератов и шпионы в значительной степени были предоставлены сами себе всякий раз, когда дело доходило до криптографии. Это привело к нехит рым решениям, причем одним из излюбленных был зигзагообразный шифр, или шифр жердевой изгороди (rail fence cipher), названный так из-за его сходства с зиг загообразным рисунком разъемного жердевого забора (показан на рис. 4.8).
Р и с. 4.8 . Ж ерд евая изгородь
Зигзагообразный шифр является простым в использовании перестановочным шиф ром, подобным маршрутному шифру союзников, но отличается от маршрутного шифра тем, что он переставляет буквы, а не слова, и это делает его более подвер женным ошибкам. И поскольку число возможных ключей намного ограниченнее, чем число путей по маршрутному шифру, зигзагообразный шифр намного легче “снести”. И союзники, и конфедераты использовали зигзагообразный шифр как полевой шифр, а шпионы, вероятно, не очень часто использовали кодовые слова. По оче видным причинам шифровальные книги должны были строго контролироваться и,
112 Глава 4
скорее всего, хранились в военной телеграфной конторе, а не носились с собой лег ко раскрывавшимися агентами под прикрытием. Иногда для важных сообщений конфедераты использовали более сложный шифр Виженера (см. “Проект 12: сокрытие шифра Виженера” в главе 6) — и для неко торых неважных с целью ввести в заблуждение противника, но его расшифровка выливалась в утомительную работу, а его шифрование становилось столь же тру доемким, что не подходило для оперативной полевой связи. Несмотря на отсутствие подготовки в области криптографии, конфедераты и южане в целом были умны и изобретательны. Наиболее впечатляющим достижением в искусстве секретных сообщений было использование микрофотографии, за 100 лет до того, как она получила широкое распространение во время холодной войны.
Ц е л ь Написать программы на языке Python, которые помогут шпиону шифровать и дешиф ровывать “двухрядные” (двухстрочные) зигзагообразные шифры. Вы должны написать программы так, чтобы уменьшить потенциальные ошибки неопытных пользователей.
Стратегия
Для шифровки сообщения с помощью зигзагообразного шифра выполните дейст вия, описанные на рис. 4.9 (перевод шифруемого текста— купи еще мэнской кар тошки).
B uy m ore M a in e p o t a t o e s
- Написать обычным текстом
BUYMOREMAINEPOTATOES
- Удалить пробелы и перевести в верхний регистр
B Y O E A N P T T E U M R M I E O A O S
- Расположить буквы одна под другой и сместить,
образовав зигзагообразный рисунок
BYOEANPTTEUMRMIEOAOS
- Объединить верхний и нижний ряды
BYOEA NPTTE UMRMI EOAOS
- Объединить в группы по пять букв
Рис. 4.9. Процесс шифрования для “двухрядного” зигзагообразного шифра
После записи открытого текста удаляются пробелы, и все буквы конвертируются в верхний регистр (шаг 2). В криптографии использование прописных букв являет ся общепринятым правилом, поскольку оно запутывает наличие собственных имен и начала предложений, давая криптоаналитику меньше подсказок для расшифровки сообщения. Далее сообщение записывается внакладку, где каждая вторая буква расположена ниже предыдущей буквы и сдвигается на один пробел (шаг 3). Именно здесь стано вится очевидной аналогия с “жердевой изгородью”.
Декодирование шифров времен Гражданской войны в США
1 1 3
Потом в линию пишется первая строка, за которой сразу же следует вторая строка на той же линии (шаг 4), а затем буквы разбиваются на пятибуквенные группы для того, чтобы создать иллюзию отдельных слов и еще больше запутать криптоанали тика (шаг 5).
Для расшифровки зигзагообразного шифра указанный процесс выполняется в об ратном порядке. Просто удалите пробелы, разделите сообщение пополам, размес тите вторую половину ниже первой, смещенную на одну букву, и прочитайте сообщение, используя зигзагообразный рисунок. Если в шифротексте число букв является нечетным, то разместите в первую (верхнюю) половину лишнюю букву. Для облегчения работы людям, которые хотят использовать зигзагообразный шифр, выполните приведенные выше шаги, написав две программы: одну для шифровки, а другую для расшифровки. По сути, рис. 4.9 является вашим псевдокодом, так что давайте перейдем к нему. И поскольку теперь вы знаете, как упаковывать код для неопытных пользователей, с самого начала примите этот подход.
Код шифровки зигзагообразным шифром
Программный код этого раздела позволяет пользователю ввести текстовое сообще ние и распечатать зашифрованные результаты в окне интерпретатора. Указанный код доступен для скачивания с ресурсов книги по адресу https://www.nostarch.com/ impracticalpython/.
Инструктаж пользователя и получение входных данных
Листинг 4.6 предоставляет инструкции пользователю вверху программы rail_fence_ cipherencrypt.py и назначает переменной открытый текст.
Листинг 4.6. Раздел документирования и данных, вводимых пользователем для rail_fence_cipher_encrypt.py, часть 1
ф г”““Зашифровать текст зигзагообразным шифром времен Гражданской войны в США. Этот шифр является “двухрядным” и предназначен для
коротких сообщений. Пример шифруемого текста:
‘Buy more Maine potatoes’ (Купи еще мэнской картошки)
Зигзагообразный стиль: B Y O E A N P T T E
Чтение зигзага:
//////////
U M R M I E O A O S
Шифротекст: BYOEA NPTTE UMRMI EOSOS »» и к #——————————————————————————————————-
О # ВХОДНЫЕ ДАННЫЕ ПОЛЬЗОВАТЕЛЯ:
шифруемая символьная цепочка (вставьте между кавычками): #
Q plaintext = “““Давай пересечем реку и отдохнем в тени деревьев”””
О # КОНЕЦ ВХОДНЫХ ДАННЫХ ПОЛЬЗОВАТЕЛЯ - НЕ РЕДАКТИРОВАТЬ
НИЖЕ ЭТОЙ СТРОКИ! #
#
114
Глава 4
Начните с многострочного литерала документирования, поместив префикс г (от англ. raw — сырой) перед первым набором тройных кавычек О* Без этого префик са программа Pylint будет горько жаловаться на косые черты \Л, используемые ни же. К счастью, инструмент pydocstyle укажет на это, так что вы сможете все испра вить (прочитайте главу 7, где узнаете все о программах Pylint и pydocstyle). Если вы хотите побольше узнать о необработанных (сырых) символьных цепочках, то см. раздел 2.4.1 в документации по языку Python (https://d0cs.pyth0n.0rg/3.6/ reference/lexical_analysis.html#string-and-bytes-literals).
Затем отделите литерал документирования и инструкции import программы от раз дела входных данных пустой строкой и сообщите пользователю, что он находится в указанном разделе, с помощью комментария в верхнем регистре 0 . При помощи комментариев четко определите требования к входным данным и поместите откры тый текст внутри тройных кавычек для того, чтобы лучше уместить длинный текст Q .
Наконец, завершите раздел вводимых пользователем данных объявлением на этот счет и предостережением не редактировать ничего ниже следующей далее строки кода Q .
Шифрование сообщения
В программу rail fence cipher encrypt.py добавьте листинг 4.7, который будет за ниматься процессами шифрования.
Листинг 4.7. Определяет функции для шифрования текста открытого сообщения. Файл rail_fence_cipher_encrypt.py, часть 2
О def m a i n ():
“““Выполнить программу шифрования сообщения с помощью
двухрядного зигзагообразного шифра.”””
message = prep_plaintext(plaintext) rails = build_rails(message) encrypt(rails)
Q def prep_plaintext(plaintext):
“““Удалить пробелы и начальные/замыкающие пробелы.”””
О message = “".join(plaintext.split())
О message = message.upper() # общепринято шифротекст писать
print("\поткрытый текст = {}".format(plaintext))
return message
#
в верхнем регистре
О clef build_rails (message) :
“““Построить символьные цепочки, беря каждый второй символ
в сообщении.”””
evens = message
2]
odds = message[1::2]
Декодирование шифров времен Гражданской войны в США
1 1 5
Q rails = evens + odds
return rails
ф def encrypt(rails):
“““Разбить буквы в шифротексте на куски по 5 букв и сцепить
в одну цепочку.”””
ф ciphertext = ’ ‘.join({rails[i:i+5] for i in range(0,
len(rails), 5)])
print(“шифротекст = {}".format(ciphertext))
О if name == ‘main’ :
mai n()
Для начала определите функцию main (), которая выполняет программу О- Наличие функции main () даст вам позже возможность использовать эту программу в каче стве модуля в другой программе, если возникнет такая необходимость. Указанная функция вызывает три другие функции: одну для подготовки входного открытого текста, другую — для построения “рядов”, используемых шифром, и третью — для разбиения зашифрованного текста на пятибуквенные фрагменты.
Далее определите функцию, которая примет входную строку и подготовит ее к шифрованию ф. Сюда входит удаление пробелов О и конвертирование букв в верхний регистр (как в шаге 2 на рис. 4.9) О . Затем, после новой строки, напеча тайте открытый текст на экране и верните его.
Теперь определите функцию для построения двух символьных цепочек, как в ша ге 3 на рис. 4.9, нарезая сообщение на четные (начиная с 0 и шагая на 2 позиции) и нечетные (начиная с 1 и шагая на 2 позиции) символы ф. Далее эти две символь ные цепочки сцепите в новую символьную цепочку с именем rails ф, которая за тем возвращается.
Определите функцию шифрования, которая в качестве аргумента принимает сим вольную цепочку rails О Примените операцию включения в список для разбие ния шифротекста на фрагменты по пять букв (как в шаге 5 на рис. 4.9) ф. Зашиф рованный текст выведите на экран. Закончите фрагментом кода, который выполня ет программу в виде модуля либо в автономном режиме ф. Вот вывод из этой программы:
открытый текст = Let us cross over the river and rest under the shade of the
trees
шифротекст = LTSRS OETEI EADET NETEH DOTER EEUCO SVRHR VRNRS UDRHS AEFHT ES
Код расшифровки зигзагообразного шифра
Код этого раздела позволяет пользователю ввести сообщение, зашифрованное зиг загообразным шифром, и напечатать открытый текст в окне интерпретатора. Этот код доступен для скачивания вместе с остальными ресурсами книги по адресу https://www.nostarch.com/impracticalpython/.
116
Глава 4
Импорт модулей, инструктаж пользователя и получение входных данных
Листинг 4.8 начинается инструктажем, аналогичным инструктажу в программе rail fence cipher encrypt.py (см. листинг 4.6), импортирует два модуля и получает от пользователя входные данные.
Листинг 4.8. Импортирует модули, инструктирует пользователя и получает от пользователя входные данные. Файл rail_fence_cipher_decrypt.py, часть 1
г”““Дешифровать зигзагообразный шифр времен Гражданской войны в США.
Этот шифр является “двухрядным” и предназначен для коротких сообщений.
Пример шифруемого текста: ‘Buy more Maine potatoes’
(Купи еще мэнской картошки)
Зигзагообразный стиль: B Y O E A N P T T E
U M R M I E O A O S
Чтение зигзага:
//////////
Шифротекст: BYOEA NPTTE UMRMI EOSOS
О import math
import itertoois
#———————————————————————————————————–
ВХОДНЫЕ ДАННЫЕ ПОЛЬЗОВАТЕЛЯ: #
дешифруемая символьная цепочка (вставить между кавычками): #
О ciphertext = .. LTSRS OETEI EADET NETEH DOTER EEUCO SVRHR VRNRS UDRHS AEFHT ES
КОНЕЦ ВХОДНЫХ ДАННЫХ ПОЛЬЗОВАТЕЛЯ - НЕ РЕДАКТИРОВАТЬ НИЖЕ ЭТОЙ СТРОКИ! #
#
Одним из отличий здесь является то, что вам нужно импортировать модули math и itertoois О Модуль math будет ИСПОЛЬЗОВаТЬСЯ ДЛЯ ОКруГЛенИЯ. Модуль itertoois представляет собой группу функций в стандартной библиотеке Python, которые создают итераторы с целью эффективной циклической обработки. Функция zip iongest о из модуля itertoois будет использоваться во время процесса рас шифровки.
Другое единственное изменение заключается в том, что вместо открытого текста пользователь должен ввести шифротекст 0 .
Декодирование шифров времен Гражданской войны в США 117
Расшифровка сообщения
Листинг 4.9 определяет функции для подготовки и декодирования шифротекста и завершает работу программы rail fence cipher decrypt.py.
Листинг 4.9. Подготовка, декодирование и печать сообщения. Файл rail_fence_cipher_decrypt.py, часть 2
ф def ma in():
“““Выполнить программу для дешифровки двухрядного зигзагообразного
шифра.”””
message = prep_ciphertext(ciphertext) rowl, row2 = split_rails(message)
decrypt(rowl, row2)
ф def prep_ciphertext(ciphertext): “““Удалить пробелы.”””
message = “".join(ciphertext.split())
print(“Хпшифротекст = {}".format(ciphertext))
return message
ф def split_rails(message):
“““Разбить сообщение на два, всегда округляя ВВЕРХ
для первого ряда.”””
ф row_l_len = math.ceil(len(message)/2) ф rowl = (message[:row_l_len]) row2 = (message[row_l_len:])
return rowl, row2
ф def decrypt(rowl, row2):
“““Построить список, чередуя буквы из 2 цепочек символов,
и напечатать.”””
ф plaintext = [] ф for rl, r2 in itertoois.zip_longest(rowl, row2):
plaintext.append(rl.lower()) plaintext.append(r2.lower())
ф if None in plaintext:
plaintext.pop()
print(“ряд 1 = {}".format(rowl))
print(“ряд 2 = {}".format(row2))
print(“Хпоткрытый текст = {}".format(‘1.join(plaintext)))
ф if name == ‘main’ :
main ()
Функция main o здесь ф аналогична функции, используемой в программе шифро вания из листинга 4.7. Вызываются три функции: одна для подготовки входной
118
Глава 4
символьной цепочки, другая — для “разбиения рядов” в зигзагообразном шифре и третья — для сшивания двух рядов вместе в читаемый открытый текст.
Начните с функции, которая повторяет шаги предобработки, используемые во вре мя шифрования 0 . Удалите пробелы между пятибуквенными фрагментами, а так же любые другие пробелы, созданные во время вставки зашифрованного текста, напечатайте и верните шифротекст.
Далее вам нужно разбить сообщение обратно на две части, обратив процесс шиф рования вспять ф. Как я уже упоминал в разд. “Стратегия” ранее в этой главе, лишняя буква в сообщении с нечетным числом символов назначается верхнему ря ду. Для улаживания нечетного случая используйте метод math, ceil о О . “Ceil” является сокращением английского слова “ceiling” и означает потолок, поэтому при делении на 2 ответ всегда округляется вверх до ближайшего целого числа. На значьте это число переменной row i ien. После того как вы узнаете длину первого ряда, используйте это значение и срез для того, чтобы разбить переменную message на две представляющие ряды символьные цепочки ф. Завершите функцию, вернув переменные рядов. Теперь это просто вопрос выбора и объединения каждой второй буквы из рядов, сшивая открытый текст вместе. Определите функцию decrypt о и передайте ей символьные цепочки ДЛЯ рядов rowl И row2 о Начните процесс перевода, создав пустой список для хранения результатов ф. Далее вам нужен простой способ спра виться со случаями, когда шифротекст имеет нечетное число букв, что приводит к двум рядам разной длины — потому что Python не даст вам пройти по двум не равным последовательностям и вызовет ошибку “Индекс вне диапазона”. Вот по чему мы импортировали модуль itertoois — его функции помогают с циклической обработкой, обходя эту проблему.
Функция itertoois.zip iongest о в качестве аргументов принимает две символь ные цепочки и выполняет их циклическое прохождение без жалоб, добавляя пустое значение (None) в список plaintext, когда она доходит до конца более короткой це почки ф. Вряд ли вы захотите напечатать это пустое значение, поэтому, если оно есть, удалите его с помощью метода рор(), который вы применили в коде мар шрутного шифра ф. Завершите процесс расшифровки, напечатав два ряда (две жерди) на экране, а затем расшифрованный шифротекст.
Завершите стандартным фрагментом кода для выполнения указанной программы в виде модуля либо в автономном режиме ф . Результат работы программы выгля дит следующим образом:
лпфротекст = LTSRS OETEI EADET NETEH DOTER EEUCO SVRHR VRNRS UDRHS AEFHT ES
/ -5Д 1 = LTSRSOETEIEADETNETEHDOTERE L 4Д 2 = EUCOSVRHRVRNRSUDRHSAEFHTES
открытый текст = letuscrossovertheriverandrestundertheshadeofthetrees
Обратите внимание, что между словами не будет пробелов, но это нормально — вы же не хотите, чтобы криптоаналитик чувствовал себя совершенно бесполезным!
Декодирование шифров времен Гражданской войны в США 119
Резюме
На этом наш набег на шифры времен Гражданской войны в США завершается. Вы написали программу, которая помогает пользователю расшифровывать маршрут ный шифр, и получили ценную информацию о том, как он работает и как его взло мать. В последующих практических проектах вы можете реализовать автоматиче скую атаку на шифр, но помните, с учетом его многочисленных возможных путей и использования кодовых слов маршрутный шифр союзников является крепким орешком и потребуется много усилий, чтобы его взломать полностью.
Затем вы написали программы для шифрования и дешифрования двухрядных зиг загообразных шифров. Учитывая, насколько утомительны и подвержены ошибкам ручные процессы шифрования и дешифрования, наличие автоматизированного способа выполнения подавляющей части работы было бы ценным вкладом для обеих сторон во время той войны. И для дальнейшего решения этих задач вы напи сали свой код, удобный для неопытного криптоаналитика или шпиона.
Дальнейшее чтение
Еще больше программ Python начального уровня для работы с перестановочными шифрами есть в книге Эла Свейгарта “Взламывание кодов с помощью Python” (Sweigart A. Cracking codes with Python. No Cramp Press, 2018).
Превосходные и хорошо иллюстрированные обзоры криптографии можно найти в книге Гэри Блэквуда “Таинственные сообщения: история кодов и шифров” (Blackwood G. Mysterious messages: a history of codes and ciphers. The Penguin Group, 2009) и книге Саймона Сингха “Книга кодов: наука секретаря от Древнего Египта до квантовой криптографии” (Singh S. The code book: the science of secretary from ancient egypt to quantum cryptography. Anchor, 2000).
Веб-страницы http://www.civilwarsignals.org/pages/crypto/crypto.html и http:// vnvw.mathaware.org/mam/06/Sauerberg_route-essay.html включают описания по пытки Эдварда Портера Александера (Edward Porter Alexander) решить маршрут ный шифр. Александер был основателем корпуса связи армии конфедератов и бле стящим военным новатором со многими впечатляющими достижениями.
Практические проекты
Отточите свои навыки криптографии с помощью приведенных ниже проектов. Ре шения доступны в приложении к книге и онлайн на веб-сайте книги.
Взламывание сообщения Линкольна
В своей книге “Таинственные сообщения: история кодов и шифров” Гэри Блэквуд воспроизводит фактическое сообщение, отправленное Авраамом Линкольном и зашифрованное маршрутным шифром:
120 Глава 4
THIS OFF DETAINED ASCERTAIN WAYLAND CORRESPONDENTS OF AT WHY AND IF FILLS IT YOU GET THEY NEPTUNE THE TRIBUNE PLEASE ARE THEM CAN UP
Примените программу routecipherdecrypt.py для решения этой криптограммы. Число столбцов и строк должно быть кратным длине сообщения, а маршрут начи нается в одном из углов, не пропускает столбцы и меняет направление при каждом изменении столбца. Определения кодового слова и решение с открытым текстом можно найти в приложении к книге.
Идентификация типов шифров
Чем скорее вы узнаете, с каким типом шифра имеете дело, тем скорее сможете его взломать. Словарные перестановочные шифры легко обнаружить, но буквенные перестановочные шифры могут выглядеть как буквенные подстановочные шифры. К счастью, отличить один от другого можно, используя частоту встречаемости букв в зашифрованном тексте. Поскольку в перестановочных шифрах буквы просто скремблируются и не заменяются, их частотное распределение будет таким же, как и для языка, на котором был написан открытый текст. Исключением, однако, явля ются военные сообщения, в которых используется жаргон и опускаются многие распространенные слова. Для этого вам нужна частотная таблица, построенная из других военных сообщений.
Напишите программу Python, которая на входе принимает символьную цепочку шифротекста и определяет, является ли он, скорее всего, перестановочным либо подстановочным шифром. Проверьте его с помощью файлов cipher_a.txt и cipher_b.txt, которые можно скачать с веб-сайта https://www.nostarch.com/ impracticalpython/. Решение задачи можно найти в приложении к книге и онлайн на веб-сайте книги в файле identify_cipher_type_practice.py.
Хранение ключа в форме словаря
Напишите короткий скрипт, который разбивает ключ маршрутного шифра на две части: одну для регистрации порядка следования столбцов, а другую — для регист рации направления чтения строк в столбце (вверх или вниз). Сохраните номер столбца в качестве ключа словаря и направление чтения в качестве значения слова ря. Пусть программа в интерактивном режиме запросит у пользователя значение ключа для каждого столбца. Решение можно найти в приложении к книге и онлайн в файле key_dictionary_practice.py.
Автоматическое генерирование возможных ключей
Попытка расшифровать маршрутный шифр, используя любую комбинацию столб цов на своем пути, потребует знания о том, какие это комбинации. Это необходимо для того, чтобы можно было ввести их в качестве аргументов в функцию расшиф ровки. Напишите программу Python, которая принимает целое число (например, число столбцов) и возвращает коллекцию кортежей. Каждый кортеж должен со
Декодирование шифров времен Гражданской войны в США 121
держать уникальный порядок следования номеров столбцов, например (1, 2, з, 4). Включите отрицательные значения, например (2, -з, 4, -1) для того, чтобы уло вить маршруты шифрования, которые идут по столбцам вверх в отличие от движе ния по столбцам вниз. Решение представлено в приложении к книге со скачиваемой версией на веб-сайте книги в файле permutations_practice.py.
Маршрутный перестановочный шифр: атака с применением грубой силы
Скопируйте и модифицируйте программу route cipher decrypt.py для взлома мар шрутного шифра на рис. 4.2. Вместо того чтобы вводить один ключ, переберите все возможные ключи — для предполагаемого числа столбцов — и напечатайте ре зультаты (используйте приведенный ранее перестановочный код, который генери рует ключи для этого четырехстолбцового шифра). Переключение порядка следо вания столбцов и разрешение восходящих и нисходящих путей по перестановочной матрице имеет сильное влияние, что четко проиллюстрировано на рис. 4.Ю. Пунк тирная линия — это факториал числа столбцов; сплошная линия улавливает эффект чтения в столбцах вверх, а также вниз (улавливается за счет включения в ключ от рицательных значений). Если бы вам пришлось иметь дело только с факториалом 4, то ваша работа в качестве криптоаналитика была бы легкой. Но по мере того, как шифр становится длиннее, число возможных ключей претерпевает взрывной рост. И некоторые фактические маршрутные шифры союзников имели 10 столбцов и бо лее!
хо5
О 5 т
1
4
5
Число столбцов
8
Факториал номера столбца
•Перестановки, включающие
отрицательные ключевые значения
Р ис. 4.1 0. Число возможных ключей относительно числа столбцов для марш рутного шиф ра
Вот четыре перевода из 384, произведенных для шифротекста на рис. 4.2:
с использованием ключа = [-4, -1, -2, -3] перевод = IS HEADING FILLER VILLAGE YOUR SNOW SOUTH GODWIN ARE FREE TO YOU WITH SUPPLIES GONE TRANSPORT ROANOKE JUST TO REST
122 Глава 4
с использованием ключа = [1, 2, -3, 4] перевод = REST ROANOKE HEADING ТО TRANSPORT WITH SNOW GONE YOU ARE FREE TO GODWIN YOUR SUPPLIES SOUTH VILLAGE IS JUST FILLER
с использованием ключа = [-1, 2, -3, 4] перевод = VILLAGE ROANOKE HEADING TO GODWIN WITH SNOW GONE YOU ARE FREE TO TRANSPORT YOUR SUPPLIES SOUTH REST IS JUST FILLER
с использованием ключа = [4, -1, 2, -3] перевод = IS JUST FILLER REST YOUR SUPPLIES SOUTH TRANSPORT ARE FREE TO YOU WITH SNOW GONE GODWIN ROANOKE HEADING TO VILLAGE
Правильный ответ присутствует, но вы можете по достоинству оценить то, как трудно было бы выбрать его оперативно, учитывая использование кодовых и фик тивных слов. Тем не менее вы сделали свою работу. Пойдите и выпейте мятного джулепа или сладкого чая. Решение этого проекта представлено в приложении к книге и по адресу https://www.nostarch.com/impracticalpython/ в файле route cipher hacker.py. Вам также понадобится программа perms.py, которая основана на предыдущем практи ческом проекте.
Сложные проекты
Для сложных проектов решения не предусмотрены.
Кодировщик маршрутного шифра Неопытному служащему телеграфа союзников нужно зашифровать следующее ни же сообщение, дополненное кодовыми словами (табл. 4.1). Помогите ему, написав программу, которая на входе принимает сообщение и автоматически заменяет
Таблица 4.1. Кодовые слова
B atteries
V icksbu rg
April
16
G ra n d
G u lf
Forts
R iver
2 5
2 9
A dm iral
P orter
H O U N D S
O D O R
C L A Y T O N
S W E E T
T R E E
O W L
B A IL E Y
H IC K O R Y
M U L T IP L Y
A D D
H E R M E S
L A N G F O R D
Декодирование шифров времен Гражданской войны в США 123
кодовые слова, заполняет нижний ряд фиктивными словами и переставляет слова с помощью ключа [-1, 3, -2, 6, 5, —4]. Используйте матрицу 6x7 и составьте соб ственные фиктивные слова.
We will run the batteries at Vicksburg the night of April 16 and proceed to Grand Gulf where we will reduce the forts. Be prepared to cross the river on April 25 or 29. Admiral Porter. Перевод. Мы пригоним батареи в Виксбург в ночь на 16 апреля и продолжим путь к заливу Г ранд, где мы сократим форты. Будьте готовы пересечь реку 25 или 29 ап реля. Адмирал Портер.
Для лексикона кодовых слов из этой таблицы рассмотрите возможность использо вания словаря Python.
Зигзагообразный многорядный шифр
Напишите версию зигзагообразного шифра, которая использует три ряда (три жер ди) вместо двух. Подсказки можно найти на веб-странице
https://en.wikipedia.org/wiki/Rail_fence_cipher.
5
КОДИРОВАНИЕ ШИФРОВ ВРЕМЕН ГРАЖДАНСКОЙ ВОЙНЫ В АНГЛИИ
В 1587 г. Мария, королева Шотландии, потеряла голову из- за клочка бумаги. Пятьдесят пять лет спустя сэр Джон Тре- ванион, сторонник другого обезглавленного монарха, Карла I, спас свою голову клочком бумаги. В чем же разница? В стега
нографии.
Стеганография — это проверенная временем практика сокрытия сообщений на столько хорошо, что об их существовании даже не подозревают. Название основа но на греческих словах “скрытое письмо”, и весьма буквальный греческий пример состоял в том, чтобы взять используемые для письма покрытые воском деревянные таблички, соскоблить воск, написать на дереве, а затем покрыть табличку новым покрытием из гладкого воска. Современный пример состоит во встраивании сооб щения в изображение путем тонкого изменения его цветовых компонентов. Даже простое 8-битное изображение JPEG содержит красок больше, чем может обнару жить человеческий глаз, поэтому без цифровой обработки или фильтрации сооб щение, по существу, невидимо.
В этой главе вы будете работать с нулевым шифром1, который вообще-то шифром не является, а считается стенографическим приемом сокрытия открытого текста внутри других символьных цепочек, состоящих из не шифровального материала. “Нулевой” означает “никакой” (null), поэтому с нулевым шифром вы решаете не шифровать сообщение. Ниже приведен пример нулевого шифра, использующего
1 Нулевой шифр (null cypher), также именуемый маскировочным шифром или тайнописью, является древней формой шифрования, где открытый текст смешивается с большим количеством нешифрован ного материала. См. https://en.wikipedia.org/wiki/Null_cipher. — Прим. перев.
Кодирование шифров времен Гражданской войны в Англии
1 2 5
первую букву в каждом слове (в английском варианте зашифровано словосочета ние “null cypher”, что означает “нулевой шифр”):
Nice uncles live longer. Cruel, insensitive people have eternal regrets.
Перевод. Хорошие дяденьки живут дольше. Жестокие, бесчувственные людиш ки испытывают вечные сожаления.
Сначала вы напишете код, который найдет скрытое сообщение, которое спасло сэра Джона, а затем вы выполните гораздо более сложную задачу написания нулевого шифра. Наконец, вы получите возможность написать программу, которая могла бы спасти голову королевы Марии, если бы она использовала ее результат.
Проект 10: шифр Треваниона
В целях защиты своих сообщений королева Мария полагалась на стеганографию и шифрование. Стратегия была разумной, но ее применение было ошибочным. Сама того не ведая, для переправки своих сообщений она полагалась на двойного агента по имени Гилберт Гиффорд. Сначала Гиффорд передал их шпиону королевы Ели заветы, который затем взломал шифр и заменил его поддельным сообщением, со блазнившим Марию изобличить себя. Остальное, как говорится, уже история. Для Джона Треваниона результат был радужнее. Сэр Джон, выдающийся кавалер, который помогал Карлу I против Оливера Кромвеля в Гражданской войне в Анг лии, был схвачен и заключен в тюрьму в Колчестерском замке. За день до казни он получил письмо от одного из своих друзей. Письмо не было переправлено контра бандой, а доставлено прямиком в руки его тюремщиков, которые осмотрели его, но не заметили никакого обмана. Прочитав его, сэр Джон попросил немного времени наедине, чтобы помолиться в часовне. Когда тюремщики вернулись за ним, он уже исчез. Вот сообщение, полученное сэром Джоном (ниже приведен его перевод на русский язык).
Worthie Sir John: Hope, that is the beste comfort of the afflicted, cannot much, I fear me, help you now. That I would saye to you, is this only: if ever I may be able to requite that I do owe you, stand not upon asking me. Tis not much I can do: but what I can do, bee you verie sure I wille. I knowe that, if deathe comes, if ordinary men fear it, it frights not you, accounting for it for a high honour, to have such a rewarde of your loyalty. Pray yet that you may be spared this soe bitter, cup. I fear not that you will grudge any sufferings; onlie if bie submission you can turn them away, ’tis the part of a wise man. Tell me, an if you can, to do for you anythinge that you wolde have done. The general goes back on Wednesday. Restinge your servant to command. R.T. Достойный сэр Джон: Надежда, которая является лучшим утешением для стражду щих, боюсь, не много способна помочь Вам сейчас. Вот что я хотел бы сказать Вам: если когда-нибудь я смогу отплатить Вам за то, что я Вам должен, не просите меня об этом. Не так уж много того, что я могу сделать, но то, что я могу сделать, будьте уверены, я сделаю. Я знаю, что на пороге смерти, если обычные люди будут ее бо яться, то она не испугает Вас, потому что считается высокой честью получить такую награду за свою верность. И все же помолитесь, чтобы Вы были избавлены от этой
126
Глава 5
чаши горечи. Я не боюсь, что Вы пожалеете о каких-либо страданиях; только из по корности вы можете их избежать, это часть мудрого человека. Скажите мне, и если сможете, я сделаю для Вас все, что сделали бы Вы. Генерал возвращается в среду. Прислушайтесь к своему слуге. Р.Т.
Как вы, наверное, уже догадались, это, казалось бы, невинное письмо содержит скрытое сообщение, показанное ниже жирным шрифтом:
Worthie Sir John: 1 lope, that is the beste comfort of the afflicted, cannot much, 1 fear me, help you now. Thai I would save to you, is this only: if ever I may be able to requite that I do owe you, stand not upon asking me. Tis not much 1 can do: but what I can do, bee you verie sure I wille. 1 knowe that, if deathe comes, if ordinary men fear it, it frights not you. accounting for it for a high honour, to have such a rewarde of your loyalty. Pray yet that you may be spared this soe bitter, cup. I fear not that you will grudge any sufferings; onlie if bie submission you can turn them away, ’tis the part of a wise man. Tell me, an if you can, to do for you anythinge that you wolde have done. The general goes back on Wednesday. Restinge your servant to command. R.T.
Этот нулевой шифр использует каждую третью букву после знака препинания, со общая сэру Джону о том, что “panel at east end of chapel slides” (панель в восточном конце часовни сдвигается). Ходят слухи, что позже были обнаружены остатки узкой лестницы в углублении в стене замка. Проход был заблокирован в момент обнаружения, но, возможно, примерно в 1642 г. для сэра Джона это был путь к спа сению. Этот побег в самую последнюю минуту не был бы возможен с помощью традици онного шифра. Только умело замаскировав послание с помощью стеганографии, автор смог так быстро передать его в руки сэра Джона. А прелесть нулевого шифра заключается в том, что даже если сэр Джон не знал схемы, но подозревал, что со общение присутствует, он смог найти его довольно быстро.
Если бы друг сэра Джона был осторожнее, скрыв зашифрованный текст вместо от крытого, то сэр Джон, вероятно, не расшифровал бы сообщение за то короткое время, что у него оставалось — если только он не был бы заранее проинформиро ван о типе шифра и ключе.
Ц е л ь Написать программный код, который находит в нулевом шифре буквы, скрытые после знаков препинания, и позволяет пользователю выбрать число букв после знака препи нания для поиска решения.
Стратегия и псевдокод
Нулевые шифры полагаются на повторяющийся шаблон, известный отправителю и получателю. Например, каждое третье слово может быть частью реального сооб щения или, еще лучше, последней буквой каждого третьего слова. В шифре Трева ниона это третья буква после знака препинания.
Для того чтобы найти шифр Треваниона, будем считать, что знаки препинания яв ляются сигналом для начала подсчета, а затем напишем программный код, который
Кодирование шифров времен Гражданской войны в Англии 127
находит каждую п-ю букву после знака и сохраняет эти буквы в символьной цепоч ке или списке. Разобравшись в том, как это сделать, вы можете легко отредактиро вать программный код для работы с любой начальной точкой, такой как каждое заглавное слово, каждая вторая буква в слове или начальная буква каждого третье го слова.
Единственным реальным спорным моментом являются знаки препинания. Напри мер, хотел ли автор нулевого шифра, чтобы знаки препинания были включены в открытый текст? Как обрабатывать второй знак препинания в желаемом диапазо не счета? Что происходит, если два знака препинания встречаются подряд?
Если внимательно посмотреть на шифр Треваниона, то вы должны увидеть, что там есть двойные знаки препинания, вызванные повторным использованием слова tis.
Кроме того, в конце послания имеется путаница знаков препинания, где автор ука зывает свои инициалы. Для того чтобы справиться с этим, сэр Джон и его друг, возможно, установили некоторые правила до заключения сэра Джона в тюрьму, или сэр Джон просто использовал метод проб и ошибок для их выработки.
Опираясь на концевую часть послания, знаки препинания не включают в число букв. Если бы друг сэра Джона хотел, чтобы они были включены, то скрытое по слание заканчивалось бы заглавной буквой Т, потому что Т находится в трех сим волах после знака препинания, а не в трех буквах после. Это означает, что если чи татель встречает знак препинания внутри предела счета, то он должен начать свой счет сначала.
Таким образом, вот эти правила:
♦ начинать счет букв сначала с каждым знаком препинания;
♦ обнулять счет, если встречается знак препинания;
♦ знаки препинания не могут быть частью открытого сообщения.
Поскольку вы можете не знать, каким должно быть число букв, напишите про граммный код так, чтобы он проверял все количества вплоть до предела, которое предоставляет пользователь. Псевдокод будет довольно простым:
Загрузить текстовый файл и удалить из него пробелы Получить от пользователя входные данные о том, сколько букв после знаков препинания надо просматривать и экзаменовать Перебрать в цикле число букв от 1 до этого значения просмотра вперед
Создать пустую символьную цепочку, которая будет содержать перевод Создать счетчик Создать маркер О первый_найден и установить его равным False Перебрать в цикле символы текста
Если символ является знаком препинания
Счетчик = О первый.найден = True
В противном случае, если Q первый_наден = True
Счетчик +1
128 Глава 5
Если Счетчик = значение просмотра вперед Добавить символ в цепочку перевода
Показать перевод для этого значения просмотра вперед
Обратите внимание, что переменная first found (первый найден) ф будет оста ваться False до тех пор, пока не будет обнаружен знак препинания, после чего она будет установлена равной True ф. Это не дает программе вести счет до тех пор, пока не будет найден первый знак препинания.
Теперь вы готовы написать код!
Код шифра Треваниона
Программный код этого раздела отыщет нулевой шифр в стиле шифра Треваниона, закодированный с помощью определенного числа букв после каждого знака препи нания. Вам также понадобится текстовый файл, содержащий шифр Треваниона. И скрипт, и текстовый файл можно скачать с https://www.nostarch.com/ impracticalpython/ соответственно в виде файлов nullcipherfinder.py и trevanion.txt. Сохраните эти файлы в одной папке.
Загрузка текста
Листинг 5.1 импортирует несколько полезных модулей и загружает текстовый файл, содержащий нулевой шифр.
Листинг 5.1. Импортирует модули и загружает текст нулевого шифра. Файл null_cipherjinder.py, часть 1
ф import sys
import string
ф def load_text(file):
“““Загрузить текстовый файл в виде символьной цепочки.”””
ф with open(file) as f:
ф return f.read().strip()
Сначала импортируйте теперь уже хорошо знакомый модуль sys для обработки ис ключений, которые могут возникнуть во время ввода пользователем данных ф. Также импортируйте модуль string для получения доступа к полезным коллекциям констант, таких как буквы и знаки препинания.
Далее определите функцию загрузки текстового файла, содержащего нулевой шифр ф. Эта функция аналогична той, которую вы использовали для загрузки файла словаря в главе 2. Она будет вызвана функцией main () позже для фактиче ской загрузки файла.
Запустите функцию load_text() с помощью инструкции with для того, чтобы от крыть файл ф. Используя with, вы знаете, что файл будет автоматически закрыт
Кодирование шифров времен Гражданской войны в Англии 129
после его загрузки. Примените метод read о для загрузки содержимого и метод strip о для удаления начальных и замыкающих пробелов. Обратите внимание, что вы можете сделать это в одной строке кода в инструкции return О
Отыскание скрытого сообщения
В листинге 5.2 определяется функция, которая отыскивает скрытое сообщение. Она требует двух аргументов. Первый — это сообщение, которое является исходным текстовым файлом в виде цепочки символов, лишенной пробелов, и второй — это число букв, которые нужно проверять после знака препинания. Это проверочное значение берется у пользователя в рамках функции main ().
Листинг 5.2. Отыскивает скрытые буквы. Файл null_cipher_finder.py, часть 2
def solve_null_cipher(message, lookahead):
“““Решить нулевой шифр, основываясь на числе букв
после знака препинания,
message = текст нулевого шифра как цепочка символов без пробелов lookahead = конечная точка диапазона букв после знака препинания »» »» »»
О f°r i in range(1, lookahead + 1 ) :
О plaintext = '’
count = 0 found_first = False
О f°r char in message:
О if char in string.punctuation:
count = 0 found_first = True
ф elif found_first is True:
count += 1 ф if count == i:
plaintext += char
e print(“Используя сдвиг {} после знака препинания = {}”.
format(i, plaintext))
print ()
Рассматривайте значение lookahead как конечную точку диапазона в цикле for, для того чтобы вы могли проверить все промежуточные буквы сообщения на наличие скрытого послания. Установите диапазон в виде (l, lookahead + 1) ф; благодаря этому вы начнете с первой буквы после знака препинания и включите выбранное пользователем число в оценку. Теперь задайте несколько переменных ф. Сначала инициализируйте пустую сим вольную цепочку для хранения переведенного открытого текста. Затем установите счетчик равным 0. Наконец, установите переменную found first равной False. Помните, что программа использует эту переменную для того, чтобы отложить подсчет до тех пор, пока не встретится первый знак препинания.
130 Глава 5
Затем начните перебирать символы в сообщении О- Если вы обнаружите знак пре пинания, то сбросьте значение счетчика в 0 и установите значение параметра found_first равным True О Если вы уже нашли знак препинания, а текущий сим вол не является знаком препинания, то переместите счетчик на l 0 . Если вы нашли букву, которую ищете, то это означает, что счетчик достиг текущего значения lookahead (i) — добавьте букву в символьную цепочку с открытым текстом 0 .
После того как проэкзаменуете все символы в сообщении для текущего значения lookahead, покажите текущий ключ и перевод О
Определение функции mainQ
В листинге 5.3 определена функция main о . Из главы 3 вы, возможно, помните, что функция ma i n o подобна менеджеру проекта вашей программы: она принимает входные данные, отслеживает продвижение работы и сообщает другим функциям, когда нужно приступать к работе.
Листинг 5.3. Определяет функцию ла±п(). Файл null_cipher_finder.py, часть 3
def m a i n ():
“““Загрузить текст, решить нулевой шифр.”””
загрузить и обработать сообщение: #
О filename = input(“ХпВведите полное имя файла для перевода сообщения: “)
О try:
loaded_message = load_text(filename)
except IOError as e:
print(”{}. Завершение программы.”. format(e), file=sys.stderr)
sys.exit(1)
О print(“ХпПЕРВОНАЧАЛЬНОЕ СООБЩЕНИЕ =”)
print(”{}".format(loaded_message), “\n”) print(“ХпСписок проверяемых знаков препинания = {}”.
format(string.punctuation), “\n”)
удалить пробелы: #
О message = ’ join(loaded_message.split())
получить от пользователя диапазон возможных ключей шифра: #
0 while True:
0 lookahead = input("\пЧисло букв, проверяемых после \
0 if lookahead.isdigit() :
знака препинания: “)
lookahead = int(lookahead)
break
else: 0 print(“Пожалуйства, введите число.”, file=sys.stderr)
print ()
Кодирование шифров времен Гражданской войны в Англии 131
выполнить функцию декодирования шифра #
О solve_null_cipher(message, lookahead)
Начните с запроса у пользователя имени файла (имя + расширение) О? а затем используйте инструкцию try для вызова функции load texto 0 . Если файл не найден, то покажите ошибку красным цветом — для тех, кто использует окно ин терпретатора IDLE, — и выйдите из программы с использованием команды sys.exit (l), где 1 указывает на завершение программы с ошибкой.
Напечатайте сообщение, за которым следует список знаков препинания из модуля string О- Программа распознаёт как знаки препинания только те символы, кото рые находятся в этом списке.
Далее возьмите загруженное сообщение и удалите все пробелы О* Вы будете под считывать только буквы и знаки препинания, поэтому пробелы будут просто мешать. Начните цикл while, который продолжит запрашивать у пользователя входные данные в случае, если он введет неверное значение 0 . Запросите у поль зователя число проверяемых букв после знака препинания Q . Оно будет рассмат риваться как диапазон, начиная с 1 и заканчивая выбранным числом плюс 1. Если входное значение является цифрой Q , то преобразуйте его в целое число, т. к. ин струкция input возвращает значение в виде цепочки символов. Затем примените инструкцию break для выхода из цикла.
Если пользователь вводит недопустимое значение, например “Bob”, то примените инструкцию print для запроса числа и сделайте шрифт красным для пользователей интерактивной оболочки с помощью конструкции sys.stderr ф . Цикл while затем повторит запрос на ввод данных.
Передайте переменную lookahead вместе с сообщением в функцию шифрования solve null_ О- Теперь осталось ТОЛЬКО вызвать функцию main ().
Выполнение функции mainQ
Вернувшись в глобальное пространство, завершите программный код, вызвав maino, но только если программа выполняется в автономном режиме, а не импор тируется в другую программу (листинг 5.4).
Листинг 5.4. Вызывает функцию main(). Файл null_cipher_finder.py, часть 4
if name == 1__main__1 :
m a in ()
Ниже приведен пример вывода из законченной программы, используя на входе шифр Треваниона:
Введите полное имя файла для перевода сообщения: trevanion.txt
ПЕРВОНАЧАЛЬНОЕ СООБЩЕНИЕ =
Worthie Sir John: Hope, that is the beste comfort of the afflicted, cannot much, I fear me, help you now. That I would saye to you, is this only: if ever
132
Глава 5
I may be able to requite that I do owe you, stand not upon asking me. ‘Tis not much I can do: but what I can do, bee you verie sure I wille. I knowe that, if deathe comes, if ordinary men fear it, it frights not you, accounting for it for a high honour, to have such a rewarde of your loyalty. Pray yet that you may be spared this soe bitter, cup. I fear not that you will grudge any sufferings; onlie if bie submission you can turn them away, ’tis the part of a wise man. Tell me, an if you can, to do for you anythinge that you wolde have done. The general goes back on Wednesday. Restinge your servant to command. R.T.
Список проверяемых знаков препинания = !”#$%&’()*+,-./:;?@[]Ля~}|{’_
Число букв, проверяемых после знака препинания: 4
Используя сдвиг 1 после знака препинания = HtcIhTiisTbbliiiatPcIotTatTRRT
Используя сдвиг 2 после знака препинания = ohafehsftiuekfftcorufnienohe
Используя сдвиг 3 после знака препинания = panelateastendofchapelslides
Используя сдвиг 4 после знака препинания = etnapthvnnwyoerroayaitlfogt
Из полученного результата можно заключить, что программа проверяла до четвер той буквы после знака препинания, но, как хорошо видно, она находит решение с использованием трех букв после знака препинания.
Проект 11: написание нулевого шифра
Вот незаконченный пример очень слабого нулевого шифра, основанного на начале каждого слова. Потратьте минуту и попробуйте закончить предложение:
Н_________ е_________ 1_________ р_________ m_________ е_________ .
Вам, вероятно, будет трудно, потому что независимо от того, используете ли вы буквы или даже целые слова, потребуется тяжелая работа и время на то, чтобы соз дать нулевой шифр, который не читается с затруднениями и не вызывает подозре ний. Суть проблемы заключается в контексте. Если шифр встроен в переписку, то во избежание подозрений эта переписка должна быть связной. А это означает, что она должна касаться соответствующей темы и оставаться верной этой теме в течение разумного числа предложений. Как вы, наверное, убедились, черновой набросок даже одного предложения на любую тему представляет собой непростую задачу! Ключ состоит в том, чтобы убедительно избегать контекста, и хороший способ сде лать это — применить список. Никто не ожидает, что список покупок будет жестко организован или иметь смысл. Списки также могут быть адаптированы к получате лю. Например, переписчики могут обсуждать книги или фильмы и обмениваться списками своих самых любимых из них. Заключенный может начать изучать ино странный язык и получать регулярные списки слов от своего наставника. Предпри ниматель может получать ежемесячные остатки продукции с одного из своих скла
Кодирование шифров времен Гражданской войны в Англии 133
дов. Со списками контекст соблюдается даже при перетасовке слов, благодаря чему правильная буква отыскивается в правильном месте.
Ц е л ь
Написать программный код, который скрывает нулевой шифр в списке слов.
Код спискового шифра
Программный код list cipher.py в листинге 5.5 встраивает нулевой шифр внутрь списка слов словаря под предлогом изучения слов. Вам также понадобится про грамма load dictionary.py, которую вы использовали в главах 2 и 3. Указанный файл можно скачать вместе со следующим ниже скриптом с веб-сайта h ttp s :// v n v w .n o s ta rc h .c o m /im p ra c tic a lp y th o n /. Наконец, вам понадобится один из файлов словаря, который вы использовали в главах 2 и 3. Список подходящих онлайновых словарей можно найти в табл. 2.1. Все вышеупомянутые файлы должны храниться в одной папке.
Листинг 5.5. Скры вает нулевой шифр в списке. Файл list_cipher.py
ф from random import randint
import string
import load_dictionary
написать короткое сообщение, которое не содержит знаки препинания #
или числа! #
input_message = “Panel at east end of chapel slides”
message = '’
for char in input_message:
0 if char in string.ascii_letters:
message += char
print(message, “\n”)
О message = “".join(message.split())
0 # открыть файл словаря
word_list = load_dictionary.load(‘2of4brif.txt1)
построить список слов словаря со скрытым сообщением #
0 vocab_list = []
0 for letter in message:
size = randint(6, 10) 0 for word in word_list:
if len(word) == size and word[2].lower() == letter.lower()
and word not in vocab_list:
vocab_list.append(word)
break
134 Глава 5
ф if len(vocab_list) +; внизу — реальное сообщ ение, показанное с помощью инструмента выделения и сделанного черным цветом выделения
вручную; 2) когда вам нужно отправить больше чем несколько сообщений. Как вы увидите, короткая программа на Python значительно упростит этот процесс!
Учет типов шрифтов, кернинга и межзнакового интервала
Размещение невидимого текста — ключевое проектное решение. Одним из вариан тов является использование пробелов между видимыми словами фальшивого со общения, но это может вызвать проблемы, связанные с интервалами, которые сде лают конечный продукт подозрительным. Пропорциональные шрифты используют переменную ширину символов для улуч шения читаемости. Примером таких шрифтов являются Arial и Times New Roman. Моноширинные шрифты характеризуются постоянной шириной символов, что способствует выравниванию текста и распознаванию отдельных символов, в осо бенности тонких, таких как символы ( или {. В результате моноширинные шрифты пользуются популярностью в интерфейсах программирования. Примером таких шрифтов Я В Л Я Ю Т С Я C onsolas И Courier New.
Написание текста невидимыми чернилами 143
Кернинг — это типографская процедура по регулировке расстояния и наложения между отдельными глифами1 символов с целью улучшения их визуальной привле кательности. Процедура, именуемая настройкой межзнакового интервала (или разрядкой/уплотнением, или трекингом), используется для настройки интервала между символами во всех строках или блоках текста с той же целью. Эти настрой ки способствуют различимости и удобочитаемости, обеспечивая, чтобы буквы бы ли расположены не настолько близко друг к другу, что они становятся неразличи мыми, или не настолько далеко друг от друга, что слова не распознаются. Обратите внимание, что все мы читаем слова, а не буквы. Если сомневаетесь в этом, то про чтите вот это: peopl raed wrds nt lttrs. Corase, contxt hips2.
Сначала настраивается кернинг между парами букв, а затем межзнаковый интервал, в ходе которого сохраняется относительный кернинг пар букв. Как упоминалось ранее, эти переменные ширины и автоматические исправления могут вызвать про блемы при попытке скрыть символы между словами, использующими пропорцио нальные шрифты:
То a great mind nothing is little. То a great m ind nothing T o ^ ijj g reat|mind|Jnо thingjjis little. Скрытые буквы обнаружены
Пропорциональный шрифт без скрытых букв
is little. Пропорциональный шрифт со скрытыми буквами между словами
To a great mind nothing is little. То a great mind nothing is little.
To(3a|]great|mind§nothing0is little.
Моноширинный шрифт без скрытых букв Моноширинный шрифт со скрытыми буквами между словами Скрытые буквы обнаружены
Если вы используете моноширинный шрифт, то единообразный интервал обеспе чивает удобное укрытие. Но поскольку в профессиональной переписке чаще при меняются пропорциональные шрифты, то техника невидимых чернил должна фокусироваться на более легко контролируемых промежутках между строками.
Использование пустых строк между абзацами — самый простой способ програм мирования и чтения, и он не должен требовать длинного фальшивого сообщения, в результате чего вы можете сжато резюмировать основные моменты сделки. Это важно, т. к. вы не хотите, чтобы в ваше видимое фальшивое сообщение добавля лись пустые страницы. Следовательно, след вашего скрытого сообщения должен быть меньше, чем вашего фальшивого.
Как избежать проблем
Когда вы разрабатываете программное обеспечение, неплохо периодически зада ваться вопросом о том, как пользователь может все это испортить. Единственное,
1 В типографике глиф — это характерная особенность шрифта. В вычислительной технике это эле мент компьютерного символа, соответствующий графеме или графемоподобной единице текста: это может быть буква, число, знак пунктуации или пиктограмма, декоративный символ, графическая мет ка. См. h ttp s://ru .w ikip e d ia .o rg /w iki/I^riH (|>. — Прим. перев.
2 Перевод: People read words, not letters, because context helps — люди читают слова, а не буквы, пото му что помогает контекст. — Прим. перев.
144 Глава б
что здесь может пойти не так, — это то, что шифровальный процесс изменит буквы в вашем скрытом сообщении, в результате чего настройки кернинга и межзнаково го интервала могут подтолкнуть слово выйти за пределы разрыва строки, что при ведет к автоматическому переносу строки. В результате в фальшивом сообщении получится неравномерное и подозрительно выглядящее пространство между абза цами. Один из способов избежать этого — нажимать клавишу немного раньше, когда вы печатаете в каждой строке реального сообщения. Это оставит не которое пространство в конце строки для размещения изменений из-за шифрова ния. Конечно, вам все равно придется проверить результаты. Заранее считать, что программный код работает, так же рискованно, как допустить, что Джеймс Бонд мертв!
Управление документами Word с помощью python-docx
Бесплатный сторонний модуль под названием python-docx позволяет Python управ лять файлами Microsoft Word (.docx). Для скачивания и установки сторонних моду лей, упомянутых в этой книге, вы будете использовать программу-инсталлятор pip, т. е. систему управления пакетами, которая упрощает установку программного обеспечения на основе Python. Python 3 в Windows и macOS версии 3.4 и более поздних поставляется с предустановленным инсталлятором pip; для Python 2 пре дустановка pip начинается с версии 2.7.9. Пользователям Linux может потребовать ся установить pip отдельно. Если вы обнаружите, что вам нужно установить либо обновить pip, обратитесь к инструкции по адресу https://pip.pypa.io/en/stable/ installing/ либо поищите в Интернете на предмет установки pip в вашей конкретной операционной системе.
С помощью инструмента pip установить python-docx можно, просто выполнив команду pip install python-docx в интерактивном окне PowerShell либо окне тер минала, в зависимости от вашей операционной системы. Онлайновые инструкции для python-docx доступны по адресу https://python-docx.readthedocs.io/en/latest/.
В ЭТОМ проекте вам нужно усвоить объекты paragraph И run. Модуль python-docx организует типы данных, используя три объекта в следующей иерархии:
♦ document — весь документ СО СПИСКОМ объектов paragraph;
♦ paragraph — блок текста, разделенный с использованием клавиши
в Word; содержит список объектов run;
♦ run — связная символьная цепочка с текстом одинакового стиля. Абзац paragraph считается объектом блочного уровня, который python-docx опре деляет следующим образом: “элемент блочного уровня размещает содержащийся в нем текст между его левым и правым краями, добавляя дополнительную строку всякий раз, когда текст выходит за его правую границу. Для объекта paragraph гра ницы обычно являются полями страницы, но они также могут быть границами столбцов, если страница выложена в столбцах, либо границами ячеек, если paragraph находится внутри ячейки таблицы. Таблица также является объектом блочного уровня”.
Написание текста невидимыми чернилами 145
Объект paragraph имеет разнообразные свойства, которые определяют его разме щение в контейнере — обычно это страница — и способ разделения его содержи мого на отдельные строки. Доступ к свойствам форматирования объекта paragraph М О Ж Н О получить С П О М О Щ Ь Ю объекта ParagraphFormat, ДОСТУПНОГО через СВОЙСТВО ParagraphFormat объекта paragraph, И М О Ж Н О Задать все СВОЙСТВа paragraph С П О М О Щ Ь Ю группировки стилей абзаца либо применить их непосредственно к объекту paragraph.
Объект run — это объект внутристрочного уровня, который появляется в пределах абзацев или других объектов блочного уровня. Объект run имеет свойство font только для чтения, обеспечивающее доступ к объекту font. Объект font предостав ляет свойства для получения и настройки форматирования символов для конкрет ного объекта run. Этот функционал вам понадобится для установки белого цвета текста вашего скрытого сообщения. Стиль относится к коллекции атрибутов Word для абзацев и символов (объектов run) или их комбинации. Стиль включает в себя такие знакомые всем атрибуты, как шрифт, цвет, отступ, межстрочный интервал и т. д. Возможно, вы заметили, что некоторые из них отображены в Word на группе Styles (Стили) на вкладке Н о те (Главная) ленты (рис. 6.6). Любое изменение стиля — даже одной буквы — требует создания нового объекта run. В настоящее время доступны только те стили, кото рые находятся в открытом файле .docx. Это может измениться в будущих версиях python-docx.
AaBbCc AaBbCcDdE AaBbCcDdE A A B I Г ~ 1
|B oxH eader| BoxNormal
11 Caption
Chap_name Chap.Num
Styles
Р и с. 6.6. Панель стилей в Microsoft W ord 2 0 1 6
Полную документацию по использованию стилей в python-docx можно найти по адресу http://python-docx.readthedocs.io/en/latest/user/styles-using.htmI.
Вот пример абзацев и фрагментов текста так, как их видит python-docx:
Я — один абзац из одного фрагмента, потому что весь мой текст имеет одинако вый стиль.
Я — один абзац с двумя фрагментами. Я — второй фрагмента, потому что мой стиль изменен на полужирный. Я — один абзац с тремя фрагментами. Я — второй фрагмент, потому что мой стиль изменен на полужирный. Третий фрагмент является моим последним словом.
Если что-то из этого кажется неясным, не волнуйтесь. Знать python-docx в деталях не потребуется. Как и в любом фрагменте программного кода, вам главным обра зом нужно знать только то, что вы хотите сделать. Поиск в Интернете обязательно даст много полезных предложений и всеобъемлющих образцов программного кода.
146
Глава 6
ПРИМЕЧАНИЕ
Для того чтобы все работало гладко, не меняйте стили в реальном (скрытом) сообще нии и обеспечьте, чтобы каждая строка заканчивалась жестким переводом строки, вручную нажимая клавишу . К сожалению, Word не имеет специального симво ла для мягких переводов строк, вызываемых автоматическим переносом слов. Так что вы не сможете войти в существующий документ Word, применить автоматические разрывы строк и использовать функционал “Найти и заменить”, для того чтобы изме нить их все на жесткие переводы строк. Такова жизнь “крота”.
Скачивание ресурсных файлов
Внешние файлы, которые вам понадобятся, можно скачать с https://www. nostarch.com/impracticalpython/ и должны быть сохранены в той же папке, что и код:
♦ template.docx — пустой документ Word, отформатированный с использованием
стилей, шрифтов и полей, официально используемых в корпорации Холмса;
♦ fakeMessage.docx — фальшивое сообщение без фирменного бланка и даты в до
кументе Word;
♦ realMessage.docx— реальное сообщение в открытом тексте без фирменного
бланка и даты в документе Word;
♦ realMessage Vig.docx— реальное сообщение, зашифрованное с помощью шиф
ра Виженера;
♦ example_template_prep.docx — пример фальшивого сообщения, используемого для создания шаблонного документа (программа не требует запуска этого файла).
ПРИМЕЧАНИЕ
Если вы используете Word 2016, то простой способ сделать пустой файл шаблона — написать фальшивое сообщение (включая фирменный бланк) и сохранить файл. За тем удалить весь текст и сохранить файл снова под другим именем. Когда вы назна чаете переменной этот пустой файл, работая с python-docx, все существующие сти ли будут сохранены. Разумеется, вы можете использовать файл шаблона с уже вклю ченным фирменным бланком, но для того, чтобы узнать о python-docx побольше, мы построим фирменный бланк тут, используя Python.
Возьмите паузу на то, чтобы просмотреть в Word первые четыре документа. Эти файлы содержат входные данные для программы elementary ink.py. Фальшивое и реальное сообщения — второй и третий перечисленные выше пункты — также показаны на рис. 6.7 и 6.8.
Обратите внимание, что реальное сообщение содержит несколько цифр и специ альные символы. Они не будут зашифрованы с помощью таблицы Виженера, кото рую мы будем использовать, и я включил их для того, чтобы это подчеркнуть. В идеале ради максимальной секретности они будут прописаны (например, “три” вместо 3 и ’’процент” вместо %), когда позже мы добавим шифр Виженера.
Написание текста невидимыми чернилами 147
Dear Mr. Gerard:
I received your CV on Monday. It is very impressive, but I am sorry to inform you that Mr. Holmes is not looking for additional staff at this time.
While we do not normally accept unsolicited applications, I will keep your CV on file for future
consideration. If it is convenient, please send me a list of references, especially those pertaining to skills in negotiation, accounting, and data mining (preferably using the Python programming language). A recent photograph is also recommended.
Best of luck to you. Feel free to check back at this time next year in the event a position becomes
available. Use this email address, and include your name and the word “check-back” in the subject line.
Sincerely yours,
Emil Kurtz Associate Director
International Affairs
Уважаемый Г-н Жерар
Я получил ваше резюме в понедельник. Оно весьма впечатляет, но я с сожалением сообщаю вам, что мистер Холмс в настоящее время не ищет дополнительный персонал
Хотя мы обычно не принимаем не запрошенные заявки, я сохраню ваше резюме в файле для дальнейшего рассмотрения. Если это удобно, пожалуйста, пришлите мне список рекомендаций, в особенности тех, которые касаются навыков ведения переговоров, бухгалтерского учета и глубинного анализа данных (желательно с использованием языка программирования Python). Также рекомендуется сделать свежую фотографию Желаю Вам удачи
Не стесняйтесь обратиться к нам снова в это время в следующем году в случае, если позиция станет доступной Используйте этот адрес электронной почты и укажите свое имя и слово ‘Повторное обращение” в строке темы.
С искренним уважением,
Эмиль Курц заместитель директора по международным делам
Р ис. 6.7. “Фальшивый” текст в ф айле fakeM essage.docx
The Colombian deal will be for 2 new venture wildcat wells, one each in the Llanos & Magdalena
Basins. These wells include a carry of thirty percent for the national oil company
and will test at least 3 К meters of vertical section. In return, the client will be perm itted
to drill ten wells in the productive Putumayo province, earning a sixty % interest with a fifty
percent royalty rate, increasing to the standard eighty five percent royalty five years
after start of production in each well.
Колумбийская сделка будет заключена в отношении 2 новых венчурных поисково-разведочных
скважин, по одной в бассейнах Льянос и Магдалена. Эти скважины включают в себя перенос
тридцати процентов для национальной нефтяной компании и будет испытывать как минимум ЗК метров вертикального участка. В свою очередь, клиенту будет разрешено пробурить десять
скважин в продуктивной провинции Путумайо, зарабатывая шестьдесят % прибыли с пятьюдесятью процентной ставкой роялти в начале и стандартными восьмидесяти пятью
процентами роялти через пять лет после начала добычи в каждой скважине.
Р ис. 6.8. Р еальное сообщ ение в ф айле realM essage.docx
148
Глава б
Псевдокод
В следующем ниже псевдокоде описывается загрузка двух сообщений и шаблон ного документа, чередование и сокрытие реального сообщения в пустых строках с помощью белого шрифта, а затем сохранение гибридного сообщения.
Создать активы: В Word создать пустой документ с требуемым форматированием/стилями (шаблон) В Word создать безобидное фальшивое сообщение, которое будет видно и иметь достаточно число пустых строк для хранения реального сообщения В Word создать реальное сообщение, которое будет скрыто Импортировать docx для того, чтобы получить возможность оперировать документами Word с помощью Python Использовать docx для загрузки фальшивого и реального сообщений в виде списков Использовать docx для назначения переменной пустого документа Использовать docx для добавления заголовка фирменного бланка в пустой документ Создать переменную-счетчик для строк в реальном сообщении Определить функцию для форматирования межабзацного интервала с помощью docx Для строки в фальшивом сообщении:
Если строка пуста, и в реальном сообщении все еще есть строки:
Использовать docx и счетчик для заполнения пустой строки из реального сообщения Использовать docx для окраски шрифта реального сообщения белым цветом Нарастить счетчик для реального сообщения
В противном случае:
Использовать docx для написания фальшивой строки Выполнить функцию настройки межабзацного интервала Использовать docx для сохранения конечного документа Word
Код
Программа elementary ink.py в листинге 6.1 загружает реальное сообщение, фаль шивое сообщение и пустой шаблонный документ. Она скрывает реальное сообще ние в пустых строках фальшивого сообщения с помощью белого шрифта, а затем сохраняет гибридное сообщение как безобидный и профессионально выглядящий фрагмент корреспонденции, который может быть прикреплен к электронной почте. Соответствующий программный код можно скачать с веб-сайта
https://www.nostarch.com/impracticalpython/.
Импорт модуля python-docx, создание списков и добавление фирменного бланка
Листинг 6.1 импортирует python-docx, превращает строки текста в фальшивом и реальном сообщениях в элементы списка, загружает шаблонный документ, задаю щий стили, и добавляет фирменный бланк.
Листинг 6.1. Импортирует python-docx, загружает важные файлы .docx и добавляет фирменный бланк. Файл elementaryjnk.py, часть 1
Написание текста невидимыми чернилами 149
import docx
О from docx.shared import RGBColor, Pt 0 # получить текст из фальшивого сообщения и сделать
каждую строку элементом списка #
fake_text = docx.Document(1fakeMessage.docx1)
fake_list = [] for paragraph in fake_text.paragraphs:
fake_list.append(paragraph.text)
0 # получить текст из реального сообщения и сделать
каждую строку элементом списка #
real_text = docx.Document(1realMessage.docx1)
real_list = []
for paragraph in real_text.paragraphs:
О if len(paragraph.text) != 0: # удалить пустые строки
real_list.append(paragraph.text)
0 # загрузить шаблон, который устанавливает стиль, шрифт, поля и пр.
doc = docx.Document(’template.docx1)
0 # добавить фирменный бланк
doc.add_heading(1Морланд Холмс’, 0)
subtitle = doc.add_heading(‘Global Consulting & Negotiations’, 1) subtitle.alignment = 1 doc.add_heading(’’, 1)
0 doc.add_paragraph(‘17 декабря 2018’)
doc.add_paragraph(’’)
После импорта модуля docx — не как “python-docx” — используйте docx. shared для получения доступа к объектам цвета (RGBColor) и длины (pt) в модуле docx О . Это позволит вам изменять цвет шрифта и устанавливать интервал между строками. Следующие два блока кода загружают документы Word с фальшивым 0 и реаль ным 0 сообщениями в виде списков. Место, где в каждом документе Word была нажата клавиша , определяет, какие элементы будут в этих списках. Для того чтобы скрыть реальное сообщение, удалите все пустые строки, в результате чего ваше сообщение станет максимально коротким О . Теперь можно использо вать индексы списков для слияния двух сообщений и отслеживания того, какой яв ляется каким.
Затем загрузите шаблонный документ, содержащий предустановленные стили, шрифты и поля 0. Модуль docx будет писать в эту переменную и в конечном итоге сохранит ее в качестве окончательного документа. После загрузки и подготовки входных данных отформатируйте фирменный бланк окончательного документа в соответствии с официальным стилем, принятым
150
Глава б
в корпорации Холмса 0 . Функция add heading () добавляет абзац в стиле заголовка с текстовыми и целочисленными аргументами. Целое число о обозначает заголовок самого высокого уровня или заголовочный стиль, унаследованный у шаблонного документа. Подзаголовок отформатирован с помощью 1, т. е. следующего имеюще гося заголовочного стиля, и выровнен по центру, опять-таки с целым числом 1 (о — левое выравнивание, 2 — правое выравнивание). Обратите внимание, что при добавлении даты указывать целое число не требуется 0 . Если аргумент не указан, то по умолчанию форматирование наследуется у существующей стилевой иерар хии, которая в шаблоне выравнивается по левому краю. Другие инструкции в этом блоке кода просто добавляют пустые строки.
Форматирование и чередование сообщений
Листинг 6.2 выполняет реальную работу, форматируя интервал между строками и чередуя сообщения.
Листинг 6.2. Ф орматирование абзацев и чередование строк ф альшивого и реального сообщений. Файл ele m e n ta ry jn k .p y , часть 2
О def set_spacing(paragraph):
“““Использовать docx для установки строчного интервала
между абзацами.”””
paragraph_fomat = paragraph. paragraph_format paragraph_format.space_before = Pt(0) paragraph_format.space_after = Pt(0)
0 length_real = len(real_list)
count_real = 0 # индекс текущей строки в реальном (скрытом) сообщении
чередовать строки реального и фальшивого сообщений #
for line in fake_list: 0 if count_real +, тем самым вы выделите весь текст, а затем установите цвет подсветки (рис. 6.4) в темно-серый, для того чтобы увидеть оба сообщения. Секретное сообщение должно быть раскрыто (рис. 6.10).
M r. Ge
o lo m b ian deal w ill b e fo r 2 n e w v e n tu re w ild cat wells, one each in th e lla n o s & M a g d a len a
I received yo
looking fo r ad ditional
P at this tim e .
asm s. T hese w e lls include a carry o f th irty p e rcent for th e rvational oil co m pan
consideration. If it is co n ve n ie n t, please send m e a list o f referen ces, especially th ose pertaining to sk
in n e g o tiatio n , accounting, and d ata m ining (p re fe ra b ly using th e Python p rog ra——!— ‘————–’
you. Feel fre e to check back at this tim e next ye ar in th
Use this em ail address, and include yo ur n a m e and th e w o rd “check-back” in th e subject line.
to drill ten wells in th e prod uctive P utu m ayo province, earning a sixty % in terest w ith a fif
p ercen t ro y alty ra te , increasing to th e standard eig hty five pe rc en t royalty five year
a fte r start o f p rod uction in each well
Emil Kurtz
In te rn a tio n a l A ffairs
Р и с. 6.1 0. Д окум ент W ord выделен темно-серы м цветом для показа ф альшивого сообщения и незаш иф рованного реального сообщ ения
Написание текста невидимыми чернилами 153
Добавление шифра Виженера
До этого программный код использовал простую текстовую версию реального со общения, поэтому любой, кто изменит цвет подсветки документа, сможет прочи тать и узнать скрытую в нем конфиденциальную информацию. Поскольку вы знае те, что мистер Курц зашифровал ее с помощью шифра Виженера, вернитесь назад и поменяйте код, заменив открытый текст шифротекстом. Для этого найдите сле дующую строку:
real_text = docx.Document(1realMessage.docx’)
Эта строка загружает реальное сообщение в виде открытого текста, поэтому изме ните имя файла на то, которое показано ниже полужирным шрифтом:
real_text = docx. Document (1 reaIMessage_Vig. docx1)
Повторно выполните программу и снова откройте скрытый текст, выделив весь до кумент и сделав цвет подсветки темно-серым (рис. 6.11).
Dear M r. G erard:
qiyx kvpj ns ssp 2 zs) zczhhvc iw yham h jijxg, brc qopl gz hui
I received your CV on M o n d ay. It is very im pressive, but I am sorry to in form you th a t M r. H olm es is not
looking fo r ad ditional staff at this tim e .
я в ш т а ш и ш ш
W hil e w e do not norm ally accept unsolicited applications,
I w ill keep yo ur CV on file fo r fu tu re
consideration. If it is co n ve n ie n t, please send m e a list o f referen ces, especially those p e rtaining to skills
in nego tiation, accounting, and data m ining (p re ferab ly using th e Python p rog ram m ing language). A
recent ph otograp h is also reco m m en d ed .т д д ш т и д и и
Best o f luck to you. Feel fre e to check back at this tim e next ye ar in th e ev e n t a position becom es available.
Use this em ail address, and include yo ur na m e and th e w o rd “check-back” in th e subject line.
Р ис. 6.1 1. Д окум ент W ord, выделенный темно-серы м цветом, для показа ф альшивого сообщ ения и заш иф рованного реального сообщ ения
Секретное сообщение должно быть видимым, но нечитаемым для тех, кто не может расшифровать шифр. Сравните зашифрованное сообщение на рис. 6.11 с незашиф рованной версией на рис. 6.10. Обратите внимание, что в обеих версиях встречают ся числа и знак %. Они были сохранены для демонстрации потенциальных лову шек, связанных с выбором шифрования. Вы хотели бы улучшить шифр Виженера, включив эти символы — либо просто прописав их. Благодаря этому даже если ваше сообщение будет обнаружено, вы оставите минимально возможное количество подсказок относительно его темы.
154 Глава 6
Если вы хотите закодировать с помощью шифра Виженера свое собственное сооб щение, то поищите в Интернете по запросу “online vigenere encoder” (онлайновый кодировщик Виженера). Вы найдете несколько веб-сайтов, таких как http:// www.cs.du.edu/--snarayan/crypt/vigenere.html, которые позволяют набирать или вставлять открытый текст. И если вы хотите написать собственную Python- программу шифрования с использованием шифра Виженера, то обратитесь к книге Эла Свейгарта “Взламывание кодов с помощью Python” (Sweigart A. Cracking codes with Python. No Cramp Press, 2018).
Если вы поигрываете со своими реальными сообщениями, зашифрованными или незашифрованными, убедитесь, что вы используете тот же шрифт, что и в фальши вом сообщении. Например, типографский шрифт Helvetica Italic и размер шрифта 12 пт. Как вы помните из разд. “Учет типов шрифтов, кернинга и межзнакового интервала” ранее в этой главе, при попытке перемешать шрифты, в особенности пропорциональные и моноширинные, скрытые строки сообщения могут быть пере несены на новую строку, что приведет к неравномерному расстоянию между абза цами реального сообщения.
Обнаружение скрытого сообщения
Могла ли Джоан Ватсон или любой другой детектив быстро найти ваше скрытое послание? По правде говоря, скорее всего, нет. На самом деле, когда я пишу это, я смотрю эпизод сериала “Элементарно”, где Джоан занята расследованием компа нии, читая стопку с распечатками электронной почты! Использование шифра Виженера, возможно, было просто вкраплением ленивой режиссуры в общем-то разумно поставленный сериал. Тем не менее мы можем поразмышлять над тем, что могло бы вас выдать. Во-первых, поскольку окончательная информация о сделке, вероятно, не была от правлена до даты, близкой к дате сделки, поиск может быть ограничен корреспон денцией, отправленной после заключения сделки, тем самым устраняя много шума. Разумеется, детектив не будет точно знать, что конкретно он ищет— или даже о том, существует ли “крот”, — что оставляет большое пространство для поиска. И всегда есть вероятность, что информация была передана в телефонном разговоре или во время тайной встречи. Допуская, что имелся поддающийся обработке объем электронной почты и разве дывалась гипотеза скрытого сообщения, следователь мог бы обнаружить ваши не видимые чернила несколькими способами. Например, орфографический корректор Word не будет отмечать белые, бессмысленные зашифрованные слова до тех пор, пока они не будут сделаны видимыми. Если вы в качестве проверки прокручивали документ и сбросили цвет шрифта на нескольких скрытых словах, то они будут окончательно скомпрометированы, даже после того, как их цвет был восстановлен до белого. Корректор сразу же подчеркнет их обличительной красной волнистой линией (рис. 6Л 2).
Если ведущий расследование детектив использует альтернативу текстовому редак тору Word для открытия документа, то орфографический корректор этого продукта,
Написание текста невидимыми чернилами
1 5 5
скорее всего, обнаружит скрытые слова (рис. 6.13). Указанный риск в некоторой степени сглаживается за счет доминирования на рынке текстового редактора Microsoft Word.
I received your CV on Monday. It is very impressive, but I am sorry to inform you that Mr. Holmes is not
looking for additional staff at this time.
\ЛЛЛЛЛ/ЧЛ/ ЧЛЛЛАЛЛЧЛЛЛЛЛ ЧЛЛААЛАЛЛ/ \ЛЛЛЛ/ \ЛЛ\ЛЛЛАЛЛ\ЛАЛЛЛЛЛЛЛЛАЛ/ While we do not normally accept unsolicited applications, I will keep your CV on file for future
\ЛЛЛЛЛАЛЛД/\ЛАЛ/ WVWWVN
consideration. If it is convenient, please send me a list of references, especially those pertaining to skills
in negotiation, accounting, and data mining (preferably using the Python programming language). A
recent photograph is also recommended.
Р и с. 6.1 2 . Р ан ее обнаруженны е невидимые заш иф рованны е слова, подчеркнутые инструментом W ord орф ограф ической и грамматической коррекции
Dear Mr. Gerard:
I received your CV on Monday. It is very impressive, but I am sorry to inform you that Mr. Holmes is not
looking for additional staff at this tim e.
While we do not normally accept unsolicited applications, I will keep your CV on file for future
consideration. If it is convenient, please send me a list of references, especially those pertaining to skills
in negotiation, accounting, and data mining (preferably using the Python programming language). A
recent photograph is also recommended.
Best of luck to you. Feel free to check back at this tim e next year in the event a position becomes
available. Use this email address, and include your name and the word “check-back” in the subject line.
Sincerely yours,
Emil JKvirt? Associate Director
International Affairs
Р и с. 6 .1 3. С редство проверки орф ограф ии в текстовом редакторе LibreOffice W riter выделит невидимые слова
Во-вторых, использование сочетания клавиш + для выделения всего тек ста в Word не откроет скрытый текст, но будет указывать на то, что некоторые пус тые строки длиннее других (рис. 6.14), намекая очень наблюдательным на то, что что-то тут не так.
В-третьих, открытие документа Word с помощью функции предварительного про смотра в некоторых программах электронной почты может выявить скрытый текст, когда содержимое выбирается с помощью прокрутки или с помощью нажатия ком бинации клавиш + (рис. 6.15).
Но в отличие от обнаружения текста при выделении скрытого текста в панели предварительного просмотра почтовой службы Yahoo!Почта то же самое неверно в панели предварительного просмотра Microsoft Outlook на рис. 6.16.
156
Глава б
December 17, 2015
ar Mr. Gerard:
Morland Holmes
GLOBAL C O N S ULTA N TING & N E G O TIA TIO N S
I received your CV on Monday. It is very impressive, but I am sorry to inform you that Mr. Holmes is not
king for additional staff at this time.
hile we do not normally accept unsolicited applications, I will keep your CV on file for future
deration. If it is convenient, please send me a list of references, especially those pertaining to skills
i negotiation, accounting, and data mining (preferably using the Python programming language). A
icent photograph is also recommended.
Best of luck to you. Feel free to check back at this time next year in the event a position becomes available.
Use this email address, and include your name and the word “check-back” in the subject line.
Sincerely yours,
Emil Kurtz
Associate Director
International Affairs
Р и с. 6.1 4. В ы деление всего документа W ord показы вает различия в длине пустых строк.
Morland Holmes
ИШШЯШШЕ Fvr Gmxizfgmb qiyx kvpj ns ssp 2 zsj zczhhvc iwyhamh jijxg, brc qopl gz hui Jxoasq & Yothyxsae
I received your CV on Monday.i It is very impressive, but I am sorry to inform you that Mr. Holmes is not
poking for additional staff at this tim e.
Zmgvrq. Fvrwc isypq ubppsps n gydfl sd fw v rk drvaqbg jm d hui Imhvslmz bmj ocztyzm
сопзНегаЁюТТГТТГсопуётё^^
Р и с. 6.1 5. Вы деление всего документа в панели предварительного просмотра почтовой службы Y ah o o !Почта показы вает скрытый текст
Написание текста невидимыми чернилами 157
Morland Holmes
GLOBAL CONSULTANTING & NEGOTIATIONS
December 17, 2015
Dear Mr. Gerard:
i received your CV on Monday. It is very impressive, but I am sorry to inform you that Mr. Holmes is not looking for additional staff at this time.
While we do not normally accept unsolicited applications, I will keep your CV on file for future
consideration. If It is convenient, please send me a list of references, especially those pertaining to skills
Р ис. 6.1 6. При выделении всего докум ента в панели предварительного просмотра Microsoft Outlook скрытый текст не показывается
Наконец, сохранение документа Word в виде обычного текстового файла (*.txt) удалит все форматирование и оставит скрытый текст открытым (рис. 6.17).
Dear Mr. Gerard: Fvr Gmxizfgmb qiyx kvpj ns ssp 2 zsj zczhhvc iwyhamh jijxg, brc qopl gz hui Dxoasq & Yothyxsae I received your CV on Monday. It is very impressive, but I am sorry to inform you that Mr. Holmes is not looking for additional staff at this time. Zmgvrq. Fvrwc isypq ubppsps n gydfl sd fvvvrk drvaqbg jmd hui lmhvslmz bmj ocztyzm While we do not normally accept unsolicited applications, I will keep your CV on file for future consideration. If it is convenient, please send me a list of references, especially those pertaining to skills in negotiation,
Р и с. 6 .1 7. С охранение докум ента W ord в виде обычного текстового ф айла (* txt) показы вает скрытый текст
Для того чтобы скрыть секретное сообщение с помощью стеганографии, вы долж ны скрыть не только содержимое сообщения, но и тот факт, что сообщение вообще существует. Наши электронные невидимые чернила не всегда могут гарантировать это, но с точки зрения “крота” только что перечисленные недостатки предусматри вают, что он либо допустил ошибку, которую теоретически можно было бы кон тролировать, либо следователь предпринял особые и маловероятные действия, такие как прокрутка текста, сохранение файлов в другом формате или использова ние менее распространенного текстового процессора. Допуская, что “крот” из се риала “Элементарно” учитывал перечисленные выше приемлемые риски, связанные с применением электронных невидимых чернил, это дает правдоподобное объясне ние того, почему внутреннее расследование компании потерпело неудачу.
Резюме
В этой главе вы применили стеганографию для того, чтобы скрыть зашифрованное сообщение в документе Microsoft Word. Вы использовали сторонний модуль python-docx для прямого доступа к документу и манипуляции им с помощью языка
158
Глава б
Python. Аналогичные сторонние модули доступны для работы с другими популяр ными типами документов, такими как электронные таблицы Excel.
Дальнейшее чтение
Онлайновую документацию по модулю python-docx можно найти по адресам https:// python-docx.readthedocs.io/en/Iatest/ и https://pypi.python.org/pypi/python-docx. Книга Эла Свейгарта “Автоматизируй скучные вещи с помощью Python” (Sweigart A. Automate the boring stuff with Python. No Cramp Press, 2015) охватывает модули, которые позволяют языку Python оперировать PDF-файлами, файлами Word, элек тронными таблицами Excel и многим другим. Глава 13 содержит полезное учебное руководство по модулю python-docx, а приложение к книге охватывает установку сторонних модулей с помощью менеджера установки пакетов pip.
Программы Python начального уровня для работы с шифрами можно найти в книге Эла Свейгарта “Взламывание кодов с помощью Python” (Sweigart A. Cracking codes with Python. No Cramp Press, 2018).
Книга Гэри Блэквуда “Таинственные сообщения” (Blackwood G. Mysterious mes sages. Penguin Group, 2009) является интересным и хорошо иллюстрированным по вествованием об истории стеганографии и криптографии.
Практический проект: проверка числа пустых строк
Улучшите программу скрытых сообщений, написав функцию, которая сравнивает число пустых строк в фальшивом сообщении с числом строк в реальном сообще нии. Если для скрытия реального сообщения места недостаточно, то указанная функция должна предупредить пользователя и сообщить ему о том, сколько пустых строк нужно добавить в фальшивое сообщение. Вставьте указанную функцию в копию программного кода elementary jnk.py непосредственно перед загрузкой шаблонного документа и вызовите эту функцию. Решение данной задачи можно найти в приложении к книге и на веб-сайте по адресу https://www.nostarch.com/ impracticalpython/ в файле elementary ink practice.py. Для тестирования скачайте файл realMessageChallenge.docx с того же веб-сайта и воспользуйтесь этим файлом в качестве реального сообщения.
Сложный проект: использование моноширинного шрифта
Перепишите код программы elementary ink.py для моноширинных шрифтов и скройте собственное короткое сообщение в промежутках между словами. Описание моноширинных шрифтов см. в разд. ”Учет типов шрифтов, кернинга и межзнако вого интервала” ранее в этой главе. Как обычно, для сложных проектов решения не предоставляются.
7
РАЗВЕДЕНИЕ ГИГАНТСКИХ КРЫС С ПОМОЩЬЮ ГЕНЕТИЧЕСКИХ АЛГОРИТМОВ
Генетические алгоритмы — это универсальные оптимиза торы, предназначенные для решения многосложных задач. Изобретенные в 1970-х годах, они относятся к классу эволю ционных алгоритмов, названных так потому, что они имитиру ют дарвиновский процесс естественного отбора. Они особенно полезны тогда, когда имеется мало сведений о задаче, когда предстоит решить не линейную задачу либо при поиске решений грубой силой в большом поисковом пространстве. Что еще лучше, эти алгоритмы легко понять и имплементировать.
В этой главе вы будете использовать генетические алгоритмы для выведения расы суперкрыс, которые способны терроризировать мир. После этого вы переметнетесь на другую сторону и за считаные секунды поможете Джеймсу Бонду взломать вы сокотехнологичный сейф. Оба проекта должны дать вам хорошее представление о механизме и силе генетических алгоритмов.
Поиск наилучших решений из всех возможных
Генетические алгоритмы оптимизируют. Это означает, что они отбирают лучшее решение (с учетом некоторых критериев) из набора имеющихся альтернатив. На пример, если вы ищете самый быстрый маршрут для поездки на автомобиле из Нью-Йорка в Лос-Анджелес, то генетический алгоритм никогда не предложит вам лететь. Он может выбирать только из разрешенного множества условий, которые вы предоставляете. Как оптимизаторы, эти алгоритмы работают быстрее, чем тра диционные методы, и способны избегать преждевременного схождения к неопти мальному ответу. Другими словами, они эффективно проводят поиск в пространст
160
Глава 7
ве решения, причем делают это достаточно тщательно, избегая подбора хорошего ответа, когда есть еще лучше.
В отличие от систем исчерпывающего поиска, которые применяют чистую грубую силу, генетические алгоритмы не апробируют все возможные решения. Вместо это го они постоянно градуируют (оценивают) решения по степени признака, а затем используют их для того, чтобы, двигаясь вперед, делать “информированные догад ки”. Простой пример — игра “тепло-холодно”, где вы ищете скрытый предмет в то время, как кто-то сообщает вам о том, становится ли теплее или холоднее, основы ваясь на вашей близости к предмету или направлении поиска. Генетические алго ритмы используют функцию приспособленности, аналогичную естественному от бору, с целью отбрасывания “более холодных” решений и концентрации на “более теплых”. Базовый процесс заключается в следующем:
Случайно сгенерировать популяцию решений.
Измерить приспособленность каждого решения.
Выбрать наилучшие (самые теплые) решения и отбросить остальные.
Скрестить (рекомбинировать) элементы в наилучших решениях для того, чтобы
принять новые решения.
Мутировать небольшое число элементов в решениях, изменив их значение.
Вернуться к шагу 2 и повторить.
Цикл “отобрать-скрестить-мутировать” продолжается до тех пор, пока не достигнет условия останова, например отыскания известного ответа, отыскания “достаточно хорошего” ответа (на основе минимального порога), завершения заданного числа итераций или достижения крайнего срока. Поскольку эти шаги очень похожи на процесс эволюции, дополненный выживанием наиболее приспособленных, приме няемая в генетических алгоритмах терминология часто является скорее биологиче ской, чем вычислительной.
Ловите момент стать сумасшедшим ученым с секретной лабораторией, полной ки пящих мензурок, пузырящихся пробирок и машин, то и дело издающих свои “БЗЗЗТТТ” на все лады. Так что наденьте черные резиновые перчатки и займитесь превращением проворных мусорщиков в огромных монстров-людоедов.
Ц е л ь
Применить генетический алгоритм для симуляции разведения крыс со средним весом1 50 кг.
1 Если быть более точным, с физической точки зрения здесь и далее речь, конечно, идет не о весе, а о массе. Но поскольку в обиходе используют термин “вес”, то будем употреблять его, подразумевая массу как физическую величину. — Прим. ред.
Разведение гигантских крыс с помощью генетических алгоритмов 161
Стратегия
Ваша мечта — развести расу крыс размером с бульмастифов (мы уже установили, что вы — сумасшедший). Вы начнете с Rattus norvegicus, бурой крысы, затем до бавите немного искусственных подсластителей, немного атомной радиации 1950-х годов, много терпения и щепотку Python, но никакой генной инженерии — вы являетесь сторонником старой школы! Крысы вырастут от менее полкилограм ма до ужасающих 50 кг размером примерно с самку бульмастифа (рис. 7.1).
Р и с. 7.1. С равнение размеров бурой крысы, самки бульмастиф а и человека
Прежде чем приступить к такому масштабному начинанию, разумно смоделиро вать результаты на языке Python. И вы сформулировали нечто лучшее, чем просто план, — вы нарисовали некий графический псевдокод (рис. 7.2).
Процесс, показанный на рис. 7.2, описывает работу генетического алгоритма. Ваша цель состоит в том, чтобы произвести популяцию крыс со средним весом 50 кг из начальной популяции весом гораздо меньше, чем этот. В дальнейшем каждая попу ляция (или поколение) крыс представляет собой кандидатное решение задачи. Как и любой животновод, вы отбираете нежелательных самцов и самок, которых гу манно отправляете — для вас поклонников Остина Пауэрса — в злой контактный зоопарк. Затем вы спариваете и разводите оставшихся крыс, т. е. выполняете про цесс, в генетическом программировании именуемый скрещиванием.
Потомство оставшихся крыс будет, по существу, того же размера, что и их родите ли, поэтому вам нужно некоторых из них мутировать. В то время как мутация явля ется редким явлением и обычно приводит к нейтральному признаку, вплоть до не благоприятного (в данном случае к низкому весу), иногда вы успешно выращиваете большую крысу.
162
Глава 7
Режим
Заселить: установить первоначальную популяцию и весовой диапазон
— —– 11
11
. ч
■ * > ; :
N
\
Градуировать оценить приспособленность путем сравнения целевого веса с весом в среднем по популяции
9
сг
Отобрать: отбраковать самых маленьких самцов и самок
Min
Max
Развести:перезаселить со случайными весами, основываясь на весовом диапазоне отобранных крыс
Д.4
••
1
Мутировать: случайно изменить веса у нескольких крыс. Большинство результатов снижает вес
у
Рис. 7.2. Подход к разведению суперкрыс на основе генетического алгоритма
Затем весь процесс превращается в большой повторяющийся цикл, выполняемый органически или программно, заставляющий меня задуматься, не являемся ли мы в действительности просто виртуальными существами в инопланетной симуляции. В любом случае конец цикла — условие останова — это момент, когда крысы дос тигают нужного размера либо вы уже просто не можете больше переносить крыс.
Для ввода в симуляцию вам понадобится некоторая статистика. Используйте мет рическую систему, т. к. вы являетесь ученым — неважно, сумасшедшим или адек ватным. Вы уже знаете, что средний вес самки бульмастифа составляет 50 кг, и по лезную статистику по крысам можно найти в табл. 7.1.
Разведение гигантских крыс с помощью генетических алгоритмов 163
Таблица 7.1. Статистика веса и размножения бурых крыс
Параметры
Минимальный вес, грамм
Средний вес (самки), грамм
Средний вес (самца), грамм
Максимальный вес, грамм*
Количество детенышей в помете
Пометы в год
Опубликованные значения
200
250
300-350
600
8-12
4-13
Продолжительность жизни (на свободе, в неволе), лет
1-3; 4-6
- Исключительные особи в неволе могут достигать 1000 граммов.
Поскольку существуют как домашние, так и дикие бурые крысы, в некоторых ста тистических данных может иметься широкий разброс. За крысами в неволе, как правило, лучше ухаживают, чем за дикими крысами, в результате чего они больше весят, чаще размножаются и имеют больше детенышей. Поэтому, когда имеется диапазон, вы можете выбрать из более высокого уровня. В этом проекте начните с принятия допущений, приведенных в табл. 7.2.
Таблица 7.2. Входные допущения для генетического алгоритма разведения суперкрыс
Переменная или значение
Комментарии
GOAL = 50000
NUM_RATS = 20
Целевой вес в граммах (самка бульмастифа)
Общее число взрослых крыс, которых ваша лаборатория может поддерживать
INIТIAL_MIN_WT = 200
Минимальная масса взрослой крысы (в граммах) в начальной популяции
INITIAL_MAX_WT = 600
Максимальная масса взрослой крысы (в граммах) в начальной популяции
INITIAL_MODE_WT = 300
Наиболее распространенный вес взрослых крыс (в граммах) в начальной популяции
MUTATE_ODDS =0.01
Вероятность мутации, происходящей у крысы
MUTATE_MIN =0 . 5
MUTATE_MAX =1. 2
LITTER_SIZE = 8
Скаляр на весе крысы с наименее благоприятной мутацией
Скаляр по весе крысы с наиболее благоприятной мутацией
Число детенышей на пару спаривающихся крыс
LITTERS_PER_YEAR = 10
Число пометов в год на пару спаривающихся крыс
GENERATION_LIMIT = 500
Поколенческое отсечение для останова программы размножения
Поскольку крысы размножаются очень часто, вам не придется учитывать продол жительность жизни. Даже если вы сохраните некоторых родителей из предыдущего
164 Глава 7
поколения, они будут быстро отбракованы, поскольку их потомство от поколения к поколению увеличивается в весе.
Код размножения суперкрыс
Код super rats.py соблюдает общий рабочий поток, приведенный на рис. 7.2. Указанный код также можно скачать с веб-сайта https://www.nostarch.com/ impracticalpython/.
Ввод данных и допущений
Листинг 7.1 в глобальном пространстве в начале программы импортирует модули и назначает константам статистические данные, скаляры и допущения из табл. 7.2. После того как программа будет закончена и работоспособна, не стесняйтесь по экспериментировать со значениями из этой таблице и посмотреть, как они влияют на ваши результаты.
Листинг 7.1. Импортирует модули и задает константы. Файл super_rats.py, часть 1
О import time
import random import statistics
Q # КОНСТАНТЫ (веса в граммах)
О GOAL = 50000
NUM_RATS = 2 0 INITIAL_MIN_WT = 200
•IN ITI AL_MAX_WT = 600
INITIAL_MODE_WT = 300
MUTATE_ODDS = 0 . 0 1 MUTATE_MIN = 0 . 5
MUTATE_MAX = 1 . 2 LITTER_SIZE = 8
LITTERS_PER_YEAR = 1 0
GENERATION_LIMIT = 500
обеспечить для племенных пар четное число крыс: #
О if NUM_RATS % 2 != 0:
NUM_RATS += 1
Начните С импорта модулей time, random И statistics О * Модуль time будет ИС- пользоваться для регистрации времени выполнения вашего генетического алгорит ма. Засекать время работы генетических алгоритмов интересно хотя бы тем, чтобы они позволяют испытать восторг от того, как быстро они способны находить ре шение.
Модуль random удовлетворит стохастическим потребностям алгоритма, а модуль statistics будет использоваться для получения средних значений. Такое примене
Разведение гигантских крыс с помощью генетических алгоритмов 165
ние указанного модуля является слабоватым, но я хочу, чтобы вы о нем знали, т. к. он бывает довольно удобным.
Далее задайте входные переменные, описанные в табл. 7.2, и обязательно обратите внимание на то, что единицами измерения являются граммы Q . Для имен исполь зуйте прописные буквы, т. к. они представляют константы Q .
Прямо сейчас мы собираемся исходить из допущения, что используются племен ные пары, поэтому убедитесь, что пользователь ввел четное число крыс, а если это не так, добавьте крысу О- В разд. “Сложные проекты " далее в этой главе вы смо жете поэкспериментировать с альтернативными гендерными распределениями.
Инициализация популяции
Листинг 7.2 является закупочным представителем программы. Он “идет” в зоомага зин и выбирает крыс для первоначальной племенной популяции. Так как вы хотите иметь племенные пары, он должен выбрать четное число крыс. И поскольку вы не можете позволить себе одно из этих причудливых логовищ для вулкановых хомяч ков с неограниченным пространством, вам нужно будет в каждом поколении под держивать число взрослых крыс постоянным, хотя их число может временно уве личиваться с учетом размещения помета. Помните, что крысы будут нуждаться все в большем и большем пространстве по мере того, как они будут вырастать до раз мера крупных собак!
Листинг 7.2. Определяет функцию, создающую первоначальную популяцию крыс. Файл su perj-ats.p y, часть 2
О clef populate (num_rats, min_wt, max_wt, mode_wt) :
“““Инициализировать популяцию треугольным распределением весов.”””
G return [int(random.triangular(min_wt, max_wt, mode_wt))\
for i in range(num_rats) ]
Функция populate () должна знать количество взрослых крыс, к которому вы стре митесь, минимальный и максимальный вес крыс, а также наиболее часто встре чающийся вес О Обратите внимание, что все эти аргументы будут использовать константы, находящиеся в глобальном пространстве. Для того чтобы к ним обра титься, вам не нужно передавать их в функцию в качестве аргументов. Но я делаю это здесь и в последующих функциях для ясности и потому, что доступ к локаль ным переменным выполняется эффективнее.
Вы будете использовать четыре упомянутых выше аргумента с модулем random, который включает в себя разные типы распределений. Здесь вы будете применять треугольное распределение, потому что оно обеспечивает четкий контроль над ми нимальным и максимальным размерами и позволяет статистически моделировать асимметрию.
Поскольку бурые крысы существуют как в дикой природе, так и в неволе — в зоо парках, лабораториях и в качестве домашних животных, их вес смещен в сторону
166
Глава 7
увеличения. Дикие крысы, как правило, меньше, поскольку их жизнь неприятна, груба и коротка, хотя лабораторные крысы этот момент могут оспорить! Примени те операцию включения в список для перебора числа крыс в цикле и назначения веса каждой из них. Свяжите все это вместе инструкцией return 0 .
Измерение приспособленности популяции
Измерение приспособленности крыс — это двухэтапный процесс. Сначала програ дуируйте всю популяцию, сравнив средний вес всех крыс с целью — бульмасти- фом. Затем проградуируйте каждую отдельную крысу. Возможность размножаться снова получают только те крысы, чей вес находится в верхних п процентах, как определено переменной num rats. Х отя средний вес популяции является допусти мым показателем приспособленности, его основная роль здесь заключается в опре делении момента, когда можно прекратить цикл и объявить об успехе.
В листинге 7.3 определены функции fitness () и select (), которые вместе образуют измерительную часть вашего генетического алгоритма.
Листинг 7.3. Определяет измерительный шаг генетического алгоритма. Файл super_rats.pyf часть 3
О def fitness(population, goal):
“““Измерить пригодность популяции, основываясь на
среднем значении атрибута относительно цели.”””
ave = statistics.mean(population)
return ave / goal
О def select(population, to_retain):
“““Отбраковать популяцию, оставив только заданное
число ее членов.”””
Q sorted_population = sorted(population)
О to_retain_by_sex = to_retain//2
ф members_per_sex = len(sorted_population)//2 ф females = sorted_population[:members_per_sex] males = sorted_population[members_per_sex:]
e selected_females = females[-to_retain_by_sex:] selected_males = males[-to_retain_by_sex:]
ф return selected_males, selected_females
Определите функцию, которая будет градуировать приспособленность текущего поколения О - Используйте модуль statistics для получения среднего значения популяции и верните это значение, деленное на целевой вес. Когда это значение будет равно I или больше, вы будете знать, что пришло время прекратить размно жение.
Затем определите функцию, которая отбраковывает популяцию крыс, основываясь на весе, вплоть до значения num rats, представленного здесь параметром to retain ф.
Разведение гигантских крыс с помощью генетических алгоритмов 167
Она также примет аргумент population, который будет представлять родителей ка ждого поколения.
Теперь отсортируйте популяцию так, чтобы можно было отличить больших особей от малых О- Возьмите число крыс, которое вы хотите сохранить, и разделите его на 2, используя деление с округлением вниз так, чтобы результат был целым чис лом О . Выполните этот шаг для того, чтобы можно было оставить самых больших самцов и самок крыс. Если вы выберете только самых крупных крыс в популяции, то вы теоретически будете выбирать только самцов. Общее число членов текущей популяции М О Ж Н О получить ПО полу, разделив sorted_population на 2, снова ис пользуя деление с округлением вниз ф .
Самцы крыс, как правило, крупнее самок, поэтому сделайте два упрощающих до пущения: во-первых, примите за основу, что ровно половина популяции— это самки и, во-вторых, что самая большая самка крысы не тяжелее самого малого сам ца крысы. Это означает, что первая половина отсортированного популяционного списка представляет самок ф , а вторая половина — самцов. Затем создайте два но вых списка, разделив sorted population пополам, взяв нижнюю половину для са мок и верхнюю половину для самцов. Теперь осталось только взять самых больших крыс с конца каждого из этих списков ф — используя отрицательный срез — и вернуть их ф . Оба списка содержат родителей следующего поколения.
При первом выполнении этой функции она лишь отсортирует крыс по полу, т. к. первоначальное число крыс уже равно константе n u m r a t s . После этого аргумент входящей популяции будет включать в себя как родителей, так и детенышей, и его значение будет превышать константу n u m r a t s .
Разведение нового поколения
Листинг 7.4 определяет в программе шаг “скрещивания”, т. е. он порождает сле дующее поколение. Ключевым допущением является то, что вес каждого детеныша будет больше или равен весу матери и меньше или равен весу отца. Исключения из этого правила будут обрабатываться в “мутационной” функции.
Листинг 7.4. Определяет функцию, которая порождает новое поколение крыс. Файл super_rats.py, часть 4
ф def breed(males, females, litter_size):
“““Скрестить гены среди членов (весов) популяции…
ф random.shuffle(males)
random.shuffle(females)
ф children = []
ф for male, female in zip(males, females):
ф for child in range(litter_size):
ф child = random.randint(female, male)
ф children.append(child)
ф return children
168
Глава 7
Функция breed () в качестве аргументов принимает списки весов отобранных сам цов и самок, возвращенных из функции select о, а также размер помета О- Затем произвольно перетасуйте два списка 0 , потому что вы отсортировали их в функ ции select о и их обход в цикле без перетасовки приведет к тому, что самый маленький самец будет спарен с самой маленькой самкой и т. д. Вы должны учиты вать любовь и романтику; самого крупного самца может притягивать к самой миниатюрной самке! Создайте пустой список для хранения их детенышей О- Теперь что касается шуры- муры. С помощью встроенной функции zip о пройдите в цикле по перетасованным спискам, спаривая самца с самкой из каждого списка О- Каждая пара крыс может иметь несколько детенышей, поэтому начните еще один цикл, который в качестве диапазона использует размер помета 0 . Размер помета — это константа, именуе мая l i t t e r s i z e , которую вы указали во входных параметрах, поэтому если значе ние равно 8, то вы получите восемь детенышей. Для каждого детеныша выберите вес наугад между весом матери и отца Q . Обра тите внимание, что вам не нужно указывать male + 1, потому что randint о исполь зует все числа в предоставленном диапазоне. Обратите также внимание на то, что эти два значения могут быть одинаковыми, но первое значение (вес матери) нико гда не может быть больше второго (веса отца). Это еще одна причина для упро щающего допущения о том, что самки должны быть не больше самого маленького самца. Завершите цикл, добавив каждого детеныша в список children Q , а затем верните children ф .
Мутирование популяции
Небольшой процент детенышей должен испытывать мутации, и большинство из них должны приводить к признакам, которые являются неблагоприятными. Это означает более низкие, чем ожидалось, веса, включая “коротышек”, которые не вы живут. Но время от времени полезная мутация все-таки приведет к более тяжелой крысе.
В листинге 7.5 определена функция mutate о, которая применяет допущения о му тации, указанные в списке констант. После вызова mutate () настанет время прове рить приспособленность новой популяции и начать цикл заново, если целевой вес не был достигнут.
Листинг 7.5. Определяет функцию, которая мутирует небольшую часть популяции. Файл super_rats.py, часть 5
О def mutate(children, mutate_odds, mutate_min, mutate_max):
“““Случайно изменить веса крыс, используя входные шансы
и дробные изменения.”””
0 for index, rat in enumerate(children): if mutate_odds >= random.random():
ф children[index] = round(rat * random.uniform(mutate_min,
mutate_max))
return children
Разведение гигантских крыс с помощью генетических алгоритмов 169
Указанная функция нуждается в списке детенышей, шансах возникновения мута ции, а также минимальном и максимальном влиянии мутации О- Влияния — это скаляры, которые вы будете применять к весу крысы. В вашем списке констант в начале программы (и в табл. 7.2) они смещены в минимальную сторону, т. к. большинство мутаций не приводят к благоприятным признакам.
Пройдите в цикле по списку детенышей и примените enumerate о — удобную встроенную функцию, которая действует как автоматический счетчик для получе ния индекса 0 . Затем примените метод random о для того, чтобы сгенерировать случайное число между 0 до 1, и сравните его с вероятностью возникновения мута ции.
Если переменная mutate odds больше или равна случайно сгенерированному числу, то крыса (вес) в этом индексе мутирует. Выберите значение мутации из равномер ного распределения, определяемого минимальными и максимальными значениями мутации; это, в сущности, случайное значение из диапазона между минимумом и максимумом. Поскольку эти значения смещены к минимуму, результатом, скорее всего, будет потеря веса, нежели его увеличение. Умножьте текущий вес на этот мутационный скаляр и округлите его до целого числа ф . Завершите программу, вернув список мутировавших детенышей.
ПРИМЕЧАНИЕ
Что касается достоверности мутационной статистики, то вы можете найти исследова ния, которые демонстрируют, что благоприятные мутации являются очень редкими. Но есть и другие, которые сообщают о том, что такие мутации более распространены, чем мы себе представляем. Разведение собак показало, что достижение резких изме нений в размерах (например, чихуа-хуа относительно датских догов) не требует мил лионов лет эволюции. В знаменитом исследовании XX в. российский генетик Дмитрий Беляев начал с 130 серебристых лисиц и за 40-летний период добился значительных физиологических изменений, просто выбирая в каждом поколении самых прирученных лисиц.
Определение функции mainQ
В листинге 7.6 представлена функция main (), которая управляет другими функция ми и определяет момент, когда вы выполнили условие останова. Она также будет показывать все важные результаты.
Листинг 7.6. Определяет функцию main (). Файл 8uper_rats,py» часть 6
def ma in():
"”” Инициализировать популяцию, отобрать, вывести и мутировать,
показать результаты.”””
О generations = 0
О parents = populate(NUM_RATS, INITIAL_MIN_WT, INITIAL_MAX_WT,
INITIAL_MODE_WT)
print(“первоначальные веса популяции = {}".format(parents))
popl_fitness = fitness(parents, GOAL)
170
Глава 7
print(“первоначальная приспособленность популяции = {}”.
format(popl_fitness))
print(“оставляемое число = {}".format(NUM_RATS))
Q ave_wt = []
О while popl_fitness - 20
\ ч
10
0
Улучшение
Исследование чувствительности
60
50
40
I 30 • - 20
10
о
Вазовая линия (входные данные табл 7.2)
j Улучшение
0,25
0,5
0,75
1
Вероятность мутации
500
1000
1500
2000
Число крыс
Рис. 7.3. Влияние двух параметров на время, необходимое для достижения целевого веса
них крыс. Точно так же, попытка увеличить шансы мутации свыше 0,3 принесет малую отдачу. С диаграммами подобного рода легко планировать путь вперед. Горизонтальная пунктирная линия, помеченная как “базовая линия”, представляет средний резуль тат использования входных данных табл. 7.2. Это время можно потенциально сократить более чем на 10 лет, просто оставляя вместо 20 крыс 50. Также следует сосредоточиться на увеличении числа благоприятных мутаций. Это будет плодо творнее, но рискованнее и труднее контролировать. Если повторно выполнить симуляцию с использованием 50 крыс и подтолкнуть шансы мутации до 0,05, то теоретически проект сможет завершиться за 14 лет, улучшив свои шансы на 246% по сравнению с первоначальным уровнем. Теперь это действительно оптимизация! Разведение суперкрыс было забавным и простым способом разобраться в основах генетических алгоритмов. Но для того чтобы по-настоящему оценить их силу, сто ит попробовать нечто посложнее. Вам нужна задача, решаемая грубой силой, т. е. исчерпывающим поиском среди всех вариантов, но которая слишком велика для грубой силы, и следующий проект — именно такая задача.
Проект 14: взламы вание вы сокотехнологичного сейфа
Вы — Q2, и у Джеймса Бонда есть проблема. Он должен присутствовать на эле гантном званом обеде в особняке злодея, ускользнуть в личный кабинет этого человека и взломать его стенной сейф — детская игра для агента 007, за исключе
2 Q — это персонаж книг и фильмов о Джеймсе Бонде. Так же как и М, это кодовое имя, в данном случае оно означает первую букву слова Quartermaster (интендант, начальник снабжения). Q — глава Q Branch, исследовательского центра Британской секретной службы. Он постоянно снабжает Джейм са Бонда полезной шпионской техникой. См. https://ru.wikipedia.org/wiki/Q_(6oHAHaiia). — Прим перев
Разведение гигантских крыс с помощью генетических алгоритмов 173
нием одного: этот сейф— цифровой Humperdink BR549, который принимает 10 цифр, давая 10 млрд возможных комбинаций. И колеса замка не начинают вра щаться до тех пор, пока не будут введены все числа. Приложить стетоскоп к этому сейфу и медленно поворачивать диск не получится! Помните: вы — Q, и у вас уже есть устройство автодозвона, которое может исчер пывающе перебирать все возможные решения, но у Бонда просто не будет времени его использовать. И вот почему. Кодовый замок в действительности следует называть перестановочным замком, потому что он требует упорядоченных комбинаций, которые по определению явля ются перестановками. Более конкретно, замки опираются на перестановки с повто рением. Например, допустимой — хотя и небезопасной — комбинацией может быть 999999999.
Вы использовали итератор перестановок permutations о модуля itertoois во время работы с анаграммами в главе 3 и в разд. “Практические проекты” главы 4, но здесь это не поможет, потому что итератор permutations о возвращает перестанов ки без повторов. Для того чтобы создать правильный вид перестановки для замка, вам нужно использовать итератор product о модуля itertoois, который вычисляет декартово произведение из многочисленных наборов чисел:
» > from itertoois import product » > combo = (1, 2) » > for perm in product (combo, repeat=2) :
print (perm)
(1 , 1 ) (1, 2) (2 , 1 )
(2 , 2 )
Необязательный именованный аргумент repeat позволяет вам взять произведение итерируемого объекта, умножаемого на самого себя, что в данном случае вам как раз и нужно сделать. Обратите внимание, что функция product о возвращает все возможные комбинации тогда, как функция permutations о возвращает только (1, 2) и (2, 1). Дополнительно об итераторе product о можно прочитать на веб странице https://d0cs.pyth0n.0rg/3.6/library/itert00ls.htmI#itert00ls.pr0duct.Lfcting. Листинг 7.8 представляет собой программу на Python под названием brute_ force cracker.py, в которой итератор product () используется для того, чтобы грубой силой добиться правильной комбинации.
Листинг 7.8. Применяет грубую силу для поиска комбинации сейфа. Файл brute_force_cracker.py
О import time
from itertoois import product
start_time = time.time ()
О combo = (9, 9, 7, 6, 5, 4, 3)
174 Глава 7
применить декартово произведение для генерирования перестановок #
с повтором #
О f°r perm in product([0, 1, 2, 3, 4, 5, 6, 7, 8, 9], repeat=len(combo)):
О if perm == combo:
print(“Взломано! {} {}".format(combo, perm))
end_time = time.time ()
ф print(“ХпВремя выполнения этой программы составило {} с е к у н д format
(end_time - start_time))
Импортируйте модуль time и из модуля itertoois итератор product О* Получите время начала, а затем введите безопасную комбинацию в виде кортежа 0 . Далее примените функцию product о, которая для заданной последовательности возвра щает кортежи всех перестановок с повтором. Последовательность содержит все допустимые элементы из первых цифр (0-9). В качестве аргумента repeat следует задать число цифр в комбинации О- Сравните каждый результат с комбинацией и выведите “Взломано!”, если они совпадают, вместе с комбинацией и совпадающей перестановкой О- Завершите, показав на экране время выполнения Q .
Это отлично работает для комбинаций длиной до восьми цифр. После этого время ожидания становится все более неудобным. Таблица 7.3 представляет собой реги страцию времени выполнения программы в зависимости от числа цифр в комби нации.
Таблица 7.3. Времена выполнения относительно цифр в комбинации (процессор с тактовой частотой 2,3 ГГц)
Число цифр
Время выполнения, секунды
5
6
7
8
9
10
0,035
0,147
1,335
12,811
133,270
1396,955
Обратите внимание, что добавление цифры в комбинацию увеличивает время вы полнения на порядок. Это экспоненциальный рост. С 9 цифрами вы бы ожидали ответа более 2 минут. С 10 цифрами— более 20 минут! Для Бонда это долго, для того чтобы “поход в ванную комнату” оставался незамеченным. К счастью, вы — Q, и вы кое-что знаете о генетических алгоритмах. От вас требуется лишь одно — каким-то образом судить о приспособленности каждой кандидатной комбинации. Варианты включают в себя мониторинг колебаний в энергопотреблении, измерение временных задержек в операциях и прослушивание звуков. Будем считать, что ис пользуется звукоусилительный инструмент, а также инструмент для предствраще-
Разведение гигантских крыс с помощью генетических алгоритмов
1 7 5
ния блокировки после ввода нескольких неправильных комбинаций. Из-за сторож ков в сейфе BR549 звуковой инструмент первоначально может сообщать вам толь ко о том, сколько цифр являются правильными, а не какие именно цифры, но по прошествии очень небольшого времени ваш алгоритм может сойтись на решении.
Ц е л ь
Применить генетический алгоритм для быстрого отыскания комбинации сейфа в боль шом поисковом пространстве.
Стратегия
Стратегия здесь проста. Вы сгенерируете случайную последовательность из 10 чи сел и сравните ее с реальной комбинацией, градуируя результат на основе совпаде ний; в реальном мире вы отыщите число совпадений, используя звуковой детектор, прикрепленный к двери сейфа. Затем вы измените одно значение в своем решении и снова сравните. Если найдено еще одно совпадение, то вы отбросите старую по следовательность и двинетесь вперед с новой; в противном случае вы сохраните старую последовательность и повторите попытку.
Поскольку одно решение полностью заменяет другое, это представляет собой 100-процентное скрещивание генетического материала, поэтому вы, по существу, используете только отбор и мутацию. Отбор плюс мутация сами по себе генериру ют надежный алгоритм восхождения к вершине холма. Восхождение к вершине холма— это оптимизационный технический прием, который начинается с произ вольного решения и изменяет (мутирует) в решении одно-единственное значение. Если результат представляет собой улучшение, то новое решение сохраняется и процесс повторяется.
Проблема с восхождением к вершине холма заключается в том, что алгоритм мо жет застрять в локальных минимумах или максимумах и не отыскать оптимальное глобальное значение. Представьте, что вы ищете самое низкое значение в волнооб разной функции из рис. 7.4. Текущая наилучшая догадка отмечена большой черной
Р ис. 7.4. Пример алгоритма восхождения к верш ине холма, “застрявшего” в локальном минимуме
176
Глава 7
точкой. Если величина вносимого вами изменения (мутации) слишком мала, для того чтобы “избежать” локальной котловины, то алгоритм не найдет истинную нижнюю точку. С точки зрения алгоритма, поскольку каждое направление приво дит к более худшему ответу, он, должно быть, нашел истинный ответ. Поэтому он преждевременно сходится на решении.
Использование скрещивания в генетических алгоритмах помогает избежать про блем преждевременного схождения, а также допускает относительно большие му тации. Поскольку здесь вы не беспокоитесь о соблюдении биологического реализ ма, мутационное пространство может охватывать все возможные значения в ком бинации. Благодаря этому вы не сможете застрять, и восхождение к вершине холма становится приемлемым подходом.
Код взломщика сейфов
Код программы safe cracker.py принимает комбинацию из п цифр и использует восхождение к вершине холма, для того чтобы достичь комбинации из случайной первоначальной точки. Указанный код можно скачать с веб-сайта https://www. nostarch.com/impracticalpython/.
Настройка и определение функции fitnessQ
Листинг 7.9 импортирует необходимые модули и определяет функцию fitness ().
Листинг 7.9. Импортирует модули и определяет функцию f it n e s s (). Файл safe_cracker.py, часть 1
О import time
from random import randint, randrange
Q def fitness(combo, attempt):
“““Сравнить элементы в двух списках и подсчитать
число совпадений.”””
grade = О
0 for i, j in zip(combo, attempt):
if i = j:
grade += 1
return grade
После импорта нескольких хорошо знакомых модулей О определите функцию fitness о, которая в качестве аргументов принимает истинную комбинацию и предпринятое решение 0 . Назовите переменную grade и установить ее равной 0. Затем примените встроенную функцию zip о для перебора каждого элемента в комбинации и вашей попытки о Если они являются одинаковыми, то добавьте 1 в переменную grade и верните ее. Обратите внимание, что вы регистрируете не ин декс, который совпадает, а просто факт, что функция нашла совпадение. Этим эму-
Разведение гигантских крыс с помощью генетических алгоритмов 177
лируется выход из устройства обнаружения звука. Первоначально оно способно вам сообщить лишь одно — сколько блокировочных колес повернуто, а не их местоположение.
Определение и запуск функции mainQ
Поскольку эта программа является короткой и простой, большая часть алгоритма выполняется не в нескольких функциях, а в функции main () (листинг 7.10).
Листинг 7.10. Определяет функцию ma±n() и запускает и хронометрирует программу, если она не была импортирована. Файл safe_cracker.py, часть 2
def m a in ():
"”” Применить алгоритм восхождения к вершине холма
для решения комбинации замков… .
О combination = ‘6822858902’
print(“Комбинация = {}".format(combination))
конвертировать комбинацию в список: #
О combo = [int(i) for i in combination]
сгенерировать догадку и проградуировать приспособленность: #
0 best_attempt = [0] * len(combo)
best_attempt_grade = fitness(combo, best_attempt)
О count = 0
эволюционировать догадку #
ф while best_attempt != combo:
скрестить #
О next_try = best_attempt[:]
мутировать #
lock_wheel = randrange(0, len(combo))
Q next_try[lock_wheel] = randint(0, 9)
проградуировать и отобрать #
ф next_try_grade = fitness(combo, next_try)
if next_try_grade > best_attempt_grade:
best_attempt = next_try[:] best_attempt_grade = next_try_grade
print(next_try, best_attempt)
count += 1
print ()
ф print(“Взломано! {}".format(best_attempt), end=’ ‘)
print(“за {} попыток!”. format(count))
178
Глава 7
if name == 1__main__1 :
start_time = time.time ()
m a i n ()
end_time = time. time ()
duration = end_time - start_time
© print("\пВремя выполнения этой программы составило {:.5f} секунд.”,
format(duration))
Предоставьте истинную комбинацию в качестве переменной О и примените опе рацию включения в список для ее конвертации в список ради удобства продвиже ния вперед ф. Сгенерируйте список нулей, по длине равный комбинации, и назо вите его лучшей попыткой best attempt О* На данный момент комбинация являет ся такой же хорошей, как и любая другая. Вы должны сохранить это имя — best attempt, потому что по мере подъема к вершине холма вам нужно сохранять только лучшее решение. После того как вы сгенерировали первоначальную попыт ку, проградуируйте ее с помощью функции fitness (), а затем задайте значение пе ременной best_attempt_grade.
Начните переменную count с нуля. Программа будет использовать эту переменную для регистрации числа попыток, потребовавшихся на взлом кода ф. Теперь начните цикл while, который продолжается до тех пор, пока вы не отыщете комбинацию О Назначьте копию переменной best attempt переменной next try Q . Вы копируете ее для того, чтобы не столкнуться с проблемами зеркальных измене ний; при изменении элемента в next try вы не хотите случайно изменить best attempt, потому что вы, возможно, будете продолжать ее использовать в слу чае, если next try не пройдет тест на приспособленность.
Теперь самое время мутировать копию. Каждая цифра в комбинации поворачивает колесо блокировки в сейфе, поэтому назовите переменную lock wheei и случайно установите ее равной местоположению индекса в комбинации. Он представляет местоположение единственного изменяемого в этой итерации элемента. Далее слу чайно выберите цифру и используйте ее для замены значения в местоположении, индексируемом переменной lock wheei ф. Проградуируйте значение переменной next try, и если оно лучше, чем предыдущая попытка, то сбросьте переменные best attempt и best attempt grade в новые зна чения ф. В противном случае best attempt останется для следующей итерации не изменной. Напечатайте next try И best attempt бок о бок так, чтобы можно было прокрутить попытки, когда программа закончится, и посмотреть, как они эволю ционировали. Завершите цикл, нарастив счетчик.
Когда программа отыщет комбинацию, покажите значение переменной best attempt и число попыток, потребовавшихся на то, чтобы ее отыскать О* Пом ните, что аргумент end=’ 1 предотвращает возврат каретки в конце напечатанной строки и помещает пробел между концом текущей строки и началом следующей строки.
Разведение гигантских крыс с помощью генетических алгоритмов 179
Завершите программу с помощью условной инструкции для выполнения функции maino в автономном режиме и покажите время выполнения с точностью до пяти знаков после точки Q). Обратите внимание, что программный код хронометрирова ния идет после условного и, следовательно, не будет выполняться, если программа импортируется как модуль.
Резюме
Последние несколько строк результата работы программы safe cracker.py показаны ниже. Для краткости я опустил большинство эволюционирующих сравнений. Про гон был для 10-значной комбинации.
[6, 8, б, 2, 0, 5, 8, 9, 0, 0] [6, 8, 2, 2, 0, 5, 8, 9, 0, 0]
[6, 8, 2, 2, 0, 9, 8, 9, 0, 0] [6, 8, 2, 2, 0, 5, 8, 9, 0, 0]
[6, 8, 2, 2, 8, 5, 8, 9, 0, 0] [6, 8, 2, 2, 8, 5, 8, 9, 0, 0]
[6, 8, 2, 2, 8, 5, 8, 9, 0, 2] [6, 8, 2, 2, 8, 5, 8, 9, 0, 2]
Взломано! [б, 8, 2, 2, 8, 5, 8, 9, 0, 2] за 78 попыток!
Время выполнения этой программы составило 0.69172 секунд.
Десять миллиардов возможных комбинаций, и программа нашла решение всего за 78 попыток и менее чем за секунду. Даже Джеймс Бонд был бы этим впечатлен.
Ну, всё. Довольно с нас генетических алгоритмов. Вы использовали пример рабо чего потока для размножения гигантских грызунов, а затем подрезали его, для того чтобы в мгновение ока подняться к вершине холма с применением грубой силы. Если вы хотите продолжить играть в цифрового Дарвина и поэкспериментировать с генетическими алгоритмами, то длинный список примеров приложений можно найти (https://en.wikipedia.org/wiki/List_of_genetic_algorithm_ в Википедии applications). Примеры включают:
♦ моделирование глобальных изменений температуры;
♦ оптимизацию загрузки контейнеров;
♦ оптимизацию маршрута движения грузовиков;
♦ сети мониторинга за уровнем грунтовых вод;
♦ поведение самообучающегося робота;
♦ сворачивание белка;
♦ анализ редких событий;
♦ взламывание кодов;
♦ кластеризацию для функций подгонки;
♦ фильтрацию и обработку сигналов.
180
Глава 7
Дальнейшее чтение
Книга Клинтона Шеппарда “Генетические алгоритмы с помощью языка Python” (Sheppard С. Genetic algorithms with Python. Amazon Digital Services LLC, 2016) представляет собой введение в генетические алгоритмы начального уровня с ис пользованием языка Python. Она продается в мягкой обложке либо как недорогая электронная книга по адресу https://Ieanpub.com/genetic_algorithms_with_python/.
Сложные проекты
Продолжайте разводить суперкрыс и взламывать суперсейфы с помощью предло женных ниже проектов. Как обычно, в случае сложных проектов вы предоставлены самим себе; никаких решений не предусмотрено.
Формирование крысиного гарема
Поскольку один самец крысы может спариваться с несколькими самками, то нет необходимости иметь равное число самцов и самок крыс. Перепишите программ ный код super rats.py для размещения переменного числа самцов и самок. Затем повторите программу с таким же суммарным числом крыс, как и раньше, но ис пользуйте 4 самцов и 16 самок. Как это повлияет на количество лет, необходимых для достижения целевого веса 50 ООО грамм?
Создание более эффективного взломщика сейфов
В настоящее время код safe cracker.py написан так, что при отыскании совпадения запирающего колеса это совпадение явно не сохраняется. Пока работает цикл while, ничто не мешает тому, чтобы правильное совпадение было стохастически переза писано. Измените код так, чтобы индексы правильных догадок были исключены из будущих изменений. Сравните время работы двух версий программного кода с целью оценки влияния этого изменения на результат.
8
ПОДСЧЕТ СЛОГОВ В СТИХОТВОРЕНИЯХ ХОККУ
Поэзия, возможно, является высшей формой литературы. Она, как выразился Кольридж, представляет собой “лучшие слова в лучшем порядке”. Поэт обязан— с величайшей крат костью — рассказать историю, развить идею, описать сцену или вызвать бурю чувств, при этом подчиняясь строгим правилам
рифмы и ритма, стиля и структуры.
Компьютеры любят правила и структуру и даже имеют потенциал к тому, чтобы вызывать эмоции. В своей книге 1996 г. Чарльз Хартман “Виртуальная муза: экспе рименты с компьютерной поэзией” (Hartman С. Virtual muse: experiments in computer poetry) отметил ранние попытки написать алгоритмы, которые могли бы имитировать человеческую поэзию. Цитируя Хартмана, можно констатировать, что “сложность поэтического взаимодействия, хитрый танец между поэтом, текстом и читателем, вызывает игру колебаний. В этой игре правильно запрограммированный компьютер имеет шанс в некоторых интересных движениях проскочить мимо”.
Ранние программы, описанные Хартманом, могли в лучшем случае порождать по эзию плохих битников. В то время цель состояла в том, чтобы “ввести в язык рас считанные кусочки механизированной анархии, вернуть результаты обратно в мир, где живет язык, и посмотреть, как оседает пыль”. Как мы уже говорили в несколь ких главах, контекст является слабым звеном в программировании таких вещей, как анаграммы имен собственных и нулевые шифры. Для того чтобы писать ком пьютерные стихи, которые проходят “высшую” проверку литературы, игнориро вать контекст не получится.
Сделать так, чтобы компьютер симулировал это самое человеческое из человече ских усилий, представляет собой интригующую задачу— и, конечно же, мы не
182
Глава 8
можем пройти мимо. В этой и следующей главах вы научите свой компьютер гене рировать традиционную форму японской поэзии, именуемую хокку (или хайку).
Японская поэзия хокку
Хокку состоит из трех строк соответственно по пять, семь и пять слогов. Стихи редко рифмуются, и их предмет прямо или косвенно обычно вращается вокруг естественного мира — главным образом времен года. При надлежащей реализации хокку может погрузить вас в сцену, как будто вызывая воспоминания.
Я привел здесь три примера хокку. Первое стихотворение принадлежит Бусону (1715-1783), второе— Иссе (1763-1828), а третье— вашему покорному слуге на основе воспоминаний о детских дорожных приключениях.
Standing still at dusk Listen… in far distances The song of froglings!
— Buson
Good friend grasshopper Will you play the caretaker For my little grave?
— Issa
Faraway cloudbanks That I let myself pretend Are distant mountains.
— Vaughan
Застыв, я в сумерках Прислушался… в дали Поют лягушки!
— Бусон
Добрый друг кузнечик, He сыграешь ли ты в смотрителя Могилки крохотной моей? — Исса
Небесные гряды холодные, Которые грезились мне Как далекие горы.
— Воган
Из-за своего вызывающего воспоминания характера каждое хокку имеет для про граммиста встроенный “эксплуатируемый разрыв”. Его прекрасно подытожил Пи тер Бейленсон в своей книге 1955 г. “Японская поэзия хокку” (Beilenson P. Japanese Haiku): “Хокку не всегда должно быть законченным или даже ясным утверждени ем. Читатель должен добавить к словам свои собственные ассоциации и образы и таким образом стать сотворцом своего собственного удовольствия от стихотворе ния”. Хартман добавляет: “Ум читателя наиболее активно работает над редкими материалами. Мы рисуем самые ясные созвездия из наименьшего количества звезд. Таким образом, фактор бессмыслицы низок для крошечного словосочетания, кото рое может быть проникнуто образным значением”. Проще говоря, труднее испор тить короткое стихотворение. Читатели всегда исходят из того, что в словах поэта имелся смысл, и придумают его сами, если не могут найти.
Несмотря на это преимущество, натренировать ваш компьютер писать стихи — не заурядное достижение, и вам понадобится аж две главы на то, чтобы это сделать. В этой главе вы напишете программу, которая подсчитывает число слогов в словах и словосочетаниях ради соблюдения слоговой структуры хокку. В главе 9 вы будете использовать технический прием, именуемый анализом марковских цепей, для
улавливания сути хокку — неуловимой вызывающей воспоминания компоненты — и преобразовывать существующие стихи во что-то новое и иногда, возможно, луч шее.
Подсчет слогов в стихотворениях хокку 183
Проект 15: подсчет слогов
Подсчет слогов на английском языке сопряжен с трудностями. Как выразился Чарльз Хартман, проблема лежит в причудливой орфографии и запутанной лин гвистической истории английского языка. Например, такое слово, как aged (преста релый, состаренный), может произноситься одним слогом либо двумя в зависимо сти от того, описывает ли оно человека или сыр. Как программе точно подсчитать слоги, не вырождаясь в бесконечный список частных случаев?
Ответ заключается в том, что она не сможет это сделать. По крайней мере, не без “шпаргалки”. К счастью, эти шпаргалки существуют благодаря отрасли науки, именуемой обработкой естественного языка (ЕЯ), которая занимается взаимодей ствием между точным и структурированным языком компьютеров и тонким, часто неоднозначным “естественным” языком, используемым людьми. Примеры исполь зования обработки ЕЯ включают машинный перевод с языка на язык, обнаружение спама, понимание запросов к поисковой системе и предсказательное распознавание текста для пользователей мобильных телефонов. Самое большое влияние обработ ки ЕЯ проявится еще впереди: глубинный анализ огромных объемов ранее непри годных, плохо структурированных данных и участие в безупречных разговорах с нашими компьютерными повелителями.
В этой главе вы будете использовать набор данных для обработки ЕЯ с целью под счета слогов в словах или словосочетаниях. Вы также напишете код, который оты скивает слова, отсутствующие в этом наборе данных, а затем поможет вам постро ить вспомогательный словарь. Наконец, вы напишете программу, которая поможет вам проверить ваш код подсчета слогов. В главе 9 вы будете использовать этот алгоритм подсчета слогов в качестве модуля в программе, которая поможет вам вычислительно произвести самое высокое достижение в литературе: поэзию.
Ц е л ь
Написать программу на языке Python, которая подсчитывает число слогов в англий ском слове или словосочетании.
Стратегия
Для вас и для меня подсчет слогов не представляет труда. Положите тыльную сто рону ладони чуть ниже подбородка и начните говорить. Всякий раз, когда ваш под бородок касается руки, вы произносите слог. У компьютеров нет рук или подбо родков, но каждый гласный звук представляет слог— и компьютеры могут под считывать гласные звуки. Однако это не так элементарно, поскольку для этого нет простого правила. Некоторые гласные в письменном языке являются немыми, на
184
Глава 8
пример е в слове like, а другие объединяются, составляя один звук, например оо в слове тоо. К счастью, число слов в английском языке не бесконечно. Существу ют достаточно исчерпывающие списки, которые включают в себя подавляющую часть необходимой информации.
Корпус — это причудливое название, используемое для сборника текстов. В главе 9 вы будете использовать тренировочный корпус, состоящий из хокку, который ин структирует язык программирования Python о том, как писать новое хокку. В этой главе вы будете использовать этот же корпус для извлечения числа слогов.
Ваш счетчик слогов должен оценивать и словосочетания, и отдельные слова, т. к. в конечном итоге вы будете использовать его для подсчета слогов в целых строках хокку. Программа на входе примет немного текста, подсчитает число слогов в каж дом слове и вернет суммарное число слогов. Вам также придется учесть такие вещи, как знаки препинания, пробелы и отсутствующие слова.
Первостепенные шаги, которые необходимо выполнить, таковы:
Скачать большой корпус информации о количестве слогов.
Сравнить указанный корпус с тренировочным корпусом поэзии хокку и выявить
все слова, отсутствующие в корпусе информации о количестве слогов.
Составить словарь отсутствующих слов и их количеств слогов.
Написать программу, которая использует корпус информации о количестве сло гов и словарь отсутствующих слов с целью подсчета слогов в тренировочном корпусе.
Написать программу, которая проверяет программу подсчета слогов относи
тельно обновлений тренировочного корпуса.
Использование корпуса
Естественно-языковой инструментарий Natural Language Toolkit (NLTK) — это по пулярный набор программ и библиотек для работы с данными человеческого языка в Python. Он был создан в 2001 г. в рамках курса компьютерной лингвистики на кафедре компьютерных и информационных наук в Университете Пенсильвании. Развитие и расширение продолжались с помощью десятков участников. Узнать о нем больше можно, посетив официальный сайт NLTK по адресу http:// www.nltk.org/.
В этом проекте вы будете использовать NLTK для доступа к словарю произноше ния Университета Карнеги — Меллона (CMUdict). Указанный корпус содержит почти 125 тыс. слов в сопоставлении с их произношением. Он является машиночи таемым и пригоден для таких задач, как распознавание речи.
Установка NLTK
Инструкции по установке NLTK в UNIX, Windows и macOS можно найти по адресу http://www.nltk.org/install.html. Если вы используете Windows, то я предлагаю вам
Подсчет слогов в стихотворениях хокку 185
начать с открытия окна командной строки либо интерактивной оболочки PowerShell и попытаться установить с помощью pip:
python -m pip install nltk
Проверить установку можно, открыв интерактивную оболочку Python и набрав:
» > import nltk
Если вы не получите ошибку, то все готово. В противном случае следуйте инструк циям на только что процитированном веб-сайте.
Скачивание словаря произношения CMUdict
Для получения доступа к корпусу CMUdict (либо любому другому корпусу NLTK) вы должны его скачать. Это можно сделать с помощью удобной программы для скачивания NLTK. После того как вы установили NLTK, введите следующую команду в интерактивной оболочке Python:
» import nltk » > nltk.download()
Теперь должно открыться окно скачивания NLTK (рис. 8.1). Перейдите на вкладку Corpora (Корпусы) вверху, затем щелкните на cmudict в столбце Identifier (Иден тификатор). Далее прокрутите вниз окна и назначьте каталог для скачиваний; я использовал принятый по умолчанию C:\nltk_data. Наконец, нажмите кнопку Download (Скачать), для того чтобы скачать словарь CMUdict.
/ NLTK Downloader
File View Sort Help
□
X
У
alpino
Alpino Dutch Treebank
2.7 MB
not installed
biocreative_ppi
BioCreAtlvE (Critical Assessment of Information Extr.
218.3 KB
not installed
brown
brown_tei
cess.cat
cess.esp
chat80
city.database
cmudict
comparative_sentence-
comtrans
conll2000
conll2002
conll2007
crubadan
Download
Brown Corpus
Brown Corpus (TEI XML Version)
CESS-CAT Treebank
CESS-ESP Treebank
Chat-80 Data Files
City Database The Carnegie Mellon Pronouncing Dictionary (0.6) Comparative Sentence Dataset
ComTrans Corpus Sample
CONLL 2000 Chunking Corpus
3.2 MB
8.3 MB
5.1 MB
2.1 MB
not installed
not installed
not installed
not installed
18.8 KB
not installed
17 KB
not installed
875.1 KB
installed
272.6 KB
not installed
11.4 MB
not installed
738.9 KB not installed
CONLL 2002 Named Entity Recognition Corpus
1.8 MB
not installed
Dependency Treebanks from CoNLL 2007 (Catalan a
1.2 MB
not installed
Crubadan Corpus
5.0 MB
not installed ▼
Refresh
Server Index: h t t p s : / / r a w .g ith u b u a e rc o n te n t .c o m /n lt k /n ltk _ d a ta /g h -p a g e a /in d e :
Download Directory: С : \n lt k _ d a t a
I
Рис. 8.1. Окно скачивания N LTK с выбранным для скачивания корпусом cmudict
186
Глава 8
Когда скачивание корпуса CMUdict завершится, выйдите из программы скачивания и введите в интерактивную оболочку Python следующее:
» > from nltk.corpus import cmudict »>
Если вы не столкнулись с ошибкой, то корпус был успешно скачан.
Подсчет звуков вместо слогов
Корпус CMUdict разбивает слова на множества фонем— чувственно различных единиц звука в определенном языке — и помечает гласные для лексического уда рения с помощью чисел (0, 1 и 2). Корпус CMUdict помечает каждую гласную одним и только одним из этих чисел, поэтому эти числа можно использовать для идентификации гласных в слове. Взгляд на слова как на множество фонем поможет вам обойти несколько проблем. Во-первых, словарь произношения CMUdict не будет включать в письменное слово непроизносимые гласные. Например, вот как он видит слово scarecrow (пугало):
[[‘S’, ‘К’, 1А Е 1 1 , ‘R’, ‘К’, f R 1 , ‘OWO’]]
Каждый элемент с числовым суффиксом представляет произносимую гласную. Об ратите внимание, что немое е в конце scare (пугать) правильно опущено.
Во-вторых, иногда многочисленные и расположенные подряд письменные гласные произносятся как одна фонема. Например, вот как словарь CMUdict представляет слово house (дом):
[[‘НН’, 1A W 1 ‘, e S ’ ]]
Обратите внимание, как указанный корпус для целей произношения рассматривает письменные двойные гласные ои как одну гласную, ’ a w i ’ .
Обработка слов с несколькими произношениями
Как я уже упоминал в преамбуле к главе, некоторые слова имеют несколько разных произношений; слова aged (престарелый, состаренный) и learned (усвоенный, уче ный) — это лишь два таких примера:
[[‘EY11, 1J H 1, ’ D ’ ] , [’ EY1’, 1JH 1, ЧНО’, 1D 1]] [[’!/, ‘ER1’, ‘N 1, ’ D ’ ] , [ ’ L ’ , ‘ER11, ’ N ’ , 1IHO’, ‘D’]]
Обратите внимание на вложенные списки. Корпус признает, что оба слова могут произноситься одним или двумя слогами. Это означает, что для определенных слов он вернет более одного слога, и это вам придется учитывать в своем коде.
Управление отсутствующими словами
Корпус CMUdict очень полезен, но с ним слово либо есть, либо его нет. Потребова лось всего несколько секунд на то, чтобы найти более 50 слов, таких как dewdrop, bathwater, dusky, ridgeline, storks, dragonfly, beggar и archways (соответственно: po-
Подсчет слогов в стихотворениях хокку 187
синка, вода в ванне, сумеречный, гребневая линия, аисты, стрекоза, нищий и свод чатый проход), отсутствующих в корпусе CMUdict на тестовом примере из 1500 слов. Поэтому одна из ваших стратегий должна состоять в том, чтобы прове рить словарь CMUdict на предмет отсутствующих слов, а затем устранить любые упущения, создав корпус для вашего корпуса!
Тренировочный корпус
В главе 9 вы будете использовать тренировочный корпус из нескольких сотен хокку для того, чтобы “научить” вашу программу писать новые поэтические миниатюры. Но вы не можете рассчитывать на то, что словарь CMUdict будет содержать все слова в этом корпусе, потому что некоторые из них будут японскими словами, такими как sake (саке). И как вы убедились, в словаре CMUdict отсутствуют даже некоторые распространенные английские слова.
Поэтому первым делом следует проверить все слова в тренировочном корпусе на членство в CMUdict. Для этого вам нужно будет скачать тренировочный корпус train.txt с веб-сайта https://www.nostarch.com/impracticalpython/. Сохраните его в той же папке, что и все программы Python из этой главы. Указанный файл содер жит чуть менее 300 стихотворений хокку, которые были случайно продублированы около 20 раз ради того, чтобы обеспечить надежный тренировочный набор.
После того как вы найдете слова, которые отсутствуют в словаре CMUdict, вы на пишете сценарий, который поможет вам подготовить словарь Python, использую щий слова в качестве ключей и количества слогов в качестве значений; затем вы сохраните этот словарь в файл, который может поддерживать CMUdict в программе подсчета слогов.
Код отыскания отсутствующих слов
Программный код этого раздела отыщет слова, отсутствующие в корпусе CMUdict, поможет подготовить словарь, состоящий из слов и количеств их слогов, а также сохранить словарь в файл. Указанный код можно скачать с веб-сайта по адресу https://www.nostarch.com/impracticalpython/ как файл missing words finder.py.
Импорт модулей, загрузка корпуса CMUdict и определение функции mainQ
Листинг 8.1 импортирует модули, загружает корпус CMUdict и определяет функ цию main (), которая выполняет программу.
Листинг 8.1. Импортирует модули, загружает CMUdict и определяет m a in ( ). Файл missing_words_finder.py, часть 1
import sys from string import punctuation
О import pprint
188
Глава 8
import json
from nltk.corpus import cmudict
О cmudict = cmudict.diet() # Словарь произношения
университета Карнеги - Мелона #
О def ma i n ():
О haiku = load_haiku(1 train.txt’) ф exceptions = cmudict_missing(haiku) ф build_dict = input(“ХпПостроить словарь исключений вручную (у/n)? \n”)
if build_dict.lower() == ’n’:
sys.exit()
else:
e missing_words_dict = make_exceptions_dict(exceptions)
save_exceptions(missing_words_dict)
Вы начинаете с нескольких уже знакомых команд импорта и нескольких новых. Модуль pprint позволяет “красиво” напечатать ваш словарь отсутствующих слов в структурном и удобном для чтения формате О- Вы запишете этот же словарь как долговременные данные с помощью объектной нотации JavaScript (json), т. е. тек стового способа обмена данными между компьютерами, который хорошо работает со структурами данных Python; этот модуль является составной частью стандарт ной библиотеки, стандартно применяемой в многочисленных языках, при этом данные являются безопасными и удобочитаемыми. Завершите импортом корпуса CMUdict.
Далее вызовите метод diet () модуля cmudict, превратив корпус в словарь со сло вами в качестве ключей и их фонемами в качестве значений ф. Определите функцию main (), которая будет вызывать функции для загрузки трени ровочного корпуса, отыскания отсутствующих в словаре CMUdict слов, построения словаря со словами и их количествами слогов и сохранения результатов О- Вы определите эти функции после определения main ().
Вызовите функцию для загрузки тренировочного корпуса хокку и назначьте воз вращенное множество переменной haiku О - Затем вызовите функцию, которая отыщет отсутствующие слова и вернет их в виде множества ф. Используя множе ства, вы автоматически удаляете дублирующие слова, которые вам не нужны. Функция cmudict missing о также покажет число отсутствующих слов и некоторую другую статистику.
Теперь спросите пользователя, намерен ли он строить словарь вручную с целью решить вопрос с отсутствующими словами, и назначьте введенное им значение пе ременной buiid dict ф. Если он хочет остановиться, то выйдите из программы; в противном случае вызовите функцию, которая строит словарь ф, а затем еще од ну, которая сохраняет словарь на диск. Обратите внимание, что если пользователь хочет продолжить работу, то он не ограничивается нажатием клавиши , хотя она и указана в подсказке.
Подсчет слогов в стихотворениях хокку 189
Загрузка тренировочного корпуса и отыскание отсутствующих слов
Листинг 8.2 загружает и подготавливает тренировочный корпус, сравнивает его содержимое со словарем CMUdict и отслеживает различия. Эти задачи разделены между двумя функциями.
Листинг 8.2. Определяет функции загрузки корпуса и оты скивает слова, отсутствующие в словаре CMUdict. Файл m issing_words_finder.py, часть 2
О def load_haiku(filename):
“““Открыть и вернуть тренировочный корпус хокку в виде множества.”””
with open(filename) as in_file:
О haiku = set(in_file.read().replace(1 , ’ 1).split())
ф return haiku
def cmudict_missing(word_set):
“““Отыскать и вернуть слова в множестве слов,
отсутствующих в cmudict.”””
О exceptions = set()
for word in word_set:
word = word.lower().strip(punctuation)
if word.endswith("’s”) or word.endswith("’s”):
word = word[:-2]
О if word not in cmudict: exceptions.add(word)
print("\писключения:”) print(^exceptions, sep=,\nl)
ф print(“ХпЧисло уникальных слов в корпусе хокку = {}”
.format(len(word_set)))
print(“Число слов в корпусе слов не из cmudict = {}”
.format(len(exceptions)))
membership = (1 - (len(exceptions) / len(word_set))) * 100
ф print(“членство в cmudict = {:.If}{}".format(membership, ’%’))
return exceptions
Определите функцию, которая читает слова из тренировочного корпуса хокку ф . Хокку в train.txt были продублированы много раз, плюс к тому оригинальные хокку содержат дубликаты слов, таких как moon (луна), mountain (гора) и the. Нет никако го смысла оценивать слово более одного раза, поэтому загрузите слова в виде мно жества, удалив повторы ф . Также необходимо заменить дефисы пробелами. Дефи сы весьма популярны в хокку, но для того чтобы выполнить проверку на их нали чие в корпусе CMUdict, вам нужно отделить слова с обеих сторон. Завершите функцию, вернув МНОЖ е С Т В О haiku о
Теперь самое время отыскать пропущенные слова. Определите функцию cmudict missingo, которая в качестве аргумента принимает последовательность— в дан
190 Глава 8
ном случае множество слов, возвращаемых функцией ioad_haiku(). Создайте пус тое множество exceptions, в котором будут храниться все отсутствующие слова 0. Переберите каждое слово в множестве haiku, конвертируя его в нижний регистр и удаляя любые начальные или замыкающие знаки препинания. Обратите внимание, что вы не удаляете их внутреннюю пунктуацию, кроме дефисов, потому что сло варь CMUdict распознает слова, такие как wouldn’t. Притяжательные слова, как правило, не находятся в корпусе, поэтому удалите замыкающие ’s, т. к. они не по влияют на количество слогов.
П Р И М Е Ч А Н И Е
Будьте осторожны с фигурными апострофами (’), создаваемыми программным обес печением для обработки текстов. Они отличаются от прямых апострофов ( ’ ) , исполь зуемых в простых текстовых редакторах и интерактивных оболочках и не могут быть распознаны словарем CMUdict. Если вы добавляете новые слова в тренировочные или JSON-файлы, то в сокращениях или притяжательных существительных непре менно используйте прямой апостроф.
Если слово в CMUdict не найдено, то добавьте его в множество исключений exceptions О Напечатайте эти слова в качестве проверки вместе с некоторой базо вой информацией 0, например числом уникальных слов, числом отсутствующих слов и процентом слов тренировочного корпуса, являющихся членами CMUdict. Установите процентное значение точности равным одному десятичному знаку 0. Завершите работу функции, вернув множество исключений exceptions.
Построение словаря отсутствующих слов
Листинг 8.3 продолжает код скрипта missing words finder.py, теперь дополняя кор пус CMUdict посредством назначения количеств слогов отсутствующим словам в качестве значений словаря Python. Поскольку число отсутствующих слов должно быть относительно небольшим, пользователь может задать количества вручную; поэтому напишите код, помогающий ему взаимодействовать с программой.
Листинг 8.3. Позволяет пользователю вручную подсчитывать слоги и создавать словарь. Файл m issing_words_finder.py, часть 3
О def make_exceptions_dict(exceptions_set):
“““Вернуть словарь слов и количеств слогов из множества слов.”””
0 missing_words = {}
print(“Введите число слогов в слове. Ошибки можно исправить в конце. \п”)
for word in exceptions_set:
while True: 0 num_sylls = input(“Введите число слогов в {}: “.
0 if num_sylls.isdigit():
format(word))
break
else:
print(”
file=sys.stderr)
Недопустимый ответ!”,
Подсчет слогов в стихотворениях хокку 191
О missing_words[word] = int(num_sylls)
print ()
Q pprint.pprint(missing_words, width=l)
Q print("\пВнести изменения в словарь перед сохранением?”)
print(..
0 - Выйти и сохранить
1 - Добавить слово либо изменить количество слогов
2 - Удалить слово
»» I I I I ^
ф while True:
choice = input("\пСделайте свой выбор: “)
if choice == f 0’:
break
elif choice == f1 1 :
word = input("\пДобавляемое или изменяемое слово: “) missing_words[word]=int(input(“Введите число слогов в {}: "
.format(word)) )
elif choice == 12’:
word = input("\пВведите удаляемое слово: “)
О missing_words.pop(word, None)
print("\пИзменения в новых словах и слогах:”)
ф pprint.pprint(missing_words, width=l)
return missing_words
Начните с определения функции, которая в качестве аргумента принимает множе ство исключений exceptions, возвращаемых функцией cmudict missing о О - Сразу же назначьте переменной с именем missing words пустой словарь О . Сообщите пользователю, что если он допустит ошибку, то у него будет шанс исправить ее позже; затем примените циклы for и while для того, чтобы перебрать множество отсутствующих слов и показать каждое слово пользователю, на входе запрашивая число слогов. Слово будет ключом словаря, а переменная num syiis станет его зна чением О- Если введенное значение является цифрой Q , выйдите из цикла. В про тивном случае предупредите пользователя и снова в цикле while попросите ввести данные. Если входные данные пройдут проверку, добавьте значение в словарь в виде целого числа Q .
Примените pprint для вывода каждой пары “ключ — значение” на экран в отдель ной строке для проверки. Параметр width действует как аргумент новой строки Q .
Дайте пользователю возможность внести последние изменения в словарь missing words перед сохранением его в виде файла Q . Примените тройные кавычки для показа пользователю меню вариантов, а затем цикл while для поддержки вари антов активными до тех пор, пока пользователь не будет готов сохранить ело-
192
Глава 8
варь О В°т э™ ТРИ варианта: Выйти — вариант вызывает команду break; Доба вить слово либо изменить количество слогов существующего слова — требует на входе указать слово и количество слов; Удалить слово — удаляет запись, используя метод pop () из словарного типа О- Добавление аргумента None в pop () означает, что программа не вызовет системное исключение KeyError, если пользо ватель введет слово, которого в словаре нет.
Завершите программу, позволив пользователю последний раз взглянуть на словарь, в случае если были внесены изменения Q), а затем верните его.
Сохранение словаря отсутствующих слов
Долговременные данные— это данные, которые сохраняются после завершения программы. Для того чтобы сделать словарь missing words доступным для исполь зования в программе count syllables.py, которую вы напишете в этой главе позже, его нужно сохранить в файл. Листинг 8.4 как раз это и делает.
Листинг 8.4. Сохраняет словарь отсутствующих слов в файл и вызывает main (). Файл missing_words_f»nder.py, часть 4
О def save_exceptions(missing_words):
“““Сохранить словарь исключений exceptions как файл json.”””
0 json_string = json.dumps(missing_words) Q f = open(‘missing_words.json’, ‘w’)
f.write(json_string)
f.close ()
О print("\пФайл сохранен как missing_words.json”)
0 if name == ‘main’ :
m a i n ()
Для сохранения словаря используйте модуль json. Определите новую функцию, которая в качестве аргумента принимает множество отсутствующих слов О- На значьте словарь missing words новой переменной с именем json string О ; затем откройте файл с расширением .json Q , запишите на диск переменную json string и закройте файл. Покажите имя файла пользователю в качестве напоминания О . Завершите фрагментом кода, который позволяет программе работать как модуль либо в автономном режиме 0. Метод json.dumps о сериализует словарь missing words в символьную цепочку. Сериализация — это процесс конвертации данных в более удобный для передачи либо хранения формат. Например:
» > im port js o n » > d = { ’s c a r e c r o w 1: 2 , » > j s o n . dumps (d) ‘{“sake”: 2, “scarecrow”: 2, “moon”: 1}’
’m oon’: 1 ,
’s a k e ‘: 2)
Подсчет слогов в стихотворениях хокку 193
Обратите внимание, что сериализованный словарь с обеих сторон ограничен оди нарными кавычками, что и делает его цепочкой символов.
Здесь показан частичный вывод из скрипта missing words finder.py. Список отсут ствующих слов вверху и ручное количество слогов внизу для краткости были сокращены.
- -обрезано- - froglings
scatters
paperweights
hibiscus
cumulus
nightingales
Число уникальных слов в корпусе хокку = 1523
Число слов в корпусе не из cmudict = 58
членство в cmudict = 96.2%
Построить словарь исключений вручную (у/п)?
У Введите число слогов в woodcutter: 3 Введите число слогов в morningglory: 4
Введите число слогов в cumulus: 3 –обрезано- -
Не волнуйтесь — вам не придется самим назначать все количества слогов. Файл missingwords.json завершен и готов к скачиванию в любой момент, когда он вам потребуется.
ПРИМЕЧАНИЕ В случае слов, которые имеют несколько произношений, таких как ja g g e d (зазубрен ный) или ou r (наш), вы можете вынудить программу использовать то, которое вы предпочитаете, вручную открыв файл missing_words.json и добавив пару “ключ — зна чение” (в любом месте, т. к. словари не упорядочены). Я сделал это со словом sake (сакё), для того чтобы программа использовала двухсложное японское произношение. Поскольку в этом файле в первую очередь проверяется членство слова, программа переопределит значение словаря CMUdict.
Теперь, когда вы уладили уязвимости в корпусе CMUdict, вы готовы написать про граммный код, который подсчитывает слоги. В главе 9 вы будете использовать ука занный код в качестве модуля программы markov haiku.py.
Код подсчета слогов
Этот раздел содержит код программы count syllables.py. Вам также понадобится файл missing words.json, созданный в предыдущем разделе. Оба файла можно скачать с веб-сайта https://www.nostarch.com/impracticaIpython/. Сохраните их вместе в одной папке.
194 Глава 8
Подготовка, загрузка и подсчет
Листинг 8.5 импортирует необходимые модули, загружает словарь CMUdict и сло варь отсутствующих слов и определяет функцию, которая будет подсчитывать сло ги в данном слове или словосочетании.
Листинг 8.5. Импортирует модули, загружает словари и подсчитывает слоги. Файл count_syllab!es.py, часть 1
import sys from string import punctuation
import json
from nltk.corpus import cmudict
Загрузить словарь слов из корпуса хокку, но не из cmudict #
with open(‘missing_words.json1) as f:
missing_words = json.load(f)
ф cmudict = cmudict.diet()
Q def count_syllables(words):
“““Использовать корпусы для подсчета слогов в английском слове
или словосочетании.”””
подготовить слова для корпуса cmudict #
words = words.replace(’- f, f ‘)
words = words.lower().split()
О num_sylls = 0
О f°r word in words:
word = word.strip(punctuation)
if word.endswith("’s”) or word.endswith("’s”):
word = word[:-2]
ф if word in missing_words:
num_sylls += missing_words[word]
else: О for phonemes in cmudict[word][0]: for phoneme in phonemes:
Q if phoneme[-1].isdigit():
num_sylls += 1
ф return num_sylls
После нескольких уже знакомых импортов загрузите файл missing_words.json, со держащий все слова и их количества слогов, отсутствующие в корпусе CMUdict. Использование json. load о восстанавливает словарь, сохраненный в виде сим вольной цепочки. Затем превратите корпус CMUdict в словарь, используя метод diet () О *
Подсчет слогов в стихотворениях хокку
1 9 5
Определите функцию count syiiabies (), которая будет подсчитывать слоги. Она должна принимать как слова, так и словосочетания, потому что в конечном итоге вы захотите передавать ей строки из хокку. Подготовьте слова, как вы делали ранее в программе missing words finder.py © .
Задайте переменную num syiis для хранения количества слогов и установите ее равной о О- Теперь начните перебирать входные слова в цикле, удаляя знаки пре пинания и ’5С обоих концов. Обратите внимание, что вы можете запутаться в фор мате апострофа, поэтому предоставлены две версии: одна с прямым апострофом и одна с фигурным апострофом О . Затем проверьте, является ли слово членом не большого словаря отсутствующих слов. Если слово найдено, то добавьте значение из словаря для этого слова в num syiis © . В противном случае начните просматри вать фонемы, которые представляют значение в CMUdict; по каждой фонеме про смотрите цепочки символов, которые его составляют Q . Если в конце цепочки вы найдете цифру, это указывает, что данная фонема является гласной. Если проиллю стрировать использование слова aged, то только первая цепочка (здесь выделенная жирным шрифтом) заканчивается цифрой, поэтому указанное слово содержит одну гласную:
[[1EY11, 1 JH’, 1D 1], [1EY11, 1JH’, ‘IHO’, ‘D’]]
Обратите внимание, что вы используете первое значение ([ о ]) в случае, если име ется несколько произношений; помните, что корпус CMUdict представляет каждое произношение во вложенном списке. Это может привести к случайной ошибке, т. к. правильный выбор будет зависеть от контекста.
Проверьте, имеет ли конец фонемы цифру, и если имеет, то добавьте 1 в num syiis Q . Наконец, верните общее количество слогов в слове или словосочетании ф .
Определение функции main()
Завершая программу, листинг 8.6 определяет и запускает функцию maino. Про грамма вызовет эту функцию при запуске программы в автономном режиме, на пример для выборочной проверки слова или фразы, но она не будет вызвана, если вы импортируете syliabie counter в качестве модуля.
Листинг 8.6. Определяет и вызывает функцию main(). Файл count_syllables.py, часть 2
def main():
О while True:
print(“Счетчик слогов”)
ф word = input(“Введите слово или словосочетание либо нажмите Enter,
чтобы выйти: “)
© i f word == 1f:
sys.exit()
О try:
num_syllables = count_syllables(word)
196
Глава 8
print(“число слогов в {} равно: {}”
.format(word, num_syllables))
print()
except KeyError:
print(“Слово не найдено. Попробуйте еще раз.\п”,
file=sys.stderr)
ф if name == 1 main__1 :
m a i n ()
Определите функцию maino, а затем начните цикл while О- Попросите пользова теля ввести слово или словосочетание ф . Если пользователь нажимает клавишу без ввода, программа завершает работу О- В противном случае начните блок try-except, благодаря которому программа не зависнет, если пользователь введет слово, не найденное ни в одном словаре О* Исключение должно быть вы звано только в автономном режиме, т. к. вы уже подготовили программу, которая будет выполняться на тренировочном корпусе хокку без исключений. В этом блоке вызывается функция count syiiabiesо, ей передаются входные данные, а затем результаты показываются в интерактивной оболочке. Завершите работу стандарт ным кодом, который позволяет программе работать автономно либо как модуль в другой программе Q .
Программа проверки вашей программы
Вы тщательно приспособили программу подсчета слогов, обеспечив ее работу с тренировочным корпусом. По мере продолжения работы с программой хокку вы можете добавить в этот корпус одно или два стихотворения, но добавление нового хокку может внести новое слово, которого нет ни в словаре CMUdict, ни в вашем словаре исключений. Прежде чем вернуться и перестроить словарь исключений, проверьте, действительно ли это необходимо.
Листинг 8.7 автоматически подсчитает слоги в каждом слове вашего тренировочно го корпуса и покажет любое слово (слова), на котором проверка оказалась безус пешной. Эту программу можно скачать с веб-сайта https://www.nostarch.com/ impracticalpython/ как файл test_count_syllables_w_fiill_corpus.py. Сохраните его в той же папке, что и файлы count syllables.py, train.txt и missing words.json.
Листинг 8.7. Пытается подсчитать слоги в словах в тренировочном корпусе и перечисляет все безуспешные попытки. Файл te8t_count_8yllables.w_fulLcorpus.py
import sys
import count_syllables
with open(1 train.txt.1) as in_file:
О words = set(in_file.read().split())
Подсчет слогов в стихотворениях хокку 197
О missing = []
О for word in words:
try:
num_syllables = count_syllables.count_syllables(word)
##print(word, num_syllables, end=’\n’) # раскомментируйте для
показа количеств слов #
О except KeyError:
missing.append(word)
0 print(“Отсутствующие слова:”, missing, file=sys.stderr)
Откройте обновленный тренировочный корпус train.txt и загрузите его в виде мно жества, удалив дубликаты О- Создайте пустой список с именем missing, который будет содержать любые новые слова, для которых невозможно подсчитать сло ги 0 . Слова в missing не будут ни в словаре CMUdict, ни в вашем словаре missing_words.
Переберите в цикле слова из нового тренировочного корпуса О и примените блок try-except ДЛЯ обработки СИСТеМНОГО исключения KeyError, который будет вызван, если count syllables.py не сможет отыскать слово О* Добавьте это слово в отсутст вующий список, а затем покажите список Q . Если программа показывает пустой список, то все слова в новом хокку уже присут ствуют либо в CMUdict, либо в missing_words.json, поэтому вносить никаких изме нений не требуется. В противном случае у вас есть возможность вручную добавить слова в файл missing_words.json либо повторно выполнить скрипт missing_ words finder.py для того, чтобы перестроить файл missing_words.json.
Резюме
В этой главе вы узнали, как скачать модуль NLTK и использовать один из его набо ров данных, словарь произношения университета Карнеги — Меллона (CMUdict). Вы сверили набор данных CMUdict с тренировочным корпусом хокку и построили вспомогательный словарь Python с любыми отсутствующими словами. Вы сохра нили этот словарь Python как долговременные данные с помощью объектной нота ции JavaScript (JSON). Наконец, вы написали программу, которая умеет подсчиты вать слоги. В главе 9 вы воспользуетесь своей программой подсчета слогов в каче стве вспомогательного средства при генерировании новаторских стихотворений хокку.
Дальнейшее чтение
Книга Чарльза О. Хартмана “Виртуальная муза: эксперименты с компьютерной по эзией” (Hartman С. О. Virtual muse: experiments in computer poetry. Wesleyan University Press, 1996) представляет собой увлекательный взгляд на раннее сотруд ничество людей и компьютеров при написании стихотворений.
198
Глава 8
Книга Стивена Берда, Эвана Клейна и Эдварда Лопера “Обработка естественного языка с помощью Python: анализ текста с помощью естественно-языкового инстру ментария NLTK” (Bird S., Klein Е., Loper Е. Natural language processing with Python: analyzing text with the natural language toolkit. O’Reilly, 2009) представляет собой доступное введение в обработку ЕЯ с использованием языка Python с большим ко личеством упражнений и полезной интеграцией с веб-сайтом NLTK. Новая версия книги, обновленная до Python 3 и NLTK 3, доступна в Интернете по адресу http://www.nltk.org/book/.
Статья Стивена Ф. Деанджелиса “Растущая важность обработки естественного язы ка” (DeAngelis S. F. The growing importance of natural language processing) из журна ла Wired посвящена расширяющейся роли обработки ЕЯ в Больших данных. Ее он лайновая версия доступна по адресу https://www.wired.com/insights/2014/02/ growing-importance-natural-language-processing/.
Практический проект: счетчик слогов против файла словаря
Напишите программу на языке Python, которая позволит вам протестировать скрипт countsyllables.py (либо любой другой программный код Python для подсче та слогов) относительно файла словаря. Разрешив пользователю указать число слов для проверки, выберите слова наугад и покажите список, состоящий из слов и их количеств слогов на отдельных строках. Результат должен выглядеть аналогично приведенному далее выводу:
ululation 4
intimated 4 sand 1 worms 1 leatherneck 3
contenting 3
scandals 2 livelihoods 3
intertwining 4 beaming 2 untruthful 3
advice 2 accompanying 5 deathly 2
hallos 2
Скачиваемые файлы словарей перечислены в табл. 2.1. Решение указанной задачи можно найти в приложении к книге либо скачать с веб-сайта https://www. nostarch.com/impracticalpython/ как файл test_count_syllables_w_dict.py.
9
НАПИСАНИЕ СТИХОТВОРЕНИЙ ХОККУ С ПОМОЩЬЮ АНАЛИЗА МАРКОВСКИХ ЦЕПЕЙ
Компьютеры способны писать стихи путем перекомпоновки Х\ I существующих стихотворений. Это, в сущности, то, что де- 1/ лают люди. Мы с вами не придумали язык, на котором гово рим, — мы его выучили. Для того чтобы говорить или писать, мы просто рекомбинируем существующие слова— и редко по- настоящему в оригинальной манере. Как выразился Стинг о написании музыки, “не думаю, что в поп-музыке есть такая вещь, как композиция. Мне кажется, то, что мы делаем в поп-музыке, называется подборкой… Я — хороший подборщик”.
В этой главе вы собираетесь написать программу, которая ставит “наилучшие слова в наилучшем порядке” в виде хокку. Но для этого Python нуждается в хороших примерах, поэтому вам нужно будет предоставить тренировочный корпус хокку японских поэтов.
В целях более или менее осмысленной перекомпоновки этих слов вы будете ис пользовать марковские цепи, названные так в честь русского математика Андрея Маркова. Анализ марковских цепей, являющийся важной частью теории вероятно стей, — это процесс, который пытается предсказать последующее состояние на ос нове свойств текущего состояния. Современные приложения указанного метода включают распознавание речи и рукописного текста, оценивание производительно сти компьютера, фильтрацию спама и алгоритм PageRank компании Google для по иска в Интернете.
С помощью анализа марковских цепей, тренировочного корпуса и программы под счета слогов из главы 8 вы сможете создавать новые хокку, которые следуют сло говым правилам этого жанра и в значительной степени остаются “в теме”. Вы так же узнаете, как использовать модуль журналирования Python logging, помогающий
200
Глава 9
осуществлять мониторинг поведения вашей программы с легкой нерегулярной об ратной связью. И в разд. ”Сложные проекты " далее в этой главе вы сможете заин триговать своих друзей из социальных сетей тем, чтобы испытать их литературные навыки и попробовать отличить ваше симулированное хокку от реального.
Проект 16: анализ марковских цепей
Как и генетические алгоритмы в главе 7, анализ марковских цепей звучит внуши тельно, но его легко реализовать. Вы делаете это каждый день. Если вы слышите, как кто-то говорит: “Это элементарно, любезный…”, то автоматически думаете: “Ватсон”. Всякий раз, когда ваш мозг слышал эту фразу, он брал образец. Основы ваясь на серии образцов, он может предсказывать ответ. С другой стороны, если вы слышали, как кто-то сказал: “Хочу сходить…”, то вы можете подумать: “в ванную комнату” либо “в кино”, но, вероятно, не “в Хоума, Луизиана”. Тут есть много воз можных решений, но некоторые из них более вероятны, чем другие.
Еще в 1940-х годах Клод Шеннон (Claude Shannon) впервые использовал марков ские цепи для статистического моделирования последовательности букв в тексте. Например, для каждого появления диграммы th в англоязычной книге следующей наиболее вероятной буквой является буква е.
Но вы не просто хотите знать, какая буква является наиболее вероятной; вам требу ется знать фактическую вероятность получения этой буквы, а также шансы получе ния любой другой буквы, что представляет собой задачу, идеально подходящую для компьютера.
Для того чтобы решить эту задачу, вам нужно сопоставить каждую двухбуквенную диграмму во фрагменте текста с буквой, которая следует сразу за ней. Эта задача является классическим словарным приложением, где диграммы выступают в каче стве ключей, а буквы в качестве значений.
Применительно к буквам в словах, марковская модель — это математическая мо дель, которая вычисляет вероятность появления буквы на основе предыдущих к букв подряд, где к — это целое число. Модель порядка 2 означает, что вероят ность появления буквы зависит от двух букв, которые ей предшествуют. Модель порядка 0 означает, что каждая буква является независимой. И эта же логика при менима к словам. Рассмотрим два примера хокку.
A break in the clouds The moon a bright mountaintop Distant and aloof
В разрыве облаков Луна сияет, как вершина гор Далекая и чуждая
Glorious the moon Therefore our thanks dark clouds come То rest our tired necks
Луна — великолепна И наша благодарность грядам облаков За успокоенье шеям нашим утомленным
Словарь Python, который увязывает каждое слово хокку с каждым последующим словом, выглядит следующим образом:
Написание стихотворений хокку с помощью анализа марковских цепей 201
‘а1: [‘break1, ’bright1]/
‘aloof1: [‘glorious’],
‘and’: [‘aloof’],
‘break’: [‘in’],
‘bright’: [‘mountaintop’],
‘clouds’: [’the’, ‘come’],
‘come’: [’t o ‘],
‘dark’: [‘clouds’],
‘distant’: [‘and’],
‘glorious’: [’the’],
‘in’: [’the’],
‘moon’: [‘a’, ’therefore’],
‘mountaintop’: [‘distant’],
‘our’: [’thanks’, ’tired’],
‘rest’: [‘our’],
’thanks’: [‘dark’],
’the’: [‘clouds’, ‘moon’, ‘moon’],
’therefore’: [‘our’],
’tired’: [’necks’],
’to’: [‘rest’]
Поскольку существует только два хокку, то большинство ключей словаря имеют только одно значение. Но посмотрите на нижнюю часть списка: слово moon (луна) появляется дважды. Это происходит потому, что марковская модель хранит каждое вхождение слова как отдельное, повторное значение. И поэтому если для ключа the вы выбираете значение наугад, то шансы выбора слова moon вместо слова clouds равны 2:1. И наоборот, модель автоматически отсеивает крайне редкие или невоз можные комбинации. Например, за одним словом the потенциально могут следо вать многие слова, но только не за еще одним словом the\
Приведенный далее словарь увязывает каждую пару слов с последующим словом, а это означает, что мы имеем модель порядка 2.
‘a break’: [‘in’],
‘a bright’: [‘mountaintop’],
‘aloof glorious’: [’the’],
‘and aloof’: [‘glorious’],
‘break in’: [’the’],
‘bright mountaintop’: [‘distant’],
‘clouds come’: [’to’],
‘clouds the’: [‘moon’],
‘come to’: [‘rest’],
‘dark clouds’: [‘come’],
‘distant and’: [‘aloof’],
‘glorious the’: [‘moon’],
‘in the’: [‘clouds’],
‘moon a’: [‘bright’],
‘moon therefore’: [‘our’],
202
Глава 9
‘mountaintop distant’: [‘and*],
‘our thanks’: [‘dark’],
‘our tired’: [’necks’],
‘rest our’: [’tired’]/
’thanks dark’: [‘clouds’],
’the clouds’: [’the’],
’the moon’: [‘a’, ’therefore’],
’therefore our’: [’thanks’],
’to rest’: [‘our’]
Обратите внимание, что увязывание переходит от первого хокку ко второму, по этому словарь содержит элементы ‘and aloof’: [‘glorious’] И ‘aloof glorious’: [’the’]. Такое поведение означает, что ваша программа может перепрыгивать от одного хокку к другому и не ограничивается только парами слов в одном хокку. Этот процесс может свободно образовывать новые пары слов, которые поэты, воз можно, никогда не задумывали.
Ввиду очень короткого тренировочного корпуса словарная пара the moon является единственной, имеющей несколько ключей. Для всех остальных вы ’’заперты” в од ном результате. В этом примере размер тренировочного корпуса в большой степени определяет число значений в расчете на ключ, но при наличии у нас более крупного корпуса значение к в марковской модели будет иметь более сильное влияние.
Размер к определяет, несете ли вы чушь, демонстрируете плагиат или же создаете вразумительный образец оригинальности. Если к равно 0, то вы будете выбирать слова случайно на основе совокупной частоты этого слова в корпусе и, вероятно, произведете много тарабарщины. Если к является большим, то результаты будут жестко ограничены, и вы начнете воспроизводить тренировочный текст дословно. Таким образом, малые значения к способствуют творчеству, а большие значения ведут к дублированию. Задача состоит в том, чтобы найти баланс между ними.
Для иллюстрации, если применить марковскую модель порядка 3 к предыдущим хокку, то все полученные ключи будут иметь одно значение. Два значения, связан ные со словарной парой the moon, теряются, потому что эта пара слов становится двумя ключами, каждый с уникальным значением:
’the moon a’: [‘bright’],
’the moon therefore’: [‘our’]
Поскольку хокку являются короткими — всего 17 слогов длиной и имеющиеся тре нировочные корпуса являются относительно малыми, использование к, равного 2, должно быть достаточным для обеспечения некоторого порядка, при этом по- прежнему допуская в вашей программе творческие замены слов.
Ц е л ь
Написать программу, которая генерирует хокку с помощью анализа марковских цепей. Дать пользователю возможность изменять хокку, независимо регенерируя вторую и третью строки.
Написание стихотворений хокку с помощью анализа марковских цепей 203
Стратегия
Ваша общая стратегия симулирования хокку будет заключаться в построении мар ковских моделей порядков 1 и 2 с помощью тренировочного корпуса стихотворе ний хокку, написанных людьми. Затем вы примените указанные модели и про грамму countsyllables.py из главы 8 для создания новых хокку, которые отвечают требуемой слоговой структуры 5-7-5 трех строк хокку.
Программа должна строить хокку по одному слову за раз, инициируя хокку слу чайным словом, взятым из корпуса, выбирая второе слово хокку с использованием марковской модели порядка 1, а затем выбирая каждое последующее слово с ис пользованием модели порядка 2.
Каждое слово выводится из префикса, т. е. слова или пары слов, которые опреде ляют, какое слово будет выбрано дальше в хокку; ключ в словарях сочетания слов представляет этот префикс. И следовательно, слово, которое префикс определяет, является суффиксом.
Выбор и отбрасывание слов
Когда программа отбирает слово, она сначала подсчитывает слоги в слове, и если слово не подходит, то она выбирает новое слово. Если в стихотворении нет воз можных слов, основанных на префиксе, то программа прибегает к тому, что я на зываю призрачным префиксом, т. е. префиксу, который в хокку не встречается. На пример, если словарной парой в хокку является temple gong (храмовый гонг) и все последующие слова в марковской модели имеют слишком много слогов для того, чтобы завершить строку по правилам, то программа случайно отбирает новую пару слов и использует ее для отбора следующего слова в хокку. Новый префикс сло варной пары не должен включаться в строку, т. е. словарная пара temple gong за менена не будет. Хотя новое подходящее слово можно выбрать несколькими спо собами, я предпочитаю именно этот технический прием, потому что он позволяет упростить, поддерживая единообразный процесс на протяжении всей программы.
Эти шаги можно выполнить с помощью функций, показанных на рис. 9.1 и 9.2. Если исходить из того, что вы работаете на пятисложной строке, то рис. 9.1 являет ся примером того, что произойдет на высоком уровне, если все выбранные слова соответствуют слоговой цели. Программа случайно отбирает начальное слово the из корпуса, а затем подсчитыва ет его слоги. Далее оно выбирает слово bright (яркий) из модели порядка 1, основы ваясь на префиксе the. Затем она подсчитывает число слогов в строке и добавляет это число в число слогов в строке. Поскольку сумма слогов не превышает пяти, программа добавляет bright в строку, переходит к отбору слова autumn (осень) из модели порядка 2 на основе префикса the bright, а затем повторяет процесс подсче та слогов. Наконец, программа отбирает слово moon на основе префикса bright autumn, подсчитывает слоги и — поскольку суммарное число слогов в строке равно пяти — добавляет слово moon в строку, завершая ее.
204 Глава 9
Марковская модель порядка 1
Выбрать слово после одного слова
Марковская модель порядка 2
Марковская модель порядка 2
Выбрать слово после пары слов
Выбрать слово после пары слов
Выбрать слово наугад
Подсчитать слоги
Построить строку хокку
шX I '
- i ifhei
bright j autumnj
moon
„ т
, : Инициировать слово
Инициировать суффикс
■
"
- Г Первый суффикс
t
Второй суффикс
Первая словарная пара
Вторая словарная пара
Рис. 9.1. Высокоуровневый графический псевдокод для пятисложной строки хокку
Марковская модель порядка 1
Выбрать слово после одного слова
Марковская модель порядка 2
Марковская модель порядка 2
Выбрать слово после пары слов
Выбрать слово после пары слов
3
Подсчитать слоги
Построить строку хокку
| temple; gong j glorious
Выбрать слово наугад
i-The; I ‘- т #
Инициировать слово
г Инициировать суффикс
Г
Первый суффикс
I
Второй суффикс
Первая словарная пара
Вторая словарная пара
full white
moon
V 7
Новая словарная пара
Новый суффикс
The
temple gong moon
} окончательная строка хокку
Рис. 9.2. Выбор нового суффикса со случайно отобранным призрачным префиксом (“full white” — иссиня-белый)
Написание стихотворений хокку с помощью анализа марковских цепей 205
На рис. 9.2 представлен случай, когда для успешного завершения пятисложной строки программа должна задействовать призрачный префикс.
Будем считать, что единственным словом, которое следует за префиксом temple gong в марковской модели, является glorious (великолепный, славный). Это слово имеет слишком много слогов для того, чтобы уместиться в строке, поэтому про грамма наугад отбирает призрачный префикс — full white. Слово moon следует за призрачным префиксом и удовлетворяет оставшемуся числу слогов в строке, по этому программа добавляет его в строку. Затем программа отбрасывает префикс full white, и на этом строка завершается. С помощью этого приема префиксации вы не можете гарантировать, что контекстуально новый суффикс будет иметь смысл, но в то же время он является одним из способов включить в процесс творчество.
Переход от одной строки к другой
Марковская модель — это ’’особый соус”, который позволяет наполнить хокку кон текстом и смыслом, продолжающимися от одной строки к другой. Японские поэты обычно писали стихотворения хокку, в котором каждая строка является отдельной фразой, но контекстуальная нить перетекает от строки к строке, как в этом хокку Бон Чо:
In silent midnight Our old scarecrow topples down Weird hollow echo
— Bon Cho
В тихую полночь Старое пугало падает вниз Странное эхо глухое
— Бон Чо
Хотя поэты предпочитали, чтобы каждая строка хокку представляла собой закон ченную мысль, они не следовали этому правилу строго. Вот пример из хокку Бусона:
My two plum trees are So gracious see, they flower One now, one later
— Buson
Мои два сливовых дерева Так милостивы, смотри, они цветут Одно сейчас, другое позже
— Бусон
Первая строка хокку Бусона не является грамматической сама по себе, поэтому чи татель должен перейти к следующей строке, не прерываясь. Когда в стихосложении поэтическая фраза движется от одной строки к другой без паузы или синтаксиче ского разрыва, это называется анжамбеманом (enjambment; от фр. enjamber — пе решагивать, перепрыгивать), или перетеканием. По словам Чарльза Хартмана, ав тора книги ’’Виртуальная муза”, анжамбеман— это то, что придает метрическим строкам значительную часть их гибкой подвижности. И это хорошо, т. к. очень трудно получить алгоритм написания связного стихотворения без некоторого грамматического перетекания от строки к строке. Для того чтобы побудить вашу программу продолжать ’’мысль” по нескольким строкам, вам нужно использовать словарную пару с конца предыдущей строки в качестве начального префикса для текущей строки.
206 Глава 9
Наконец, вы должны дать пользователю возможность не только строить стихотво рение, но и редактировать его в интерактивном режиме, регенерируя вторую и тре тью строки. Написание стихотворений в большинстве своем состоит из переписы вания, и было бы бессовестно оставлять пользователя в зависшем положении с двумя идеальными строками и не давать ему возможности снова бросить кости на несговорчивую строку.
Псевдокод
Если вы следуете стратегии, которую я только что изложил, то ваш высокоуровне вый псевдокод должен выглядеть следующим образом:
Импортировать модуль count_syllables Загрузить текстовый файл тренировочного корпуса Обработать тренировочный корпус на предмет пробелов, разрывов новой строки и т. д. Сопоставить каждое слово в корпусе со следующим словом (марковская модель порядка 1) Сопоставить каждую словарную пару в корпусе со следующим словом (марковская модель порядка 2) Дать пользователю возможность выбрать: генерировать полное хокку, переделать строки 2 или 3, либо выйти из программы Если это первая строка:
Целевое количество слогов = 5 Получить случайное слово из корпуса » import logging
О » > logging.basicConfig(level=logging.DEBUG,
format=’%(levelname)s - % (message)s’)
» > word = ’scarecrow1 » > VOWELS = ‘aeiouy’
num_vowels = 0 » > for letter in word:
if letter in VOWELS:
num_vowels += 1
О logging.debug(‘буква и количество = %s-%s’, letter, num_vowels)
DEBUG - буква и количество = s-0
DEBUG - буква и количество = с-0
DEBUG - буква и количество = а-1
DEBUG - буква и количество = г-1
DEBUG - буква и количество = е-2
DEBUG - буква и количество = с-2
DEBUG - буква и количество = г-2
DEBUG - буква и количество = о-З
DEBUG - буква и количество = w-3
Для использования модуля logging сначала его импортируйте ф . Затем задайте, какую отладочную информацию вы хотите видеть и в каком формате 0 . Уровень отладки d e b u g является самым низким уровнем информации и используется для ди агностики деталей.
Обратите внимание, что в распечатке используется символьное форматирование со спецификатором %s. Существует возможность включить дополнительную инфор мацию, например дата и время выводятся на экран с помощью format=’% (asctime)s’, но в данном фрагменте кода вам лишь нужно проверить, правильно ли программа подсчитывает гласные.
Для каждой оцениваемой буквы введите собственное текстовое сообщение, которое будет печататься вместе со значениями переменных. Обратите внимание, что необ ходимо конвертировать несимвольные объекты, такие как целые числа и списки,
210
Глава 9
в символьные цепочки ф . Далее следует результат работы модуля logging. Вы увидите совокупное количество наряду с тем, какие буквы фактически изменяют количество.
Как и строительные леса, модуль logging предназначен не для пользователя, а для разработчика. И подобно функции print (), журналирование может замедлить рабо ту вашей программы. Для отключения сообщений журналирования просто вставьте вызов logging. disable (logging. c r i t i c a l ) после импорта модуля, как показано ниже:
» > import logging » > logging. disable (logging. CRITICAL)
Инструкция с отключающим вызовом, размещенная вверху программы, позволяет легко ее найти и активировать/деактивировать сообщения. Функция logging, disable () будет подавлять все сообщения на указанном уровне или ниже. По скольку уровень c r i t i c a l является самым высоким, его передача в функцию logging.disable о отключает все сообщения. Такое техническое решение гораздо лучше, чем вручную отыскивать и отключать инструкции print о комментариями!
Код
Программный код markovhaiku.py этого раздела будет принимать тренировочный корпус train.txt, готовить марковские модели в виде словарей и генерировать хокку по одному слову за раз. Программа count syllables.py и файл missing words.json из главы 8 обеспечат, чтобы скрипт markov haiku.py использовал правильное число слогов в каждой строке. Все эти файлы можно скачать с веб-сайта https:// www.nostarch.com/impracticalpython/ (папка главы 9). Обязательно сохраните их вместе в одном каталоге.
Настройка
Листинг 9.1 импортирует необходимые модули, затем загружает и подготавливает внешние файлы.
Листинг 9.1. Импортирует, загружает и готовит тренировочный корпус. Файл m arkov_haiku.py, часть 1
О import sys
import logging
import random
from collections import defaultdict
from count_syllables import count_syllables
ф logging.disable(logging.CRITICAL) # закомментировать для активации
#
отладочных сообщений
logging.basicConfig(level=logging.DEBUG, format=f% (message)s1)
Написание стихотворений хокку с помощью анализа марковских цепей 211
ф def load_training_file(file):
“““Вернуть текстовый файл в виде символьной цепочки.”””
with open(file) as f:
О raw_haiku = f.read()
return raw_haiku
0 def prep_training(raw_haiku):
“““Загрузить символьную цепочку, удалить символы новой строки,
разбить на слова по пробелам и вернуть список.”””
corpus = raw_haiku.replace(1\n*, 1 ‘).split()
return corpus
Начните с импортов, перечисленных в отдельных строках ф . Модуль logging вам понадобится для получения отладочных сообщений, а контейнерный тип defauitdict поможет построить словарь из списка, автоматически создавая новый ключ без выдачи ошибки. Вы также импортируете функцию count syllables из программы count syllables.py, которую вы написали в главе 8. С остальными им- портами вы должны быть уже знакомы.
Поместите инструкцию, отключающую журналирование, сразу после импорта, бла годаря чему вы сможете ее легко найти. Для того чтобы увидеть журнальные сооб щения, необходимо закомментировать эту инструкцию ф. Следующая инструкция конфигурирует то, что вы увидите, как это было описано в предыдущем разделе. Я решил убрать с экрана обозначение уровня.
Затем определите функцию загрузки текстового файла тренировочного корпуса ф. Используйте встроенную функцию reado, которая будет читать данные в виде символьной цепочки, подготовленную программой перед конвертацией в спи сок ф. Верните эту цепочку для использования в следующей функции.
Функция prep training о ф в качестве аргумента принимает выход функции load training file о . Затем она заменяет символы новой строки пробелами, разби вает текст на слова по пробелам и конвертирует слова в элементы списка. В конце указанная функция возвращает корпус в виде списка.
Построение марковских моделей
Марковские модели — это просто словари Python, которые в качестве ключа ис пользуют слово или пару слов и в качестве значения слово, которое следует сразу за ними. Статистическая частота замыкающих слов улавливается повторением замыкающего слова в списке значений — подобно множествам, словари не могут иметь повторяющихся ключей, но они могут иметь повторяющиеся значения.
В листинге 9.2 определены две функции. Обе функции в качестве аргумента при нимают корпус и возвращают марковскую модель.
212
Глава 9
Листинг 9.2. Определяет функции, которые строят марковские модели порядков 1 и 2. Файл markov_haiku.py, часть 2
О def map_word_to_word(corpus):
“““Загрузить список и применить словарь для увязки слова
с последующим словом.”””
0 limit = len(corpus) - 1 0 dictl_to_l = defaultdict(list)
О for index, word in enumerate(corpus):
if index 4:
random_word(corpus)
else: О logging.debug(“случайное слово и слоги = %s %s\n”, word,
num_syls)
return (word, num_syls)
Определите функцию и передайте ей список corpus О- Затем задайте переменную word И используйте метод choice о модуля random для выбора слова ИЗ corpus О -
Примените функцию count syiiabies о из модуля count syiiabies, которая будет подсчитывать слоги в слове; сохраните их количество в переменной num_syls О Я не поклонник однословных строк в хокку, поэтому не даю функции выбирать слово из более четырех слогов (напомним, что самые короткие строки хокку имеют
214 Глава 9
ПЯТЬ слогов). Если ЭТО происходит, ТО вызывайте ф ун кц и ю random word () рекурсив
но до тех пор, п о ка не получите приемлемое слово О - О бр ати те внимание, что по ум о л чани ю Python им еет м аксим альную гл у б и н у рекур сии, равную 1000 вызовам, но коль скоро вы используете правильный тренировочны й корпус х о к к у , сущ еству
ет мало ш ансов то го , что вы ее превысите до то го, к а к найдете подходящ ее слово.
Если бы это было не та к, то вы м огли бы обратиться к р еш ен ию этого условия поз
ж е , ВЫ ЗЫ Вая фуНКЦИЮ С ПОМОЩЬЮ ЦИКЛа while.
Если слово содержит менее пяти слогов, то примените модуль logging для вывода на экран слова и его количества слогов О; затем верните слово и количество сло гов в виде кортежа.
Применение марковских моделей
Для того чтобы выбрать одно-единственное слове, которое следует за инициирую щим словом, используйте марковскую модель порядка 1. После этого программа должна отбирать все последующие слова с помощью модели порядка 2, которая в качестве ключей использует словарные пары. Листинг 9.4 определяет отдельную функцию для каждого из этих действий.
Листинг 9.4. Две функции для отбора слова с учетом префикса, марковской модели и количества слогов. Файл m arkov_haiku.py, часть 4
О def word_after_single(prefix, suffix_map_l, current_syls/ target_syls):
"”” Вернуть все приемлемые слова в корпусе, которые следуют за одним словом.”””
О accepted_words = []
ф suffixes = suffix_map_l.get(prefix)
О if suffixes != None:
ф for candidate in suffixes:
num_syls = count_syllables(candidate) if current_syls + num_syls target_syls:
continue
elif line_syls + num_syls
0
3
1
0 .2
0 ,1 3
1
0 ,2
1 X 109
7 ,9 X 10е
1 5 ,6 х 10е
** Средняя точка представленных диапазонов.
Для ввода в программу можно использовать оценочные значения из указанной таб лицы, либо те, которые вы найдете в Интернете, либо те, которые вы вычислите сами (в последнем столбце таблицы).
Выбор размеров радиопузыря
Радиоволны, которые не фокусируются в луч для нацеленной передачи, являются случайными. Думайте об этом как о “планетной утечке”. Поскольку мы решили не сообщать о нашем присутствии инопланетянам, которые могут прийти и нас
Мы одни? Разведывание парадокса Ферми 233
съесть, почти все наши радиопередачи являются случайными. Эти передачи обра зуют расширяющуюся сферу вокруг Земли, в настоящее время диаметром около 225 световых лет.
Пузырь в 225 световых лет звучит впечатляюще, но на самом деле имеет совер шенно конкретный размер. Фронт радиоволн подчиняется закону обратных квадра тов, т. е. при расширении он непрерывно теряет плотность мощности. Дополни тельные потери мощности могут быть вызваны поглощением или рассеянием. В какой-то момент сигнал ослабляется настолько, что становится неотделимым от фонового шума. Даже с помощью нашей лучшей технологии — радиотелескопов программы прорывного прослушивания Breakthrough Listen1 — мы смогли обнару жить, что наш собственный радиопузырь простирается только примерно на 16 световых лет.
Поскольку мы по-настоящему расследуем причину, почему мы не обнаружили инопланетян, то в целях этого проекта вы должны принять допущение о том, что у других цивилизаций есть технология, подобная нашей. Еще одно допущение должно заключаться в том, что, как и мы, все инопланетяне имеют параноидальное планетарное сознание и не передают сигналы типа ма вот и мы”, которые объявили бы об их присутствии. Разведывание размеров случайных пузырей, варьирующихся от немного меньших, чем те, которые в настоящее время обнаруживаются, до тех, которые не намного больше, чем наши собственные радиопередачи, должно быть разумным местом для начала. Это предполагает диаметрический диапазон от 30 до 250 световых лет. Хотя мы не можем обнаружить пузырь в 250 световых лет, будет интересно посмотреть, каковы были бы шансы, если бы мы смогли.
Генерирование формулы вероятности обнаружения
По мере увеличения числа развитых цивилизаций в галактике возрастает и вероят ность того, что одна из них обнаружит другую. Это интуитивно понятно, но как назначить фактические вероятности?
Самое приятное в компьютерах то, что они позволяют нам пробиваться к решени ям, которые бывают интуитивными, а бывают и осознанными. Одним из подходов здесь было бы сделать трехмерную модель диска Млечного Пути, случайно рас пределить цивилизации повсюду и измерить расстояния между ними, используя один из многих инструментов Python для вычисления евклидова расстояния. Но с учетом потенциально сотен миллионов анализируемых цивилизаций этот метод был бы вычислительно дорогостоящим.
1 Breakthrough Listen — проект Юрия Мильнера по поиску разумной внеземной жизни во Вселенной, рассчитанный на 10 лет и с проектным бюджетом 100 млн долларов. Является составной частью про екта Мильнера под названием “Прорывные инициативы” (Breakthrough Initiatives) и был анонсирован одновременно с проектом Breakthrough Message. См. https://ru.wikipedia.org/wiki/Breakthrough_Listen. — Прим. перев.
234 Глава 10
Поскольку мы имеем дело с огромными неизвестными, нет необходимости в сверх точности или прецизионности. Мы просто хотим держаться в поле зрения, т. е. быть более или менее точными, поэтому простое упрощение состоит в том, чтобы структурировать галактику на ряд ’’эквивалентных” по радиопузырям объемов, раз делив объем галактического диска на объем радиопузыря (рис. 10.3).
Площадь галактического диска = 7,85 * 109 световых лет
Куб эквивалентного размера
Радиопузырь
Высота галактического диска = 1000 световых лет
200 св. лет 4,19 М св. лет
161 св. лет 4,17 М св. лет
Вид в поперечном разрезе (вертикально преувеличено) (без соблюдения шкалы)
Вид в плане (ячейки без соблюдения шкалы)
Р и с. 1 0 .3. М оделирование галактики с использованием кубов с объемами, эквивалентными 200-светогодичному радиопузырю
Объемы можно найти, используя следующие уравнения:
объем диска = n x R 2 х высота диска ;
объем радипузыря = у х к х г3;
объем диска шкалированный объем диска = ————————- , объем радиопузыря
w
где R — радиус галактического диска; г — радиус радиопузыря.
Шкалированный объем диска — это число эквивалентных объемов, которые “впи сываются” в галактику. Думайте о них как о рамках, пронумерованных от 1 до мак симального числа объемов.
Размещая цивилизации, вы просто выбираете номер рамки наугад. Дублирующие выборки указывают на несколько цивилизаций в одной рамке. Допустим, что циви лизации в одной рамке способны друг друга обнаружить. Это не совсем верно (рис. 10.4), но поскольку вы будете использовать большое число цивилизаций, то расхождения будут тяготеть к обнулению друг друга, как при суммировании длин ного ряда округленных чисел.
Во избежание необходимости повторять это упражнение всякий раз, когда вы изменяете число цивилизаций и/или размеры радиопузырей, результаты можно за-
Мы одни? Разведывание парадокса Ферми 235
__ _ _ Радиопузырь
Эквивалентный объем 2
___ I
Эквивалентный объе1и 3
Цивилизации (+), которые должны быть обнаружены, но не обнаружены
Цивилизации (+), которые не должны быть обнаружены, но обнаружены
Рис. 10.4. Проблемы обнаружения на уровне индивидуального эквивалентного объема
фиксировать в виде формулы — многочленного уравнения, которое можно исполь зовать для генерирования всех будущих оценок вероятности. Многочлен— это сумма или разность множества алгебраических членов. Всем известное квадратное уравнение, которое мы все изучали в школе, является многочленным уравнением второй степени (т. е. показатели степени переменных не превышают 2):
ах2 + Ьх + с = 0. Многочлены порождают хорошие кривые, поэтому они идеально подходят к этой задаче. Но для того чтобы указанная формула работала с переменным числом ци вилизаций и размерами пузырей, вам нужно будет использовать отношение числа цивилизаций к суммарному объему. Суммарный объем представлен шкалирован ным объемом диска, который совпадает с суммарным числом эквивалентных объ емов. На рис. 10.5 каждая точка представляет вероятность обнаружения для отношения ниже ее. Показанное на рисунке уравнение является многочленным выражением,
Вероятность обнаружения
Отношение цивилизаций к шкалированному галактическому объему
Рис. 10.5. Вероятность обнаружения в зависимости от отношения числа цивилизаций к шкалированному объему галактики
236
Глава 10
которое генерирует линию, соединяющую точки. С помощью этой формулы можно предсказать вероятность любого отношения цивилизаций в расчете на объем, вплоть до значения 5 (выше этого мы просто будем считать, что вероятность рав на 1,0).
На рис. 10.5 отношения цивилизация к объему разнесены по оси х. Отношение 0,5, например, означает, что существует в два раза меньше цивилизаций, чем имеется эквивалентных объемов радиопузырей, отношение 2 означает, что существует в два раза больше цивилизаций, чем объемов, и т. д. Ось у — это вероятность того, что эквивалентный объем содержит более одной цивилизации.
На рис. 10.5 следует отметить еще одну вещь — для обеспечения того, чтобы у всех цивилизаций был сосед по комнате, требуется много цивилизаций. Представьте, что 999 999 из 1 000 000 эквивалентных объемов содержат по крайней мере две ци вилизации и вы задействуете свои богоподобные силы, случайно размещая одну новую цивилизацию. Существует шанс один на миллион, что эта новая цивилиза ция окажется в оставшемся объеме с одним обитателем. Этот последний эквива лентный объем является пресловутой иголкой в стоге сена!
ПРИМЕЧАНИЕ
Аксиома компьютерного моделирования состоит в том, чтобы начинать с простого и постепенно добавлять сложность. Самое простое “базовое” допущение состоит в том, что развитые цивилизации случайно распределены по всей галактике. В разд. “Слож ные проекты” далее в этой главе вы получите возможность оспорить это допущение, используя концепцию галактических обитаемых зон.
Код вероятности обнаружения
Программный код вероятности обнаружения случайно выбирает местоположения (эквивалентные объемы радиопузырей) для заданного числа местоположений и ци вилизаций, подсчитывает, сколько местоположений встречается только один раз (т. е. содержит только одну цивилизацию), и повторяет эксперимент несколько раз, сходясь на оценке вероятности. Затем этот процесс повторяется для нового числа цивилизаций. Результат представлен в виде вероятности против отношения циви лизаций на объем вместо фактического числа цивилизаций и превратился в много членное выражение, вследствие чего результаты легко переносятся. А это означает, что указанную программу требуется выполнить всего один раз.
Для генерирования многочленного уравнения и проверки того, что оно вписывается В данные, В Ы будете использовать библиотеки NumPy И matplotlib. Библиотека NumPy привносит поддержку больших многоразмерных массивов и матриц и включает больше число математических функций, которые с ними работают. Библиотека matplotlib поддерживает двухмерное и рудиментарное трехмерное графопострое ние, a NumPy обеспечивает ей численное математическое расширение.
Существует несколько способов установки этих научных дистрибутивов Python. Один из способов — использовать sciPy, библиотеку Python с открытым исходным кодом, применяемую для научных и технических вычислений (см. h ttp s ://s c ip y .o rg /
Мы одни? Разведывание парадокса Ферми 237
in d e x .h t m l) . Если вы намерены много заниматься анализом данных и графопо строением, то можете скачать и использовать бесплатный пакет, такой как Anaconda или Enthought Canopy, который работает с Windows, Linux и macOS. Эти пакеты избавят вас от задачи поиска и установки всех необходимых библиотек для науки о данных в нужной версии. Список такого рода пакетов, а также ссылки на их веб-сайты можно найти по адресу h t t p s : / / s c i p y .o r g / i n s t a l l .h t m l . Как вариант можно скачать эти продукты напрямую с помощью менеджера пакетов pip. Я сделал это, используя инструкции по адресу h t t p s : / / s c i p y .o r g / i n s t a l l .h t m l . Поскольку библиотека matplotlib требует большого числа модулей, от которых она зависит, они должны быть установлены одновременно. В Windows я выполнил сле дующую специфичную для Python 3 команду из интерактивной оболочки PowerShell, запущенную из моей папки Python35 (тройку в python3 можно убрать, если у вас не установлено несколько версий):
$ python3 -m pip install — user numpy scipy matplotlib ipython jupyter pandas sympy nose
Все остальные модули, которые вам понадобятся, поставляются в комплекте с Python. Что касается программного кода в листингах 10.1 и 10.2, то вы можете набрать его самостоятельно либо скачать его копию с веб-сайта книги h t t p s : / /w w w .n o s t a r c h .c o m / im p r a c t ic a l p y t h o n /.
Вычисление вероятности обнаружения для диапазона цивилизаций
Листинг 10.1 импортирует модули и выполняет всю только что описанную работу, за исключением вписывания многочлена и изображения результатов проверки качества на основе библиотеки matplotlib.
Листинг 10.1. Импортирует модули, случайно выбирает местоположения радиопузырей, эквивалентных по объему, и вычисляет вероятность наличия нескольких цивилизаций в каждом местоположении. Файл probability_of_detection.py, часть 1
О from random import randint
from collections import Counter
import numpy as np
import matplotlib.pyplot as pit
О NUM_EQUIV_VOLUMES = 1000
число местоположений, #
MAX_CIVS = 5000 TRIALS = 1000
в которые помещать цивилизации #
максимальное число развитых цивилизаций #
число моделирований заданного #
числа цивилизаций #
CIV STEP SIZE = 100
размер шага подсчета цивилизаций #
X = [] #
У = П
значения х для вписывания многочлена #
значения у для вписывания многочлена #
Глава 10
238 О for num_civs in range(2, MAX_CIVS + 2, CIV_STEP_SIZE): civs_per_vol = num_civs / NUM_EQUIV_VOLUMES
num_single_civs = 0
О for trial in range(TRIALS):
locations = [] # эквивалентные объемы, содержащие цивилизацию
О while len(locations) from collections import Counter » > alist = [124, 452, 838, 124, 301] » > count = Counter (alist) » > count
Counter({124: 2, 452: 1, 301: 1, 838: 1}) » > value_count = Counter (count, values ())
value_count Counter({1: 3, 2: 1}) value_count[1]
3
Список alist содержит пять чисел, причем одно из них (124) дублируется. При вы полнении контейнерного типа counter на этом списке создается словарь с числами в качестве ключей и количеством их появлений в качестве значений. Передачей значения контейнерного типа counter в переменную count— с помощью метода values () — создается еще один словарь с предыдущими значениями в качестве ключей и количеством их появлений в качестве новых значений. Вы хотите знать, сколько чисел встречается только один раз, поэтому для возврата количества не упорядоченных чисел используйте метод словаря value count [1]. Они, разумеется, будут представлять объемы эквивалентных радиопузырей, содержащих одну- единственную цивилизацию.
Теперь примените результаты, полученные от контейнерного типа counter, для вы числения вероятности нескольких цивилизаций в расчете на местоположение для текущего числа распределяемых цивилизаций 0. Это 1 минус число одноместных расположений, деленное на число цивилизаций в каждом испытании, умноженное на число испытаний.
Проследите за этим вычислением, печатая это соотношение цивилизаций к объему и вероятность того, что несколько цивилизаций имеют общее местоположение 0. Первые несколько строк этого вычисления выглядят следующим образом:
0 . 0 0 2 0 0 . 0 0 2 0 0.1020 0.0970
0.2020 0.1832
240
Глава 10
0.3020 0.2607
0.4020 0.3305
0.5020 0.3951
0.6020 0.4516
0.7020 0.5041
Данная распечатка служит в качестве первоначального шага контроля качества и является необязательной; в случае, если вы хотите ускорить время выполнения, то ее следует закомментировать. Завершите программу, добавив значения в списки х И у ф .
Генерирование предсказательной формулы и проверка результатов
В листинге 10.2 применяется библиотека NumPy для выполнения многочленной рег рессии вероятности обнаружения против соотношения цивилизаций на объем, рас считанного в листинге 10.1. Это многочленное уравнение будет использоваться в следующей ниже программе получения оценочных значений вероятности. Для проверки того, что результирующая кривая вписывается в точки данных, библиоте ка matplotlib строит график фактических и предсказываемых значений.
Листинг 10.2. Выполняет многочленную регрессию и строит график проверки ее качества. Файл probability_of_detection.py, часть 2
О coefficients = np.polyfit(х, у, 4) # вписывание многочлена 4-й степени
ф р = пр.polyld(coefficients)
print("\п{}".format(р))
О хр = пр.linspace(0, 5)
О _ = pit.plot(х, у,
хр, р(хр), ‘-’)
0 pit.ylim(-0.5, 1.5)
ф pit.show()
Начните с назначения переменной coefficients результата метода poiyfito биб лиотеки NumPy ф . Указанный метод в качестве аргументов принимает списки х и у, а также целое число, представляющее степень подгонки многочлена. Он возвраща ет вектор коэффициентов р, который минимизирует квадратическую ошибку.
Если вы напечатаете переменную coefficients, то на выходе получите следующее:
[-0.00475677
0.066811 -0.3605069
0.92146096
0.0082604 ]
Для получения полного выражения передайте переменную coefficients в метод polyld о и назначьте результаты новой переменной ф . Напечатайте эту перемен ную, и вы увидите уравнение, аналогичное показанному на рис. 10.5:
4
3
2
-0.004757 х + 0.06681 х - 0.3605 х + 0.9215 х + 0.00826
Мы одни? Разведывание парадокса Ферми 241
С целью проверки адекватности воспроизведения многочленом входных данных вы захотите построить взаимосвязь отношения цивилизаций к объему на оси х от веро ятности на оси у. Для получения значений по оси х можно воспользоваться методом linspace () библиотеки NumPy, который возвращает числа, равномерно расположен ные через заданный интервал 0 . Используйте диапазон (0, 5), благодаря чему бу дет покрыт почти весь вероятностный диапазон.
Для того чтобы разнести символы, соответствующие вычисленным и предсказан ным значениям, сначала передайте методу plot о списки х и у, выводя их на гра фик с помощью точки ( ‘. ‘)> которая эквивалентна точкам на рис. 10.5 О- Затем передайте значения, предсказанные для оси х (хр), и для получения вероятности, предсказанной для оси у , передайте функции р ту же самую переменную, выведя результаты на график с помощью тире (’-’)•
Закончите программу, ограничив ось» значениями -0.5 и 1.5 0 и применив метод show (), который фактически покажет график на экране (рис. 10.6) 0 . Результи рующий график является простым и разреженным, поскольку его единственная цель — подтвердить, что многочленная регрессия работает по назначению. Много членную подгонку можно изменить, увеличив или уменьшив третий аргумент в шаге 0.
Р ис. 10.6. Вычисленные результаты (точки) против результатов, предсказанных многочленом (линия)
Вооружившись этими результатами, вы теперь способны в мгновение ока оценить вероятность обнаружения для любого числа цивилизаций. Языку Python нужно только решить многочленное уравнение.
Построение графической модели
Графическая модель будет представлять собой двухмерный вид галактического диска сверху вниз. Нанесение размера нынешнего эмиссионного пузыря Земли на этом изображении позволит увидеть в перспективе как размер галактики, так и наше крошечное место в ней.
242
Глава 10
Моделирование Млечного Пути всецело касается моделирования спиральных рука вов. Каждый спиральный рукав представляет собой логарифмическую спираль — геометрическую фигуру, настолько распространенную в природе, что ее окрестили spira mirabilis— “чудесной спиралью”. Если вы сравните рис. 10.7 с рис. 10.1, то сможете увидеть, насколько близко структура урагана напоминает структуру галак тики. Глаз урагана можно даже представить, как сверхмассивную черную дыру, где облачный вихрь вокруг ядра (“глаз бури”) представляет горизонт событий!
Р и с. 10.7. Ураган Игорь
Поскольку спирали расходятся от центральной точки, или полюса, построить их график будет легче с помощью полярных координат (рис. 10.8).
(О, у)
Р ис. 10.8. Пример системы полярных координат
При переходе к полярным координатам координаты (х, у ), используемые в более знакомой декартовой системе координат, заменяются парами (г, 0), где г — это расстояние от центра, а 0 — угол, образованный радиусом г и осью х. Координаты полюса равны (0, 0).
Мы одни? Разведывание парадокса Ферми 243
Полярное уравнение логарифмической спирали имеет вид:
,
не г - а е где г — расстояние от начала координат; 0 — угол от оси х\ е — основание нату рального логарифма; а и b — произвольные константы. Указанную формулу можно использовать для начертания одиночной спирали; за тем повернуть и перечертить спираль три раза, получив четыре рукава Млечного Пути. В результате получатся спирали из кругов разных размеров, которые будут представлять звезды. На рис. 10.9 приведен пример одной такой имплементации графической модели. Поскольку симуляции являются стохастическими, то каждая из них будет немного отличаться, причем в модели имеется много переменных, ко торые можно отрегулировать, изменив ее внешний вид.
Р и с. 10.9. Млечный Путь, смоделированный с использованием логариф мических спиралей
Я сгенерировал изображение на рис. 10.9 с помощью tkinter (произносится “ти- кэйинтер”) — библиотеки графического интерфейса, используемой по умолчанию, для разработки настольных приложений на Python. Хотя tkinter в основном пред назначена для графических элементов, таких как окна, кнопки, полосы прокрутки и т. д., она также может создавать графики, диаграммы, заставки, простые игры и многое другое. Одним из ее преимуществ является то, что в рамках стандартного дистрибутива Python она переносится во все операционные системы, и нет необхо димости устанавливать внешние библиотеки. Она также хорошо документирована и проста в использовании. Большинство машин на основе Windows, macOS и Linux поставляются с предуста новленной библиотекой tkinter. Если у вас ее нет или вам нужна ее последняя вер сия, то ее можно скачать и установить с https://www.activestate.com/. Как всегда, если модуль уже установлен, то его можно без ошибок импортировать в окно ин терпретатора:
244
Глава 10
» >
i m p o r t t k i n t e r
Книги с вводными курсами по Python иногда включают обзоры библиотеки tkinter, а официальную онлайновую документацию можно найти по адресу https://d0cs.pyth0n.0rg/3/library/tk.html. Несколько других ссылок на tkinter включены в разд. “Дальнейшее чтение " далее в этой главе.
Шкалирование графической модели
Шкала графической модели находится в световых годах на пиксел, а ширина каж дого пиксела будет равна диаметру радиопузыря. В связи с этим, когда исследуе мый радиопузырь меняет диаметр, меняются и единицы шкалы, и графическую модель необходимо будет перестроить. Следующее ниже уравнение шкалирует мо дель в пузырь:
радиус диска шкалированный радиус диска = —————————- , диаметр радиопузыря
где радиус диска равен 50 ООО, а единица длины — это световые годы.
Когда отобранный радиопузырь мал, графическая модель “увеличивается”, а когда он велик, то она “уменьшается” (рис. 10.10).
| / ГапахтнсаМгечхкоПуги
D
к |
/Га лает пса Млечного Пути
Р и с. 10.10. Влияние д иам етра радиопузыря на внешний вид галактической модели
Мы одни? Разведывание парадокса Ферми 245
Код симулятора галактики
Программный код симулятора галактики будет вычислять вероятность обнаруже ния для любого числа цивилизаций и размеров радиопузырей, а затем сгенерирует графическую галактическую модель. Когда используется пузырь размером с наш текущий эмиссионный пузырь, он разместит и проаннотирует наш пузырь красным цветом в приблизительном местоположении нашей Солнечной системы. Указан ный код можно скачать код с веб-сайта https://www.nostarch.com/impracticaIpython/.
Ввод данных и ключевых параметров
Листинг 10.3 начинает программу galaxy simulator.py с импорта модулей и разме щения вверху часто запрашиваемой информации о вводимых пользователем значе ниях.
Листинг 10.3. Импортирует модули и задает константы. Файл galaxy_simulator.py, часть 1
О import tkinter as tk
from random import randint, uniform, random
import math #===================================================================
О # ГЛАВНЫЕ ВХОДНЫЕ ДАННЫЕ
шкала (диаметра радиопузыря) в световых годах: #
О SCALE = 2 2 5 # ввести 225, чтобы увидеть радиопузырь Земли
число развитых цивилизаций из уравнения Дрейка: #
О NUM_CIVS = 15600000
#===================================================================
Для того чтобы не набирать полное имя модуля tkinter при вызове его классов, импортируйте указанный модуль как tk О - Если вы работаете с Python 2, то ис пользуйте Tkinter — С ПрОПИСНОЙ Т. Вам также понадобятся модули random И math.
Используйте комментарий, для того чтобы выделить главный раздел вводимых пользователем данных 0 , и задайте два входных значения. Задайте: scale д л я диа метра обнаруживаемого электромагнитного пузыря вокруг каждой цивилизации в световых годах 0; num civs — для числа моделируемых цивилизаций, которое можно задать, используя все что угодно, начиная с уравнения Дрейка до чистой догадки ф .
Настройка холста tkinter и задание значений константам
Программный код в листинге 10.4 создает экземпляр объекта окна tkinter с хол стом, на котором можно рисовать объекты. Здесь появится карта галактики, или
246
Глава 10
графическая модель. Он также задает значения констант, связанных с размерами Млечного Пути.
Листинг 1 0 .4 . Настраивает окно и холст модуля t k i n t e r и задает константы. Файл galaxy_simulator.py, часть 2
|
настроить изображение холста #
О root = tk.Tk()
root.title(“Галактика Млечного Пути”)
0 с = tk.Canvas(root, width=1000/ height=800, bg=‘blackf) О c.grid()
О с .configure(scrollregion=(-500, -400, 500, 400))
фактические размеры Млечного Пути (св. годы) #
0 DISC_RADIUS = 50000 DISC_HEIGHT = 1000
О DISC_VOL = math.pi * DISC_RADIUS**2 * DISC_HEIGHT
Начните с создания окна с обычным именем root (корневое) О- Оно является ок ном верхнего уровня, которое будет содержать все остальное. В следующей строке кода назначьте окну заголовок— “Галактика Млечного Пути”, КОТОрыЙ ПОЯВИТСЯ в левом верхнем углу рамки окна (рис. 10.9 с примером).
Далее в корневое окно добавьте компонент графического интерфейса, именуемый виджетом. Английское слово widget расшифровывается как “Windows gadget” (графический компонент в Windows). В tkinter существует 21 стержневой виджет, включая надписи, рамки, переключатели и полосы прокрутки. Задайте виджет хол ста canvas, который будет содержать все рисуемые объекты 0 . Указанный виджет является универсальным и предназначен для графики и других сложных макетов. Укажите родительское окно, ширину и высоту экрана, а также цвет фона. Задайте имя холста как с (от англ. canvas).
Виджет canvas можно разделить на строки и столбцы, как обычную или электрон ную таблицу. Каждая ячейка в этой решетке может содержать другой виджет, и эти виджеты могут охватывать несколько ячеек. Внутри ячейки виджет можно выров нять с помощью параметра прилипания sticky. Для управления каждым виджетом в окне необходимо использовать диспетчер геометрии grid. Поскольку в этом про екте вы используете только один виджет, то в менеджер передавать ничего не по требуется 0 . Завершите конфигурирование холста canvas, настроив использование области про крутки scroiiregion 0 . В результате начало координат (0, 0) будет установлено в центр холста. Это нужно для того, чтобы нарисовать спиральные рукава галакти ки с помощью полярных координат. Без этого по умолчанию началом координат будет левый верхний угол холста canvas.
Аргументы, переданные в configure, задают пределы холста canvas. Они должны составлять половину ширины и высоты холста; например, пределы прокрутки 600,
Мы одни? Разведывание парадокса Ферми 247
500 потребуют размеров холста 1200, юоо. Показанные здесь значения хорошо ра ботают на небольшом ноутбуке, но не стесняйтесь их позже поменять, если вам требуется более крупное окно. После раздела входных данных следуют размерные константы галактики 0 . Неко торые из этих переменных можно задать в функциях, но наличие их в глобальном пространстве делает объяснение кода логичнее. Первые две — это радиус и высота галактического диска из рис. 10.2. Последняя константа представляет собой объем диска 0.
Шкалирование галактики и вычисление вероятности обнаружения
В листинге 10.5 определены функции шкалирования размеров галактик (на основе диаметра используемого радиопузыря) и вычисления вероятности обнаружения одной цивилизацией другой. Последняя функция — это то место, где вы применяе те многочленное уравнение, построенное с помощью описанной ранее программы probabilityofdetection.py.
Листинг 10.5. Ш калирование размеров галактики и вы числение вероятности обнаружения. Файл galaxy_simulator.py, часть 3
0 def scale_galaxy():
"”” Шкалирование размеров галактики на основе
размера радиопузыря (scale) .
disc_radius_scaled = round (DISC_RADIUS / SCALE)
0 bubble_vol = 4/3 * math.pi * (SCALE / 2)**3 0 disc_vol_scaled = DISC_VOL/bubble_vol 0 return disc_radius_scaled, disc_vol_scaled
0 def detect_prob(disc_vol_scaled):
“““Вычислить вероятность, что галактические цивилизации
обнаружат друг друга.”””
#
отношение цивилизаций к шкалированному объему галактики
0 ratio = NUM_CIVS / disc_vol_scaled 0 if ratio = 5:
detection_prob = 1
0 else:
установить верхние соотношения #
равными единичной вероятности #
detection_prob = -0.004757 * ratio**4 + \
0.06681 * ratio3 - 0.3605 *
ratio2 + 0.9215 * ratio + 0.00826
0 return round(detection_prob, 3)
Определите функцию scale galaxy о , которая будет шкалировать размеры галакти ки в размер радиопузыря 0 . Она будет использовать константы из глобального
248
Глава 10
пространства, поэтому передавать ей какие-либо аргументы не потребуется. Вы числите шкалированный радиус диска, а затем объем радиопузыря, используя уравнение объема сферы, и назначьте результаты переменной bubble voi 0. Затем разделите фактический объем диска на bubble voi, получив прошкалирован- ный объем диска 0 . Это число показывает количество ’’эквивалентных” по радио пузырям объемов, которые могут поместиться в галактике. Каждый пузырь пред ставляет возможное местоположение цивилизации.
Завершите работу функции, вернув переменные disc radius scaled И disc voi scaled 0. Теперь определите функцию detect probo, которая будет вычислять вероятность обнаружения. В качестве аргумента она принимает прошкалированный объем дис ка 0 . Для члена х в многочлене вычислите отношение числа цивилизаций к прош- калированному объему диска 0 . Поскольку многочленная регрессия в конечных точках может иметь проблемы, примените условные выражения, которые установят очень малые соотношения равными 0 и большие соотношения равными 1 0. В противном случае примените многочленное выражение, сгенерированное кодом probability of detection.py 0, затем верните вероятность, округленную до трех зна ков после точки 0.
Использование полярных координат
Листинг 10.6 определяет функцию отбора случайных местоположений (jc, у) с ис пользованием полярных координат. Указанная функция будет выбирать место положение нескольких звезд, размещенных в графической модели. Поскольку изо бражение является двухмерным, отсутствует необходимость выбирать местополо жение координаты z.
Листинг 10.6. Определяет функцию случайного подбора пары (х, у) с помощью полярных координат. Файл galaxy_simulator.py, часть 4
0 def random_polar_coordinates(disc_radius_scaled): “““Сгенерировать случайную (х, у) внутри диска
из равномерного распределения для 2-мерного изображения.”””
0 г = random () 0 theta = uniform(0, 2 * math.pi) 0 x = round(math.sqrt(г) * math.cos(theta) * disc_radius_scaled) у = round(math.sqrt(r) * math.sin(theta) * disc_radius_scaled)
0 return x, у
Указанная функция в качестве аргумента принимает прошкалированный радиус диска 0 . Используйте функцию random о для выбора вещественного значения в диапазоне от 0,0 до 1,0 и назначьте его переменной г 0 . Далее, из равномерного распределения случайно выберите theta между 0 и 360 градусами (2п — это ради- анный эквивалент 360°) 0 .
Преобразование, равномерно генерирующее точки на единичном диске, выглядит следующим образом:
Мы одни? Разведывание парадокса Ферми 249
х = vr*cos0 ;
y = \Jr * sin 0 .
Указанные уравнения дают значения (jc, у) в диапазоне от 0 до 1. Для шкалирования результатов в галактический диск умножьте на прошкалированный радиус диска Q- Завершите функцию, вернув х и у 0 .
Построение спиральных рукавов
Листинг 10.7 определяет функцию, которая строит спиральные рукава, используя уравнение логарифмической спирали. Данная спираль может показаться волшеб ной, но большая часть волшебства крутится вокруг первоначальной голой спирали, облекая рукав плотью. Вы достигнете этого, варьируя размер звезд, случайно меняя их местоположение на крошечное число и дублируя спираль для каждого рукава, немного сдвигая ее назад и приглушая ее звезды.
Листинг 10.7. Определяет функцию spirals (). Файл galaxy_simulator.py, часть 5
0 def spirals(b, г, rot_fac, fuz_fac, arm):
“““Построить спиральные рукава для изображения в tkinter,
используя формулу логарифмической спирали.
b - произвольная константа в уравнении логарифмической спирали г - радиус прошкалированного галактического диска rot_fac - коэффициент поворота fuz_fac - случайный сдвиг в позиции звезды в рукаве, применительно к переменной ‘fuzz’
arm - рукав спирали (0 - главный рукав, 1 - замыкающие звезды)
II II II
0 spiral_stars = [] 0 fuzz = int(0.030 * abs(r)) # случайно сдвинуть местоположения звезд
theta_max_degrees = 520
О for i in range(theta_max_degrees):
range(0, 600, 2) не для черной #
дыры #
theta = math.radians(i) x = r * math.exp(b * theta) * math.cos(theta + \
math.pi * rot_fac) + randint(-fuzz, fuzz) * fuz_fac у = r * math.exp(b * theta) * math.sin(theta + math.pi * \
rot_fac) + randint(-fuzz, fuzz) * fuz_fac
spiral_stars.append((x, y))
0 for x, у in spiral_stars:
0 if a m == 0 and int (x % 2) == 0:
c.create_oval(x-2, y-2, x+2, y+2, fili=‘white1, outline=ff)
elif arm == 0 and int(x % 2) != 0:
c.create_oval(x-1, y-1, x+1, y+1, fill=‘white1, outline=fl)
250
Глава 10
Q elif arm == 1:
с .create_oval(x, у, x, у, fill=‘white1, outline=’*)
Определите функцию spirals о О- Ее параметры перечислены в литерале доку ментирования функции. Первые два параметра, ь и г, принадлежат уравнению ло гарифмической спирали. Следующий, rot fac, является коэффициентом поворота, который позволяет перемещать спираль вокруг центральной точки для обеспечения возможности создания нового спирального рукава. Коэффициент напыления fuz fac позволяет отрегулировать то, как далеко вы перемещаете звезды от центра спиральной линии. Наконец, параметр arm позволяет указать либо ведущий рукав, либо тянущийся позади рукав из затухающих звезд. Тянущийся позади рукав будет сдвинут, т. е. нанесен немного позади ведущего рукава, и его звезды будут меньше.
Инициализируйте пустой список для хранения местоположения звезд, которые бу дут составлять спираль 0 . Задайте переменную fuzz, где вы умножаете произволь ную константу на абсолютное значение радиуса прошкалированного диска Q . Звезды создаются только спиральным уравнением — они выстраиваются в линию (см. две левые части на рис. 10.11). Напыление будет немного перемещать звезды по спирали вперед и назад, по обе стороны от спиральной линии. Его эффект мож но увидеть на ярких звездах в самой правой панели рис. 10.11. Я определил эти значения методом проб и ошибок; не стесняйтесь поэкспериментировать с ними, если захотите.
Только логарифмическая спираль
Со сдвигом и напылением
Рис. 10.11. Заполнение спиральных рукавов путем сдвига спиралей и случайного изменения местоположения звезд
Теперь самое время построить спиральные линии. Сначала примените диапазон значений, которые будут представлять 0 в уравнении логарифмической спирали Q . Диапазон около 520 произведет галактику, как на рис. Ю.9, которая имеет цент ральную “черную дыру”. В противном случае используйте диапазон ( 0 , 600, 2 ) — или аналогичный— для получения яркого центрального ядра, полностью запол ненного звездами (рис. 10.12). С этими значениями вы можете повозиться до тех пор, пока не получите желаемый результат. Пройдите в цикле по значениям в theta
Мы одни? Разведывание парадокса Ферми 251
и примените уравнение логарифмической спирали, используя косинус для значения х и синус для значения у. Обратите внимание, что к результату добавляется значе ние fuzz, умноженное на коэффициент напыления. Добавьте каждую пару (х, у) В СПИСОК spiral stars.
Рис. 10.12. Граф ическая модель без центральной черной дыры (сравните с рис 10.9)
Позже, в функции main(), вы сможете указать переменную rot fac, которая будет двигать спираль вокруг центра. После того как программа построит четыре основ ных рукава, она применит rot fac для построения четырех новых рукавов, слегка сдвинутых от первых четырех, создавая полосу тусклых, тянущихся за ними звезд, видимых слева от каждой дуги, состоящей из ярких звезд, на рис. 10.11.
Теперь, когда у вас есть список местоположений звезд, начните цикл for по коор динатам (х, у) 0 . Затем примените условную инструкцию для выбора главного, ведущего рукава и местоположений, для которых х является четным 0 . Для этого используйте метод create ovai о виджета canvas, который создает размещаемый на графике звездный объект. Первые четыре аргумента этого метода определяют ограничительную рамку, в которую помещается овал. Чем крупнее число после х и у, тем больше овал. Сделайте заливку белым цветом и не используйте контур; по умолчанию контур изображается тонкой черной линией.
Если значение х является нечетным, то сделайте звезду на шаг меньше. И если зна чение рукава равно 1, то звезда находится в смещенном рукаве, поэтому сделайте ее как можно меньше 0.
ПРИМЕЧАНИЕ Звездные объекты предназначены только для визуального воздействия. Ни их размер, ни количество не подлежит шкалированию. На самом деле они должны иметь гораздо меньший размер и быть намного многочисленнее (более 100 млрд!).
252
Глава 10
Рассеяние звездной дымки
Пространство между спиральными рукавами не лишено звезд, поэтому следующая функция (листинг 10.8) случайно рассеивает точки в галактической модели без уче та спиральных рукавов. Думайте об этом как о свечении, которое вы видите на фотографиях далеких галактик.
Листинг 10.8. Определяет функцию star_haze(). Файл ga1axy_$lmulator.py, часть 6
О def star_haze(disc_radius_scaled/ density):
“““Случайно распределите тусклые звезды в галактическом диске.
disc_radius_scaled = радиус галактического диска,
прошкалированный в плотность радиопузыря
density = множитель для варьирования числа размещенных звезд к и п
ф for i in range(0, disc_radius_scaled * density):
О x, у = random_polar_coordinates(disc_radius_scaled)
О с.create_text(x, y, fill=‘white1, font=(‘Helvetica’, ‘7’),
text=’.’)
Определите функцию star hazeo и передайте ей два аргумента: прошкалирован ный радиус диска и целочисленный множитель, которые функция будет использо вать для увеличения базового числа случайных звезд ф . Поэтому если вместо лег кой дымки вы предпочитаете густой туман, то при вызове указанной функции в main () увеличьте значение плотности.
Начните цикл for, где максимальное значение диапазона равно прошкалированно- му радиусу диска, умноженному на плотность О . Используя значение радиуса, вы шкалируете число звезд в размер изображаемого диска. Затем вызовите функцию random_polar_coordinates () ДЛЯ получения пары (х, у) Q .
Завершите создание изображаемого на холсте объекта с помощью пары (х, у) Q . Поскольку вы уже использовали наименьший размер овала для звезд вдоль и во круг спирали, то вместо create_oval () Примените метод create_text (), с помощью которого можно для представления звезды использовать точку. Параметр размера шрифта позволит вам масштабировать звезды звездной дымки до тех пор, пока вы не найдете нечто эстетически приемлемое.
На рис. 10.13 представлено сравнение галактической модели без звездной дымки (слева) и со звездной дымкой (справа).
С дымкой можно поступить поизобретательнее. Например, вы можете сделать звезды более многочисленными и окрасить их в серый цвет или же применить цикл для изменения их размера и цвета. Только не используйте зеленый цвет, т. к. зеле ных звезд во Вселенной нет!
Мы одни? Разведывание парадокса Ферми 253
( Галагт пса Мючного Пути
м
Р ис. 10.13. Галактические модели без звездной дымки (слева) и со звездной дымкой (справа)
Определение функции mainQ
В листинге 10.9 определена функция maino программы galaxysimulator.py. Она будет делать вызовы функций шкалирования галактики, вычислять вероятность обнаружения, строить изображение галактики и размещать статистику. Она также будет ВЫПОЛНЯТЬ ОСНОВНОЙ ЦИКЛ модуля tkinter.
Листинг 10.9. Определяет и вызывает функцию m a i n ( ) . Файл galaxy_simulator.py, часть 7
def main():
“““Рассчитать вероятность обнаружения и изобразить
галактику и статистику.”””
О disc_radius_scaled/ disc_vol_scaled = scale_galaxy()
detection_prob = detect_prob(disc_vol_scaled)
построить 4 основных спиральных рукава и 4 тянущихся позади рукава #
Q spirals(b=-0.3, r=disc_radius_scaled, rot_fac=2, fuz_fac=1.5/ агтп=0)
spirals(b=-0.3, r=disc_radius_scaled, rot_fac=l.91, fuz_fac=1.5, arm=l) spirals(b=-0.3, r=-disc_radius_scaled, rot_fac=2, fuz_fac=1.5, arm=0)
spirals(b=-0.3, r=-disc_radius_scaled, rot_fac=-2.09, fuz_fac=1.5,
spirals(b=-0.3, r=-disc_radius_scaled, rot_fac=0.5, fuz_fac=1.5, arm=0) spirals (b=-0.3, r=-disc_radius_scaled, rot_fac=0.4, fuz_fac=1.5, am=l)
spirals(b=-0.3, r=-disc_radius_scaled, rot_fac=-0.5, fuz_fac=1.5, arm=0) spirals(b=-0.3, r=-disc_radius_scaled, rot_fac=-0.6, fuz_fac=1.5, arm=l) star_haze(disc_radius_scaled, density=8)
arm-1)
изобразить легенду #
О с.create_text(-455, -360, fill=‘white1, anchor=‘wf,
text=‘OflHH пиксел = {} св. лет 1.format(SCALE))
254 Глава 10
с.create_text(-455, -330, fill=‘white’, anchor=‘wf,
text=fДиаметр радиопузыря = {} св. лет1 .format(SCALE))
с.create_text(-455, -300, fill=1 white 1, anchor=fw f,
text=‘Вероятность обнаружения для {:,} цивилизаций = {}’.
format(NUM_CIVS, detection_prob))
разместить пузырь Земли диаметром 225 св. лет и аннотировать 0 #
О if SCALE == 225:
ф c.create_rectangle(115, 75, 116, 76, fill=‘red’, outline=’’)
с .create_text(118, 72, fill=‘red’, anchor=‘w’,
text=” from random import randint » > trials = 100000 » > success = 0 » > for trial in range(trials):
faces = set() for rolls in range(6):
roll = randint(1, 6) faces.add(roll) if len(faces) = 6: success += 1
» > print (“вероятность успеха = {>11 .format (success/trials))
вероятность успеха = 0.01528
В этом примере используются цикл for и функция randint, которая шесть раз под ряд случайно выбирает число от 1 до 6, представляющее одну из граней кубика. Каждый результат добавляется в множество граней с именем faces, которое не до пускает дубликатов. Длина множества достигнет 6 в единственном случае: когда каждый бросок дает уникальное число, т. е. равняется успешному случаю. Внеш ний цикл for выполняет испытание с шестью бросаниями кубика 100 тыс. раз под ряд. Деление числа успехов на число испытаний дает ту же самую вероят ность 0,015, что и детерминированное выражение.
Симуляция Монте-Карло использует многократный случайный отбор экземпля ров — в этом случае каждое бросание кубика является случайной выборкой экзем пляра — с целью предсказания разных результатов в заданном диапазоне условий. В данном примере диапазоном условий были один шестигранный кубик, шесть бросаний без повторов в расчете на одно испытание и 100 тыс. испытаний.
Конечно же, симуляция Монте-Карло обычно применяется в более сложных зада чах — с большим числом переменных и широким диапазоном неопределенности, где результаты нелегко предсказать.
Существует несколько типов симуляции Монте-Карло, но большинство приложе ний выполняют следующие базовые действия:
♦ перечислить входные переменные;
♦ обеспечить распределение вероятностей для каждой переменной;
264 Глава 11
♦ начать цикл:
• случайно выбрать значение из распределений для каждого входа;
• использовать значения в детерминированном вычислении, которое всегда бу
дет производить одни и те же выходы из одних и тех же входов;
• повторить указанное число раз;
♦ агрегировать результаты и сгенерировать статистику, такую как средний резуль
тат вычисления.
На примере бросания кубика эти шаги были бы следующими:
♦ входные переменные — результаты шести бросаний кубика;
♦ распределение вероятности для броска — равномерное (1/6 для каждой грани);
♦ цикл:
• случайно отобранное значение — бросание кубика (значение, взятое из рас
пределения);
• вычисление — добавить шесть значений в множество, и если длина множест
ва равна 6, то добавить 1 в переменную успешности success;
• повтор — 100 ООО раз;
♦ агрегирование: разделить переменную успешности success на 100 ООО для полу
чения вероятности.
Нассим Талеб (Nassim Taleb), признанный критиками автор книг ’’Черный лебедь” (The Black Swan) и ’’Одураченные случайностью” (Fooled by Randomness), является поклонником симуляции Монте-Карло. Он утверждает, что наш мозг устроен так, чтобы быстро вытаскивать нас из неприятностей, а вовсе не решать сложные зада чи неопределенности или вероятности. Мы не созданы для сильно асимметричных распределений и нелинейностей, но мозг некоторых людей внутренне более спосо бен понимать риски, используя симуляцию Монте-Карло, чем другие методы. В реальной жизни мы не наблюдаем распределения вероятностей, мы просто на блюдаем события.
Каждый прогон симуляции Монте-Карло представляет одно событие, например исчерпались или нет ваши пенсионные накопления. Для многих из нас указанная симуляция делает риск реальным. Она помогает нам понять, насколько плохим или хорошим может все оказаться, т. е. то, что мы не всегда можем извлечь из матема тических абстракций. Вооруженные сущностным пониманием, полученным благо даря симуляции Монте-Карло, мы можем подготовиться и к обороне от неприятно стей, и к эксплуатации преимуществ.
В поддержку математики, лежащей в основе задачи Монти Холла, вы разработаете приложение симуляции Монте-Карло, как в предыдущем примере бросания кубика. Затем, в главе 12, вы задействуете симуляцию Монте-Карло для построения симу лятора пенсионных накоплений с целью планирования вашей (или ваших родите лей) обеспеченной жизни на пенсии.
Задача Монти Холла 265
Проект 18: вериф икация утверждения вое Савант
Для подтверждения правоты вое Савант мы применим подход Монте-Карло и про- симулируем десятки тысяч ’’игр” для того, чтобы увидеть, как все сложится. Эта программа будет минималистской, т. к. цель состоит в простом подтверждении без какого-либо украшательства.
Ц е л ь
Написать простую программу на языке Python, в которой симуляция Монте-Карло ис пользуется с целью определения вероятности выигрыша в задаче Монти Холла путем изменения первоначального варианта выбора.
Стратегия
Правильный ответ к задаче Монти Холла — поменять двери после того, как Монти покажет козу. По статистике, это удвоит ваши шансы на победу!
Посмотрите на рис. 11.1. В начале игры все двери закрыты, и шансы на то, что за данной дверью скрывается приз, равны 1 к 3. Конкурсант может выбрать только одну дверь, имея в виду, что шансы на получение приза за одной из двух других дверей равны 2 к 3. После того как будет показана коза, шансы остаются 2 к 3, но они возвращаются к оставшейся двери. Помните, Монти знает, где спрятан приз, и никогда эту дверь не покажет. Поэтому вероятность успеха составляет 1/3 в слу чае, если вы останетесь при своем первом варианте выбора против 2/3 в случае его изменения.
Выбранный конкурсантом вариант
Раскрытие
Выбранный конкурсантом вариант
Рис. 11.1. Шансы на победу в задаче Монти Холла до и после раскрытия двери, за которой скрывалась коза
Если вы сомневаетесь в математической формуле, то можете применить симуля цию Монте-Карло, которая предоставит подтверждающие доказательства, точно так же, как мы сделали с примером бросания кубика. Просто нужно выбрать выиг
266 Глава 11
рышную дверь наугад, выбрать вариант конкурсанта наугад и зарегистрировать ре зультат, когда оба этих значения совпадают. Если повторить эту процедуру тысячи раз, то результат сойдется на детерминированном математическом решении.
Код верификации утверждения Мэрилин вое Савант
Описанная в этом разделе программа montyhallrncs.py автоматизирует процесс выбора дверей и регистрации результатов, благодаря которой вы сможете выпол нять тысячи испытаний и оценивать их менее чем за секунду. Код программы мож но скачать с веб-сайта https://www.nostarch.com/impracticalpython.com/.
Получение входного числа прогонов
Листинг 11.1 начинает программу monty hall rncs.py с того, что просит пользова теля указать, сколько прогонов— или игр— он хочет симулировать. Вы также предоставите пользователю значение по умолчанию. Это отличный способ напра вить пользователя к разумному первому отклику, а также сэкономить на несколь ких нажатиях клавиш.
Листинг 11.1. Импортирует модули и определяет функцию userjprompt (). Файл m onty_hall_mcs.py, часть 1
О import random
Q def user_prompt(prompt, default=None):
“““Позволить использование значения по умолчанию при вводе.”””
О prompt = ‘{} [{}]: ‘•format(prompt, default)
О response = input(prompt)
О if
response and default:
return default
else:
return response
#
ввести число раз, которое симуляция будет выполняться
О num_runs = int(user_prompt(“Введите число прогонов”, “20000”))
Начните с импорта модуля random, необходимого для выполнения симуляции Мон- те-Карло О- Затем определите функцию с именем user prompt о, которая просит пользователя ввести число выполняемых игр либо принимает значение по умолча нию, если оно указано 0 . Данная функция принимает два аргумента: первый — это текстовая подсказка, которая сообщает пользователю о том, что ему нужно сде лать; второй — значение по умолчанию, которое с самого начала равно None. Сразу же переопределите переменную prompt, для того чтобы она выводилась на экран со значением по умолчанию в скобках, как предусмотрено правилами Q . Назначьте введенное пользователем число переменной response о Если пользователь нажи мает клавишу без ввода числа и значение по умолчанию существует, то
Задача Монти Холла 267
функция user prompt о возвращает значение по умолчанию о В противном случае указанная функция возвращает введенное пользователем число. С помощью этой функции можно определить число прогонов, назначив возвращаемое значение пе ременной num runs О Каждый прогон будет представлять конкурсанта, играющего в игру один раз.
Выполнение симуляции Монте-Карло и вывод результатов на экран
Листинг 11.2 случайно подбирает выигрышную дверь и первый выбранный пользо вателем вариант, затем агрегирует и показывает статистику. Интересно, что второй выбранный пользователем вариант — поменять двери или выбрать ту же дверь — для получения правильного ответа не требуется. Если первоначальный вариант вы бора является выигрышной дверью, то правильный ответ — не менять двери. Точно так же, если первоначальный вариант выбора и выигрышная дверь различаются, то правильный ответ— поменять двери. Нет никаких причин моделировать то, что конкурсант может сделать или не сделать.
Листинг 11.2. Выполняет симуляцию Монте-Карло и выводит результаты на экран. Файл montyJiall_mcs.py, часть 2
задать счетчики количеств для способов выигрыша #
О first_choice_wins = О pick_change_wins = О
О doors = [‘а’, ‘b’, ‘с’]
выполнить Монте-Карло #
О f°r i in range(num_runs):
winner = random.choice(doors)
pick = random.choice(doors)
О if pick == winner:
first_choice_wins += 1
else:
pick_change_wins += 1
О print(“Выигрыши с исходным вариантом выбора = {}”.
format(first_choice_wins))
print(“Выигрыши с измененным вариантом выбора = {}”.
format(pick_change_wins))
print(“Вероятность выигрыша с первого угадывания: {:.2f}”
.format(first_choice_wins / num_runs))
print(“Вероятность выигрыша при изменении варианта: {:.2f}”
.format(pick_change_wins / num_runs))
О input("\пНажмите клавишу Enter для выхода из программы.”)
268
Глава 11
Задайте две переменные для отслеживания того, что является выигрышным резуль татом: замена двери либо пребывание в том же состоянии О- Затем создайте спи сок, представляющий три двери 0 .
Симуляция Монте-Карло начинается с цикла for, который перебирает число прого нов 0. Внутри этого цикла выберите выигрышную дверь и первый вариант, выбранный пользователем из списка дверей, используя функцию random.choiceO, и назначьте их переменным.
Поскольку мы имеем дело с двоичной системой — пользователь меняет дверь или остается при своем первоначальном выборе — вам понадобится условное выраже ние, которое увеличивает счетчики только на основе отношения переменной pick к переменной winning Q -
Завершите программу, представив окончательные результаты. Покажите фактиче ские значения, а также вычисленные вероятности © Сообщите пользователю о том, что программа завершила работу 0. Ниже приведен пример результатов для 20 тыс. прогонов по умолчанию:
Введите число прогонов [20000]:
Выигрыши с исходным вариантом выбора = 6628
Выигрыши с измененным вариантом выбора = 13372
Вероятность выигрыша с первого угадывания: 0.33
Вероятность выигрыша при изменении варианта: 0.67
Нажмите клавишу Enter для выхода из программы.
Некоторых людей компьютерная распечатка не впечатляет. Им нужно что-то по убедительнее, поэтому в следующем проекте вы переупакуете свой код в более практичный формат — укомплектованный дверьми, призами и козами. Он также будет ответом призыву Мэрилин вое Савант к школьникам присоединиться и по мочь восстановить ее честь.
Проект 19: игра в Монти Холла
Трехдверная игра, используемая в задаче Монти Холла, для вас проста настолько, что вы можете ее построить с помощью модуля tkinter. Вы начали работать с гра фикой tkinter в главе 10. Теперь вы обопретесь на эти знания, добавив нажимае мые пользователем интерактивные кнопки.
Ц е л ь
Просимулировать задачу Монти Холла с использованием графического интерфейса, построенного с помощью модуля tkinter. Отслеживать то, какая линия поведения приводит к победе: смена двери или бездействие. Вдобавок обновлять и показывать эту статистику по мере прохождения игры.
Задача Монти Холла 269
Краткое введение в объектно-ориентированное программирование
Модуль tkinter был написан с использованием объектно-ориентированного про граммирования (ООП). ООП — это языковая модель, построенная вокруг структур данных, именуемых объектами, состоящих из данных, методов и взаимодействий между ними — в отличие от действий и логики, используемых в процедурном про граммировании. Объекты создаются из классов, которые для объектов подобны схематическим макетам.
ООП — абстрактное понятие, и его легче оценить по достоинству, когда вы пишете большие, сложные программы. Оно уменьшает дублирование кода и упрощает его обновление, сопровождение и повторное использование. По этой причине боль шинство коммерческих программ теперь создают с использованием ООП.
Если бы вы реализовывали ООП в мелких программах, как те, которые мы писали до сих пор, то большинство из них выглядели бы переусложненными. По сути дела, одна из моих самых любимых цитат, приписываемых британскому компьютерщику Джо Армстронгу, касается как раз этого аспекта ООП: ’’беда с объектно- ориентированными языками заключается в том, что они несут с собой всю эту не явную среду. Вы хотели получить банан, а получили гориллу с бананом и всеми джунглаям в придачу!”
Несмотря на это, объекты, производимые ООП, оказывают очень хорошую под держку графических интерфейсов и игр, даже в некоторых небольших проектах. Давайте рассмотрим пример использования настольной игры типа ’’Подземелья и Драконы”, в которой игроки могут быть разными персонажами — гномами, эльфа ми и волшебниками. В этих играх используются карточки с персонажами, на кото рых перечисляются важные сведения о каждом типе персонажа. Если ваша игровая фигура будет представлять гнома, то она унаследует характеристики, описанные на карточке (рис. 11.2).
Листинги 11.3 и 11.4 воспроизводят игру в стиле настольной игры, позволяя вам создавать виртуальные карточки для гнома и эльфа, называть свои фигуры и уст раивать для них сражение. Исход сражения будет влиять на одну из жизненных сил персонажа, которые представляют его здоровье. Обязательно обратите внимание на то, как ООП позволяет вам легко создавать множество идентичных объектов — в данном случае гномов или эльфов — путем их ’’штамповки” из предопределенно го шаблона, именуемого классом.
Листинг 11.3. Импортирует модуль random, создает класс Dwarf и экземпляр объекта dwarf
ф » > import random
Q » > class Dwarf (object) :
О def init(self, name) : О self.name = name self.attack = 3
270
Глава 11
self.defend = 4 self. body = 5
© def talk(self):
print (“Я - человек-клинок, не подходи, а то зарежу!!!1’)
О > » lenn = Dwarf (“Лэнн”)
» > print (“Имя гнома = {)11. format (lenn. name))
Има гнома = Лэнн » > print (“Атакующая мощь Лэнна = {)” . format (lenn. attack))
Атакующая мощь Лэнна = 3 » >
О »> lenn.talk()
Я - человек-клинок, не подходи, а то зарежу!!!
Гном
Ты — гном. Со своим широким топором в руках ты — мааенький, но крепкий боец. Ты умеешь находить и обезвреживать ловушки
Атаковать Бросить 3 боевых кубика
Защитить Бросить 4 боевых кубика
Ходить Бросить 2 стандартных кубика
Жизненная сила 5
Рис. 11.2. Карточка с персонажем “Гном” из ролевой настольной игры
Начните с импорта модуля random для симулирования бросания кубика О? эт0 по ведение вашего персонажа во время сражения. Теперь определите для гнома его класс, написав с заглавной буквы имя класса, и передайте ему аргумент object, ко торый будет именем вашего гнома 0 . Класс — это шаблон для создания объектов определенного типа. Например, когда вы создаете список или словарь, вы создаете их из класса.
Определение класса гномов Dwarf выглядит как карточка на рис. 11.2; это, в сущно сти, генетический макет гнома. Он будет задавать атрибуты, такие как мощь и жизненная сила, и методы, т. е. то, как персонаж двигается или говорит. Атрибу
Задача Монти Холла 271
ты — это переменные, привязанные к экземпляру класса, а методы — это атрибуты, которые одновременно являются функциями, которым при выполнении передается ссылка на их экземпляр. Класс — это тип данных, и при создании объекта этого типа данных он также называется экземпляром этого класса. Процесс задания на чальных значений и поведения экземпляра называется созданием экземпляра, или инстан цирован ием.
Затем определите метод-конструктор, также именуемый методом инициализации. Он устанавливает начальные значения атрибутов вашего объекта Q . Метод __init__о — это специальный встроенный метод, который Python вызывает авто матически во время создания нового объекта. В этом случае вы передадите два аргумента: ключевое слово self и имя вашего объекта.
Параметр self является ссылкой на создаваемый экземпляр этого класса, или ссыл кой на экземпляр, на котором был вызван метод, технически именуемый экземпля ром контекста. Если вы создадите нового гнома и назовете его мСтив”, то за кули сами self станет Стивом. Например, self .attack становится “атакой Стива”. Если же вы создадите другого гнома по имени Сью, то self этого объекта станет “Сью”. Благодаря этому область действия атрибута здоровья Стива отделена от здоровья Сью.
Далее под определением конструктора перечислите несколько атрибутов гнома Q . Вам понадобится имя, для того чтобы можно было отличать одного гнома от друго го, а также значение ключевых боевых характеристик. Обратите внимание, как этот список напоминает карточку на рис. 11.2. Определите метод talk о и передайте ему параметр self 0 . Передавая ему пара метр self, вы связываете метод с объектом. В более сложных играх методы могут включать в себя поведение, такое как движение и способность обезвреживать ло вушки.
После завершения определения класса создайте экземпляр класса Dwarf и назначьте этот объект локальной переменной lenn, имени гнома О . Теперь напечатайте имя и атрибуты атаки, для того чтобы продемонстрировать, что у вас есть к ним доступ. Завершите вызовом метода talk о Q . Он должен показать на экране сообщение. Листинг 11.4 создает персонаж эльфа, используя аналогиченые действия, которые вы применяли в листинге 11.3, и пускает его в бой с гномом. Атрибут жизненной силы body у эльфа обновляется, отражая исход битвы.
Листинг 11.4. Создает класс эльфов Elf, инстанцирует объект elf, симулирует битву и обновляет атрибут объекта
О »> class Elf(object):
def init(self, name) : self. name = name self.attack = 4 self.defend = 4 self.body = 4
272
Глава 11
» > esseden = Elf (“Эсседен”) » > print (“Имя эльфа = {>11 .format (esseden.name)) Имя эльфа = Эсседен » > print(“Жизненная сила Эсседена = {}".format(esseden.body)) Жизненная сила Эсседена = 4 »>
ф » > lenn_attack_roll = random.randrange(1, lenn.attack + 1)
» > print(“Нападающий бросок Лэнна = {)” .format(lenn_attack_roll)) Нападающий бросок Лэнна = 3
О » > esseden_defend_roll = random.randrange(1, esseden.defend + 1)
» > print (“Защитный бросок Эсседена = {}”. format (esseden_defend__roll)) Защитный бросок Эсседена = 1 » >
О > » damage = lenn_attack_roll - esseden_defend_roll
» > if damage > 0:
esseden.body -= damage
О »> print (“Жизненная сила Эсседена = {}” .format (esseden.body))
Жизненная сила Эсседена = 2
Определите класс эльфов Elf и предоставьте несколько атрибутов ф . Сделайте их немного отличающимися от гномов и хорошо сбалансироваными, как у эльфа. Ин станцируйте эльфа по имени Эсседен и обратитесь к его атрибутам паше и body С П О М О Щ Ь Ю инструкции print.
Дайте вашим двум персонажам повзаимодействовать между собой, используя бро сок виртуального кубика с максимальным значением, равным значению атаки или защиты персонажа. Примените модуль random для выбора значения броска в диапа зоне от 1 до атрибута атаки Лэнна плюс 1 ф, затем повторите этот процесс, для получения защиты Эсседена ф. Вычислите урон Эсседену, вычтя значение броска Эсседена из значения броска Лэнна ф, и если значение урона является положи тельным числом, то вычтите его из атрибута жизненной силы Эсседена. Примените print () для подтверждения текущего состояния здоровья эльфа 0 . Как можно себе представить, создание многих подобных персонажей и отслежива ние их изменяющихся атрибутов с использованием процедурного программирова ния может быстро усложниться. ООП обеспечивает вашей программе модульную структуру, позволяет легко скрывать сложность и принадлежность области дейст вия с помощью инкапсуляции, позволяет решать задачи небольшими фрагментами кода и создает совместные шаблоны, которые можно модифицировать и использо вать в любом месте.
Стратегия и псевдокод
Теперь вернемся к нашей трехдверной игре. Правила игры формируют львиную долю псевдокода программы:
Инициализировать окно игры и показать закрытые двери и инструкции Выбрать выигрышную дверь наугад
Задача Монти Холла 273
Получить от игрока вариант выбора двери Открыть дверь, которая не является выигрышной либо выбранным игроком вариантом Получить выбранный игроком вариант со сменой или без смены двери Если игрок поменял дверь: Открыть новую дверь Если это победная дверь:
Зарегистрировать как выигрыш за смену двери
В противном случае:
Зарегистрировать как выигрыш за бездействие
В противном случае если игрок бездействует:
Открыть выбранную дверь Если это победная дверь:
Зарегистрировать как выигрыш за бездействие
В противном случае:
Зарегистрировать как выигрыш за смену двери
Показать число выигрышей в каждой стратегии в игровом окне Обнулить игру и закрыть все двери
Полезно начинать разработку игры с набросков того, как должно выглядеть игро вое окно, дополненное инструкциями, сообщениями и типами кнопок. Сомневаюсь, что вы хотите видеть мои грубые каракули, поэтому вместо них взгляните на рис. 11.3.
Именно так будет выглядеть состояние игры после первого раунда, причем стати стика побед будет видна в правом нижнем углу. Обратите внимание, что переклю чатели для смены дверей не активны до тех пор, пока не будет сделан первоначаль ный выбор.
f Задача Монти Холла
а
х
За одной дверью ДЕНЬГИ!
Выберите дверь г а ОВОК
Вторая мировая * г; воина J л А •
- г. »
- , • »
- hrH
- Щ Щ N
- •• : \ I
\л! »:
•
’ U
: :
- 4
Великая депрессия
американская мечта
великая инфляция
Нефтяное эмбарго
ц Крах дот-комов
5 j
Великая оеиессия
• 10-летние казначейские облигации
- S&P 500 — — Темп инфляции
Р и с. 12.2. Годовой тем п инфляции плюс суммарная возвратность на ф ондовом и облигационном рынках, 1 9 2 6 -2 0 1 3 гг
Длительное изучение трендов (рис. 12.2) финансовыми исследователями привело к некоторым полезным наблюдениям о рынках США.
♦ Восходящие (бычьи) рынки, как правило, длятся в пять раз дольше, чем нисхо
дящие (медвежьи) рынки.
♦ Вредные высокие темпы инфляции могут сохраняться в течение всего десяти
летия.
♦ Облигации, как правило, обеспечивают низкие финансовые возвраты, которые
с трудом идут в ногу с инфляцией.
♦ Финансовые возвраты от акций легко опережают инфляцию, но ценой большой
ценовой волатильности.
♦ Финансовые возвраты акций и облигаций часто обратно коррелируют; это озна чает, что возвратность облигаций уменьшается по мере увеличения возвратно сти акций, и наоборот.
♦ Ни акции крупных компаний, ни казначейские облигации не могут гарантиро
вать вам легкую прогулку.
Основываясь на этой информации, финансовые консультанты рекомендуют боль шинству пенсионеров иметь диверсифицированный портфель, включающий не
Обеспечение сохранности пенсионных сбережений 293
сколько типов инвестиций. Эта стратегия использует один тип инвестиций в каче стве ’’хеджирования” против другого, демпфируя максимумы, но поднимая мини мумы, тем самым теоретически снижая волатильность.
На рис. 12.3 годовая возвратность инвестиций строится с использованием S&P 500 и гипотетического 40/50/10-процентного сочетания соответственно S&P 500, 10-летних казначейских облигаций и наличных денежных средств. Трехмесячный казначейский вексель, являющийся очень краткосрочной облигацией со стабильной ценой и низкой возвратностью (все равно что деньги, засунутые под матрас), пред ставляет наличные денежные средства.
Годовая суммарная возвратность
1926-2013
———–S&P 500
———— 40/50/10-процентное сочетание
Р ис. 12.3. Годовая возвратность S & P 500 по сравнению с сочетанием S & P 500, 10-летних казначейских облигаций и наличных денежны х средств, 1 9 2 6 -2 0 1 3 гг.
Этот диверсифицированный портфель обеспечивает более легкую прогулку, чем только фондовый рынок, в то же время обеспечивая защиту от инфляции. Но он явно даст другие результаты, чем онлайновые калькуляторы, которые исходят из допущения, что возвратность всегда остается постоянной и положительной по величине.
Используя исторические данные, вы улавливаете истинную измеренную продолжи тельность хороших и плохих времен, а также самые высокие максимумы и самые низкие минимумы. Вы также учитываете то, что полностью игнорируется правилом 4 процентов: черного лебедя.
Черные лебеди— это логически вытекающие, невероятные события. Они могут быть хорошими, как встреча с вашим супругом, либо плохими, как Черный поне дельник, крах фондового рынка в октябре 1987 г. Преимущество симуляции Монте- Карло заключается в том, что она может учитывать эти неожиданные события; а недостаток в том, что вам нужно их запрограммировать, и если они действитель но являются непредсказуемыми, то как узнать, что включать?
294
Глава 12
Черные лебеди, которые уже произошли, подобные Великой депрессии, запечатле ны в годовых величинах в списках исторических возвратов. Поэтому общий подход заключается в использовании исторических результатов и допущении того, что в будущем не произойдет ничего хуже или лучше. Когда в симуляции используют ся данные Великой депрессии, моделируемый портфель будет испытывать то же самое поведение акций, облигаций и инфляции, что и реальные портфели в то время.
Если использование прошлых данных кажется слишком ограничительным, то про шлые результаты всегда можно отредактировать с тем, чтобы отразить более низ кие минимумы и более высокие максимумы. Но большинство людей практичны и счастливее, имея дело с событиями, о которых известно, что они произошли — в отличие от зомби-апокалипсисов или инопланетных вторжений, — поэтому ис тинные исторические результаты предлагают заслуживающий доверие способ вбрызнуть реальность в финансовое планирование.
Некоторые экономисты утверждают, что данные по инфляции и возвратности до 1980 г. имеют ограниченное применение, поскольку в настоящее время Федераль ная резервная система играет более активную роль в денежно-кредитной политике и контроле инфляции. С другой стороны, именно такое мышление оставляет нас беззащитными перед черными лебедями!
Самая большая неопределенность
Самая большая неопределенность в пенсионном планировании заключена в той дате, когда вы — либо ваш оставшийся в живых супруг — умрете, которую финан совые консультанты эвфемистически именуют ’’концом плана”. Эта неопределен ность влияет на каждое решение, связанное с пенсией, например когда вы выходите на пенсию, сколько вы тратите, находясь на пенсии, когда вы начинаете принимать социальное обеспечение, сколько вы оставляете своим наследникам и т. д.
Страховые компании и правительство работают с этой неопределенностью с помо щью актуарных таблиц продолжительности жизни. Исходя из опыта смертности населения, актуарные таблицы продолжительности жизни предсказывают ожидае мый срок жизни на пенсии в данном возрасте, выраженный как среднее оставшееся число лет, ожидаемое до наступления смерти. Таблицу продолжительности жизни для страхования жизни можно найти по адресу https://www.ssa.gov/oact/STATS/ table4c6.html. Исходя из этой таблицы, 60-летняя женщина в 2014 г. имела бы ожидаемый срок жизни на пенсии, равный 24,48 годам; это означает, что конец плана произойдет в течение ее 84-го года жизни.
Актуарные таблицы прекрасно работают для больших групп населения, но для отдельных людей они являются лишь отправной точкой. При подготовке своего собственного пенсионного плана следует проэкзаменовать ряд величин, приспо собленных под семейную историю и личные проблемы со здоровьем.
Для того чтобы справиться с этой неопределенностью в своей симуляции, рассмат ривайте годы на пенсии как случайную величину, значения которой выбираются случайно из частотного распределения. Например, можно ввести наиболее вероят
Обеспечение сохранности пенсионных сбережений 295
ное, минимальное и максимальное число лет, в течение которых вы ожидаете нахо диться на пенсии, и использовать эти значения для построения треугольного рас пределения. Наиболее вероятное значение может быть взято из актуарной таблицы, но конечные точки должны основываться на видах относительно вашего личного здоровья и семейной истории.
Примерный результат, основанный на треугольном распределении, по годам на пенсии для 60-летнего мужчины показан на рис. 12.4. Минимальный пенсионный срок был установлен равным 20 годам, наиболее вероятный — 22 годам, а макси мальный— 40 годам. Число значений, выбираемых из распределения, состави ло 1000.
Годы жизни на пенсии для 1000 продолжительностей жизни
120
100
80
60
40
20
20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
H i
.
.
Годы на пенсии
Р ис. 12.4. Число продолжительностей жизни в сравнении с годами на пенсии на основе 1000 значений, выбранных из треугольного распределения
Как видите, для симуляции доступен каждый возможный временной интервал между минимальным и максимальным значениями, но интервалы сужаются по час тоте от наиболее вероятного значения до максимального значения, указывая на то, что жизнь до 100 лет возможна, но маловероятна. Отметим также, что график зна чительно скошен в высокую сторону. Этим обеспечиваются консервативные ре зультаты, поскольку с финансовой точки зрения преждевременная смерть является оптимистичным результатом, а жизнь, более продолжительная, чем ожидалось, представляет наибольший финансовый риск.
Качественный способ представления результатов
Трудность с симуляцией Монте-Карло состоит в отыскании смысла в тысячах про гонах и представлении результатов в легко усваиваемом виде. Большинство онлай новых калькуляторов представляют результаты, используя график, подобный гра фику на рис. 12.5. В этом примере для симуляции из 10 тыс. прогонов калькулятор строит график нескольких отобранных результатов, где возраст расположен по оси х и значение инвестиций — по оси у. Кривые сходятся слева на начальном значе нии инвестиций при выходе на пенсию и заканчиваются справа их значением
296
Глава 12
в конце плана. Также может быть представлена совокупная вероятность того, что во время пенсионного срока деньги останутся. Финансовые консультанты считают вероятности ниже 80-90% рискованными.
В 95th Percentile Scenario Щ 75th Percentil» Scenario
[~] 50th Parc an tile Scenario
Q 25th Percentile Scenario
Q 5th Parcantila Scenario
Ranked 500th
Rankad 2.500th
Ranked 5.000th
Rankad 7.500th
Ranked 9.500th
Р и с. 12.5. Пример изображения из типичного симулятора пенсионных накоплений в ф инансовой отрасли
A G E
Наиболее важной частью информации из этого типа анализа является вероятность исчерпания денег. Также интересно посмотреть конечные и средние результаты и сводку входных параметров. В вашем симуляторе на языке Python вы можете распечатать их в окне интерпретатора, как показано ниже:
Тип инвестиций: облигации Начальная стоимость: $ 1 ООО ООО Годовой вывод средств: $ 4 0 ООО Годы на пенсии (мин.-наиболее вероятн.-макс.): 17-25-40 Число прогонов: 20 ООО
Шансы исчерпания денег: 36.1%
Средний результат: $883 84 3 Минимальный результат: $0 Максимальный результат: $7 607 789
Что касается графического представления, то вместо дублирования того, что сдела ли другие, давайте найдем новый способ представления результатов. Подмноже ство результатов каждого случая, т. е. деньги, остающиеся в конце пенсионного срока, можно представить в виде вертикальной линии на столбчатом графике (рис. 12.6).
f Результат в каждом случае (показаны первые 500 прогонов)
Обеспечение сохранности пенсионных сбережений 297
Симулированные жизни
Р и с. 12.6. Результаты симулированных пенсионных сроков, показанные на столбчатом граф ике в виде вертикальных столбиков
На данном графике каждый столбик представляет собой пенсионную часть одной смоделированной продолжительности жизни, а высота каждого столбика — деньги, оставшиеся в конце этого срока. Поскольку каждый столбик представляет не ин тервалы непрерывного измерения, а отдельную категорию, то столбики можно рас положить в любом порядке, не затрагивая данные. Зазоры, представляющие случаи, когда деньги были исчерпаны, могут включаться в порядке их возникновения в си муляции. Вместе с количественной статистикой, регистрируемой в окне интерпре татора, этот график обеспечивает качественный способ представления результатов.
Пики и впадины этого графика представляют собой меняющиеся судьбы многих возможных фьючерсов. В одной жизни вы можете умереть нищим, а в другой мультимиллионером. Это напоминает старую поговорку ’’Иду туда, если бы не ми лость Божья”, но с другой стороны, она подкрепляет замечание генерала Эйзенхау эра о том, что ’’планы бесполезны, но планирование необходимо”. С помощью финансового планирования вы можете ’’поднять впадины” на графике и устранить либо значительно уменьшить ваши шансы разориться на пенсии.
Для создания указанного график вы будете использовать библиотеку matplotlib, которая поддерживает двухмерное и рудиментарное трехмерное графопостроение. Дополнительные сведения о библиотеке matplotlib и ее установке см. в разд. “Код вероятности обнаружения”главы 10.
Псевдокод
Основываясь на предыдущем обсуждении, мы должны сфокусировать стратегию разработки программы на нескольких важных параметрах жизни на пенсии и симу лировании результатов с использованием исторического поведения финансовых рынков. Ниже приведен высокоуровневый псевдокод:
Получить от пользователя входное значение типа инвестиций (акции, облигации или их сочетание) Сопоставить выбранный вариант типа инвестиций со списком исторических финансовых возвратов Получить от пользователя входное значение начальной стоимости инвестиций
298
Глава 12
Получить от пользователя входное значение начальной годовой суммы выводимых средств Получить от пользователя входное значение минимальной, наиболее вероятной и максимальной продолжительностью жизни на пенсии Получить от пользователя входное значение числа выполняемых случаев Создать список для хранения результатов Перебрать случаи в цикле: Для каждого случая:
Извлечь случайную непрерывную выборку из списка финансовых возвратов для пенсионного срока Извлечь тот же интервал из инфляционного списка Для каждого года в выборке: Если год не равен году 1:
Скорректировать выводимые средства на инфляцию
Вычесть выводимые средства из инвестиций Скорректировать инвестиции на финансовые возвраты Если инвестиции 99:
О print(“ХпПроблема с введенными годами.”, file=sys.stderr)
print(“Требуется, чтобы мин. yrs.py, масть 1
start_value = default_input(“Введите исходную стоимость \
инвестиций: \п”, ‘2000000’)
while not start_value.isdigit():
start_value = input(“Недопустимое значение! Введите целое число: “)
О withdrawal_l = default_input(“Введите годовой вывод средств до налогов
для первых 5 лет (текущие $): \п”, ‘100000’)
Обеспечение сохранности пенсионных сбережений 313
while not withdrawal_l.isdigit():
withdrawal_l = input(“Недопустимое значение! Введите целое число: “)
Q withdrawal_2 = default_input(“Введите годовой вывод средств до налогов \
для остатка срока (текущие $): \п”, ‘80000’)
while not withdrawal_2.isdigit ():
withdrawal_2 = input(“Недопустимое значение! Введите целое число: “)
min_years = default_input(“Введите минимум лет на пенсии: \п”, ‘18’)
В разделе ввода данных пользователем замените исходную переменную выводи мых средств withdrawal двумя переменными выводимых средств и отредактируйте подсказку, для первой запросив сумму в течение первых пяти лет пенсионного сро ка ф и для второй сумму в течение оставшейся части ф . Установите значения по умолчанию, тем самым показывая, что пользователь ожидает более высокий вывод средств в течение первых пяти лет. Включите циклы while, которые проверяют вводимые пользователем значения.
В функции montecario () измените код, который корректирует сумму выводимых средств на инфляцию.
Листинг 12.9. Корректирует д ве переменные вы водимы х средств на инфляцию и определяет, какую из них следует использовать. Файл nest_eggjrics_1stJ>yrs.py, часть 2
не корректировать на инфляцию первые пять лет #
if index == 0:
ф withdraw_infl_adj_l = int(withdrawal_l)
e withdraw_infl_adj_2 = int(withdrawal_2)
else:
О withdraw_infl_adj_l = int(withdraw_infl_adj_l * (1 + infl))
О withdraw_infl_adj_2 = int(withdraw_infl_adj_2 * (1 + infl))
ф if index >), распаковав кортеж vent location xy ф. Завершите метод вызовом метода vector о, который вычислит вектор движения частицы
Выброс частицы
В листинге 13.3 представлен метод vector о, который определяет ориентацию за пуска частицы и вычисляет ее начальные векторные компоненты dx (дельта jc) и dy (дельта у).
Листинг 13.3. Определяет метод v e c t o r () класса P a r t ic le . Файл tvashtar.py, часть 3
ф def vector(self):
“““Вычислить вектор частицы при запуске.”””
ф orient = random.uniform(60, 120) ф radians = math.radians(orient) ф self.dx = self.vel * math.cos(radians) self.dy = -self.vel * math.sin(radians)
90 значит вертикальный #
Метод vector () ф вычисляет вектор движения частицы. Начните с того, что выбе рите направление запуска частицы, и назначьте это направление переменной ори ентации orient ф. Поскольку взрывные вулканические извержения выстреливают материал не прямо вверх, а в многочисленных направлениях, выберите направле ние наугад, используя диапазон, который составляет 30° по обе стороны от 90°, где 90 является вертикальным запуском.
Диапазон переменной orient был выбран методом проб и ошибок. Этот параметр, наряду с константами velocity S02 и gravity, представляет собой “ручки” управле ния, которые можно поворачивать в целях калибровки поведения частицы S 02 на снимке шлейфа. После того как вы отрегулировали константы так, чтобы макси мальная высота частицы соответствовала вершине шлейфа, можно настроить диа пазон углов так, чтобы частицы S 0 2 достигали— но не превышали— боковых границ шлейфа (рис. 13.4). Модуль math использует радианы, а не градусы, поэтому конвертируйте перемен ную orient в радианы ф. Радиана — это стандартная единица измерения угла, рав
Симуляция инопланетного вулкана 329
ная углу, получающемуся, когда радиус делает полный круг по окружности (см. левую часть рис. 13.5). Одна радиана составляет чуть меньше 57,3°. Правая часть рис. 13.5 представляет сравнение радиан и градусов для нескольких общеиз вестных углов. Для конвертации градусов в радианы можно либо— как какой- нибудь простак — умножить градусы на п и разделить на 180, либо применить мо дуль m a th !
Р ис. 13.4. Калибровка переменной ориентации o r i e n t по ш лейф у вулкана Тваш тар
тг/2 (90°)
7Г/4
Зтс/4
Р ис. 13.5. О пред ел ение радианы (слева) и общеизвестны х углов в радианах и градусах (справа)
Объекты движутся в p y g a m e с шагом х и у. Направление и скорость частицы исполь зуются для получения ее векторных компонент дельта х (с/х) и дельта у (dy). Они представляют разницу между начальной позицией частицы и ее местоположением после завершения одного игрового цикла.
Векторные компоненты вычисляются с помощью тригонометрии. Полезные триго нометрические соотношения приведены на рис. 13.6.
330
Глава 13
Р ис. 13.6. О бщ еизвестны е тригоном етрические уравнения, используемые в играх
Для угла 0 используется переменная orient. Атрибут self .vel приравнивается к г. Зная эти две компоненты, можно применить тригонометрическое соотношение для получения self .dx И self .dy О - Д™ получения self .dx умножьте self .vel на KO- синус ориентации orient, а ДЛЯ получения self.dy умножьте self.vel на синус ориентации orient. Обратите внимание, что вы должны сделать значение self.dy отрицательным, т. к. частицы выбрасываются вверх, а значения у в pygame увеличи ваются вниз.
Обновление частиц и обработка граничных условий
Листинг 13.4 завершает класс Particle, определяя метод обновления частиц по мере их перемещения по экрану. Сюда входит применение силы гравитации, рисо вание линии для отслеживания траектории частицы и “уничтожение” частицы, когда она выдвигается за пределы экрана или под поверхность Ио.
Листинг 13.4. Определяет метод u p d a t e () и завершает класс P a r t i c l e . Файл tvashtar.py, часть 4
О def update(self):
“““Применить гравитацию, нарисовать траекторию и обработать
граничные условия.”””
О self.dy += Particle.GRAVITY © pg.draw.line(self.background, self.color, (self.x, self.y),
(self.x + self.dx, self.y + self.dy))
О self.x += self.dx
self.y += self.dy
© if self.x self.screen.get_width():
©self .kill ()
0 if self.y Particle.IO_SURFACE_Y:
self.kill()
Симуляция инопланетного вулкана 331
Определите метод update о, который в качестве аргумента принимает self 0 . Примените силу гравитации, добавляя атрибут класса gravity в self.dy во время каждого игрового цикла 0 . Гравитация— это вектор силы, который работает только в вертикальном направлении, поэтому под ее влиянием оказываться только self. dy.
Для отрисовки траектории позади частицы используйте метод draw, line о пакета pygame, который в качестве аргументов принимает фоновой снимок Ио, цвет части цы и координаты предыдущего и текущего местоположений частицы 0 . Для полу чения текущего местоположения атрибуты self.dx и self.dy добавляются В self.x И self. у.
Затем обновите атрибуты self.x И self.y, добавив self.dx И self.dy, точно так же, как вы сделали в методе draw, line () О . Теперь проверьте, не прошла ли частица левую или правую границу экрана 0 . Ис пользуйте self.x, равный нулю для левой стороны, и получите ширину атрибута screen для правой стороны. Если частица вышла за пределы одной из сторон экра на, то примените встроенный в пакет метод kill о, удалив ее из всех групп, кото рые ее содержат 0 . Как вы увидите позже, для управления спрайтами пакет pygame использует контейнеры, именуемые спрайтовыми группами, а удаление спрайта из группы выводит его из игры. Повторите этот процесс для направления у 0 , но для максимального значения ис пользуйте константу 10 SURFACE Y KJiaCCa Particle, которая остановит частицу вблизи поверхности Ио, где остановится частица S 0 2 (см. рис. 13.2 и 13.4).
Определение функции main()
Листинг 13.5 определяет первую часть функции maino, которая задает игровой экран, заголовок окна, легенду, спрайтовую группу и игровые часы.
Листинг 13.5. Определяет первую часть функции main (). Файл tvashtar.py, часть 5
def main() :
“““Настроить и выполнить экран и цикл игры.”””
0 screen = pg.display.set_mode((639, 360)) e pg.display.set_caption(‘Симулятор вулкана на Ио’) 0 background = pg.image.load(’tvashtar_plume.gif’)
#
настроить легенду с цветовой кодировкой
0 legend_font = pg.font.SysFont(‘None’, 24) 0 water_label = legend_font.render(’– H20’, True, WHITE, BLACK) h2s_label = legend_font.render(’– H2S’, True, DK_GRAY, BLACK)
332
Глава 13
co2_label = legend_font.render(’– C02’, True, GRAY, BLACK) so2_label = legend_font.render(’– S02/S2’, True, LT_GRAY, BLACK)
0 particles = pg.sprite.Group()
0 clock = pg.time.Clock()
Первым делом следует задать переменную screen, используя метод display.set mode () пакета pygame О - Аргументами являются размеры в пикселах; в данном слу чае вы используете значения немного меньше, чем у снимка NASA. Это делается для обеспечения хорошей подгонки. Обратите внимание, что размеры должны быть предоставлены в виде кортежа, поэтому необходимо включить два набора скобок.
Далее объявите свое игровое окно с помощью метода display. set caption () пакета pygame 0, затем назначьте переменной background фотографию NASA шлейфа вул кана Тваштар О- Используйте метод image .load о пакета pygame, который создаст новый объект Surface из снимка. Пакет pygame поддерживает несколько форматов графических изображений, включая PNG, JPG и GIF. Возвращенный объект surface наследует информацию о цвете и прозрачности из снимкового файла. Поскольку вы импортируете снимок в оттенках серого, выбор цвета будет ограничен.
Теперь добавьте код построения легенды, которая будет изображаться в левом верхнем углу экрана.
Назовите переменную legend_font И используйте метод font.SysFont о пакета pygame для выбора None при размере 24 О - Вы будете использовать его при визуа лизации текста. Модуль font пакета pygame позволяет отрисовывать новый набор шрифтов TrueType на новом объекте surface. Если вы не хотите указывать шрифт, то pygame поставляется со встроенным стандартным шрифтом, к которому вы мо жете обратиться, передав значение None в качестве имени шрифта.
Разместите имена частиц в порядке возрастания их атомных масс, где самые легкие расположены на вершине. Для создания надписи вызовите функцию render о на созданном ранее объекте legend font, создав новый объект surface 0. Передайте ему текст, затем True (для устранения контурных неровностей — антиалиасинг — что сделает текст визуально более гладким), а затем цвет описываемой частицы. Последний аргумент, black, является необязательным и делает цвет фона надписи черным ради разборчивости текста над всеми нарисованными на экране траекто риями частиц. Повторите этот процесс для трех оставшихся частиц и добавьте S2 в so2_iabei, т. к. оба газа имеют одинаковую атомную массу и в симуляции будут вести себя одинаково.
Теперь создайте спрайтовую группу с именем particles Q . Поскольку в играх обычно по экрану перемещается несколько спрайтов, то для управления ими пакет pygame использует контейнер — спрайтовую группу. В действительности же вы просто обязаны поместить спрайты в группу, иначе они ничего не сделают.
Завершите этот раздел, создав объект хронометрирования clock для отслеживания и управления кадровой частотой симуляции 0 . “Часы” пакета pygame контролирует
Симуляция инопланетного вулкана 333
скорость выполнения игры на основе числа кадров в секунду (frames per second, fps). Вы установите это значение в следующем разделе.
Завершение функции main()
Листинг 13.6 завершает функцию maino тем, что устанавливает скорость, с кото рой будет выполняться симуляция (в кадрах в секунду), и начинает цикл while, который выполняет эту симуляцию фактически. Он также обрабатывает события, которые происходят, когда пользователь контролирует программу с помощью мы ши, джойстика или клавиатуры. Поскольку это не настоящая игра, а симуляция, управление со стороны пользователя ограничивается закрытием окна. Листинг за канчивается в глобальной области стандартным фрагментом кода для выполнения программы в виде модуля либо в автономном режиме.
Листинг 13.6. Запускает игровые часы и цикл и обрабаты вает события в функции main О . Файл tvashtar.py, часть 6
О while True:
О clock.tick(25) 0 particles.add(Particle(screen, background))
О for event in p g .event.g e t ():
if event.type == pg.QUIT:
pg . quit ()
sys.exit()
ф screen.blit(background, (0, 0))
screen.blit(water_label, (40, 20)) screen.blit(h2s_label, (40, 40)) screen.blit(co2_label, (40, 60)) screen.blit(so2_label, (40, 80))
Q particles.update()
particles.draw(screen)
О
pg.display.flip()
ф if name == “main
main()
Начните цикл while, который выполняет симуляцию О - Затем примените метод clock.tick(), который установит в симуляции скоростной лимит ф . Передайте ему 25, в результате чего максимальная частоту кадров будет установлена равной 25 кадрам в секунду. Указанное значение можно свободно увеличить, если есть желание получить более энергичный вулкан.
Теперь настало время для того, чтобы на сцене появилась сама звезда шоу. Инстан цируйте частицу с помощью класса Particle, передав ему в качестве аргументов
334 Глава 13
переменные screen и background, и добавьте новую частицу в спрайтовую группу particles О - С каждым кадром из вулканического жерла будет случайно появлять ся и запускаться новая частица, производя приятный глазу брызг частиц (рис. 13.7).
Симулятор вулкана на Ио
X
Р и с. 13.7. Запуск симуляции, в которой случайные частицы генерируются со скоростью 25 кадров в секунду
Начните цикл for по обработке событий О* Все события, произошедшие во время текущего кадра, регистрируются и хранятся пакетом pygame в событийном буфере. Его метод event.get о создает список всех этих событий для их вычисления по очереди. Если происходит событие quit (когда пользователь закрывает окно игры), ТО вызываются метод quit о пакета pygame и системный метод exit о, которые завершают симуляцию.
Для визуализации игровых объектов и обновления визуального изображения pygame использует процесс под названием “блиттинг”. Указанный процесс означает блоч ную передачу растровых данных, с помощью которой просто копируются пикселы одного прямоугольного объекта surface в другой. При переносе фона на экран вы покрываете экран снимком Ио. С помощью блочной передачи можно взять тот же самый снимок и несколько раз отобразить его на экране в разных местах. Этот про цесс может замедлиться, поэтому разработчики игр используют умные технические решения для устранения данного недостатка, такие как перенос растровых фраг ментов в каждом игровом цикле только вокруг тех областей, которые в настоящее время обновляются, а не всего экрана.
Для переноса фона на экран вызовите метод blit о на объекте screen и передайте ему необходимые аргументы с источником и местом назначения О - В первом при мере переменная фона background является источником, а местом назначения — координаты левого верхнего угла фона. Поскольку фон будет покрывать весь эк ран, используйте точку начала координат экрана, т. е. (О, 0). Повторите это для надписей легенды, поместив их в левый верхний угол экрана.
Симуляция инопланетного вулкана 335
Затем вызовите метод update о на группе частиц particles О - Этот метод не обновляет экран, но заставляет спрайты выполнять собственные методы update (). После этого вы используете метод draw () для переноса спрайтов на экран, основы ваясь на атрибутах rect каждого спрайта. Для этого метода нужна поверхность ри сования, поэтому передайте ему объект screen.
Метод draw о позаботился о переносе спрайтов, поэтому сейчас для обновления фактической графики игры вам нужно лишь применить метод flip о Q . Флип- пинг, или отражение, — это вид двойной буферизации, где все переносится с объ екта screen на фактический экран. Отражение обходит внутренне присущий мед ленный процесс передачи графики, способный вызывать мерцание на экране, вы полняя работу на закулисном прямоугольнике и после этого используя версию метода blit () для окончательного копирования на экран.
Листинг заканчивается вне функции main () фрагментом кода, который позволяет программе работать как модуль либо в автономном режиме Q .
Выполнение симуляции
На рис. 13.8 показан результат работы симулятора в течение примерно минуты. Шлейф водяного пара выходит за пределы верхней части окна. Следующий по вы соте шлейф образуется сероводородом, за которым следует углекислый газ, а затем двуокись серы/сернистый газ (S2), которые по конструкции идеально соответству ют шлейфу Тваштара.
Для выполнения симулятора только с S 0 2 перейдите к методу init_ класса particle и измените строки, в которых вы выбираете экземплярные атрибуты gas и color!
self.gas = self.color
’ S02’ = random.choice(list(Particle.gases_colors.values()))
Р и с. 13.8. Результаты выполнения программы tvashtar.py в течени е одной минуты
336
Глава 13
Выбирая цвет наугад, вы сохраняете ощущение движения в шлейфе после того, как все возможные углы ориентации self.orient были исчерпаны. А если вы хотите ускорить или замедлить извержение, то перейдите к функции maino и поэкспери ментируйте с параметром кадров в секунду метода clock, tick ().
В реальной жизни состав материала шлейфа был выведен с помощью спектроско пии, т. е. измерительного приема, который позволяет анализировать взаимодейст вие света с веществом. Сюда входят как видимые, так и невидимые длины волн, которые поглощаются, излучаются или рассеиваются. ’’Спектры выбросов” вместе с красками, наносимыми на поверхность, давали ключевое доказательство наличия шлейфов, богатых серой.
Резюме
В этой главе вы узнали, как использовать пакет pygame для симулирования гравита ции и построения анимации извержения внеземного вулкана. В следующей главе вы воспользуетесь пакетом pygame для построения настоящей аркадной игры с взаимодействием игроков и условиями выигрыша-проигрыша.
Дальнейшее чтение
Книга Энди Хэрриса ’’Программирование игр: L-линия, экспресс-линия в обуче нии” (Harris A. Game programming: the L-line, the express line to learning. Wiley, 2007) представляет собой чрезвычайно полезное и подробное 570-страничное вве дение В pygame.
Книга Джонатана Харбора ’’Еще больше языка Python для абсолютного новичка” (Harbour J. More Python for the absolute beginner. Cengage Learning Course Technology, 2012) основана на предшествующей книге ’’Язык Python для абсолют ного новичка” (Python for the Absolute Beginner), в которой используется игровой ПОДХОД на основе pygame.
Книга Эла Свейгарта “Учим Python, делая крутые игры” (Sweigart A. Invent your own computer games with Python. 4th Edition. No Starch Press, 2016) является хоро шим введением в Python и в проектирование игр для начинающих.
Онлайновое ’’руководство для новичков” по pygame доступно по адресу https:// www.pygame.org/docs/tut/newbieguide.html, “шпаргалку” можно найти по адресу http://www.cogsci.rpi.edu/~destem/gamedev/pygame.pdf
а соответствующую
Статья Уильяма Дж. Макдониела и соавт. “Трехмерное моделирование газа и пыли в плюме вулкана Пеле на Ио” (McDoniel W. J. et al. Three-dimensional simulation of gas and dust in Io’s Pele Plume) документирует симуляцию плюма вулкана Пеле на Ио с использованием прямой симуляции Монте-Карло и суперкомпьютеров в Те хасском центре передовых вычислений в Университете Техаса. Статья доступна по адресу http://cfpl.ae.utexas.edu/wp-content/uploads/2016/01/McDoniel_PeleDust.pdf.
Симуляция инопланетного вулкана 337
Практический проект: весь путь до конца
Вы — один из лучников короля Генриха в битве при Азенкуре. Французы атакуют, и вы хотите выстрелить по ним как можно дальше. Под каким углом вы держите свой длинный лук?
Если у вас когда-либо были уроки физики, то вы, вероятно, знаете, что ответом бу дет 45°. Но можно ли доверять этому тонкошеему физику? Для проверки лучше выполнить быструю компьютерную симуляцию. Скопируйте и отредактируйте код tvashtar.py так, чтобы он случайно выстреливал частицы под углами 25, 35, 45, 55 и 65 градусов. Сделайте self.color белым для 45° и серым для всех других углов (рис. 13.9).
Белый = 45 градусов
•.. -v*
Р ис. 13.9. Симулятор вулкана на Ио, модиф ицированный для углов выброса под углами 25, 35, 45, 55 и 65 градусов
Решение задачи, practice_45.py, можно найти в приложении к книге либо скачать с веб-сайта https://www.rwstarch.com/impracticalpython/. Сохраните его в той же папке, что и файл tvashtar_plume.gif.
Сложные проекты
Продолжите свои эксперименты с приведенными ниже сложными проектами. Ни каких решений не предусмотрено.
Ударный купол
Считается, что видимость гигантских шлейфов Ио усиливается за счет конденсации газа в пыль в ударном куполе, т. е. точке, где частицы газа достигают своей верши ны и начинают падать обратно на поверхность. Используйте атрибут self.dy, отре дактировав цвет траектории в копии программы tvashtar.py. Траектории в вершине
338
Глава 13
Р ис. 13 .10. Вы деление ударного купола с помощью более светлых красок контура
шлейфа должны быть ярче, чем те, что расположены ниже (рис. 13.10). Как и во всех сложных проектах, никакого решения не предусмотрено.
Источник выбросов
Скопируйте и отредактируйте программу tvashtar.py так, чтобы симулировался только газ S 0 2, и частицы были представлены маленькими белыми кружками без тянущихся позади траекторий (рис. 13.11).
Р ис. 13 .11. Снимок симуляции S 0 2, где кружки представляют отдельные частицы
Полет пули
Если выстрелить из пистолета прямо вверх на планете без атмосферы, то упадет ли пуля на поверхность с той же скоростью, с какой она вылетела из дула? Многие
Симуляция инопланетного вулкана 339
отвечают на этот вопрос с трудом, но вы можете ответить на него с помощью языка Python. Скопируйте и отредактируйте программный код tvashtar.py так, чтобы он выбрасывал одну частицу S 0 2 с ориентацией 90°. Напечатайте атрибут self.y час тицы и абсолютное значение self.dy в координате точки запуска (у = 300). Срав ните начальные и конечные значения скорости в этой точке, для того чтобы убе диться в их одинаковости или схожести.
ПРИМЕЧАНИЕ
Эпизод 50 американской научно-популярной телепередачи “Разрушители легенд (Myth Busters) посвящен мифу о том, что пули, выпущенные в воздух, сохраняют свою смер тоносную способность, когда они в конечном итоге падают обратно. Они обнаружили, что пули, выпущенные совершенно вертикально на Земле, будут падать, замедляясь на обратном пути из-за сопротивления ветра. Если выстрелить немного не по верти кали, то пули сохранят свое вращение и баллистическую траекторию и вернутся на Землю со смертельной скоростью. Это был единственный миф, который когда-либо получал все три рейтинга (развенчан, правдоподобен и подтвержден)!
14
КАРТОГРАФИРОВАНИЕ МАРСА С ПОМОЩЬЮ ОРБИТАЛЬНОГО СПУТНИКА
Орбитальный спутник Марса Mars Orbiter был успешно вы веден на марсианскую орбиту, но все оказалось не так хо рошо. Орбита Марса является высокоэллиптической, и карто графические задачи проекта требуют низкой высоты круговой орбиты. К счастью, для исправления ситуации на борту имеется достаточно топлива, если допустить, что яйцеголовые в Центре управления поле тами обладают терпением и умением для того, чтобы его вытащить!
В данной главе вы спланируете и построите игру, основанную на этом скрипте. Вы снова будете использовать пакет pygame (обзор пакета pygame см. в разд. “Кусочек пакета pygame” главы 13) и внесете свой вклад в развитие междисциплинарного STEM-образования1, сделав игру достаточно реалистичной для того, чтобы научить игроков основам орбитальной механики.
П Р И М Е Ч А Н И Е
Хотя игровой спутник и индийский орбитальный спутник Mars Orbiter носят одинаковое название, упоминаемый в игре космический зонд Mars Orbiter не имеет прямого отно шения к миссии зонда Mars Orbiter, запущенного Индийской организацией космических исследований (ISRO) в 2014 г. Игровой спутник создан по образцу беспилотной разве дывательной станции Mars Global Surveyor, запущенной NASA в 1996 г.
1 STEM (science, technology, engineering и mathematics) — наука, технология, инженерное дело и мате матика. — Прим. перев.
Картографирование Марса с помощью орбитального спутника 341
Астродинамика для геймеров
Поскольку вы хотите, чтобы ваша игра была максимально реалистичной, будет уместно привести быстрый обзор некоторых фундаментальных научных положе ний, лежащих в основе космического полета. Он будет кратким, сладким и приспо собленным для разработки игры и для самой игры.
Закон универсальной гравитации (или всемирного тяготения)
Теория гравитации утверждает, что массивные объекты, такие как звезды и плане ты, деформируют пространство и время вокруг себя, подобно тому как тяжелый шар для боулинга, помещенный на матрас, вызывает прогиб, который становится внезапным и резким рядом с шаром, но быстро выравнивается. Это поведение ма тематически фиксируется законом всемирного тяготения Исаака Ньютона:
тх т2 р - — ° ' где F — это сила тяжести; т\ — масса объекта 1; т2 — масса объекта 2; d — рас стояние между объектами; G — гравитационная постоянная (6,674 • 10-11 Н • м2/кг2).
Два объекта притягиваются друг к другу в соответствии с произведением их масс, деленным на квадрат расстояния между ними. Таким образом, гравитация намного сильнее, когда объекты находятся близко друг к другу, как глубокий прогиб матра са прямо под шаром для боулинга. В качестве иллюстрации 100-килограммовый человек будет весить на 250 грамм меньше на вершине горы Эверест, чем на уров не моря, где он будет на 8848 м ближе к центру Земли. (Предполагается, что масса планеты составляет 5,98 • 1024 кг, а уровень моря— 6,37*106 м от центра.)
Сегодня мы обычно думаем о гравитации как о поле (как о матрасе в аналогии с шаром для боулинга), а не как о ньютоновской точке притяжения. Это поле по- прежнему определяется законом Ньютона и приводит к ускорению, обычно выра женному в метрах на секунду в квадрате (м/с2).
Согласно второму закону движения Ньютона, сила равна произведению массы и ускорения (F = та). Сила, приложенная объектом 1 (т) к объекту 2 {mi), может быть вычислена переписыванием уравнения гравитации следующим образом:
—G • т,
а = —
’
где а — ускорение; G — гравитационная постоянная; т\ — масса одного из объек тов; d — расстояние между объектами. Сила направлена от объекта 2 к центру масс объекта 1 (т).
Притяжение очень малых объектов к большим обычно игнорируется. Например, сила, приложенная спутником массой 1000 кг к Марсу, примерно в 1,6 10-21 раз меньше силы, приложенной Марсом к спутнику! Поэтому в своей симуляции мож но безопасно проигнорировать массу спутника.
342
Глава 14
ПРИМЕЧАНИЕ Для упрощения в этом проекте расстояние вычисляется от центральных точек объек тов. В реальной жизни орбитальный спутник испытывал бы тонкие изменения в грави тационном ускорении из-за изменений формы планеты, топографии, плотности коры и т. д. По данным Британской энциклопедии, эти изменения приводят к тому, что гра витационное ускорение на поверхности Земли варьируется примерно на 0,5%.
Законы движения планет Кеплера
В 1609 г. астроном Иоганн Кеплер обнаружил, что орбиты планет представляют собой эллипсы, что позволило ему объяснить и предсказать движение планет. Он также обнаружил, что отрезок линии, проведенный между Солнцем и вращающей ся по орбите планетой, формирует собой равные площади за равные промежутки времени. Эта идея, известная как второй закон движения планет Кеплера, проде монстрирована на рис. 14.1, где планета показана в разных точках своей орбиты.
(W iBA,) = (W iB 4>
Р и с. 14.1. Второй закон движения планет Кеплера: орбитальная скорость увеличивается по мере приближения планет к Солнцу
Этот закон применим ко всем небесным телам, и он означает, что орбитальный объект ускоряется по мере приближения к телу, вокруг которого он вращается, и замедляется, когда он удаляется.
Орбитальная механика
Вращение по орбите, в сущности, представляет собой вечное свободное падение. Вы падаете в ядро гравитационного колодца планеты — расположенного в ее бук вальном ядре, — но ваша тангенциальная скорость настолько быстра, чтобы вы продолжаете не попадать по планете (рис. 14.2). До тех пор пока вы уравновеши ваете свой импульс силой тяжести, орбитальное вращение никогда не закончится.
При вращении вокруг планеты в вакууме пространства могут происходить вещи, которые противоречат интуитивному пониманию. Без трения или сопротивления ветра космический корабль может вести себя неожиданным образом.
Картографирование Марса с помощью орбитального спутника 343
Г равита!.
I N Скорость
Орбита
Р и с. 14.2. О рбита достигается, когда скорость космического аппарата удерживает его ”в свободном падении” вокруг небесного тел а
Полет назад
Если вы когда-нибудь смотрели кинофильм “Звездный путь” (Star Trek), то вы на верняка заметили, как вращающийся по орбите космический корабль “Энтерпрайз”, кажется, управляет своей траекторией вокруг планет, как автомобиль, едущий по трассе. Это, конечно, можно сделать— и определенно выглядит круто, но такое движение требует затрат драгоценного топлива. Если нет необходимости постоян но направлять определенную часть космического аппарата на планету, то нос кос мического аппарата всегда будет указывать в одном и том же направлении на про тяжении всей его орбиты. В результате этого на каждой орбите будет время, когда будет казаться, что аппарат летит назад (рис. 14.3).
Орбитальное ^направление
Р и с. 14.3. Космические аппараты на орбите сохраняют прежнее положение, если только их не вынуждают к иному
Можно обвинить в этом Ньютона и его закон инерции, который гласит, что покоя щийся объект остается в покое, а движущийся объект остается в движении с той же скоростью и в том же направлении, если только на него не действует несбаланси рованная сила.
Поднятие и опускание орбит
Тормоза в космосе не работают, трения нет, а инерция воспринимается очень серь езно. Для снижения орбиты космического корабля нужно запустить двигатели для уменьшения его скорости с тем, чтобы он продолжил падать в гравитационный ко лодец планеты. Для этого необходимо ретроградировать космический корабль так,
344 Глава 14
чтобы его нос был обращен в противоположную сторону от нынешнего вектора скорости— причудливый способ сказать, что вы должны лететь задом наперед. При этом, разумеется, предполагается, что основные двигатели находятся в задней части космического аппарата. И наоборот, если требуется поднять орбиту, то необ ходимо проградуировать космический аппарат, в результате чего его нос будет на ходиться в направлении, в котором вы путешествуете. Эти две идеи показаны на рис. 14.4.
Проградация
Орбитальное направление
Орбитальное направление
Р и с. 14.4. Проградация и ретроградация определяются ориентацией носовой части космического аппарата относительно направления его движения вокруг тела, которое он облетает
г о I |лл рсццацИЯ
Занятие внутренней дорожки
Допустим, вы гонитесь за другим вращающимся по орбите космическим кораблем. Что нужно для того, чтобы его догнать: ускориться или замедлиться? Согласно второму закону Кеплера, нужно замедлиться. В результате этого ваша орбита будет снижена, что приведет к более высокой орбитальной скорости. Это подобно тому, как в скачках вы хотите занять внутреннюю дорожку.
На левой стороне рис. 14.5 два космических челнока находятся бок о бок на одной и той же орбите, двигаясь с одинаковой скоростью.
Проградация
Р ис. 14.5. Орбитальный парадокс: замедлиться, чтобы ускориться!
ироитальное направление
Картографирование Марса с помощью орбитального спутника 345
Ближайший к планете челнок поворачивается на 180° и включает ретроградную тягу, замедляя свою непосредственную скорость. Внешний челнок выполняет про- градное движение, которое увеличивает его непосредственную скорость. Они одновременно останавливают тягу, и внутренний челнок падает на более низкую орбиту, тогда как внешний челнок переносится на более высокую орбиту. Через час или около того внутренний челнок уже движется гораздо быстрее из-за его боль шей сближенности с планетой и полным ходом продвигается к тому, чтобы догнать и перегнать внешний челнок.
Округлость эллиптической орбиты
Высокоэллиптические орбиты можно сделать круговыми, применяя импульсы дви гателя в зависимости от ситуации в апоапсиде либо периапсиде. Апоапсида (име нуемая апогеем, если объект вращается вокруг Земли) — это самая высокая точка на эллиптической орбите — точка, в которой объект находится дальше всего от те ла, вокруг которого он вращается (рис. 14.6). Периапсида (перигей, если объект вращается вокруг Земли) — это самая низкая точка на орбите.
Для поднятия периапсиды космический аппарат выполняет проградную тягу в апо апсиде (см. левую часть рис. 14.7). Для снижения орбиты во время скругления кос-
Апоапсида
Периапсида
Р и с. 14.6. Р асположение апоапсиды и периапсиды на эллиптической орбите
Проградная тяга
в апоапсиде
Орбитальное
п —– начальная 5ита
ч
///
направление
Ретроградная тяга
в периапсиде
Р и с. 14.7. Округление и поднятие орбиты в апоапсиде (слева) и скругление и опускание орбиты в периапсиде (справа)
346
Глава 14
мический аппарат должен выполнить ретроградную тягу в периапсиде (см. правую часть рис. 14.7).
Часть этого маневра, которая несколько противоречит интуитивному пониманию, заключается в том, что первоначальная орбита (т. е. орбита, которая была бы) и ко нечная, или фактическая, орбита совпадут в точке приложения импульса двигателя.
Поднятие и опускание орбит с помощью гомановской траектории перехода
Орбита с переходом по эллипсу методом Гомана использует эллиптическую орбиту с целью перехода между двумя круговыми орбитами в одной плоскости (рис. 14.8). Орбита может быть поднята или опущена. Маневр происходит относительно мед ленно, но при этом расходуется минимально возможный объем топлива.
Рис. 14.8. Переход на нижнюю круговую орбиту с использованием гомановского перехода с орбиты на орбиту
Для перехода на орбиту с другой периапсидой и апоапсидой космическому аппара ту требуются два импульса двигателя. Один импульс перемещает космический аппарат на переходную орбиту, а другой перемещает его на конечную, целевую орбиту. При поднятии орбиты космический аппарат применяет изменение скорости в направлении движения, а при опускании орбиты — изменение скорости, проти воположное направлению движения. Как показано на рис. 14.8, изменения скоро стей происходят на противоположных сторонах орбиты. Без второй тяги двигателя орбиты все равно пересекутся в точке первой тяги. Это показано на рис. 14.7 справа.
Поднятие и опускание орбит методом однотангенциального сжигания Метод однотангенциального сжигания2 переносит космический аппарат между орбитами быстрее, но менее эффективно, чем гомановский переход. Термин “сжи
2 Однотангенциальное сжигание (one-tangent bum) — это метод переноса, в котором переходная орбита является касательной по отношению к первоначальной орбите. См. h ttp s ://w w w .aero s p ac en g in e erin g .n et/ o rb it-m a n e u v e rs /. — Прим. перев.
Картографирование Марса с помощью орбитального спутника 347
гание” — это просто еще один вариант обозначения тяги или импульса двигателя. Как и в случае с переходом методом Гомана, орбиты могут быть подняты или опу щены.
Для выполнения указанного маневра требуется два импульса двигателя, первый касательный (тангенциальный) к орбите, а второй— некасательный (рис. 14.9). Если первоначальная орбита является круговой, как на рисунке, то все точки вдоль нее представляют собой апоапсиду и периапсиду, и космический аппарат может применить свое первое сжигание в любое время.
Конечная
Р ис. 14.9. Переход на более высокую круговую орбиту методом однотангенциального сжигания
Так же как и при гомановском переходе с орбиты на орбиту, проградное сжигание поднимает орбиту, а ретроградное сжигание ее снижает. Если орбита является эл липтической, то первым сжиганием будет проградное сжигание в апоапсиде для поднятия орбиты либо ретроградным сжиганием в периапсиде для ее опускания.
Исполнение спиральной орбиты со спиральным переносом
Спиральный перенос использует непрерывное, низкоскоростное сжигание топлива с целью изменения размера орбиты. В игровом процессе его можно симулировать, используя как ретроградное, так и проградное сжигание, которые характерны крат ковременностью и равномерностью распределения, как показано на рис. 14.10.
Для опускания орбиты все сжигания топлива должны быть ретроградными; для поднятия орбиты космический аппарат использует проградные сжигания.
Исполнение синхронных орбит
На синхронной орбите космическому аппарату для одного оборота вокруг планеты требуется столько же времени, сколько требуется планете на то, чтобы сделать один оборот вокруг своей оси. Если синхронная орбита является параллельной эк ватору, без наклона орбиты, то такая орбита является стационарной; для наблюда теля на небесном теле, вокруг которого осуществляется вращение, спутник кажется неподвижным, с фиксированным положением на небе. Спутники связи обычно ис-
348
Глава 14
Р и с. 14 .10. Д виж ение по спиральной орбите с использованием коротких ретроградных сжиганий топлива через регулярные промежутки времени
пользуют геостационарные орбиты, которые имеют высоту 22 236 миль над Зем лей. Аналогичная орбита будет называться аэростационарной вокруг Марса и селеностационарной вокруг Луны.
Проект 22: игра “Орбитальный спутник Марса”
В реальной жизни для точного исполнения орбитальных маневров применяется ряд уравнений. В игровом процессе вы будете использовать свою интуицию, терпение и рефлексы! Вам также в определенной степени нужно будет летать по приборам, используя в основном показания высоты космического аппарата и измерение округлости орбиты.
Ц е л ь Применить пакет pygame для построения аркадной игры, которая учит основам орби тальной механики. Цель игры состоит в том, чтобы подтолкнуть спутник на круговую орбиту картографирования, не исчерпав топливо либо не сгорев в атмосфере.
Стратегия
Начните этап проектирования с эскиза игры, как вы делали в главе 13. Этот эскиз должен охватывать все основные моменты игры: как она будет выглядеть, как она будет звучать, какие предметы будут двигаться и как игра будет общаться с игро ком (рис. 14.11).
Эскиз на рис. 14.11 описывает главный игровой процесс. Вам понадобится отдель ный эскиз для описания условий выигрыша-проигрыша. Ключевыми моментами главного игрового процесса являются следующие:
Картограф ирование М арса с помощью орбитального спутника 349
♦ Точка наблюдения — это пункт управления полетом. Игровой экран должен напоминать монитор в Центре управления полетами, с которого игрок может управлять блуждающим космическим зондом.
♦ Марс находится впереди и в центре. Все любят красную планету, и поэтому
она будет занимать центр угольно-черного экрана.
♦ Марс анимирован. Марсианский глобус будет медленно вращаться вокруг сво ей оси и отбрасывать тень. Спутник заметно потускнеет, когда он будет прохо дить через эту тень.
Ф Первоначальная орбита спутника выбирается наугад. Спутник появится при запуске с рандомизированной, но ограниченной ориентацией и скоростью. В редких случаях это может привести к мгновенному проигрышу в игре. Это все же лучше, чем реальные миссии, которые терпят неудачу в 47% случаев!
Ф В проградации либо ретроградации спутника нет необходимости. Постоян ное вращение космического зонда перед запуском его двигателей значительно уменьшает игровой процесс. Будем считать, что направляющие двигатели рас положены массивом вокруг фюзеляжа, и для выбора запускаемого двигателя вы будете использовать клавиши со стрелками.
…правильно для круговой орбиты картографирования без исчерпания топлива либо сгорания в атмосфере…
Вводный текст, показываемый при запуске в течении 15 с
Орбитальное направление спутника, выбираемое наугад
Марс медленно вращается
Регистрируемая орбитальная траектория
Постоянная надпись с описанием ключевых команд управления справа внизу
Стрелка влево… Стрелка вправо… Стрелка вве р х… С т рекавниз… Очистить траекторию.
Постоянное напоминание об условия выигрыша внизу
— — Высота 69-120 миль. Отклонение орбиты .
♦ Индикация данных размещается вверху экрана. Вы будете показывать ин формацию, полезную для процесса игры в прямоугольниках вверху окна. Клю чевыми данными являются скорость космического зонда, его высота, топливо и эксцентриситет орбиты (мера округлости орбиты).
♦ Краткий инструктаж показывается при запуске. Знакомящий с игрой текст появится в центре экрана, когда игра начнется, и будет оставаться примерно в течение 15 секунд. Указанный текст не будет нарушать игровой процесс, по этому игрок может сразу же начать манипулировать спутником.
♦ Условия выигрыша и ключевые элементы управления показываются в по стоянных надписях. Критическая информация, такая как цели полета и клави ши управления, будет постоянно изображена в левом и правом нижних углах экрана.
Эскиз игры на рис. 14.12 описывает, что происходит в случаях успеха и неудачи. Игроку нужно вознаграждение, когда он выигрывает, и интересный результат, когда проигрывает.
Для выигрышного и проигрышного результатов ключевыми моментами являются следующие: ♦ Изменение изображения спутника для аварии и сгорания. Если спутник снижается на высоту менее 68 миль, то он сгорает в атмосфере. Движущееся изображение спутника будет заменено его светящейся красной версией, которая прилипнет к поверхности Марса; это похоже на то, что можно увидеть на реаль ном дисплее управления полетами.
♦ Спутник теряется в космосе, если у него заканчивается топливо. Хотя это нереально, спутник улетает за пределы экрана и в космические глубины, если у него закончится топливо. Это будет зудеть над ухом игрока как назойливая муха!
♦ Условия выигрыша разблокируют приз. Если спутник достигает круговой орбиты в пределах целевого диапазона высот, то новый текст будет побуждать игрока нажать клавишу .
♦ Нажатие клавиши изменяет изображение Марса. Когда клавиша
разблокирована, ее нажатие приводит к изменению снимка Марса на радужное
изображение, где холодные цвета представляют участки с высокой влажностью
почвы, а теплые цвета — более сухие участки.
Картограф ирование М арса с помощью орбитального спутника 351
Скорость
Высота
Топливо
Эксцентриситет
Условие выигрыша разрешает картофафирование
Картографируемый Марс имеет цветное наложение для влажных (опенки синего) и сухих (опенки красного) участков
Высота 60-120 миль. Отклонение орбиты. Избегайте верхних слоев атмосферы
в 68 миль
Авария на низкой высоте. Спутник останавливается на Марсе и становится красным
Авария с топливом заставляет спутник улететь за пределы экрана
Стрелка вл ево … Стрелка вправо… Стрелка вве р х… Cm рек а в н и з… Очистить траекторию…
Р и с. 14.12. Игровой эскиз выигрышного и проигрышного результатов в игре “Орбитальный спутник Марса”
Размер спутника и его орбитальная скорость не будут реалистичными для процесса игры, но общее поведение будет правильным. Вы должны суметь правильно вы полнить все орбитальные маневры, описанные в разд. “.Аэродинамика для гейме ров” ранее в этой главе.
Активы игры
Для игры “Орбитальный спутник Марса” вам понадобятся активы. Это два изобра жения спутника, два снимка планеты и звуковой файл. Их можно заготовить сразу в начале процесса либо собрать, когда они вам понадобятся. Последний подход по зволяет вам устраивать эпизодические перерывы в написании программного кода, которые отдельные разработчики предпочитают делать.
Поиск хороших, без авторских прав, графических и звуковых файлов может стать проблемой. Подходящие активы можно отыскать в Интернете бесплатно либо за отдельную плату, но лучше всего, когда это возможно, изготовить свои собствен ные. Это позволяет избежать каких-либо юридических проблем в будущем.
Спрайты (двухмерные значки или изображения), которые я использовал для этого проекта, показаны на рис. 14.13. Вам нужен спутник, красная “сожженная” версия
352
Глава 14
спутника, вид Марса с полярной шапкой по центру и тот же вид с красочным нало жением, который будет представлять картографированные градации влажности почвы. Спрайт спутника я нашел на веб-сайте бесплатных иконок AHA-SOFT ( h t t p ://w w w .a h a - s o f t .c o m / ), а затем скопировал его и перекрасил, для того чтобы сделать разбитую версию. Оба спрайта Марса — это модифицированные для игры снимки NASA.
пш
Р ис. 14 .13. Спутник, разбившийся спутник, снимок М арса и наложение на Марс, используемые в качестве игровых спрайтов
Я изготовил звуковой файл для ситуации, когда спутник запускает свои двигатели. Для этого я использовал генератор белого шума в программе с открытым исходным кодом Audacity. Бесплатную копию программы Audacity можно скачать по адресу h t t p s : / / w w w .a u d a c i t y t e a m .o r g / . Я сохранил файл в формате Ogg Vorbis, стандарт ном формате сжатия звука с открытым исходным кодом, который является бес платным и хорошо работает с Python и pygame. С пакетом pygame можно использо вать и другие форматы, такие как MP3 и WAV, но некоторые из них имеют доку ментированные проблемы либо имеют нативные компоненты, которые могут вызвать юридические проблемы, в случае если вы попытаетесь коммерциализиро вать свою игру.
Эти файлы можно скачать с веб-сайта книги по адресу h t t p s : / /w w w .n o s t a r c h .c o m / i m p r a c t i c a l p y t h o n / как файлы satellite.png, satellite_crash_40x33.png, mars.png, mars water.png и thrust audio.ogg. Скачайте их, сохранив в ту же папку, что и код.
Код
Рис. 14.14 является примером финального игрового экрана, который вы будете строить. К этому рисунку можно регулярно возвращаться, для того чтобы иметь представление о том, что конкретно делает программный код.
Полную программу (mars orbiter.py) можно скачать с h t t p s : / /w w w .n o s t a r c h .c o m / im p r a c t ic a lp y t h o n / .
Картограф ирование М арса с помощью орбитального спутника 353
% Орбитальный спутник Марса
Топливо
□ | Эксцентриситет Щ
X
•’
136.1
0.2860778
Орбитальный спутник Марса испытал неполадку при выведении на орбиту. Используйте двигатели для коррекции круговой орбиты картографирования без исчерпания топлива или сгорания в атмосфере
.
354
Глава 14
Модуль math будет использоваться для гравитационных и тригонометрических рас четов и случайного запуска спутника с произвольным местоположением и скоро стью. Импортируйте пакет pygame, как вы сделали в главе 13, используя вместо pygame псевдоним pg с целью уменьшить печатание с клавиатуры.
Завершите построение цветовой таблицы RGB ф , как это было сделано в главе 13. Это позволит вводить имена цветов вместо кортежей значений RGB, когда вам по требуется задавать один из этих цветов.
Определение метода инициализации класса Satellite
Листинг 14.2 определяет класс s a t e l l i t e и его метод инициализации, который бу дет использоваться для инстанцирования объекта-спутника в игре. Поскольку это определение метода является длинным, оно разделено на два листинга.
Листинг 14.2. Определяет первую часть метода инициализации класса s a t e l l i t e . Файл mars_orWter.py, часть 2
О class Satellite(pg.sprite.Sprite):
“““Объект Satellite, который вращается повернутым
к планете и падает и сгорает.”””
е def init_(self, background):
О super () .init_()
О self.background = background ф self.image_sat = pg.image.load(“satellite.png”).convert()
self.image_crash = pg.image.load(“satellite_crash_40x33.png”) .
ф self.image = self.image_sat
convert()
ф self.rect = self.image.get_rect() ф self.image.set_colorkey(BLACK) # устанавливает прозрачный цвет
Определите класс для объекта-спутника ф; если вам необходимо обновить знания по ОПП, то прочитайте главу 11. Передайте ему класс Sprite пакета pygame, Т . К. объекты, инстанцированные из класса satellite, будут спрайтами. Как описано в главе 13, Sprite — это встроенный класс, который служит шаблоном для создания спрайтов. Ваш новый класс унаследует из этого базового класса функции, которые понадобятся вашим спрайтам. К ним относятся такие важные атрибуты, как rect и image, с которые вы вскоре будете работать.
Затем определите метод_init_о для объекта satellite 0 и передайте ему клю чевое слово self, по соглашению являющееся специальным именем в определении класса, которое ссылается на текущий объект. Также необходимо передать методу фоновый объект background. На этом объекте будет нарисована траектория спут ника.
Картограф ирование М арса с помощью орбитального спутника 355
Внутри метода_init_о сразу же вызовите метод инициализации для встроенно го класса sprite с использованием метода super 0 . Благодаря этому инициализи руется спрайт и устанавливаются необходимые атрибуты объектов rect и image. Используя super, вам не нужно явно ссылаться на базовый класс (sprite). Дополни тельные сведения о методе super см. в листинге 11.5 или в документации на веб странице https://docs.python.org/3/library/functions.html?highlight=super#siiper.
Далее назначьте background переменной self в качестве атрибута объекта О- Затем примените метод image, load() пакета pygame для загрузки двух ваших изображений спутника— один рабочий и другой аварийный— и на том же шаге выполните на них метод convert о © .О н сконвертирует объект в графический формат, который pygame может эффективно использовать после запуска игрового цикла. Без этого шага игра может заметно замедлиться, поскольку формат png конвертируется на лету более 30 раз в секунду. Вы будете использовать только одно из двух изображений спутника за один раз, в зависимости от того, сгорел или нет игрок в атмосфере, поэтому для хранения загруженного и конвертированного изображения используйте обобщенный атрибут self, image О* Изображение несгоревшего спутника будет использоваться по умол чанию; оно будет заменено красным изображением разбившегося спутника, если объект-спутник окажется слишком близко к Марсу.
Теперь получите для изображения информацию о прямоугольнике © . Напомним, что пакет pygame помещает спрайты на объекты прямоугольной поверхности, и во время игры он должен знать размеры и расположение этих прямоугольников. Наконец, сделайте черные части изображения спутника невидимыми ф . Значок спутника находится на черном поле (см. рис. 14.13), и нужно, чтобы разбитое и сгоревшее изображение частично попало на изображение Марса, поэтому с мето дом coiorkey () объекта-изображения используйте константу black, для того чтобы сделать фон значка прозрачным. В противном случае вы увидите черный прямо угольник с наложением красного спутника на красную планету. Обратите внима ние, что если вы хотите набрать эквивалент RGB для черного цвета, то вам нужно ввести его как кортеж: (0, о, 0).
Задание первоначального положения спутника, его скорости, топлива и звука
Листинг 14.3 завершает определение метода инициализации класса satellite. Пер воначальное положение и скорость объекта-спутника выбираются случайно из ограниченного диапазона вариантов; инициализируется ориентация тарелки дис танционного зондирования, заправляется топливный бак, и добавляются звуковые эффекты.
Листинг 14.3. Завершает метод инициализации класса Satellite путем инициализации параметров. Файл mars_orbiter.py, часть 3
ф self.x = random.randrange(315, 425) self.y = random.randrange(70, 180)
356
Глава 14
0 self.dx = random.choice([-3, 3]) 0 self.dy = О О self.heading = 0 # инициализирует ориентацию тарелки О self.fuel = 100 self.mass = 1 self.distance = 0 # инициализирует расстояние между спутником
и планетой #
0 self.thrust = pg.mixer.Sound(’thrust_audio.ogg’) 0 self.thrust.set_volume(0.07) # допустимые значения: 0-1
При запуске игры спутник появится в случайной точке в верхней части экрана. Вы выберете точное местоположение из диапазона значений х и у О- Вы также выберите наугад скорость спутника, но она будет достаточно медленной для того, чтобы спутник не смог уйти с орбиты. Случайно установите скорость, равной -3 или 3. Отрицательные значения приводят к вращению против часовой стрелки, и наоборот. Используйте только атрибут dx (дельта х) 0 и дайте гравита ции самой позаботиться о dy. Как обсуждалось в главе 13, пакет pygame перемещает спрайты по экрану, используя инкрементные изменения в местоположении х (име нуемые дельта х или dx) и инкрементные изменения в местоположении у (именуе мые дельта у или dy). Эти векторные компоненты вычисляются и добавляются к текущей позиции спрайта (self.x, self.y) с каждым игровым циклом. Далее установите атрибут dy равным о о Позже метод gravity о установит перво начальное значение dy, когда он будет ускорять только что инстанцированный спутник вниз экрана в сторону планеты.
Задайте атрибут направления полета (heading) спутника О* Тарелка дистанционно го зондирования, которая будет считывать влажность почвы на поверхности плане ты, всегда должна быть направлена на Марс, и если вы помните из рис. 14.3, то это не произойдет, если вы не преодолеете инерцию. Вы будете использовать метод, который будет фактически поворачивать спутник, поэтому на данный момент про сто инициализируйте атрибут heading значением 0. Теперь долейте в топливный бак 100 единиц топлива О- Если вы хотите связать эту процедуру с реальной жизнью, то она, вероятно, будет представлять собой за ливку 100 кг гидразина, подобного использовавшемуся в зонде Магеллан, который занимался картографированием Венеры.
Далее установите массу объекта, равной 1. Она, в сущности, означает, что в урав нении гравитации вы будете использовать только массу Марса, потому что вы пе ремножаете массы двух объектов между собой. Как было сказано ранее, притяже ние спутника на Марсе является несущественным, поэтому его вычислять не требу ется. Атрибут mass спутника включен для полноты и в качестве заполнителя в случае, если позже вы захотите поэкспериментировать с другими значениями.
Следующий атрибут distance хранит расстояние между спутником и телом, вокруг которого он вращается. Фактическое значение будет рассчитываться методом, ко торый вы определите позже.
Картограф ирование М арса с помощью орбитального спутника 357
Пришло время добавить звуковые эффекты. Звуковой микшер пакета pygame ини циализируется в функции main(), НО ПОКа объявите атрибут thrust для звукового эффекта при создании тяги двигателями 0. Передайте классу sound микшера ко роткий клип белого шума в формате Ogg Vorbis (.ogg). Наконец, установите гром кость воспроизведения, используя значения от 0 до 1 0. Возможно, на вашем ком пьютере потребуется его откалибровать. В идеале вам нужно значение, которое каждый игрок сможет, по крайней мере, услышать, а затем точно настроить с помощью регулятора громкости своего компьютера.
Запуск двигателей и проверка вводимых игроком значений
В листинге 14.4 определены методы thruster о И check keys о класса Satellite. Первый определяет действия, предпринимаемые в случае срабатывания одного из двигателей спутника. Второй проверяет, не взаимодействовал ли игрок с двигате лями, нажимая клавишу со стрелкой.
Листинг 14.4. Определяет методы thruster () и check_keys () для класса Satellite. Файл mars_orbiter.py, часть 4
0 def thruster(self, dx, dy):
““Исполнить действия, ассоциированные с запуском двигателей.”””
0 self.dx += dx self.dy += dy 0 self.fuel -= 2 0 self.thrust.play()
0 def check_keys(self):
“““Проверить нажатие пользователем клавиш со стрелкой
и вызвать метод thruster().”””
0 keys = pg.key.get_pressed()
#
запустить двигатели
0 if keys[pg.K_RIGHT]:
0 self.thruster(dx=0.05, dy=0) elif keys[pg.K_LEFT]:
self.thruster(dx=-0.05, dy=0)
elif keys[pg.K_UP]:
self.thruster(dx=0, dy=-0.05)
elif keys[pg.K_DOWN]:
self.thruster(dx=0, dy=0.05)
Метод thruster о в качестве аргументов принимает self, dx и dy 0. Последние два аргумента, которые могут быть положительными либо отрицательными, немедлен но добавляются в компоненты self.dx и self.dy скорости спутника 0. Далее уро вень топлива снижается на две единицы о Изменение этого значения является
358
Глава 14
одним из способов усложнить либо упростить игру. Завершите метод вызовом ме тода play () на аудиоатрибуте thrust, который создаст шипящий звук О- Обратите внимание, что вместо возврата значений методы ООП обновляют существующие объектные атрибуты. Метод check keys о в качестве аргумента принимает ключевое слово self О* Сна чала вы используете модуль key пакета pygame. Это делается для того, чтобы опре делить, нажал или нет игрок клавишу О- Метод get pressedo возвращает кортеж булевых значений — 1 для True и 0 для False, которые представляют текущее со стояние каждой клавиши на клавиатуре. True означает, что клавиша нажата. Ука занный кортеж может индексироваться с помощью констант клавиш. Список всех констант клавиатуры можно найти по адресу https://www.pygame.org/docs/ref/ key.html.
Например, клавиша со стрелкой вправо (>) равна k right. Если эта клавиша была нажата © , то вызовите метод thruster () и передайте ему значения dx и dy ©. В пакете pygame значения х увеличиваются в правой части экрана, а значения у — в нижней. Поэтому если пользователь нажимает клавишу со стрелкой влево (), то вычтите из dx; схожим образом, если нажата клавишу со стрелкой вверх (), то уменьшите значение dy. Стрелка вправо увеличит dx, а стрелка вниз () уве личит dy. Показания в верхней части экрана помогут игроку соотнести движения спутника с базовыми значениями dx и dy (см. рис. 14.14).
Локализация спутника
По-прежнему находясь в классе satellite, листинг 14.5 определяет метод locate о . Указанный метод вычисляет расстояние спутника от планеты и определяет направ ление полета для наведения антенны на планету. Атрибут distance будет использо ваться позже при расчете силы гравитации и эксцентриситета орбиты. Эксцентри ситет — это мера отклонения орбиты от идеальной окружности.
Листинг 14.5. Определяет метод lo c a te () для класса S a t e l l i t e . Файл m ars_orbiter.py, часть 5
I !
ф def locate(self, planet):
“““Вычислить расстояние и направленность к планете.”””
е рх, ру = planet.х, planet.у о dist_x = self.x - рх dist_y = self.y - ру
#
получить направление к планете, для того чтобы направить тарелку
О planet_dir_radians = math.atan2(dist_x, dist_y) © self.heading = planet_dir_radians * 180 / math.pi © self.heading -= 90 # sprite is traveling tail-first e self.distance = math.hypot(dist_x, dist_y)
Для локализации спутника вам нужно передать методу locate о объекты satellite (self) и planet © . Сначала определите расстояние между объектами в пространст
Картограф ирование М арса с помощью орбитального спутника 359
ве х-у. Получите атрибуты х и у планеты Q ; затем вычтите их из атрибутов х и у спутника
Теперь используйте эти новые переменные расстояния для расчета угла между на правлением полета спутника и планетой с тем, чтобы можно было повернуть спут никовую антенну к планете. Модуль math использует радианы, поэтому задайте ло кальную переменную planet dir radians для хранения направления в радианах и передайте dist х и dist у в функцию math.atan2 (), которая вычислит арктан- генс О Поскольку pygame использует градусы (увы), конвертируйте угол из радиан в градусы, используя стандартную формулу; для этого в качестве альтернативы можно применить модуль math, но иногда неплохо видеть человека за занавесом Q . Этот атрибут объекта-спутника должен быть совместным, и поэтому назовите его self.heading.
В pygame фронт спрайта по умолчанию находится на востоке, т. е. спрайт-спутник вращается хвостом вперед (см. значок спутника на рис. 14.13). Для того чтобы ус тановить тарелку так, чтобы она была направлена на Марс, вам нужно вычесть 90° из направления полета, потому что в пакете pygame отрицательные углы приводят к вращению по часовой стрелке О- Этот маневр не будет задействовать ни один из топливных ресурсов игрока. Наконец, получите евклидово расстояние между спутником и Марсом, используя модуль math для вычисления гипотенузы из компонент* и у О- Вы должны сделать его атрибутом объекта-спутника, т. к. оно будет использоваться позже в других функциях.
ПРИМЕЧАНИЕ В реальной жизни удерживать тарелку спутника направленной к планете можно не сколькими способами, не затрачивая большого объема топлива. Эти способы вклю чают в себя медленное кувыркание или вращение спутника, делая конец тарелки тя желее, чем противоположный конец, используя магнитный момент или внутренние маховики — так называемые реактивные колеса, или импульсные колеса. Маховики задействуют электродвигатели, которые могут работать от солнечных батарей, устра няя необходимость в тяжелом и токсичном жидком топливе.
Вращение спутника и вычерчивание его орбиты
Листинг 14.6 продолжает класс satellite, определяя методы поворота спутниковой антенны к планете и нанесения его траектории позади. Позже, в функции main (), вы добавите программный код, который позволяет игроку стирать и перезапустить траекторию, нажимая клавишу .
Листинг 14.6. Определяет методы rotate () npath() класса Satellite. Файл mars_orbiter.py, часть 6
О def rotate(self):
“““Вращать спутник, используя градусы так, чтобы тарелка
была обращена к планете.”””
О self.image = pg.transform.rotate(self.image_sat, self.heading) ф self.rect = self.image.get_rect()
Глава 14
360 о def path(self) :
“““Обновить позицию спутника и нанести линию
для прослеживания орбитальной траектории.”””
О last_center = (self.x, self.y) ф self.x += self.dx self.y += self.dy
ф pg.draw.line(self.background, WHITE, last_center, (self.x, self.y))
Метод rotate о будет использовать атрибут heading, который вы вычисляете в ме тоде locate () для поворота спутниковой антенны в сторону Марса. Передайте клю чевое слово self в методе rotate о ф . Это означает, что метод rotate о автомати чески примет имя объекта-спутника в качестве аргумента, когда он будет вызван позже. Теперь поверните изображение спутника, используя метод transform, rotate о па кета pygame ф . Передайте ему исходное изображение и затем атрибут heading; на значьте результат атрибуту self. image, для того чтобы не ухудшить исходное глав ное изображение. С каждым игровым циклом вам нужно будет преобразовывать изображение, а преобразование изображения быстро ухудшает его качество. По этому всегда держите главное изображение про запас и создавайте новую копию всякий раз, когда выполняете преобразование. Завершите функцию, получив объект rect преобразованного изображения ф . Далее следует определить метод с именем path () и передать ему ключевое слово self ф . Он нарисует линию, отмечающую траекторию спутника, и поскольку для нанесе ния линии вам нужны две точки, задайте переменную, регистрирующую местопо ложение центра спутника в виде кортежа до его перемещения ф . Затем увеличьте местоположения х и у на величину атрибутов dx и dy ф . Завершите метод, исполь зуя метод draw, line о пакета pygame ф . Этот метод нуждается в объекте-рисунке, поэтому передайте ему атрибут background, а затем цвет линии и кортежи преды дущего и текущего местоположений х-у.
Обновление объекта Satellite
Листинг 14.7 обновляет объект-спутник и завершает определение класса. Объекты класса sprite почти всегда имеют метод update о, который при выполнении игры вызывается один раз за кадр. Все, что происходит со спрайтом, например движе ние, изменение цвета, взаимодействие с пользователем и т. д., включается в этот метод. Во избежание излишней загроможденное™ методы update () в основном вызывают другие методы.
Листинг 14.7. Определяет метод update () для класса S a t e l l i t e . Файл m ars_orbiter.py, часть 7
ф def update(self):
“““Обновлять объект-спутник во время игры.”””
ф self.check_keys()
Картограф ирование М арса с помощью орбитального спутника 361
ф self.rotate()
О self.path() ф self.rect.center = (self.x, self.y)
изменить изображение на огненно-красное, если спутник #
находится в атмосфере #
О if self.dx == 0 and self.dy == 0: self.image = self.image_crash self.image.set_colorkey(BLACK)
Начните с определения метода update о и передачи ему объекта, или ключевого слова self О Затем вызовите определенные ранее методы. Первый из них выпол няет проверку наличия взаимодействия со стороны игрока, осуществляемую через клавиатуру ф . Второй вращает объект-спутник так, чтобы его тарелка продолжала указывать на планету ф . Последний метод обновляет местоположение х-у спутни ка и наносит позади него траекторию с целью визуализации его орбиты ф .
Программа должна отслеживать местоположение спрайта спутника, когда он вращается вокруг Марса, поэтому задайте атрибут rect.center и установите его равным текущему местоположению х-у спутника ф .
Последний фрагмент кода изменяет изображение спутника в случае, если игрок па дает и сгорает в атмосфере ф . Верхние слои марсианской атмосферы находятся примерно в 68 милях над ее поверхностью. По причинам, которые я объясню поз же, будем считать, что значение высоты 68 миль (которое измеряется в пикселах от центра планеты) приравнивается к верхним слоям атмосферы. Если во время игры спутник опускается ниже этой высоты, то функция main () установит его скорость, представленную значениями dx и dy, равной 0. Проверьте, что эти значения равны 0, и если это так, то измените изображение на image crash и установите его фон прозрачным (как вы делали ранее для главного изображения спутника).
Определение метода инициализации класса Planet
В листинге 14.8 определен класс Planet, который будет использоваться для инстан цирования объекта planet.
Листинг 14.8. Начинает определение класса P la n e t. Файл mars_orbiter.py, часть 8
ф class Planet(pg.sprite.Sprite):
“““Объект-планета, который вращается и проецирует
гравитационное поле.”””
ф def init(self):
super().init_()
ф self.image_mars = pg.image.load(“mars.png”).convert()
self.image_water = pg.image.load(“mars_water.png”).convert() ф self.image_copy = pg.transform.scale(self.image_mars, (100, 100))
362
Глава 14
ф self.image_copy.set_colorkey(BLACK) ф self.rect = self.image_copy.get_rect()
self.image = self.image_copy
О self.mass = 2000 ф self.x = 400 self.y = 320 self.rect.center = (self.x, self.y)
ф self.angle = math.degrees(0)
self.rotate_by = math.degrees(0.01)
Вы, вероятно, уже хорошо знакомы с первыми шагами по созданию класса Planet. Прежде всего, вы именуете класс с большой буквы, а затем передаете ему класс sprite с тем, чтобы он удобно наследовал функции у этого встроенного в пакет pygame класса ф. Далее вы определяете метод_init__о, или метод инициализа ции вашего объекта planet ф. Затем, как и для класса satellite, вы вызываете ме тод инициализации super ().
Загрузите снимки в качестве атрибутов и одновременно конвертируйте их в графи ческий формат пакета pygame ф. Вам нужен как обычный снимок Марса, так и снимок картографированной влажности грунта. Вы могли бы использовать спрайт спутника в его естественных размерах, но снимок Марса является слишком круп ным. Отмасштабируйте снимок в 100x100 пикселов ф и назначьте масштабиро ванный снимок новому атрибуту, с тем чтобы повторные преобразования не ухуд шали главный снимок.
Теперь сделайте прозрачный цвет преобразованного снимка черным, как вы делали ранее с изображением спутника ф. Все спрайты в pygame “монтируются” на прямо угольных поверхностях, и если вы не сделаете черный цвет невидимым, то углы поверхности планеты могут накладываться и покрывать белую орбитальную траек торию, наносимую спутником (рис. 14.15).
Р и с. 14 .15. Углы прямоугольника rect М арса, наклады ваю щиеся на орбитальную траекторию
Картограф ирование М арса с помощью орбитального спутника 363
Как всегда, получите объект rect спрайта ф . Впереди будет еще одно преобразо вание, поэтому скопируйте атрибут снимка еще раз и назначьте ему логическое имя self, image.
Для того чтобы применить силу гравитации, планете нужна масса, поэтому объяви те атрибут mass и назначьте ему значение 2000 О- Ранее вы назначили спутнику массу 1; это означает, что Марс лишь в 2000 раз массивнее спутника! Это нормаль но, потому что вы не работаете в реальных единицах измерения, и шкала времени, и расстояния отличаются от реальности. Если расстояния шкалируются так, что спутник находится всего в нескольких сотнях пикселов от Марса, то также должна шкалироваться и гравитация. Невзирая на это, спутник все равно будет вести себя реалистично по отношению к гравитации.
Значение массы планеты было определено экспериментально. Для шкалирования силы гравитации можно либо изменить величину массы, либо позже применить величину гравитационной постоянной (g).
Установите атрибуты х и у объекта planet равными центральной точке экрана — вы задействуете размер экрана 800 х 645 в функции main о — и назначьте эти значения центру объекта rect ф . Наконец, задайте атрибуты, которые вам понадобятся для медленного вращения Марса вокруг своей оси ф . Вы будете использовать тот же метод transform, rotate (), который вы применяли для поворота спутника, поэтому вам нужно соз дать атрибут угла angle. Затем используйте атрибут rotate by для назначения при ращения в градусах, на которое этот угол поворота изменяется с каждым игровым циклом.
Вращение планеты
Листинг 14.9 продолжает класс Planet, определяя его метод rotate о. Этот метод вращает планету вокруг своей оси, внося небольшие изменения с каждым игровым циклом.
Листинг 14.9. Определяет метод вращения планеты вокруг своей оси. Файл mars_orbiter.py, часть 9
ф def rotate(self):
“““Вращать снимок планеты с каждым игровым циклом.”””
е last_center = self.rect.center ф self.image = pg.transform.rotate(self.image_copy, self.angle)
self.rect = self.image.get_rect()
ф self.rect.center = last_center ф self.angle += self.rotate_by
Метод rotate о в качестве аргумента также принимает объект ф . Поскольку квад ратный снимок Марса вращается, объект rect (ограничительный прямоугольник) остается стационарным и должен расширяться для размещения новой конфигура
364
Глава 14
ции (рис. 14.16). Это изменение размера может повлиять на центральную точку объекта rect, поэтому задайте переменную предыдущего центра last center и установите ее равной текущей центральной точке планеты 0 . Если вы этого не сделаете, то во время игры Марс будет колебаться вокруг своей оси.
Ограничительный прямоугольник перед вращением
Новый ограничительный прямоугольник
Р и с. 14 .16. Ограничительны й прямоугольник изменяет размер для разм ещ ения вращ аю щ ихся снимков
Затем поверните скопированный снимок с помощью метода transform, rotate о па кета pygame И назначьте его атрибуту self .image О; указанному методу необходимо передать скопированный снимок и атрибут angle. Сразу после поворота сбросьте атрибут rect снимка и переместите его центральное местоположение обратно в last center, для того чтобы смягчить любое смещение rect, которое произошло во время поворота Q .
Когда инстанцируется объект planet, атрибут угла будет начинаться с 0°, а затем увеличиваться на величину, равную ол , назначаемую в атрибуте rotate by, с каж дым кадром О
Определение методов gravityQ и updateQ
Листинг 14.10 завершает класс Planet, определяя методы gravity () и update (). В главе 13 вы рассматривали гравитацию как константу, приложенную в направле нии у. Применяемый здесь метод несколько сложнее, поскольку учитывает рас стояние между двумя объектами.
Листинг 14.10. Определяет методы gravity () и update () класса Planet. Файл mars_orbiter.py, часть 10
О def gravity(self, satellite):
“““Вычислить воздействие гравитации на спутник.”””
© G = 1.0
гравитационная постоянная для игры #
Картограф ирование М арса с помощью орбитального спутника 365
О dist_x = self.x - satellite.х dist_y = self.y - satellite.у distance = math.hypot(dist_x, dist_y)
нормализовать в единичный вектор #
О dist_x /= distance dist_y /= distance
приложить гравитацию #
Q force = G * (satellite.mass * self.mass) / (math.pow(distance, 2)) ф satellite.dx += (dist_x * force)
satellite.dy += (dist_y * force)
Q def update(self):
“““вызвать метод rotate.””” self.rotate()
Определите метод gravity о и передайте ему ключевое слово self и объект- спутник ф . Вы по-прежнему находитесь в классе Planet, поэтому ключевое слово self здесь представляет Марс.
Начните с объявления локальной переменной g; заглавная g — это универсальная гравитационная постоянная, также именуемая константой пропорциональности 0 . В реальной жизни это очень малое, эмпирически полученное число, которое, по сути дела, представляет собой конверсионное число для того, чтобы единицы изме рения работали правильно. В игре реальные единицы измерения не используются, поэтому установите ее равной 1; благодаря этому она не повлияет на уравнение гравитации. Во время разработки игры вы можете скорректировать эту константу вверх или вниз в целях точной настройки гравитации и ее влияния на орбитальные объекты.
Вам нужно знать, как далеко друг от друга находятся два объекта, поэтому получи те их расстояние в направлении х и направлении у Q . Затем примените метод hypot () модуля math для получения евклидова расстояния. В уравнении гравитации оно будет представлять г.
Поскольку вы собираетесь обратиться к магнитуде расстояния между спутником и Марсом в уравнении гравитации напрямую, то от вектора расстояния вам нужно получить только направление. Поэтому разделите dist х и dist у на расстояние distance, для того чтобы “нормализовать” вектор в единичный вектор с магниту дой, равной 1 О- Вы, в сущности, делите длину каждой стороны прямоугольного треугольника на его гипотенузу. Это оставляет нетронутым направление вектора, представленное относительными разницами в dist x и dist у, но устанавливает его магнитуду равной 1. Обратите внимание* что если не выполнить этот шаг нормали зации, то результаты будут нереалистичными, но интересными (рис. 14.17).
Вычислите силу гравитации с помощью уравнения Ньютона, которое я описал в разд. “Закон универсальной гравитации (или закон всемирного тяготения)” ранее в этой главе О- Закончите умножением нормализованных расстояний на силу
366
Глава 14
„ 1 -
Высота орбиты должна быть в пределах 69-120 миль Стрелка вниз = увеличить dy Эксцентриситет орбиты должен быть , эти функции будут вызваны функцией maino, и снимок планеты будет заменен красочным наложением, которое, мы притворимся, представляет со держание влаги в грунте. Когда игрок отпустит клавишу, вернется нормальный вид Марса. Проверки нажатия данной клавиши также будут выполняться в функции main ().
Листинг 14.13. Определяет функции, позволяющие игроку картографировать влажность грунта на Марсе. Файл mars_orbiter.py, часть 13
О def таpping_on(planet):
“““Показать снимок влажности грунта планеты.”””
ф last_center = planet.rect.center ф planet.image_copy = pg.transform.scale(planet.image_water, (100, 100)) ф planet.image_copy.set_colorkey(BLACK)
planet.rect = planet.image_copy.get_rect() planet.rect.center = last_center
ф def mapping_off(planet):
“““Восстановить нормальный снимок планеты.”””
ф planet.image_copy = pg.transform.scale(planet.image_mars, (100, 100))
planet.image_copy.set_colorkey(BLACK)
Начните с определения функции, которая в качестве аргумента принимает объект planet ф. Создайте переменную last center, как это было сделано в листинге 14.9; она будет использоваться для предотвращения колебаний планеты на своей оси ф . Затем прошкалируйте водный снимок Марса в тот же размер, что и нормальный снимок, и назначьте его атрибуту image copy планеты, поскольку многократные
370
Глава 14
преобразования снимка ухудшают его качество О- Сделайте фон снимка прозрач ным Q , получите его прямоугольник rect и установите центр прямоугольника рав ным переменной last center; благодаря этому Марс будет оставаться в центре эк рана.
Теперь определите еще одну функцию для случая, когда игрок перестает активно картографировать Марс О- В качестве аргумента она также принимает объект planet. Вам нужно только сбросить снимок планеты в исходную версию О- По скольку вы по-прежнему используете атрибут image сору, то снова получать объект rect не требуется, но при этом следует сделать цвет прозрачным.
Отбрасывание тени
Листинг 14.14 определяет функцию, которая придает Марсу ’’темную сторону” и отбрасывает тень за планетой. Тень будет представлять собой черный полупро зрачный прямоугольник, правый край которого совпадает с центром спрайта пла неты (рис. 14.19). При этом будем считать, что Солнце находится справа от экрана и что на Марсе сейчас весеннее или осеннее равноденствие.
Орбитальный спутник Марса испытал неполадеу при выведении на орбиту Используйте двигатели для корреви« рутовой орбиты картографирования без исчерпания топлива или сгорания в атмосфере
I I I
Орбитальный спутник Марса испытал неполадку при выведении на орбиту Используйте двигатели для корретии «рутовой орбиты каргарафщювания без исчерпаний топлива или сгорания в атмосфере
Высота орбиты должна быть в предел* 69-120 миль Эксцентриситет орбиты должен быть , то он выходит из полноэкранного режима, поэтому сбросьте размер экрана в 800 х 645 пикселов с помощью метода display.set_mode (), который вы вызвали в самом начале функции maino О* Если игрок нажимает клавишу , то заполните фон черным цветом, который сотрет белую орбитальную траекторию спутника Q . Когда игрок нажимает клавишу со стрелкой, объект-спутник воспроизводит шипя щий звук, но ничто в его методе check keys о не говорит ему остановиться. Поэто му передайте пакету pygame любые события keyup ф ; когда pygame засечет, что иг рок отпустил клавишу со стрелкой, то вызовите метод stop о на двигателе thrust для остановки воспроизведения звука О- Для картографирования Марса игрок должен будет удерживать клавишу , по этому используйте ТО же событие KEYUP ДЛЯ вызова функции mapping off о . Это приведет к сбросу снимка планеты в его нормальное, не картографированное со стояние.
Наконец, проверьте, является ли переменная mapping enabied истинной, что означа ет, что игрок достиг условий выигрыша и готов к картографированию Марса ® . Если ОН нажмет клавишу , ТО вызовите функцию mapping_on () , для того чтобы вместо обычного вида планеты показать снимок-наложение влажности грунта.
Применение гравитации, расчет эксцентриситета и обработка аварий
В листинге 14.18 продолжается ЦИКЛ while функции main (), воздействуя на спутник гравитацией, а затем вычисляя эксцентриситет его орбиты. Значение эксцентриси
376
Глава 14
тета будет обнаруживать, является ли орбита круговой, т. е. одним из условий вы игрыша в игре. Указанный листинг также размывает фон и реагирует на аварийные условия исчерпания топлива или сгорания в атмосфере.
Листинг 14.18. Применение гравитации, расчет эксцентриситета и устранение условий для аварий. Файл m ars.orbiter.py, часть 18
получить направление полета и расстояние до планеты #
и применить гравитацию #
О sat.locate(planet)
planet.gravity(sat)
вычислить эксентриситет орбиты #
e if tick_count % (eccentricity_calc_interval * fps) == 0:
eccentricity = calc_eccentricity(dist_list)
0 dist_list = []
повторно перенести фон для команды рисования - предотвращает #
очистку траектории #
О screen.blit(background, (0, 0))
условия аварии, связанной с топливом/высотой #
0 if sat.fuel = 69 and sat.distance 0 . Поместите текст в скобках, т. к. функция instruct iabei о ожидает список. Сделайте цвет текста светло-синим и поместите его в центре экрана.
Далее установите переменную активации картографирования mapping enabled рав ной True Q ; в противном случае, если орбита выходит за пределы целевых пара метров, установите для нее значение False.
Завершите вызовом метода update () спрайта планеты через спрайтовую группу Q , а затем фактически нарисуйте его на экране О* Аргументом метода draw () является screen, т. е. объект, на котором будут отрисованы спрайты. Повторите эти шаги для спрайта спутника.
Вывод на экран инструкций и телеметрии и отбрасывание тени
Листинг 14.20 завершает цикл while и функцию maino, выводя на экран инструк ции, показания датчиков и тень планеты. Текст введения в игру будет показываться при запуске в течение лишь короткого времени.
Листинг 14.20. Выводит на экран текст и тень планеты и вызывает функцию main (). Файл mars_orbiter.py, часть 20
#
показывать вводный текст в течение 15 секунд
О if Pg•time.get_ticks() . В результате этого будут вызваны изо бражения различных орбитальных маневров, таких как гомановский переход или однотангенциальное сжигание с циклическим просмотром рисунков. Обязательно включите комментарии о сильных и слабых сторонах каждого технического приема и приостановите игру до тех пор, пока руководство не будет открыто.
Аэродинамическое торможение
Аэродинамическое торможение — это способ экономии топлива, который для за медления космического аппарата использует атмосферное трение (рис. 14.22). Ско пируйте и отредактируйте программу mars orbiter.py с целью включения в нее аэродинамического торможения. В функции main () установите самую низкую вы игрышную высоту равной 70 милям и самую низкую безопасную высоту равной
382
Глава 14
Р ис. 14 .22. Скругление орбиты с использованием атмосф еры вместо ретроградного сжигания топлива
60 милям. Если высота спутника составляет от 60 до 70 миль, уменьшите его ско рость на небольшую величину. На рис. 14.23 показан пример использования аэродинамического торможения в иг ре для скругления эллиптической орбиты. Верхние слои атмосферы были установ лены равными высоте 80 миль. Аэродинамическое торможение служит той же цели, что и ретроградное сжигание топлива в периапсиде, но вы должны быть
Ш Орбитальный спутник Марса
X
Высота орбиты должна быть в пределах 69-120 миль. Эксцентриситет орбиты должен быть » import os
0 » > os.getcwd()
‘С:\Python35\Lib\idlelib’
0 > » os.chdir(‘С:\Ру^оп35\Материалы по Python 3’)
» > os.getcwd() ‘С: \Ру^оп35\Материалы по Python 3’
О »> os.chdir (г ‘С: \РуЬ1юп35\Материалы no Python 3\Наложение снимков планеты’)
» > os.getcwd()
О С:\Ру^оп35\Материалы по Python 3\Наложение снимков планеты’ 0 > » os•path.join(‘Наложение снимков планеты’, ‘stack_8 ‘, ‘8file262.jpg’)
‘Наложение снимков rmaHeTbi\stack_8\8file262.jpg’
О > » os.path.nonnpath(‘С :/Python35/Материалы no Python 3’)
‘С: \Ру^оп35\Материалы no Python 3’
О > » os.chdir (‘С:/Python35’)
» > os.getcwd() ‘С :\Python35’
После импорта модуля os для доступа к функционалу, зависящему от операцион ной системы О, получите текущий рабочий каталог, или cwd 0. Текущий рабо чий каталог назначается процессу при его запуске, т. е. при запуске скрипта из ко мандной оболочки указанный каталог оболочки и скрипта будет одним и тем же. Для программы на Python текущий рабочий каталог — это папка, содержащая про грамму. Когда вы получите текущий рабочий каталог, вам будет показан полный путь. Обратите внимание, что в целях экранирования символов обратной косой черты, применяемых в качестве разделителей имен файлов, необходимо использо вать дополнительные обратные косые черты. Далее вам потребуется изменить текущий рабочий каталог посредством метода os.chdir о 0, передав ему полный путь в кавычках, указав двойные обратные ко
У лучш ение астроф отосъемки с помощью наложения снимков планеты 389
сые черты. Затем вы снова получаете текущий рабочий каталог, который покажет новый путь. Если вы не хотите использовать двойную обратную косую черту, то перед сим вольным аргументом с именем пути можно ввести г, преобразовав его в сырую (не обработанную) символьную цепочку О* В сырых символьных цепочках использу ются различные правила для последовательностей с экранированием обратной ко сой черты, но даже сырая символьная цепочка не может заканчиваться одной обратной косой чертой. Путь по-прежнему будет показываться с двойной обратной косой чертой О Если вы хотите, чтобы ваша программа была совместима со всеми операционными системами, то примените метод os.path, join о и передайте ему имена папок и файлов без символа-разделителя Q . Методы os.path осведомлены об используе мой вами системе и возвращают правильные разделители. Это позволяет осуществ лять независимую от платформы манипуляцию именами файлов и папок.
Метод o s . path. normpath () корректирует разделители с учетом используемой вами системы о В приведенном выше примере с Windows неправильные UNIX- подобные разделители заменяются обратными косыми чертами. Привычная нам ОС Windows также поддерживает использование прямой косой черты и автоматически сделает преобразование ф . Полный путь к каталогу — от корня вниз — называется абсолютным путем. Для упрощения работы с каталогами можно пользоваться сокращениями, именуемые относительными путями. Относительные пути интерпретируются с точки зрения текущего рабочего каталога. Если абсолютные пути начинаются с прямой косой черты или метки диска, то относительные пути записываются иначе. В следующем ниже фрагменте кода можно поменять каталоги без ввода абсолютного пути — Python осведомлен о новом местоположении, потому что он находится внутри текущего рабочего каталога. За кулисами относительный путь присоединяется к пути, ведущему к текущему рабочему каталогу, формируя полный абсолютный путь.
os.getcwd()
‘С: \Python35\MaTepnajibi по Python 3’
o s .chdir(‘Наложение снимков планеты’) » > os.getcwd() ‘С: \Python35\MaTepnajibi по Python 3\Наложение снимков планеты1
Идентифицировать папки и еще больше сэкономить на вводе можно с помощью точки (.) и двойной точки (..). Например, в Windows .\ ссылается на текущий рабочий каталог, а .. \ указывает на родительский каталог, который содержит текущий рабочий каталог. Точка также может использоваться для получения абсо лютного пути к вашему текущему рабочему каталогу:
» > o s . path. abspath (’ . ‘)
‘С :\Руthon3 5\Материалы по Python 3\Наложение снимков планеты\для_книги’
“Точечные” папки могут использоваться в Windows, macOS и Linux. Дополнитель ные сведения о модуле os см. на веб-странице https://d0cs.pyth0n.0rg/3/Iibrary/ os.html.
390
Глава 15
Модуль утилит командной оболочки
Модуль shutii утилит командной оболочки обеспечивает высокоуровневые функ ции для работы с файлами и папками, такие как копирование, перемещение, пере именование и удаление. Поскольку он является частью стандартной библиотеки Python, загрузить shutii можно, просто введя import shutii. Примеры использова ния указанного модуля приведены в разделах кода этой главы. Между тем доку ментацию по данному модулю можно найти по адресу https://docs.python.org/ 3.7/library/shutil.html.
Видеоролик
Брукс Кларк записал используемый в данном проекте цветной видеоролик Юпите ра в ветреную ночь в Хьюстоне, штат Техас. Это mov-файл объемом 101 Мбайт длительностью около 16 секунд.
Длительность видеоролика является намеренно короткой. Период вращения Юпи тера составляет около 10 часов. Это означает, что неподвижные фотографии могут размываться с продолжительностью экспозиции всего в минуту, и признаки, кото рые вы хотите усилить за счет наложения видеокадров, могут менять положение, значительно усложняя данный процесс.
Для конвертации видеокадров в отдельные снимки я использовал бесплатный на бор мультимедийных программ Free Studio, разработанный в DVDVideoSoft. Инст румент конвертации формата Free Studio в JPG (Video to JPG Converter) позволяет захватывать изображения с постоянными времешшми или кадровыми интервала ми. Я установил интервал для выборки кадров по всей длине видео. Это было сде лано для того, чтобы улучшить шансы захвата некоторых снимков, когда воздух был неподвижен и видимость была хорошей.
Несколько сотен снимков должно быть достаточно для того, чтобы продемонстри ровать очевидное улучшение за счет наложения. В этом случае я захватил 256 кад ров.
Папку со снимками под названием video frames можно найти онлайн вместе с ре сурсами книги по адресу https://www.nostarch.com/impracticalpython/. Скачайте эту папку и сохраните ее.
Пример кадра из видео в оттенках серого показан на рис. 15.2. Видно, что полосы облаков Юпитера слабые и нечеткие, Большое красное пятно не различимо, и сни мок страдает от низкой контрастности, являющейся распространенным побочным эффектом увеличения. Шумовые артефакты также придают Юпитеру зернистый вид.
В дополнение к этим проблемам ветер тряс камеру, и неточное отслеживание за ставило планету дрейфовать в сторону к левой стороне кадра. Пример бокового дрейфа можно увидеть на рис. 15.3, в котором я наложил пять случайно выбранных кадров с черным фоном, сделанным прозрачным.
Улучш ение астроф отосъемки с помощью наложения снимков планеты 391
Р и с. 15.2. Пример кадра из видеоролика с Ю питером
Р ис. 15.3. Пример тряски и д рейф а на видеоролике с Ю питером, основанный на пяти случайно выбранных кадрах
Движение не обязательно имеет плохие последствия, потому что подергивания снимка могут сгладить дефекты, ассоциированные с поверхностью CCD-датчика, пылью на объективе или датчике и т. д. Но ключевое допущение относительно на ложения снимков заключается в том, что в результате снимки идеально выравни ваются, благодаря чему по мере усреднения снимков постоянные признаки, такие как полосы облаков Юпитера, усиливают друг друга.
Для того чтобы соотношение сигнал/шум было высоким, снимки должны быть за регистрированы. Регистрация изображений1 — это процесс преобразования дан ных в одну и ту же систему координат с целью их сравнения и интеграции. Регист рация, пожалуй, является самой сложной частью наложения снимков. Астрономы обычно используют коммерческое программное обеспечение, такое как RegiStax, RegiStar, Deep Sky Stacker или CCDStack, которое помогает им выравнивать и на кладывать свои астрофотоснимки поверх друг друга. Тем не менее вы попробуете это на практике и проделаете это самостоятельно с помощью языка Python.
1 Регистрация снимков (image registration) — это процесс координатного совмещения двух или более снимков одной и той же сцены, сделанных в разное время, с разных точек зрения и/или разными датчиками. По сути дела, она геометрически выравнивает два снимка — эталонный и датчиковый. См. h ttp s ://w w w .s c ie n c e d ire c t.c o m /s c ie n c e /a rtic le /p ii/S 0 2 6 2 8 8 5 6 0 3 0 0 1 3 7 9 . — Прим. перев.
392
Глава 15
Стратегия
Приведем необходимые для наложения снимков шаги (первый из них уже завер шен):
Извлечь снимки из видеозаписи.
Обрезать изображения вокруг Юпитера.
Прошкалировать обрезанные снимки в тот же размер.
Совместить снимки в один снимок.
Улучшить и отфильтровать итоговый снимок.
Код
Все шаги можно включить в одну программу, но я решил распределить их по трем отдельным программам. Это связано с тем, что обычно по пути хочется остано виться и проверить результаты, плюс может возникнуть желание выполнить более поздние процедуры, такие как улучшение, без необходимости полностью переза пускать весь рабочий поток. Первая программа будет обрезать снимки и масштаби ровать их, вторая будет их накладывать, а третья — улучшать.
Код обрезки и масштабирования снимков
Сначала нужно выполнить регистрацию снимков. В случае больших ярких объек тов, таких как Луна и Юпитер, одним из подходов в астрофотографии является обрезка каждого снимка так, чтобы его четыре границы были касательными к по верхности тела. В результате этого удаляется значительная часть неба и смягчаются любые проблемы, связанные с дрожанием и дрейфом. Масштабирование обрезан ных снимков обеспечивает их одинаковый размер и будет немного их сглаживать, уменьшая шум.
Программу cropnscaleim ages.py можно скачать с h ttp s ://w w w .n o s ta rc h .c o m / im p r a c tic a lp y th o n /. Сохраните ее в каталоге, который содержит папку с захвачен ными видеокадрами.
Импорт модулей и определение функции mainQ
Листинг 15.2 импортирует модули и определяет функцию maino, которая выполня ет программу crop_n_scale_images.py.
Листинг 15.2. Импортирует модули и определяет ф ункцию m a in (). Файл crop_n_scaleJm ages.py, часть 1
О import os
import sys О import shutii ф from PIL import Image, ImageOps
Улучш ение астроф отосъемки с помощью наложения снимков планеты 393
def main () :
“““Взять начальную папку, скопировать папку, выполнить функцию обрезки и очистить папку.”””
получить имя папки в текущем рабочем каталоге #
с оригинальными видеоснимками #
О frames_folder = ‘video_frames’
подготовить файлы и папки #
О del_folders(‘cropped’) О shutii.copytree(frames_folder, ‘cropped’)
run cropping function #
print(“начать обрезание и шкалирование…”)
ф os.chdir(‘cropped’)
crop_images ()
ф clean_folder(prefix_to_save=‘cropped’) # удалить
print(“Готово! \n”)
необрезанные оригиналы #
Начните с импортирования модулей операционной системы (os) и системных мо дулей (sys) ф. Импорт модуля os уже включает в себя импорт модуля sys, но этот функционал в будущем может выйти из употребления, поэтому лучше всего им портировать sys вручную. Модуль shutii содержит описанные ранее утилиты ко мандной оболочки ф. Из библиотеки обработки изображений вы будете использо вать модуль image для загрузки, обрезки, конвертации и фильтрации снимков; так же вам понадобится модуль imageops для шкалирования снимков ф. Обратите внимание, что в инструкции import нужно использовать библиотеку PIL, а не мо дуль pillow.
Начните функцию main () с назначения переменной f rames foider имени начальной папки ф. Эта папка содержит все исходные снимки, захваченные из видеоролика. Вы сохраните обрезанные снимки в новой папке с именем cropped, но утилиты ко мандной оболочки не будут создавать эту папку, если она уже существует, поэтому вызовите функцию dei foiders о , которую вы напишете через мгновение ф. В том виде, как она написана, эта функция не выдаст ошибку, если папка не существует, поэтому ее можно безопасно выполнять в любое время.
Всегда нужно работать с копией оригинальных снимков, поэтому используйте ме тод shutii. copytree о копирования папки, содержащей оригиналы, в новую папку с именем cropped ф. Теперь перейдите в эту папку ф и вызовите функцию crop images (), которая будет обрезать и масштабировать снимки. Вслед за этим выполните функцию ciean foider (), которая удаляет исходные видеокадры, кото рые были скопированы в папку cropped и все еще болтаются там без надобности ф. Обратите внимание на использование имени параметра при передаче аргумента функции clean folder (), поскольку это делает назначение функции очевиднее.
394
Глава 15
Выведите сообщение “Готово!”, уведомив пользователя о том, что программа за вершила свою работу.
Удаление и очистка папок
Листинг 15.3 определяет вспомогательные функции удаления файлов и папок про граммы crop n scale images.py. Модуль shutii откажется создавать новую папку, если в целевом каталоге уже существует папка с таким же именем. Если вы хотите выполнить эту программу более одного раза, то сначала необходимо удалить или переименовать существующие папки. Указанная программа также переименует снимки после их обрезки, и вы наверняка захотите удалить исходные снимки, пре жде чем начать их накладывать. Поскольку там будут сотни снимковых файлов, эти функции будут автоматизировать задачу, которая в противном случае окажется трудоемкой.
Листинг 15.3. Определяет функции для удаления папок и файлов. Файл c ro p _n _sca lej’mages.py, часть 2
О def del_folders(name):
“““Если в каталоге существует папка с именованным префиксом,
то удалить ее.”””
е contents = os.listdir() О for item in contents:
О if os.path.isdir(item) and item.startswith(name):
e shutii.rmtree(item)
ф def clean_folder(prefix_to_save):
“““Удалить все файлы в папке за исключением тех,
которые имеют именованный префикс.”””
Q files = os.listdir() for file in files: ф if not file.startswith(prefix_to_save):
О os.remove(file)
Определите функцию del folders о , которая удаляет папки ф. Единственным ее аргументом будет имя папки, которую вы хотите удалить.
Далее перечислите содержимое папки О , а затем начните перебирать содержимое в цикле О- Если указанная функция обнаруживает элемент, который начинается с имени папки и также является каталогом о, ТО Примените метод shutii. rmtree о для удаления папки Q . Как вскоре будет видно, для удаления папки используется другой метод, нежели для удаления файла.
ПРИМЕЧАНИЕ Всегда будьте осторожны, когда используете метод rmtree (), т. к. он удаляет папки и их содержимое безвозвратно. Можно стереть большую часть вашей системы, поте рять важные документы, не связанные с проектами Python, и даже сломать ваш ком пьютер!
Улучш ение астроф отосъемки с помощью наложения снимков планеты 395
Теперь определите вспомогательную функцию “очистки” папки и передайте ей префикс файлов, которые вы не хотите удалять О- Сначала это немного противо речит здравому смыслу, но, поскольку вы хотите сохранить только последнюю партию обработанных вами снимков, вам не нужно беспокоиться о явном перечис лении любых других находящихся в папке файлов. Если файлы не начинаются с указанного префикса, например cropped, то они автоматически удаляются.
Процесс аналогичен предыдущей функции. Перечислите содержимое папки и нач ните перебирать список в цикле Q . Если файл не начинается с указанного вами префикса ф , то используйте метод os. remove () для его удаления 0 .
Обрезка, масштабирование и сохранение снимков
Листинг 15.4 регистрирует захваченные из видео кадры путем укладки рамки во круг Юпитера и обрезки снимка по рамке (рис. 15.4). Этот метод хорошо работает с яркими снимками на черном поле (еще один пример см. в разд. “Дальнейшее чте ние” далее в этой главе).
Оригинальный видеокадр
Обрезанный снимок
Р ис. 15.4. О брезка оригинального видеокадра до разм ера Ю питера для выравнивания снимков
Обрезая снимки плотно вокруг Юпитера, вы решаете все проблемы дрожания и дрейфа.
Каждый обрезанный снимок также масштабируется в более крупный и единообраз ный размер и слегка сглаживается ради уменьшения шума. Обрезанные и отмас- штабированные снимки будут храниться в собственной папке, которую позже соз даст функция main ().
Листинг 15.4. Обрезает исходные видеокадры в прямоугольник вокруг Юпитера и масштабирует их. Файл crop_n_scaleJmages.py, часть 3
О def crop_images():
“““Обрезать и отмасштабировать снимки планеты в прямоугольник
вокруг планеты.”””
Q files = os.listdir() О for file_num, file in enumerate(files, start=l):
О with Image.open(file) as img:
0 gray = img.convert(‘L’) О bw = gray.point(lambda x: 0 if x »
( v 3 . 5 . 2 : 4 d e f2 a 2 9 0 1 a 5 , Jun 25 2 0 1 6 , 2 2 :1 8 : 5 5 )
===== RESTART: C :\P y th o n 3 5 \P y th o n 3 S t u f f \B e n f o r d \f o r _ b o o k \b e n f o r d .p y ======
[MSC v .1 9 0 0 64 b i t
(AMD64) J on
Имя файла с КОЛИЧЕСТВЕННЫМИ данными: C:\Python35\Benford\Illino / Процент первых цифр
наблюдаемые количества ожидаемые количества
[129, 62, 45, 48, 40, 25, 23, 21, 15] [123, 72, 51, 40, 32, 27, 24, 21, 19]
Данные против значений Бенфорда
Вероятности первой 1: наблюдается: 0. 2: наблюдается: 0. 3: наблюдается: 0. 4: наблюдается: 0. 5: наблюдается: 0. 6: наблюдается: 0. 7: наблюдается: 0. 8: наблюдается: 0. 9: наблюдается: 0.
цифры:
316 ожидается: ожидается: 152 ожидается: 110 ожидается: 118 098 ожидается: ожидается: 061 ожидается: 056 ожидается: 051 037 ожидается:
0.301 0.176 0.125 0.097 0.079 0.067 0.058 0.051 0.046
• Бенфорд
Данные
Проверочный статистический показатель хи-квадрат - 7.019 Критическое значение при р-значении 0.05 составляет 15.51. Наблюдаемое распределение совпадает с ожидаемым распределением
2
1 3 * ====== RESTART: C :\P y th o n 3 5 \P y th o n 3 S t u f f \B e n fo r d \fo r _ b O o k \b e n fo r d .p y ======
[MSC v .1 9 0 0 64 b i t
(AMD64)] on Wi n 3 2
Имя файла с КОЛИЧЕСТВЕННЫМИ данными: C:\Python35\Benford\Illinoj / пР«»е«т первых цифр
□
наблюдаемые количества = [124, 63, 44, 49, 30, 37, 20, 20, 21] = [123, 72, 51, 40, 32, 27, 24, 21, 19] ожидаемые количества
Данные против значений Бенфорда
Вероятности первой цифры: 1: наблюдается: 0.304 ожидается: 0.301 2: наблюдается: 0.154 ожидается: 0.17 6 3: наблюдается: 0.108 ожидается: 0.125 4: наблюдается: 0.120 ожидается: 0.097 5: наблюдается: 0.074 ожидается: 0.079 6: наблюдается: 0.091 ожидается: 0.067 7: наблюдается: 0.049 ожидается: 0.058 8: наблюдается: 0.049 ожидается: 0.051 9: наблюдается: 0.051 ожидается: 0.046
Проверочный статистический показатель хи-КБадрат = 8.872 Критическое значение при р-значении 0.05 составляет 15.51. Наблюдаемое распределение совпадает с ожидаемым распределением
1
2
3
4
5
6
7
8
9
!#l4»i»l 4»lQlg|:B|
_
J
Ln: 25 Col: О
Р ис. 16.10. Результаты выполнения распределения из beat_benford_practice.py в программе benford.ру… Озорство удалось!
Ниже показано несколько строк вывода из beat_benford_practice.py, со старыми и новыми итогами голосования по округам:
old Trump: 12412
new Trump: 13223
old Clinton: 3945
new Clinton: 3290
old Trump: 13003
new Trump: 14435
old Clinton: 7309
new Clinton: 6096
old Trump: 453287
new Trump: 735863
old Clinton: 1611946
new Clinton: 1344496
old Trump: 6277
new Trump: 6674
old Clinton: 1992
new Clinton: 1661
old Trump: 4206
new Trump: 4426
old Clinton: 1031
new Clinton: 859
Третья строка сверху представляет округ Кук (Cook County), в котором находится Чикаго. Отметим, что Клинтон по-прежнему здесь выигрывает, но с меньшим от рывом. Победа Трампа в этом “синем” округе откровенно была бы огромным крас ным флагом, сигнализирующим о том, что произошло вмешательство в голосова ние, даже если бы он победил во всем штате лишь с небольшим отрывом!
Сложные проекты
Попробуйте свои силы в следующих ниже сложных проектах. Никаких решений не предусмотрено.
430
Глава 16
Бенфордирование колеблющихся штатов
Ни один кандидат не будет жульничать в штате, в котором он гарантированно по беждает. Если вы — следователь, проводящий расследование на предмет мошенни чества с голосами избирателей, то вы, скорее всего, начнете с колеблющихся шта тов. Они могут качнуться в любом направлении, и кандидаты тратят подавляющую часть своего боевого арсенала — и времени — на эти места. По данным некоммер ческой и беспартийной политической онлайновой энциклопедия Ballotpedia (https://baIIotpedia.org), колеблющимися штатами Трампа в 2016 г. были Аризона, Айова, Мичиган, Висконсин, Огайо, Пенсильвания, Северная Каролина и Флорида. У Клинтон это были Колорадо, Невада, Нью-Гэмпшир и Вирджиния.
Отчеты о заочном (онлайновом) голосовании в штатах обычно предоставляются в нескольких форматах, таких как электронные таблицы Microsoft Excel. Соберите их для колеблющихся штатов, конвертируйте их в текстовые файлы и пропустите их через программу benford.ру. Для старта вы можете найти отчеты по штату Огайо вот здесь: https://www.sos.state.oh.us/elections/.
Пока никто не заметил
Бывший спикер Палаты представителей США Тип О’Нил (Tip O’Neill) прославился своим изречением мВся политика является локальной”. Примите это близко к серд цу и воспользуйтесь программой benford.ру, для того чтобы проверить некоторые ваши местные предвыборные гонки, такие как выборы судей, мэров, руководителей округа, шерифов и членов городского совета. Эти события обычно привлекают меньше внимания, чем гонки за места в сенате, губернатора или президента. Если вы обнаружите нарушение, то, прежде чем устраивать скандал, обеспечьте ситуа цию, в которой набор данных голосования был допустимым применением закона Бенфорда!
ПРИЛОЖЕНИЕ
РЕШ ЕН И Я ПРАКТИ ЧЕСКИ Х ПРОЕКТОВ
В этом приложении представлены решения практических проектов каждой главы. Цифровые версии доступны на веб-сайте книги по адресу https://www.nostarch.com/impracticalpython/.
Глава 1. Генератор дурацких имен
“Поросячья” латы нь
pig_L.atin_practice.py
“““Превратить слово в его эквивалент на “поросячьей” латыни.""”
import sys
VOWELS = ‘aeiouy’
while True:
word = input(“Наберите слово и получите его перевод на поросячью латынь: “)
if word[0] in VOWELS:
pig_Latin = word + ‘way’
else:
pig_Latin = word[l:] + word[0] + ‘ay1
print ()
print(”{}".format(pig_Latin), file=sys.stderr)
try_again = input("\п\пПопробуете еще раз? (Нажмите Enter либо n
для остановки)\n “)
if try_again.lower() == “n”:
sys.exit()
432 Приложение
Столбчатый график бедняка
EATOIN_practice.py
“““Увязать буквы символьной цепочки со словарем и напечатать
столбчатый график частоты.”””
import sys import pprint from collections import defaultdict
Примечание: для того чтобы столбцы уместились в окне интерпретатора IDLE, #
текст должен представлять собой короткую фразу #
text_ru = ’Как замок в своем углу из средневековой игры, я предвижу \
ужасную беду, и я все равно здесь остаюсь.1
text = ‘Like the castle in its corner in a medieval game,
I foresee terribletrouble and I stay here just the same.f
ALPHABET = ‘abcdefghijklmnopqrstuvwxyz1
модуль defaultdict позволяет строить ключи словаря на лету! #
mapped = defaultdict(list) for character in text:
character = character.lower()
if character in ALPHABET:
mapped[character].append(character)
модуль pprint позволяет печатать результат стопкой #
print("\пЕсли текст переносится на новую строку, то вам нужно растянуть
консольное окно.\п”)
print(“text = “, end=ff) print(”{}\n”.format(text), file=sys.stderr) pprint.pprint(mapped, width=110)
Глава 2. Поиск палинграммных заклинаний
Очистка словаря
dlctionary_cleanup_.practice.py
“““Удалить однобуквенные слова из списка, если не ‘а1 или »i».””” word_list = [’a’, ’nurses’, fi f, ‘stack’, fb f, ’cats’, ‘c’]
permissible = (’a’, ‘i ’)
удалить однобуквенные слова, если не “а” или “i” #
for word in word_list:
if len(word) == 1 and word not in permissible:
word_list.remove(word)
print(”{}".format(word_list) )
Решения практических проектов 433
Глава 3. Решение анаграмм
Поиск диграмм
count_dfgrams_practice.py
“““Сгенерировать буквенные пары в словарь Voldemort и отыскать
их частоту в словаре. Требует модуля load_dictionary.ру
для загрузки файла словаря английского языка.
import re
from collections import defaultdict
from itertoois import permutations
import load_dictionary
word_list = load_dictionary.load(*2of4brif.txt’)
name = ’Voldemort*
(tmvoordle) #
name = name.lower()
сгенерировать уникальные словарные пары из имени #
digrams = set()
perms = {’’.join(i) for i in permutations(name)}
for perm in perms:
for i in range(0, len(perm) - 1):
digrams.add(perm[i] + perm[i + 1])
print (Migrants, sep=,\n’)
print(“ХпЧисло диграм = {}\n”.format(len(digrams)))
применить регулярные выражения для отыскания повторяющихся #
диграмм в слове #
mapped = defaultdict(int)
for word in word_list:
word = word.lower()
for digram in digrams:
for m in re.finditer(digram, word):
mapped[digram] += 1
print(“частотность диграмм:”)
count = 0
for k in mapped:
print(”{} {}".format(k, mapped[k]))
434 Приложение
Глава 4. Декодирование шифров времен Гражданской войны в США
Взламывание сообщения Линкольна
Кодовое слово
WAYLAND
NEPTUNE
Открытый текст
captured
Richmond
Открытый текст: correspondents of the Tribune captured at Richmond please ascertain why they are detained and get them off if you can this fills it up
Перевод: корреспонденты Tribune захвачены в Ричмонде пожалуйста выясните причину их задержания и спасите их если можете терпеть нету мочи
Идентификация типов шифров
identify_cipher_type_practice.py
“““Загрузить шифротекст и использовать присутствующую долю ETAOIN
для классификации типа шифра.”””
import sys
from collections import Counter
установить произвольную долю отсечения из 6 наиболее распространенных #
букв в английском шифротексте с целевой долей #
или больше = перестановочный шифр #
CUTOFF = 0 . 5
загрузить шифротекст #
def load(filename):
“““Открыть текстовый файл и вернуть список.,,ии
with open(filename) as f:
return f .read().strip()
try:
ciphertext = load(* cipher_a.txtf)
except IOError as e:
print(”{}. Завершение программы.".format(e),
file=sys.stderr)
sys.exit(1)
подсчитать 6 наиболее распространенных букв в шифротексте #
six_most_frequent = Counter(ciphertext.lower()).most_common(6)
print(“ХпШесть наиболее часто используемых букв в английском языке = ETAOIN”)
Решения практических проектов 435
print(1\ пШесть наиболее часто используемых, букв в шифротексте =•)
print(six_most_frequent, sep=f\n)
конвертировать список кортежей в множество букв для сравнения #
cipher_top_6 = {i [ 0] for i in six_most_frequent}
TARGET = ’etaoin’
count = 0
for letter in TARGET:
if letter in cipher_top_6:
count += 1
if count/len(TARGET) >= CUTOFF:
print(“ХпЭтот шифротекст, скорее всего, был порожден ПЕРЕСТАНОВОЧНЫМ шифром”)
else:
print(“Этот шифротекст, скорее всего, был порожден ПОДСТАНОВОЧНЫМ шифром”)
Хранение ключа в виде словаря
key_dictionary_practice.py
“““Ввести символьную цепочку с ключом шифра, взять введенное пользователем
значение с направлением маршрута как значение словаря.”””
col_order = “““1 3 4 2"””
key = diet() cols = [int(i) for i in col_order.split()] for col in cols:
while True:
key[col] = input(“Направление чтенич столбца {} (u = вверх, d = вниз):
“.format(col).lower())
if key[col] == fu’ or key[col] == ’d* :
break
else:
print(“Входным значением должно быть ‘и* либо ’d’”)
print(”{}, {}".format(col, key[col]))
Автоматическое генерирование возможных ключей
permutations_practice.py
“““Для суммарного числа столбцов отыскать все уникальные расстановки
столбцов. Строит список списков, содержащий все возможные уникальные расстановки индивидуальных номеров столбцов, включая отрицательные
значения для направления маршрута (чтение в столбце вверх или вниз).
Ввод:
- суммарное число столбцов
436 Приложение
Возвращает:
- список списков уникальных порядков столбцов, включая отрицательные значения для направления шифрования маршрутным шифром
import math
from itertoois import permutations, product
#
Input total number of columns: #
НАЧАЛО ВХОДНЫХ Д А Н Н Ы Х —————————————-
num_cols = 4
#
HE РЕДАКТИРОВАТЬ НИЖЕ ЭТОЙ СТРОКИ—————————-
сгенерировать список индивидуальных номеров столбцов #
columns = [х for х in range (1, num_cols+l)]
print(“columns = {}".format(columns))
построить список списков комбинаций номеров столбцов #
функция product модуля itertoois вычисляет декартово произведение #
входных итерируемых типов #
def perms(columns):
“““Взять целое число столбцов и сгенерировать положительные и отрицательные перестановки.””” results = [] for perm in permutations(columns):
for signs in product([-1, 1], repeat=len(columns)):
results.append([i*sign for i, sign in zip(perm, signs)])
return results
col_combos = perms (columns) print(*col_combos, sep="\n”)
закомментируйте для num_cols > 4! #
print(“Факториал числа num_cols без отрицательных значений = {}”
.format(math.factorial(num_cols)))
print(“Число комбинаций столбцов = {}".format(len(col_combos)))
Маршрутный перестановочный шифр: атака с применением грубой силы
В данном практическом проекте используются две программы. Вторая, perms.py, применяется в качестве модуля в первой программе, routecipherhacker.py. Она была собрана из программы permutations_practice.py, описанной в разд. “Авто матическое генерирование возможных ключей"ранее в этом приложении.
route_cipher_hacker.py
“““Взламывание маршрутного шифра Союзников грубой силой (route_cipher_hacker.ру).
Предназначен для полнословных перестановочных шифров с переменными строками и столбцами.
Решения практических проектов 437
Исходит из того, что шифрование началось вверху или внизу столбца. Возможные ключи автоматически генерируются на основе числа вводимых столбцов и строк. Ключ указывает на порядок чтения столбцов и направление прохождения.
Отрицательные номера столбцов означают начало снизу и чтение вверх.
Положительные номера столбцов означают начало сверху и чтение вниз.
Приведенный ниже пример предназначен для матрицы 4 х 4 с ключом -1 2 -3 4.
Примечание: “О” не допускается. Стрелки показывают маршрут шифрования; для отрицательных значений ключа читать ВВЕРХ.
1 л 1 1 1 л 1 1 1 V 1 1
1 л 1 1 1 л 1 1 1 V 1 1
1 л 1 1 1 л 1 1 1 V 1 1
1 л 1 1 1 л 1 II V 1 1
1 V
1 V
1 V
1 V
НАЧАЛО
КОНЕЦ
СООБЩЕНИЕ НАПИСАНО
ПОПЕРЕК КАЖДОЙ СТРОКИ
ТАКИМ ОБРАЗОМ
ПОСЛЕДНЯЯ СТРОКА ЗАПОЛНЯЕТСЯ
Необходимые входные данные - текстовое сообщение, число столбцов,
число строк, символьная цепочка с ключом
Для генерирования ключей требует специальный модуль “perms”
Печатает использованный ключ и переведенный открытый текст
import sys
import perms
= = = = = = = = = = = = = = = = = = = = = = = = = = = = = = #
ВХОДНЫЕ ДАННЫЕ ПОЛЬЗОВАТЕЛЯ: #
символьная цепочка, подлежащая расшифровке #
(наберите или вставьте между тройными кавычками) : #
ciphertext = “““REST TRANSPORT YOU GODWIN VILLAGE ROANOKE WITH ARE YOUR IS
JUST SUPPLIES FREE SNOW HEADING TO GONE TO SOUTH FILLER
число столбцов, которые, как ожидается, находятся #
в перестановочной матрице: #
COLS = 4
число строк, которые, как ожидается, находятся в перестановочной матрице: #
ROWS = 5
КОНЕЦ ВХОДНЫХ ДАННЫХ ПОЛЬЗОВАТЕЛЯ - НЕ РЕДАКТИРОВАТЬ НИЖЕ ЭТОЙ СТРОКИ! #
438 Приложение
= = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = #
def main () :
“н"Превратить шифротекст в список, вызвать функции валидации и
расшифровки.””” cipherlist = list(ciphertext.split()) validate_col_row(cipherlist)
decrypt(cipherlist)
def validate_col_row(cipherlist) :
“““Проверить, что входные столбцы и строки являются допустимыми относительно длины сообщения.”””
factors = [] len_cipher = len(cipherlist) for i in range(2, len_cipher): # диапазон исключает 1-столбцовые шифры
if len_cipher % i == 0:
factors.append(i)
print(“ХпДлина шифра = {}".format(len_cipher))
print(“Приемлемые значения столбца/строки включают: {}”
.format(factors))
print() if ROWS * COLS != len_cipher:
print(“ХпОшибка - Входные столбцы и строки не являются кратными
длине шифра. Завершение программы.”, file=sys.stderr)
sys.exit(1)
def decrypt(cipherlist):
“““Превратить столбцы в элементы списка списков и расшифровать
шифротекст.””” col_combos = perms.perms(COLS) for key in col_combos:
translation_matrix = [None] * COLS
plaintext = '’
start = 0
stop = ROWS for k in key:
if k 0: # чтение сверху вниз столбца
col_items = list((reversed(cipherlist[start:stop])))
translation_matrix[abs(k) - 1] = col_items start += ROWS
stop += ROWS
перебрать вложенные списки в цикле, выталкивая #
последний элемент в новый список: #
for i in range(ROWS):
for matrix_col in translation_matrix: word = str(matrix_col.pop())
plaintext += word + ’ '
print(“Хписпользуя ключ = {}".format(key))
print(“переведено = {}".format(plaintext))
print(“Хпчисло ключей = {}".format(len(col_combos)))
Решения практических проектов 439
if name == 1 main__’ :
m a in ()
Модуль perms.py
“““Для суммарного числа столбцов отыскать все уникальные расстановки столбцов.
Строит список списков, содержащий все возможные уникальные расстановки индивидуальных номеров столбцов, включая отрицательные значения для
направления маршрута
Вход: -суммарное число столбцов
Возвращает:
-список списков уникальных порядков столбцов, включая отрицательные
значения для направления шифрования маршрутным шифром
from itertoois import permutations, product
строит список списков комбинаций номеров столбцов #
функция product модуля itertoois вычисляет декартово произведение #
входных итерируемых типов #
def perms (num_cols) :
“““Взять целое число столбцов и сгенерировать положительные и отрицательные перестановки.”””
results = [] columns = [х for х in range (1, num_cols+l)]
for perm in permutations(columns):
for signs in product([-1, 1], repeat=len(columns)):
results.append([i*sign for i, sign in zip(perm, signs)])
return results
Глава 5. Кодирование шифров времен Гражданской войны в Англии
Спасение королевы Марии
saveJVIary_practice.py
“““Спрятать нулевой шифр внутри списка имен, используя переменный шаблон.”””
import load_dictionary
440 Приложение
написать короткое сообщение и не использовать никаких знаков #
препинания или чисел! #
message = “Give your word and we rise”
message =
.join(message.split())
открыть файл имен #
names = load_dictionary.load(‘supporters.txt’)
name_list = []
начать список с нулевого слова, не используемого в шифре #
name_list.append(names[0])
добавить букву нулевого шифра ко 2-й букве имени, затем к 3-й, #
затем повторить #
count = 1 for letter in message:
for name in names:
if len(name) > 2 and name not in name_list:
if count % 2 == 0 and name[2].lower() == letter.lower():
name_list.append(name)
count += 1 break
elif count % 2 != 0 and name [1 ]. lower () == letter. lower () :
name_list.append(name) count += 1 break
добавить два нулевых слова в начале сообщения, #
для того чтобы сбить криптоаналитиков #
name_list.insert(3, ’Stuart’)
name_list.insert(6, ’Jacob’)
показать фиктивное письмо и список с нулевым шифром #
print (,,,,и
Your Royal Highness: \n
It is with the greatest pleasure I present the list of noble families who have undertaken to support your cause and petition the usurper for the
release of your Majesty from the current tragical circumstances. ii и и ^
ни"ПЕРЕВОД:
Ваше Королевское Высочество:
С величайшим удовольствием представляю список благородных семей, которые обязались поддержать ваше дело и ходатайствовать перед узурпатором
об освобождении Вашего Величества от нынешних трагических обстоятельств.
И II II
print(*name_list, sep=’\n’)
Решения практических проектов 441
Колчестерская поимка
colchester_practice.py
“““Решить нулевой шифр, основываясь на каждой n-й букве
в каждом n-м слове.”””
import sys
def load_text(file):
,,и"Загрузить текстовый файл в виде символьной цепочки.““и
with open(file) as f:
return f .read().strip()
загрузить и обработать сообщение: #
filename = input(“ХпВведите полное имя файла переводимого сообщения: “)
try:
loaded_message = load_text(filename)
except IOError as e:
print(”{}. Завершение программы.” . format(e), file=sys.stderr)
sys.exit(1)
проверить загруженное сообщение и его число строк #
print(“ХпИСХОДНОЕ СООБЩЕНИЕ = {}\n”.format(loaded_message))
конвертировать сообщение в список и получить его длину #
message = loaded_message.split()
end = len(message)
получить от пользователя входное значение о проверяемом интервале #
increment = int(input(“Введите макс. слово и буквенную позицию для \
проверки (напр., каждые 1 из 1, 2 из 2 и т.д.): “))
print()
отыскать буквы через определенные интервалы #
for i in ranged, increment + 1):
print(“ХпИспользуя инкремент, буква {} слова {}".format(i, i))
print()
count = i - 1
location = i - 1
for index, word in enumerate(message):
if index == count:
if location num_blanks:
print(“Фиктивному сообщению требуется еще {} пустых строк.”
.format(diff), file=sys.stderr)
sys.exit ()
line_limit(fake_list, real_list)
загрузить шаблон, который задает стиль, шрифт, края и т. д. #
doc = docx.Document(’template.docx *)
добавить фирменный бланк #
doc.add_heading(‘Морланд Холмс’, 0)
subtitle = doc.add_heading(’Глобальный консалтинг и ведение переговоров’, 1)
subtitle.alignment = 1
doc.add_heading(’’, 1)
doc.add_paragraph(‘17 декабря 2019’)
doc.add_paragraph(’’)
def set_spacing(paragraph):
им"использовать docx для задания интервала между абзацами.”””
paragraph_format = paragraph.paragraph_fо m a t
paragraph_format.spacejbefore = Pt(0)
paragraph_format.space_after = Pt(0)
length_real = len(real_list)
count_real = 0
индекс текущей строки в реальном (скрытом) сообщении #
чередовать строки реального и фиктивного сообщений #
for line in fake_list:
if count_real self.screen.get_width():
self.kill()
if self.y Particle.IO_SURFACE_Y:
self.kill()
def m a in():
“““Настроить, открыть окно и запустить цикл игры.””” screen = pg.display.set_mode((639, 360))
pg.display.set_caption(“Симулятор вулкана на Ио”)
background = pg.image.load(“tvashtar_plume.gif”)
Настроить цветную легенду #
legend_font = pg.font.SysFont(’None’, 26) text = legend_font.render(’Белый = 45 градусов’, True, WHITE, BLACK)
particles = pg.sprite.Group()
clock = pg.time.Clock()
454 Приложение
while True:
clock.tick(25)
particles.a d d (Particle(screen, background)) for event in p g .event.g e t ():
if event.type == pg.QUIT:
p g .quit()
sys.exit()
screen.blit(background, (0, 0))
screen.blit(text, (320, 170))
particles.update()
particles.draw(screen)
pg.display.flip()
if name == ” main__” :
ma i n ()
Глава 16. Выявление мошенничества с помощью закона Бенфорда
Победа над Бенфордом
beat_benford_practice.py
“““Подделать подсчет голосов так, чтобы окончательные результаты
сочетались с законом Бенфорда.”””
предметом приведенного ниже примера являются #
результаты Трампа и Клинтон в шт. Иллинойс #
на президентских выборах 2016 г. #
def load_data(filename):
“““Открыть текстовый файл с количеством и явкой в список целых
чисел.”””
with open(filename) as f:
lines = f.read().strip().split(’\nf)
return [int(i) for i in lines] # конвертировать симв. цепочки
в целые числа #
def steal_votes(opponent_votes, candidate_votes, scalar):
“““Применить скаляр для снижения количества голосов для одного
и увеличить для другого, вернуть в виде списков.
Решения практических проектов 455
Аргументы:
opponent_votes - похищаемые голоса candidate_votes - голоса, увеличиваемые на количество украденных
scalar - долевой процент, < 1, используется для снижения голосов
Возвращает: Список измененных голосов оппонента
Список измененных голосов кандидата
к и и
new_opponent_votes = []
new_candidate_votes = [] for opp_vote, can_vote in zip(opponent_votes, candidate_votes):
new_opp_vote = round(opp_vote * scalar) new_opponent_votes.append(new_opp_vote)
stolen_votes = opp_vote - new_opp_vote
new_can_vote = can_vote + stolen_votes new_candidate_votes.append(new_can_vote)
return new_opponent_votes, new_candidate_votes
def m a i n ():
“““Выполнить программу.
Загрузить данные, задать целевое победное количество голосов,
вызвать функции, показать результаты анализа закона Бенфорда.
i t II п
загрузить данные голосования #
c_votes = load_data(* Clinton_votes_Illinois.txt ) j_votes = load_data( Johnson_votes_Illinois.txt’)
s_votes = load_data(* Stein_votes_Illinois.txt’) t_votes = load_data(1Trump_votes_Illinois.txt’)
total_votes = sum(c_votes + j_votes + s_votes + t_votes)
допустим, что Трамп набирает множество голосов с 49% #
t_target = round(total_votes * 0.49)
print(“ХпПобедная цель Трампа = {:,} votes”.format(t_target))
вычислить лишние голоса, необходимые для победы Трампа #
extra_votes_needed = abs(t_target - sum(t_votes))
print(“extra votes needed = {:,}".format(extra_votes_needed))
вычислить скаляр, необходимый для генерирования лишних голосов #
scalar = 1 - (extra_votes_needed / sum(c_votes + j_votes + s_votes))
print(“scalar = {:.3}".format(scalar))
print()
перевернуть подсчеты голосов, опираясь на скаляре, #
и построить новый комбинированный список голосов #
fake_counts = []
456 Приложение
new_c_votes, new_t_votes = steal_votes(c_votes, t_votes, scalar)
fake_counts.extend(new_c_votes) new_j_votes, new_t_votes = steal_votes(j_votes, new_t_votes, scalar)
fake_counts.extend(new_j_votes) new_s_votes, new_t_votes = steal_votes(s_votes, new_t_votes, scalar)
fake_counts.extend(new_s_votes)
fake_counts.extend(new_t_votes) # добавить последний,
т. к. до сих пор изменялся #
сравнить старое и новое количества голосов и итоговые числа #
в табличной форме; #
подменить “Трампа” на “Клинтон”, если необходимо #
for i in range(0, len(t_votes)):
print(“старый Трамп: {} \t новый Трамп: {} \t "
“старая Клинтон: {} \t " новая Клинтон: {}” .format(t_votes[i], new_t_votes[i], c_votes[i],
new_c_votes[i ]))
print(”-” * 95)
print(“TOTALS:”)
print(“старый Трамп: {:,} \t новый Трамп: {:,} \t "
“старая Клинтон: {:,} новая Клинтон: {:,}” .format(su m (t_votes), s u m (new_t_votes),
sum(c_votes), sum(new_c_votes)))
записать текстовый файл для использования #
как вход в программу benford.ру; #
указанная программа будет проверять соответствие фиктивных #
голосов закону Бенфорда #
with open(‘fake_Illinois_counts.txt’, ‘w’) as f:
for count in fake_counts:
f.write(”{}\n”.format(count))
if name == 1 main__’ :
m a i n ()
ПРЕДМЕТНЫЙ УКАЗАТЕЛЬ
G
Б
Google: О алгоритм PageRank 199 О руководство по стилевому оформлению
35,39
А Абстракция 46 Актив игровой 274 Активы 351 Алгоритм: О PageRank 199 О восхождения к вершине холма 175 О генетический 159-179 О эволюционный 159 Анаграмма 61 О фразовая 65 Анализ: О марковских цепей 182, 199-226 О чувствительности 171 Анжамбеман 205 Апоапсида 345 Апогей 345 Атрибут 270 0 zorder 423 0 класса 323
Библиотека: 0 matplotlib 236, 240, 297, 300, 417 0 Natural Language Toolkit (NLTK) 184 0 NumPy 35, 236 0 OpenGL (Open Graphics Library) 320 0 OpenSource Computer Vision (OpenCV) 405 0 PIL (Python Imaging Library) 387 0 SciPy 236 0 SDL (Simple DirectMedia Library) 320 0 стандартная языка Python 209 Биграмма 80 Буква регистрочувствительная 65
в
Верификация данных 107 Видеоролик 390 Виджет 246 0 Canvas 246 0 Frame 275 0 Тк 277 Воспроизведение звуков 374
г
Галактика спиральная 230 Генератор: 0 анаграмм автоматический 91 0 псевдонимов 24—42 Гравитация 341, 364 0 универсальная 341, 365 График полулогарифмический 409 Группа спрайтовая 373
458 Предметный указатель Д
Данные: О долговременные 192 О загрузка 416 О объектно-ориентированное программирование 269
Движение планет 342 Действие 269 Деление по модулю (%) 109, 305 Дзен языка Python 30 Диграмма 87, 91, 102
з
Закон: 0 Бенфорда 416-427 0 всемирного тяготения
См. Гравитация универсальная 0 первой цифры См. Закон Бенфорда Заплатка 25 Затанна 43 Знак препинания 129 Значение 211
и
Игра Jumble 62, 91 Имена и процедуры стандартизированные 30 Имя пути 387 Инвариантность к шкале 409 Инкапсуляция 46 Инстанцирование 271 Инструкция: 0 continue 219 0 import 300 0 print 28 0 try 45 0 with 45 Инструмент выделения 141 Интервал межзнаковый (трекинг) 143 Интерфейс: 0 пользователя
° графический (GUI) 104 ° написание 219
0 прикладной программный (API) 320 Исключение 45 Итератор productQ 173
к Калькулятор пенсионных сбережений 288 Кандидат 215 Каталог текущий рабочий (cwd) 388 Кернинг 143 Класс 269, 354 0 Frame 275 0 Sprite 326, 327, 354, 360, 362 0 базовый 276 0 родительский 275, 325 Ключ 211 Код: 0 исходный См. Код проектный
° anagrams.ру 63 ° benford.py 416 ° bruteforcecracker.py 173 ° countsyllables.py 192 ° cropnscaleimages.py 392 ° elementaryink.py 148 ° enhanceimage.py 402 ° galaxy simulator.py 245 list cipher.py 133 ° ° load dictionary.py 46, 63 ° markov haiku.py 210, 214 ° mars orbiter.py 353 ° missingwordsflnder.py 187 ° monty_hall_gui.py 274 ° monty hall rncs.py 266 ° nest egg mcs.py 299 ° nest egg mcs l st_5yrs.py 312 ° nullcipherflnder.py 128 ° palindromes.py 48 ° palingrams.py 53 ° palingrams optimized.py 58 ° phrase anagrams.py 70 ° probabilityofdetection.py 237 ° pseudonyms.py 27 ° pseudonyms main fixed.py 36 ° Pylint 30, 32 ° rail fence cipher decrypt.py 116 ° rail fence cipher encrypt.py 113 ° safe cracker.py 176 ° stack images.py 399 ° super rats.py 164 ° ° ° voldemort british.py 82 0 проектный См. Код исходный
test_count_syllables_w_full_coфus.py 196 tvashtar.py 324
° анализ марковских цепей 200-222 ° верификация утверждения вое Савант
265-268
° взламывание высокотехнологичного
0 format форматирования символьных
Предметный указатель 459
сейфа 172-179
° генерирование псевдонимов 24—29 ° закон первых цифр Бенфорда 416-427 ° зигзагообразный шифр 111-118 ° игра в Монти Холла 268-286 ° игра “Орбитальный спутник Марса”
348-380
° моделирование Млечного Пути 233-256 ° наложение Юпитера 392-405 ° написание нулевого шифра 132-135 ° отыскание Волдеморта 77-90 ° анаграмма однословная 61-65 ° анаграмма фразовая 65-77 ° палинграмма 50-55 ° палиндром 47-50 ° подсчет слогов 183-198 ° разведение армии суперкрыс 160-171 ° расшифровка маршрутного шифра
92-111
° симулирование продолжительности
жизни на пенсии 288-315
° сокрытие шифра Виженера 138-157 ° шифр Треваниона 125-132 ° шлейфы на Ио 319-336
0 с применением грубой британской силы 82 Количество ожидаемое 419 Константа пропорциональности 365 Координата полярная 242, 246 Корпус 184 0 тренировочный 187, 199,207 Криптография 79, 92
л
Лебедь черный 293 Литерал документирования 34 Логика 269
м Медиана 406 Метка эпохи временная 56 Метод 269, 270 0 blit(), блочного переноса растровых
данных (блитгинг) 334
0 blit(), переноса растрового изображения
(блиттинг) 376 0 check_keys() 357 0 draw() 335 0 flip(), переноса растрового изображения
(флиппинг) 335, 380
последовательностей 254
0 getdata() 400 0 lower() 29 0 most_common() 85 0 os.chdir() 388 0 os.getcwd() 389 0 os.listdir() 394, 400 0 os.path.join() 388 0 os.path.normpath() 389 0 os.remove() 395 0 path() 359 0 рор() 100,118, 192 0 rotate() 359 0 shutil.rmtree() 394 0 split() 100 0 sys.exit(l) 45 0 thruster() 357 0 time.time() 56 0 update() 330, 335, 360, 378 0 грубой силы (исчерпывающего поиска)
66, 82
Механизм исчерпывающего поиска 160 Механика орбитальная 342-348 Микрофотография 112 Минимум и максимум 175 Млечный Путь 230 Моделирование компьютерное 236 Модель: 0 графическая 241 0 порядка 0 200 0 порядка 1 203 0 порядка 2 201 0 стиля статистическая 207 Модуль 18 0 collections 67 0 Counter 67, 71 0 cProfile 44 0 Flake8 30 0 itertoois 79 0 logging 209 0 matplotlib 236, 297, 416 0 os 386 0 pillow 385, 387 0 pycodestyle 30 0 pydocstyle 35, 38 0 pygame 319, 321, 324 0 Pylint 30 0 python-docx 138, 144 0 random 28
460 Предметный указатель
Модуль (прод.) 0 shutii 386 0 sys 28 О tkinter 229,244, 245 О импортирование 299, 416 О операционная система 386 О программа установки пакетов Preferred Installer Program (pip) 31, 35, 144, 158
О утилиты командной оболочки 386
н Наложение снимков 385 Направленность 256 Нарезка 47 Неопределенность 294 Норма ассоциации слов (WAN) 226 Нотация объектная JavaScript (json) 188
о
Обеспечение программное: 0 Anaconda 237 0 Audacity 352 0 CCDStack 391 О Deep Sky Stacker 391 О DirectX API 320 0 Enthought Canopy 237 0 Free Studio 390 0 Free Video to JPG Converter tool 390 0 Geany 21 0 IDLE:
° интерактивная оболочка 26 ° текстовый редактор 21
0 LibreOfflce Writer 140 0 Microsoft
° DirectX API 320 ° Office Suite 140 ° Outlook 155
0 OpenOffice Writer 140 0 PowerShell 31 0 PyCharm 21 0 PyScripter 21 0 RegiStar391 0 RegiStax391 0 Sphinx 35 Область глобальная 70 Обнаружение спама 183 Обработка естественного языка 183
Объект: 0 font 145 0 paragraph 145 0 run 145 0 внутристрокового уровня 145 0 блочного уровня 144 Окно: 0 командное 31 0 терминала 144 Оператор: 0 “звездочка” (*) 49 0 деления по модулю (%) 109, 305 0 сравнения (==) 62 Операция: 0 включения в множество 86 0 включения в список 47, 79, 84, 100, 115,
166, 178,300,310, 400,419, 421
Оптимизация 159, 172, 175 Орбита синхронная 347 Отбор образцов многократный случайный
263
Отладка 208 Оценивание производительности компьютера
199
Ошибка См. Отладка 0 ложная 32 0 проверка отсутствия 417 0 человеческая 95
п Палинграмма 50 Палиндром 43 Парадокс Ферми 229-261 Параметр: 0 columnspan 277 0 ipadx 278 Перевод машинный 183 Передача блочная растровых данных 334 Переменная 171 0 duration 304, 305 0 eccentricity 367, 373 Перенос спиральный 347 Перестановка 79 0 с повторением 173 Переход методом Гомана с орбиты на орбиту
346
Периапсида 345 Перигей 345
Персона: О Александер Эдвард Портер 119 О Беляев Дмитрий 169 О Бенфорд Фрэнк 408 0 Бэббидж Чарльз 140 О вое Савант Мэрилин 262 О Дауни Аллен 25 О Кеплер Иоганн 342 О Кларк Брукс 390 О Клинтон Хиллари 415 О Мария, королева Шотландии 124 О Нигрини Марк 427 О Ньюкомб Саймон 408 О Ньютон Исаак 341 О О’Нил Тип 430 О Пил Стэн 318 О Саган Карл 256 О Слаткин Бретт 40 О Стейджер Энсон 93 О Талеб Нассим 264 О Твен Марк 287 О Тертлдав Гарри 95 О Трамп Доналд 228, 414,428 О Тьюринг Алан 227 О Фридман Уильям 93 О Хартман Чарльз 181-183, 197,205, 223,
226
О Черчилль Уинстон 26 О Шекспир Уильям 228 О Шеннон Клод 200 О Шеппард Клинтон 180 О Эмерсон Ральф Уолдо 19 Планирование, важность проведения 25 Подсчет слогов 183-186 Поколение 161 Правило четырех процентов 287, 316 Проверка степени соответствия 413, 420 0 по статистическому показателю хи-квадрат
413
Проградация 344 Программирование объектно-
ориентированное 269, 320, 322
Проект: 0 “поросячья” латынь 40, 431 0 автоматический генератор анаграмм 91 0 автоматическое генерирование возможных
ключей 120,435 0 акт исчезновения 406 0 атака с применением грубой силы 121, 436
Предметный указатель 461
0 бенфордирование колеблющихся штатов 430 0 весь путь до конца 337, 452 0 взламывание Линкольна 119, 434 0 все, что есть 317 0 генератор новых слов 226 0 далекая-предалекая галактика 258, 445 0 добавление обитаемых зон в свою
галактику 260
0 зигзагообразный многорядный шифр 123 0 идентификация типов шифров 120,434 0 использование моноширинного шрифта 158 0 источник выбросов 338 0 кодировщик маршрутного шифра 122 0 колчестерская поимка 137, 441 0 марковская музыка 228 0 маршрутный перестановочный шифр 0 масть пошла, а деньги кончились 317 0 модификации игры “Орбитальный спутник
Марса” 380
0 окольный путь предсказания обнаруживаемости 260, 449 0 отыскание диграмм 91, 433 0 очистка словаря 59 0 парадокс дня рождения 286 0 перемешать и увязать 317 0 писать хокку или не писать 228 0 победа над Бенфордом 428,454 0 пока никто не заметил 430 0 полет пули 338 0 построение галактической империи
258, 446
0 потрясающе, просто потрясающе! 228 0 проверка числа пустых строк 158, 442 0 рекурсивный подход 60 0 создание более эффективного взломщика
сейфов 180
0 создание спиральной галактики с
перемычкой 260
0 спасение королевы Марии 136, 439 0 среднее имя 41 0 столбчатый график бедного чужака 41 0 столбчатый график бедняка 40, 432 0 счетчик слогов против файла словаря 198,
444
0 тест Тьюринга 227 0 ударный купол 337 0 формирование крысиного гарема 180 0 хранение ключа в виде словаря 120,435 0 ясная картинка стоит тысячи слов 316
462 Предметный указатель
Проекция букв в гласные-согласные 80 Прототип и заплатка 25 Профиль 55 Псевдокод 26 0 графический пример 161 Путь: 0 абсолютный 389 0 каталожный 387 0 относительный 389
р
Радиана 248, 328, 359 Разработка проектная 25 Разрыв строки 144 Распаковка 217 Распознавание: 0 речи 199 0 текста:
° рукописного 199 ° предсказательное 183
Регистр нижний (верхний) вывода символов
65
Регистрация изображения 391 Редактор текстовый 44 Рекомандация по написанию кода на языке
Python 22 0 РЕР 257 34 0 РЕР 8 22 Ретроградация 343 Родитель 275, 325 Руководство по написанию кода 30
с Семорднилап 51 Сериализация 192 Сжигание однотангенциальное 346 Симуляция Монте-Карло 263-286 Система операционная 21 Скрещивание 161, 167 Словарь произношения Университета Карнеги-Меллона (CMUdict) 184
Слоги, подсчет 183 Служба Yahoo! Почта 155 Снимок 395 Событие 333 0 KEYUP 375 0 QUIT 334 Сообщение управляющее 95 Сооружение строительных лесов 208
Спираль логарифмическая 242, 249, 251, 258 Список слов 44 Спутник Марса орбитальный 30 Среда разработки интегрированная (IDE) 21 Стандарт документирования NumPy 35 Статистика 262 Стеганография 124, 138 Стиль 145 0 написания кода 36 0 статистическая модель 207 Суперкласс 276
т Таблица: 0 актуарная продолжительность жизни 294 0 алфавитная 139 0 цветовая 324, 353 Текст открытый 92 Теория вероятностей См. Анализ марковских
цепей
Тип контейнерный: 0 Counter 67 0 defaultdict 211,417 Торможение аэродинамическое 381 Триграмма 80
у Уравнение: 0 Дрейка 229,231 0 квадратное 235 0 многочленное 235 Ускорение 341 Условие останова 160 Устройство с зарядовой связью (CCD) 385
Ф
Файл: 0 2of4brif.txt 44 0 словаря 44, 198 Фильтр: 0 SHARPEN 404 0 Ланцоша 398 Фильтрация: 0 проекция букв в гласные-согласные 80 0 с помощью биграмм 80 0 с помощью триграмм 80 0 спама 199
Предметный указатель 463
ч
Чернила электронные 141 О невидимые 138 Число кадров в секунду (fps) 333
ш
Шифр: О Виженера 112, 138 О зигзагообразный (шифр жердевой
изгороди) 92, 111
О маршрутный (с перестановкой маршрута)
92
О нераскрываемый 139 О нулевой 124, 132 О перестановочный 93 О подстановочный 93, 120, 139 О с перестановкой букв 101 О списковый 133 О Треваниона 124 Шифросписок 108 Шифротекст 92, 108 Шкала логарифмическая 409 Шрифт: О моноширинный 142 О применение 154 О пропорциональный 142 О типы 142, 154 О цвет 154
э
Эксцентриситет 350, 358 Эпоха UNIX 56 Эскиз игры 321, 348
Фонема 186 Формат: О MP3 352 О Ogg Vorbis 352 О RST35 О WAV 352 Функция 299 О bar_chart() 416,421 О clean_folder() 393 О count_first_digits() 416,417 О cv_map_fllter() 85 О cv_map_words() 84 О default_input() 300 О del_folders() 394 О enumerateO 169 О get_expected_counts() 416,419 О letter_pair_filter() 87 О main() 69, 74, 90, 107, 309, 331, 333, 371,
424
О prep_words() 83 О print() 26 О process_choice() 70, 72 О read() 129 О sorted() 62 О strip() 129 О trigram_fllter() 87 О validate_col_row() 107 О zip() 168, 176 О zip_longest() 116, 118
х Художник 423
Ц
Цепочка символьная 29 О необработанная 114 Цикл: О for 307 О while 29,170,303,377 О игровой 355,374 Цифра первая 417
Гэддис Т. Начинаем программировать на Python, 4-е изд.
Отдел оптовых поставок: E-mail: opt@bhv.ru
НАЧИНАЕМ ПРОГРАММИРОВАТЬ
“PYTHON4-Е ИЗДАНИЕ
• Краткое введение в компьютеры
и программирование
• Ввод, обработка и вывод данных
• Управляющие структуры и булева логика
• Структуры с повторением и функции
• Файлы и исключения
• Списки и кортежи
• Строковые данные, словари и множества
• Классы и объектно-ориентированное
программирование
• Наследование и рекурсия
• Функциональное программирование
В книге изложены принципы программирования, с помощью которых вы приобре тете навыки алгоритмического решения задач на языке Python, даже если у вас нет опыта программирования. Для облегчения понимания сути алгоритмов широко использованы блок-схемы, псевдокод и другие инструменты. Приведено большое количество сжатых и практичных примеров программ. В каждой главе предложены тематические задачи с пошаговым анализом их решения. Отличительной особенностью издания является его ясное, дружественное и легкое для понимания изложение материала. Книга идеально подходит для вводного курса по программированию и разработке программного обеспечения на языке Python.
Тони Гэддис — ведущий автор всемирно известной серии книг «Начинаем программи ровать…» (Starting Out With) с двадцатилетним опытом преподавания курсов информатики в колледже округа Хейвуд, шт. Северная Каролина, удостоен звания «Преподаватель года», лауреат премии «Педагогическое мастерство».
Моделируй, экспериментируй, играй
Включая версию Python 3
Данная книга — это набор забавных, в том числе образовательных, проектов, предназначенных для развлечения про граммистов и одновременного повыше ния их навыков. Это хорошее дополнение к традиционным самоучителям, отличная «следующая книга», расширяющая полученные ранее навыки и знакомящая с новыми полезными инструментами.
Каждый проект включает в себя интри гующий поворот с историческими собы тиями, литературными персонажами или ссылками на поп-культуру — и все это ис пользуя модули tkinter, matplotlib, cProfile, Pylint, pygame, pillow и python-docx.
• Смоделировать Млечный Путь и рас считать наши шансы обнаружить ино планетные цивилизации
• Нарисовать карту Марса и изучить
орбитальную механику с использова нием вашего собственного космиче ского спутника
И проделать многое другое! Если вы хотите приобрести немного новых навы ков в программировании или же просто слегка творчески поупражняться, то с данной книгой вы получите бесконеч ное удовольствие и станете настоящим фанатом этого языка.
Потренируйтесь в решении задач, чтобы:
• Помочь Джеймсу Бонду взломать
высокотехнологичный сейф с исполь зованием алгоритма восхождения к вершине холма
• Сочинить стихи с помощью анализа
марковских цепей
• Породить расу гигантских крыс,
применив генетические алгоритмы
• Запланировать обеспеченную жизнь на пенсии с использованием метода Монте-Карло
Об авторе
Ли Воган — программист, энтузиаст поп-культуры и педагог. Профессионал в сфере построения и анализа компью терных моделей, разработки, тестирова ния и коммерциализации программного обеспечения, а также подготовки ИТ-специалистов. Эта книга написана им с целью помочь читателям отточить свои навыки программирования на Python и получить от этого удоволь ствие!
ISBN 978-5-9775-6751-0
191036, Санкт-Петербург, Гончарная ул., 20 Тел.: (812) 717-10-50, 339-54-17, 339-54-28 E-mail: mail@bhv.ru Internet: www.bhv.ru