The words you are searching are inside this book. To get more targeted content, please make full-text search by clicking here.

В книге приведены примеры решения различных практических задач на языке Python и предложено детальное пошаговое описание процесса написания программы для каждой из них. Подобраны задачи, которые имеют несколько вариантов решений и формируют алгоритмическое мышление. Показаны основы структурного, динамического, объектно-ориентированного, функционального программирования. Приведены способы работы с функциями, алгоритмы поиска в длину, в ширину, бэктрекинга, рекурсии. Материал расположен по возрастанию сложности, код программ снабжен комментариями, разъясняющими все языковые конструкции Python.

Discover the best professional documents and content resources in AnyFlip Document Base.
Search
Published by BHV.RU Publishing House, 2024-02-12 23:29:24

Python. Красивые задачи для начинающих

В книге приведены примеры решения различных практических задач на языке Python и предложено детальное пошаговое описание процесса написания программы для каждой из них. Подобраны задачи, которые имеют несколько вариантов решений и формируют алгоритмическое мышление. Показаны основы структурного, динамического, объектно-ориентированного, функционального программирования. Приведены способы работы с функциями, алгоритмы поиска в длину, в ширину, бэктрекинга, рекурсии. Материал расположен по возрастанию сложности, код программ снабжен комментариями, разъясняющими все языковые конструкции Python.

Keywords: Python

Павел Добряк Санкт-Петербург «БХВ-Петербург» 2024


УДК 004.43 ББК 32.973.26-018.1 Д57 Добряк П. В. Д57 Python. Красивые задачи для начинающих. — СПб.: БХВ-Петербург, 2024. — 352 с.: ил. — (Для начинающих) ISBN 978-5-9775-1882-6 В книге приведены примеры решения различных практических задач на языке Python и предложено детальное пошаговое описание процесса написания программы для каждой из них. Подобраны задачи, которые имеют несколько вариантов решений и формируют алгоритмическое мышление. Показаны основы структурного, динамического, объектно-ориентированного, функционального программирования. Приведены способы работы с функциями, алгоритмы поиска в длину, в ширину, бэктрекинга, рекурсии. Материал расположен по возрастанию сложности, код программ снабжен комментариями, разъясняющими все языковые конструкции Python. Для начинающих программистов УДК 004.43 ББК 32.973.26-018.1 Группа подготовки издания: Руководитель проекта Павел Шалин Зав. редакцией Людмила Гауль Научный редактор Михаил Бабич Редактор Григорий Добин Компьютерная верстка Ольги Сергиенко Дизайн серии Марины Дамбиевой Оформление обложки Зои Канторович "БХВ-Петербург", 191036, Санкт-Петербург, Гончарная ул., 20 ISBN 978-5-9775-1882-6 © Добряк П. В., 2024 © Оформление. ООО "БХВ-Петербург", ООО "БХВ", 2024


Оглавление Предисловие ..................................................................................................................... 7 На кого рассчитана эта книга? ...................................................................................................... 10 Структура книги ............................................................................................................................. 11 Благодарности ................................................................................................................................ 12 Глава 1. Условия ........................................................................................................... 13 1.1. Квадратное уравнение, комплексные корни ........................................................................ 13 Задача ...................................................................................................................................... 13 1.2. Кирпич и дыра в стене............................................................................................................ 18 Задача ...................................................................................................................................... 18 Версия 1 ......................................................................................................................... 19 Версия 2 ......................................................................................................................... 20 Версия 3 ......................................................................................................................... 21 Глава 2. Структурное программирование ............................................................... 23 2.1. Полупроходной балл .............................................................................................................. 23 Задача 1 ................................................................................................................................... 23 Версия 1 ......................................................................................................................... 24 Версия 2 ......................................................................................................................... 25 Задача 2 ................................................................................................................................... 28 Версия 3 ......................................................................................................................... 28 Версия 4 ......................................................................................................................... 29 Задача 3 ................................................................................................................................... 31 Версия 5 ......................................................................................................................... 32 2.2. Метеостанция .......................................................................................................................... 33 Задача ...................................................................................................................................... 33 Версия 1 ......................................................................................................................... 34 Версия 2 ......................................................................................................................... 37 2.3. Седловина матрицы ................................................................................................................ 40 Задача ...................................................................................................................................... 40 2.4. Максимальный квадрат в матрице, заполненный нулями ................................................... 43 Задача ...................................................................................................................................... 43 2.5. Чемпионат по игре в тетрис ................................................................................................... 46 Задача ...................................................................................................................................... 46


4 Оглавление 2.6. Проверка правильности расстановки скобок ....................................................................... 51 Задача ...................................................................................................................................... 51 Глава 3. Функции .......................................................................................................... 57 3.1. Решето Эратосфена и числа-близнецы ................................................................................. 57 Задача ...................................................................................................................................... 57 3.2. Решето Сундарама .................................................................................................................. 65 Задача ...................................................................................................................................... 65 3.3. Лесенка .................................................................................................................................... 67 Задача ...................................................................................................................................... 67 3.4. Линейный и медианный фильтры ......................................................................................... 73 Задача 1 ................................................................................................................................... 73 Задача 2 ................................................................................................................................... 84 3.5. Алгоритм Евклида .................................................................................................................. 85 Задача ...................................................................................................................................... 85 3.6. Гипероператоры ...................................................................................................................... 91 Задача ...................................................................................................................................... 91 3.7. Ханойские башни .................................................................................................................. 102 Задача .................................................................................................................................... 102 3.8. Отображения списков ........................................................................................................... 109 Задача .................................................................................................................................... 109 Глава 4. Поиск в длину и ширину, бэктрекинг, динамическое программирование ........................................................................... 119 4.1. Лабиринт ............................................................................................................................... 119 Задача 1 ................................................................................................................................. 120 Версия 1 ....................................................................................................................... 120 Версия 2 ....................................................................................................................... 125 Версия 3 ....................................................................................................................... 130 Версия 4 ....................................................................................................................... 134 Задача 2 ................................................................................................................................. 139 4.2. Задача о восьми ферзях ........................................................................................................ 143 Задача .................................................................................................................................... 143 Версия 1 ....................................................................................................................... 143 Версия 2 ....................................................................................................................... 156 4.3. Поиск индекса элемента в списке ....................................................................................... 161 Задача .................................................................................................................................... 161 Версия 1 ....................................................................................................................... 161 Версия 2 ....................................................................................................................... 162 Версия 3 ....................................................................................................................... 163 4.4. Сжатие строки ....................................................................................................................... 167 Задача .................................................................................................................................... 167 4.5. Укладка рюкзака ................................................................................................................... 176 Задача .................................................................................................................................... 176 4.6. Подпоследовательность максимальной длины .................................................................. 180 Задача .................................................................................................................................... 181 Версия 1 ....................................................................................................................... 181 Версия 2 ....................................................................................................................... 184


Оглавление 5 Версия 3 ....................................................................................................................... 186 Версия 4 ....................................................................................................................... 191 4.7. Палиндром наибольшей длины ........................................................................................... 199 Задача .................................................................................................................................... 199 4.8. Гиперсфера ............................................................................................................................ 208 Задача .................................................................................................................................... 209 Глава 5. Объектно-ориентированное программирование .................................. 219 5.1. Графы с помощью словарей ................................................................................................ 219 Задача 1 ................................................................................................................................. 220 Задача 2 ................................................................................................................................. 220 Задача 3 ................................................................................................................................. 224 Задача 4 ................................................................................................................................. 225 Задача 5 ................................................................................................................................. 226 Задача 6 ................................................................................................................................. 227 Задача 7 ................................................................................................................................. 228 Задача 8 ................................................................................................................................. 228 5.2. Родословное древо ................................................................................................................ 230 Задача 1 ................................................................................................................................. 230 Задача 2 ................................................................................................................................. 238 Задача 3 ................................................................................................................................. 243 5.3. Период в числовой последовательности ............................................................................ 252 Задача .................................................................................................................................... 252 Версия 1 ....................................................................................................................... 252 Версия 2 ....................................................................................................................... 253 Версия 3 ....................................................................................................................... 254 5.4. Треугольник Паскаля и сочетания ...................................................................................... 257 Задача 1 ................................................................................................................................. 257 Задача 2 ................................................................................................................................. 259 Отступление про функторы ....................................................................................... 262 5.5. Гиперкуб в многомерном пространстве ............................................................................. 266 Задача .................................................................................................................................... 266 Глава 6. Функциональное программирование ..................................................... 287 6.1. Интеграл ................................................................................................................................ 288 Задача .................................................................................................................................... 288 6.2. Отображения, сохраняющие внутреннюю структуру ........................................................ 298 Общая задача ........................................................................................................................ 298 Задача 1 ................................................................................................................................. 299 Задача 2 ................................................................................................................................. 302 Задача 3 ................................................................................................................................. 304 6.3. Цепочки функций ................................................................................................................. 305 Задача .................................................................................................................................... 305 6.4. Монады .................................................................................................................................. 309 Задача .................................................................................................................................... 309 6.5. Карринг .................................................................................................................................. 319 Задача .................................................................................................................................... 319 6.6. Функторы .............................................................................................................................. 331 Задача .................................................................................................................................... 331 Выводы по главам 3, 4 и 6 .......................................................................................................... 342


6 Оглавление Глава 7. Сюрреализм .................................................................................................. 345 7.1. Фрактальные списки ............................................................................................................. 345 7.2. Фрактальный словарь ........................................................................................................... 346 7.3. Бесконечные вызовы функции ............................................................................................ 346 7.4. Функтор с бесконечными вызовами ................................................................................... 347 Заключение ................................................................................................................... 349 Предметный указатель .............................................................................................. 351


s Предисловие Язык программирования Python1 стремительно набирает популярность для разработки самых различных приложений, потеснив другие языки общего назначения. А при обучении языкам программирования он стал безусловным лидером. Причина этой популярности — лаконичность, простота и выразительность Python. Многие вспомогательные действия (например, сортировка списка чисел по возрастанию), которые раньше занимали целый «абзац» кода, на Python укладываются в одну строчку. В отличие от других языков программирования, на Python программисту не надо высматривать идею, спрятанную за множеством строк кода. В результате, глядя на решение задач на Python, программист сразу видит суть алгоритма. Кроме того, многие «технические детали» разработки программ можно теперь поначалу вообще не объяснять начинающим программистам или объяснять мимоходом. К ним относится, например, выделение памяти для списков объектов, работа с большими числами и типы данных. В отличие от других языков, Python на самом деле стал языком высокого уровня, спрятав глубоко эти технические нюансы. А раньше на них приходилось тратить несколько уроков. По правде говоря, перейдя на обучение Python, я теперь вспоминаю обучение на С++, а тем более на Pascal, с содроганием. Но то, что школьники и студенты массово перешли на изучение Python, не означает, что они научились программировать... По-прежнему остро стоит вечная проблема выбора методики обучения и учебников. Есть много новомодных концепций обучения: проектное обучение (когда команду учеников сразу кидают на решение большой практической задачи), обучение как игра, наука как развлечение, обучение во сне... (последнее — шутка, я не знаю тех, кто во сне научился программировать, но это не значит, что на этом нельзя сделать деньги). Зная историю педагогики, могу сказать, что были попытки под другими названиями внедрить эти концепции еще сто лет назад. Все они провалились и были признаны «педагогическими извращениями». Но обучению программированию далеко не сто лет. Может быть, эти концепции победят в ИТ-сфере? 1 Правильно его читать как «Па´йтон» — с ударением на первом слоге.


8 Предисловие Массовое обучение программированию проходило на моих глазах, когда я был сперва школьником и студентом, а потом университетским преподавателем, репетитором и преподавателем на курсах повышения квалификации, получив опыт преподавания языков программирования как групповой, так и индивидуальный среди школьников, студентов и взрослых людей. Информатика тесно связана с математикой, а в математике, как известно, «нет царских путей». В этом смысле я консерватор и считаю, что обучение программированию, как и математике, не может быть легким, оно требует умственных усилий, а порой и очень серьезного напряжения ума. А научившись программировать, вы станете уже другим человеком. Поэтому если вам говорят, что вас обучат программировать легко и играючи, боюсь, что вас дурачат. Как же действительно научиться программировать? По моему мнению, только решая задачи: от простых к сложным, сначала учебные, а потом и практические. Лучше еще ничего не придумали. Что же я могу рекомендовать своим ученикам? Есть классическая книга создателя языка программирования Pascal Никлауса Вирта «Алгоритмы и структуры данных» и многотомник Дональда Кнута «Искусство программирования». «Вот бы перевести их на Python!» — подумал я. Но потом понял, что это плохая идея. Многотомник не может стать учебником. Он может быть справочным пособием, пособием для обучения олимпиадному программированию, книгой уже для профессионалов, которые хотят углубить свои знания по отдельным темам. Теперь что касается перевода на Python «Алгоритмов и структур данных». Дело в том, что современное программирование на языках высокого уровня — это не только знание алгоритмов и структур данных, но еще и понимание разных стилей (парадигм программирования). Python обладает таким набором средств выразительности, что один и тот же алгоритм можно запрограммировать очень поразному — так, что будет казаться, будто программа написана на разных языках. И простой перевод программ в книге Вирта на Python познакомил бы читателя только с одним стилем. Кажется, что этого уже было бы достаточно, ведь вы бы научились программировать. Но программирование сегодня — это командная работа. И труд программиста — это не только писать свой код «с нуля», но и разбираться в чужих программах. И кто знает, с какими стилями программирования вам придется иметь дело! Получается, что сейчас будущим программистам очень не хватает фундаментальной книги «Алгоритмы, структуры данных и парадигмы программирования». Но такая книга противоречила бы «духу времени» и «духу Python». Дело в том, что она была бы очень толстой! А программы на Python очень лаконичны. Должен ли быть толстым учебник по Python? Занимаясь преподавательской деятельностью, я понимал, что очень ограничен во времени. У меня есть три месяца, полгода, год, максимум — два года, чтобы научить моего учащегося программировать. И я не могу долго объяснять ему одну тему. Да и ученикам это будет не интересно. Чтобы соответствовать «духу времени», нужно очень быстро познакомить ученика со всем спектром возможностей


Предисловие 9 программирования на Python, причем так, чтобы он мог решать задачи. А потом уже углублять знания, возможно, сделав еще несколько заходов по всем темам, но на других задачах. То есть «духу времени» соответствует циклическое обучение. Каждому циклу обучения нужна своя книга. Может быть, стоит посмотреть, какие книги на Python есть в книжных магазинах, выбрать несколько из них и заниматься по ним? Книг по Python большое разнообразие, и в этом есть свой смысл. Просмотрев книги по программированию, ориентированные на решение задач, и множество статей в Интернете, я понял, что у них есть один серьезный недостаток. В них, как правило, излагается идея алгоритма, а потом приводится код программы. Но в реальности код программы не возникает сразу. Программист пишет код постепенно, по шагам, допуская типичные ошибки, отлаживая программу. И очень хочется видеть не только готовый код с пояснениями, но и то, как он «рождался», чтобы научиться программировать самому. Итак, мои требования к идеальной книге по программированию: 1. Циклическое обучение, каждому циклу — своя книга. 2. Небольшой объем, лаконичность книги. 3. Ориентир на решение задач. 4. Доходчивое объяснение идеи алгоритма. 5. Пошаговое изложение того, как создается программа. 6. Несколько алгоритмов, если задачу можно решить разными способами. 7. Для одного и того же алгоритма писать программу в разных стилях программирования. Не удовлетворившись поисками книг по Python, которые полностью соответствовали бы моей преподавательской деятельности, я начал писать книги сам. Для первой своей книги «Python. 12 уроков для начинающих»1 я выбрал основные языковые конструкции Python, сгруппировал их по темам по возрастанию сложности и для каждой темы подобрал минимально необходимое количество задач, чтобы ученик усвоил тему. Но у меня осталась подборка решений множества красивых задач, которые не вошли в эту книгу. Их я решал со своими учениками, если у нас было много времени, задавал их как домашние задания или использовал для повторения изученного материала на следующий год (во второй цикл обучения). Кроме того, в первую книгу не вошли такие важные алгоритмы, как поиск в длину и поиск в ширину. Эти задачи я перенес в свою вторую книгу «Python. Красивые задачи для начинающих», которую вы держите перед собой. Но мне не хотелось писать простое дополнение или продолжение первой книги. Я хотел написать самостоятельное произведение, сделав книгу доступной для начинающих. Поэтому я также расположил материал по темам и возрастанию слож- 1 См. https://bhv.ru/product/python-12-urokov-dlya-nachinayushhih/.


10 Предисловие ности и снабдил код программ комментариями, вновь разъясняющими все языковые конструкции Python по мере их появления. Я назвал книгу «Python. Красивые задачи для начинающих». Но какие задачи я считаю красивыми? Те, которые: 1. Можно решить разными способами и в разных стилях программирования. 2. Можно обобщить, усложнить, придумать продолжение. 3. Идеи которых можно использовать при решении множества других задач. 4. Наконец, есть ведь и интуитивное представление о красоте. Разве не красивы, например, Ханойские башни? В общем, я приглашаю читателя насладился интеллектуальной красотой задач этой книги — красотой постановки задач, идей алгоритмов и программ. На кого рассчитана эта книга? Книга написана для читателей разного уровня погружения в программирование. Обучение программированию «с нуля». Если вы в своей жизни до сих пор не написали ни одной самой простой учебной программы (даже не знаете, что такое программа «Привет, мир!»), то, скорее всего, эта книга не для вас. Но если у вас есть способности, например, к математике, если у вас развито логическое мышление или у вас есть инженерное или техническое образование, то вы можете попробовать. Но рекомендую вам иметь какой-нибудь простой учебник для самых-самых новичков — например, есть бесплатный учебник на сайте https://pythonworld.ru/samouchitel-python. Вы можете изучить какую-нибудь тему из этого учебника, а потом закрепить материал по первой моей книге «Python. 12 уроков для начинающих» или даже по этой. Уже есть представление/опыт изучения Python. Если вы уже пробовали изучать Python самостоятельно, в школе, университете или на курсах и знаете (хотя, возможно, уже подзабыли) основные языковые конструкции языка, то эта книга вам хорошо подходит. С ее помощью вы освежите в памяти языковые конструкции Python (хотя для этого лучше подойдет моя первая книга «Python. 12 уроков для начинающих») и научитесь писать алгоритмы разного уровня сложности. Умеете программировать на других языках программирования. Книга хорошо для вас подойдет. Код программ снабжен комментариями, поясняющими синтаксис языка, и вы легко выучите Python. А разбирая задачи, улучшите свои алгоритмические способности. Хотите подготовиться к олимпиадам по программированию или к собеседованиям при приеме на работу. Книга подойдет вам идеально. Задачи, в нее включенные, — это классика программирования, а их знание — это признак хорошего ИТ-образования. Приемы,


Предисловие 11 которые используются для решения этих задач, часто встречаются в олимпиадных задачах. Решение задач из этой книги — отличная тренировка ума. Структура книги Книга разбита на главы в соответствии с языковыми конструкциями, которые нужны для решения задач. При этом все то, что встречалось в предыдущих главах, становится нужным в следующих (рис. П.1). Рис. П.1. Главы и языковые конструкции Задачи также расположены с увеличением сложности: 1. Первая глава — это проверка, понимает ли читатель вообще, что такое алгоритм, и может ли он программировать условия. 2. Вторая глава — структурное программирование. Здесь используются циклы, ветвления и встроенные в Python коллекции. Алгоритмы просты, и читатель, у которого сформировано алгоритмическое мышление, способен написать их самостоятельно. 3. В третьей главе к структурному программированию добавляются функции. Поначалу можно вполне обойтись без них (просто программа станет плохо читаемой), но потом без функций уже не написать программу никак. Алгоритмы здесь не очень сложные, однако читатель уже может с ними самостоятельно не справиться, поэтому здесь перед программированием излагается идея алгоритма. 4. В четвертой главе приводятся задачи, которые решаются с помощью алгоритмов поиска в длину, в ширину, бэктрекинга, рекурсии, динамического программирования. Здесь не добавляются новые языковые конструкции, а отрабатываются крупные приемы программирования, которые можно назвать типами алгоритмов. Это хорошие задачи для подготовки к олимпиадам или к собеседованиям. 5. Пятая глава посвящена объектно-ориентированному программированию, но многие задачи решаются самыми разнообразными средствами. 6. В шестой главе в первом разделе на примере вычисления интеграла детально объясняются простые приемы функционального программирования. Остальные же разделы сосредоточены на обманчиво простой задаче отображения коллек-


12 Предисловие ции с сохранением ее сложной структуры, что делается с помощью вереницы функций. На этой задаче отрабатываются такие сложные приемы функционального программирования, как функторы и монады. 7. Короткая седьмая глава абсурдна. Она посвящена даже не алгоритмам, а лаконичным программам, которые работают удивительными способами и при этом совершенно бесполезны, по крайней мере, на современном этапе развития программирования. Цель главы — развлечь читателя. Структура каждого раздела содержит формулировку задачи, языковые конструкции и приемы программирования, далее излагается идея алгоритма и приводится пошаговое программирование — то, как программист реально пишет программу (это является главным достоинством книги). Если в программе используются новые, еще не описанные ранее языковые конструкции, то к коду приводится примечание с разъяснением этих языковых конструкций. Если строить обучение по этой книге, то, за рядом исключений, одному полуторачасовому занятию соответствует один раздел. Таким образом, на изучение материала книги понадобится минимум 36, а обычно, по опыту, до 50 занятий. Благодарности Я благодарю многих учеников, а особенно своих детей Андрея и Артема Добряков, которым «досталось от меня» больше всех моих задач, Виктора Запорожца, занимавшегося со мной как школьником, так и студентом, и Александра Ефимова, который пришел ко мне уже во взрослом возрасте. Александр «заставил» меня объяснять алгоритмы еще более понятно, чем я делал до этого. И, конечно, спасибо моей жене Евгении Хрущевой, которая не только поправила мой русский язык, вычитывая мои книги, но еще и нашла пару ошибок в коде. Я благодарен редактору Григорию Добину. До того, как он скрупулезно прочитал мою книгу, я и не представлял, какой это огромный труд.


ГЛАВА 1 Условия Эта глава — «разминочная». Мы решим две задачи на применение условий, чтобы начать изучать Python, вспомнить его основы (если вы их уже знаете) или перейти к программированию на Python с других языков программирования. 1.1. Квадратное уравнение, комплексные корни Задача Вводятся действительные числа a, b, c. Найти корни квадратного уравнения: 0 2 cbxax =++ . Языковые конструкции: условия, вложенные и составные условия, математические формулы. Ход программирования Шаг 1. Квадратное уравнение решается по формуле 2 1,2 4 2 b b ac x a −± − = . Проблема задачи в том, что надо учесть все возможные случаи вводимых чисел. Начнем анализ формулы. Видно, что при a = 0 происходит деление на ноль. Но это не значит, что при a = 0 нет корней (просто уравнение станет линейным). В зависимости от a будут две ветки решения (листинг 1.1.1). Листинг 1.1.1 a=float(input()) b=float(input()) c=float(input()) if a!=0: pass else: pass


14 Глава 1 ПРИМЕЧАНИЯ К КОДУ Здесь и далее в примечаниях будут делаться комментарии к коду (пояснения к синтаксису Python), рассчитанные на читателей, которые Python совсем не знают, — например, только начинают его изучать, возможно, зная какой-нибудь другой язык программирования. Если листинг вам понятен, смело можете примечания пропускать. Здесь input() означает ввод текста с клавиатуры, а float — преобразование текста в число с дробной частью. Таким образом, a=float(input()) означает то, что, когда Python во время выполнения дойдет до этой строчки, он «попросит» пользователя ввести текст, который затем преобразует в число (возможно, с дробной частью) и сохранит полученное значение в переменной a. Конструкция if a!=0 означает, что весь следующий код, сделанный с отступом, в нашем случае pass, будет выполняться только, если выполняется условие a не равно 0. В строчках с отступом после слова else («иначе») идет код, который будет выполняться, если условие, написанное в if, не выполняется (в нашем случае, если a равно 0). pass означает, что, когда Python при выполнении программы дойдет до строчки со словом pass, ему ничего делать не нужно. Программисты используют этот оператор в заготовках программ. Вскоре мы заменим все pass на работающий код. Чтобы самостоятельно написать и запустить эту программу на вашем компьютере вам нужно установить какой-нибудь компилятор Python. Я рекомендую начать изучать программирование на Python со среды разработки IDLE Python. Это свободно распространяемое программное обеспечение, которое можно скачать с официального сайта: https://www.python.org/downloads/. IDLE Python представляет собой простую среду разработки, в которой вы легко сможете разобраться и научиться в ней создавать и запускать программу. Шаг 2. В зависимости от значения дискриминанта (подкоренного выражения) может быть несколько корней: 4acbD 2 −= . При D = 0 один корень, при D > 0 два корня, при D < 0 действительных корней нет, но зато есть комплексные (листинг 1.1.2). Листинг 1.1.2 a=float(input()) b=float(input()) c=float(input()) if a!=0: D=b*b-4*a*c if D==0: x=(-b)/(2*a) print(x) elif D>0: pass else: pass else: pass


Условия 15 ПРИМЕЧАНИЯ К КОДУ Отступы в программе имеют важное значение. Обратите внимание на то, что после условия if a!=0: идут восемь строчек с отступом. Это означает, что эти строчки будут выполняться, только если а не равно 0. Две строчки: x=(-b)/(2*a) и print(x) — сделаны с двойным отступом. Это означает, что они будут выполняться при дополнительном условии D равно 0. Условия можно вкладывать друг в друга на неограниченную глубину (в нашем случае условие if D==0: вложено внутрь if a!=0:), увеличивая количество отступов в начале строчек. В программе появляется альтернативное условие elif D>0: (иначе если D больше нуля). Ветвление в программе может идти не только по двум веточкам с помощью конструкции if...else... (условие выполняется или не выполняется), но и по трем и больше с помощью одного или нескольких альтернативных условий if...elif...elif... elif...else. print(x) означает «вывести значение переменной x на экран». С помощью print программа сообщает пользователю, который ее запустил, о результатах программы. Обратите внимание, что в условии if D==0: оператор «равно» написан два раза. Это не опечатка. Python, как и многие языки программирования, имеет два оператора «равно»: «равно как сравнение» и «равно как присваивание». В условии используется «равно как сравнение», т. е. ==, а в строчке x=(-b)/(2*a) — «равно как присванивание», т. е. =. Шаг 3. Рассмотрим случай D > 0. Для извлечения корня импортируем функцию sqrt из библиотеки math. Типичной ошибкой при программировании формулы a acbb x 2 4 2 2,1 −±− = является отсутствие скобок. Чтобы не ошибиться, начнем формулу с последней выполняемой операции — деления: x1=/ Видно, что числитель и знаменатель дроби — сложные выражения. Поставим скобки: x1=()/() Далее напишем числитель и знаменатель: x1=(-b-sqrt(D))/(2*a) Очень часто начинающие программисты забывают скобки в знаменателе: x1=(-b-sqrt(D))/2*a получая ошибочный результат. Программа приобретает окончательный вид в листинге 1.1.3. Листинг 1.1.3 from math import sqrt a=float(input()) b=float(input()) c=float(input())


16 Глава 1 if a!=0: D=b*b-4*a*c if D==0: x=(-b)/(2*a) print(x) elif D>0: x1=(-b-sqrt(D))/(2*a) x2=(-b+sqrt(D))/(2*a) print(x1,x2) else: pass else: pass ПРИМЕЧАНИЕ К КОДУ from math import sqrt означает «импортировать функцию извлечения квадратного корня sqrt из библиотеки math». Очень многие полезные функции, которые тем не менее нужны далеко не в каждой программе, вынесены в отдельные библиотеки (файлы). Чтобы их использовать, нужно импортировать их из соответствующих библиотек. Библиотека math содержит много полезных математических функций — например, тригонометрические. Шаг 4. При D < 0, как учат в школе, действительных корней нет, зато, как учат в университете, есть корни комплексные. Для тех, кто не знает, что это такое, поясню на примере. Пусть a = 1, b = 1, c = 13, тогда 2 1,2 4 1 1 4 1 3 1 3 ( 1) 2 22 2 13 1 1 3 1. 2 22 b b ac x a − ± − −± − −± − −± ⋅− = = == = −± ⋅ − = = −± − Для любого уравнения можно получить похожую формулу, выделив в отдельную позицию корень из минус единицы. Для него в математике вводится специальное обозначение: i −= 1 . Мы получаем ответ: x i 2 3 2 1 2,1 ±−= . Таким образом, числа вида z ImRe ⋅+= i называются комплексными (Re — действительная часть, Im — мнимая). Несмотря на кажущуюся поначалу бессмысленность таких чисел, они заняли прочное место в математике и оказались полезны как для развития самой математики, так и для множества прикладных задач.


Условия 17 Если мы по ветке D < 0 напишем нашу обычную формулу через библиотечную функцию sqrt: x1=(-b-sqrt(D))/(2*a) то Python выдаст ошибку. Вспомним, что корень можно заменить на дробную степень: n n aa 1 = , и запишем формулу в программу (листинг 1.1.4). Листинг 1.1.4 from math import sqrt a=float(input()) b=float(input()) c=float(input()) if a!=0: D=b*b-4*a*c if D>0: x1=(-b-sqrt(D))/(2*a) x2=(-b+sqrt(D))/(2*a) print(x1,x2) elif D==0: x=(-b)/(2*a) print(x) else: x1=(-b-D**0.5)/(2*a) x2=(-b+D**0.5)/(2*a) print(x1,x2) else: pass Результат выполнения программы: (-0.5-0.8660254037844386j) (-0.49999999999999994+0.8660254037844386j) Python может работать с комплексными корнями! Только мнимую единицу обозначает не i, а j. Шаг 5. Рассмотрим случай a = 0. Получим линейное уравнение: bx + c = 0. В общем случае получается один корень, вычисляемый по формуле b c x −= . Но при b = 0 получается деление на ноль.


18 Глава 1 Добавим вариант указанного линейного уравнения при b = 0. Уравнение примет вид c = 0. В этом случае если с = 0, то x — любое число, если нет, то корней нет. Значит, ветка a = 0 распадается на три случая. Мы получаем готовую программу (листинг 1.1.5). Листинг 1.1.5. Решение уравнений второй степени a=float(input()) b=float(input()) c=float(input()) if a!=0: D=b*b-4*a*c if D==0: x=(-b)/(2*a) print(x) elif D>0: x1=(-b-sqrt(D))/(2*a) x2=(-b+sqrt(D))/(2*a) print(x1,x2) else: x1=(-b-D**0.5)/(2*a) x2=(-b+D**0.5)/(2*a) print(x1,x2) else: if b==0 and c==0: print("x-любое") elif b==0: print("корней нет") else: x=-c/b print(x) ПРИМЕЧАНИЕ К КОДУ В if b==0 and c==0: мы использовали составное условие, соединив два условия: b равно 0 и с равно 0 с помощью оператора and («и»). Для связки условий также применяется оператор or («или»). Еще есть оператор отрицания условия not («не»). Какой из операторов связки использовать, надо решать в каждом конкретном случае. От этого выбора зависит ход выполнения программы. 1.2. Кирпич и дыра в стене Задача Вводятся размеры прямоугольной дыры в стене a, b и размеры кирпича x, y, z. Можно ли кирпич просунуть в стену?


Условия 19 Рис. 1.1. Дыра в стене и кирпич Толщина стены значения не имеет, дыра сквозная (рис. 1.1). Способ просовывания по диагонали не рассматривайте. Версия 1 Языковые конструкции: составные условия. Ход программирования Стороны дыры и кирпича не упорядочены. Проблемой является то, какими сторонами вставлять кирпич в стену. Это можно попробовать сделать тремя способами: (x, y), или (x, z), или (y,z). В каждом из этих вариантов есть выбор из двух способов просовывания — например: x расположить вдоль a, у — вдоль b или наоборот. Получается составное условие из шести частей, соединенных or (листинг 1.2.1) Листинг 1.2.1. Кирпич и дыра в стене. Составные условия print("введите 2 размера дыры") a=int(input()) b=int(input()) print("введите 3 размера кирпича") x=int(input()) y=int(input()) z=int(input()) if (x<=a and y<=b or y<=a and x<=b or x<=a and z<=b or z<=a and x<=b or z<=a and y<=b or y<=a and z<=b): print("войдет") else: print("не войдет") ПРИМЕЧАНИЕ К КОДУ Здесь в составном условии используются два оператора: and («и») и or («или»). Оператор and имеет больший приоритет, чем оператор or. То есть сперва выполняются все and, и только потом уже or. В нашем случае вместо: if x<=a and y<=b or y<=a and x<=b... мы могли бы поставить дополнительные скобки: if (x<=a and y<=b) or (y<=a and x<=b)...


20 Глава 1 Но мы не стали этого делать по той же причине, по которой в арифметической формуле 2*3+4*5 мы не ставим скобки, хотя могли бы это сделать: (2*3)+(4*5). Оператор or играет роль сложения, and — умножения, not — возведения в степень. Впрочем, так же как и в арифметических выражениях, во многих логических выражениях скобки нужны, и их расстановка влияет на ход выполнения программы. Версия 2 Языковые конструкции: условие, кортежи. Ход программирования Упорядочим размеры дыры и размеры кирпича с помощью функций min и max. Тогда нам станет не нужно большое составное условие. Чтобы упорядочить три размера кирпича, понадобятся в худшем случае три перестановки (листинг 1.2.2). Листинг 1.2.2. Кирпич и дыра в стене. Кортежи, min и max Результат print("введите 2 размера дыры") a=int(input()) b=int(input()) a,b=min(a,b),max(a,b) print(a,b) print("введите 3 размера кирпича") x=int(input()) y=int(input()) z=int(input()) x,y=min(x,y),max(x,y) y,z=min(y,z),max(y,z) x,y=min(x,y),max(x,y) print(x,y,z) if x<=a and y<=b: print("войдет") else: print("не войдет") введите 2 размера дыры 2 1 1 2 введите 3 размера кирпича 2 5 1 1 2 5 войдет ПРИМЕЧАНИЯ К КОДУ min — функция вычисления минимума из переменных, помещенных в скобки, max — функция вычисления максимума. В выражениях a,b=min(a,b),max(a,b)) и в трех выражениях вида x,y=min(x,y),max(x,y) используются кортежи — упорядоченные последовательности переменных для одновременных присваиваний. Эта конструкция отсутствует во многих языках программирования, поэтому поясним ее на следующем примере. Код: a=1 b=2 c=3 можно заменить на a,b,c = 1,2,3 Подобные упрощающие записи программисты называют синтаксическим сахаром.


ГЛАВА 3 Функции Отличие задач этой главы от задач предыдущей — в том, что для их решения удобны (а для двух последних разделов и обязательны) функции. Здесь мы уже имеем нетривиальные алгоритмы, которые тяжело придумать самостоятельно. Поэтому читатель, считающий, что умеет программировать, может прочитать идею алгоритма, а потом попробовать написать программу самостоятельно. Несколько особняком стоит последний раздел, посвященный функциональному программированию. Он не дает новых алгоритмов и даже не решает какую-либо хорошую задачу. С этой точки зрения его можно смело пропустить. Но он полностью меняет мышление, заставляя строить программы совершенно иным способом, чем мы привыкли! 3.1. Решето Эратосфена и числа-близнецы Задача Вводится n. Найти: 1. Простые числа, меньшие или равные n, методом решета Эратосфена. Простыми числами называются натуральные числа, которые делятся только на 1 и на самих себя (не раскладываются на множители): 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31... 2. Из найденных простых чисел найти числа-близнецы (отличающиеся друг от друга не более чем на 2): 2, 3, 5, 7 и 11, 13 и 17, 19 и 29, 31... Языковые конструкции: списки, множества, циклы, функции. Идея алгоритма Задача была решена еще древнегреческим математиком Эратосфеном — он наносил числа на восковую дощечку (рис. 3.1) и выкалывал те, которые разлагаются на множители (решето Эратосфена). Выколем сначала числа, делящиеся на 2 (рис. 3.2). Следующее невыколотое число после 2 — это 3. Выколем теперь числа, кратные трем (рис. 3.3). Далее выкалываем числа, делящиеся на 5 (рис. 3.4). Затем — числа, делящиеся на 7 (рис. 3.5). И так далее...


58 Глава 3 Рис. 3.1. Числа на «восковой дощечке» Рис. 3.2. Выкалывание чисел, делящихся на 2 Рис. 3.3. Выкалывание чисел, делящихся на 3 Рис. 3.4. Выкалывание чисел, делящихся на 5 Рис. 3.5. Выкалывание чисел, делящихся на 7


Функции 59 Ход программирования Шаг 1. Создадим список чисел от 0 до n (листинг 3.1.1). Листинг 3.1.1 n=int(input()) L=list(range(n+1)) print(L) Шаг 2. Перебирая список от числа 2, ищем «невыколотое» число (листинг 3.1.2). Выкалыванию будет соответствовать обнуление. Листинг 3.1.2 n=int(input()) L=list(range(n+1)) for i in range(2,n+1) if L[i]!=0: #здесь будет выкалывание чисел print(L) Шаг 3. Для каждого ненулевого L[i] надо выколоть все числа за ним с шагом, равным тому же L[i], — например, для L[i]=3 выкалываем 6, 9, 12... Для этого организуем вложенный цикл (листинг 3.1.3). Листинг 3.1.3 n=int(input()) L=list(range(n+1)) for i in range(2,n+1): if L[i]!=0: for j in range(L[i]*2,n+1,L[i]): L[j]=0 print(L) Шаг 4. Осталось удалить выколотые числа, т. е. нули. Самый короткий программный способ — это удалить дубликаты с помощью преобразования во множество set и обратно в список. Так мы получим список простых чисел (листинг 3.1.4). Листинг 3.1.4 n=int(input()) L=list(range(n+1)) for i in range(2,n+1): if L[i]!=0: for j in range(L[i]*2,n+1,L[i]): L[j]=0 L=sorted(list(set(L))) print(L)


60 Глава 3 Результат: 100 [0, 1, 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97] Шаг 5. Задумаемся об оптимальности работы программы. Если n = 100, то у него нет делителей со значением больше 50. То есть верхнюю границу цикла i можно обозначить как n/2 (листинг 3.1.5). Листинг 3.1.5 n=int(input()) L=list(range(n+1)) for i in range(2,n//2): if L[i]!=0: for j in range(L[i]*2,n+1,L[i]): L[j]=0 L=sorted(list(set(L))) print(L) Шаг 6. Посмотрим на нижнюю границу цикла. Мы ее выбрали как L[i]+L[i]=L[i]*2. Но это величина, делящаяся на 2, и мы ее уже выкололи при i = 2. Если мы возьмем за нее L[i]*3, то она была выколота при i = 3. Значит, стартовать нужно с L[i]*L[i]. Мы получили программу для поиска простых чисел (листинг 3.1.6). Листинг 3.1.6. Простые числа с помощью решета Эратосфена n=int(input()) L=list(range(n+1)) for i in range(2,n//2): if L[i]!=0: for j in range(L[i]*2,n+1,L[i]): L[j]=0 L=sorted(list(set(L))) print(L) Шаг 7. Из списка простых чисел выберем числа-близнецы — будем перебирать список простых чисел и смотреть на соседние. Если разница с обоими соседями больше 2, то число выкалываем (листинг 3.1.7). Листинг 3.1.7 n=int(input()) L=list(range(n+1)) for i in range(2,n//2): if L[i]!=0: for j in range(L[i]*2,n+1,L[i]): L[j]=0 L=sorted(list(set(L)))


ГЛАВА 4 Поиск в длину и ширину, бэктрекинг, динамическое программирование Эта глава не вносит новые языковые конструкции. Но она важна тем, что развивает приемы программирования из предыдущей главы, объединяя их в новые подходы к решению задач: поиск в длину с бэктрекингом и рекурсией, поиск в ширину, динамическое программирование с его разновидностью — динамикой по подотрезкам. Эти подходы мы отработаем на ряде, казалось бы, различных задач. Мы будем искать выход из лабиринта, расставлять ферзей на шахматном поле так, чтобы они друг друга не порубили, научимся укладывать рюкзак и т. д. В завершение мы мысленно покинем наш трехмерный мир и перенесемся в бесконечномерный, пересчитывая, я хотел сказать, звезды на небе, нет, точки внутри бесконечномерной сферы. В общем, изучая алгоритмы этой главы, мы выведем наше алгоритмическое мышление на новый уровень. 4.1. Лабиринт Задачи на поиск в длину и ширину начнем решать с лабиринта. Пусть лабиринт задан двумерным списком, в котором 9 означает заполненное пространство (стена), а 0 — пустое пространство (проход): L=[[9,9,9,9,9,9,0,9,9,9], [9,0,9,9,9,0,0,0,0,9], [9,0,9,0,0,0,9,9,0,9], [9,0,0,0,0,9,0,0,0,9], [9,9,0,9,0,9,0,9,0,9], [9,9,0,9,9,9,0,9,0,9], [9,0,0,0,0,9,9,9,9,9], [9,0,9,9,0,9,9,0,0,9], [9,0,0,9,0,9,9,0,0,9], [9,9,0,9,9,9,9,9,9,9]] Для удобства восприятия напишем функцию вывода на экран, в которой нули в лабиринте будем выводить нулями, а девятки заменим на решетки (листинг 4.1.1).


120 Глава 4 Листинг 4.1.1 Результат ... def show(L): for i in range(len(L)): s="" for j in range(len(L)): if L[i][j]==0: s=s+"0" elif L[i][j]==9: s=s+"#" print(s) show(L) ######0### #0###0000# #0#000##0# #0000#000# ##0#0#0#0# ##0###0#0# #0000##### #0##0##00# #00#0##00# ##0####### Задача 1 Разметить лабиринт стрелками в сторону выхода. Языковые конструкции: двумерные списки, функции, циклы. Напишем несколько версий программы. Версия 1 Сначала используем самый примитивный алгоритм. Ход программирования Шаг 1. Прежде всего, определимся с обозначениями (табл. 4.1, рис. 4.1). Таблица 4.1. Обозначения клеток в лабиринте Смысл В программе Вывод на экран Пустота 0 0 Стена 9 # Движение вправо 1 > Движение вверх 2 ^ Движение влево 3 < Движение вниз 4 v Рис. 4.1. Вывод на экран обозначений клеток в лабиринте


Поиск в длину и ширину, бэктрекинг, динамическое программирование 121 Дополним функцию вывода на экран (листинг 4.1.2). Листинг 4.1.2 def show(L): for i in range(len(L)): s="" for j in range(len(L)): if L[i][j]==0: s=s+"0" elif L[i][j]==9: s=s+"#" elif L[i][j]==1: s=s+">" elif L[i][j]==2: s=s+"^" elif L[i][j]==3: s=s+"<" elif L[i][j]==4: s=s+"v" print(s) Шаг 2. Прежде всего нужно обойти края лабиринта, найти входы (они же выходы) и проставить нужные стрелки. Будем каждый алгоритм помещать в отдельную функцию (листинг 4.1.3). Листинг 4.1.3 Результат ... def simple(L): for i in range(len(L)): if L[0][i] == 0: L[0][i] = 2 if L[-1][i] == 0: L[-1][i] = 4 if L[i][0] == 0: L[i][0] = 3 if L[i][-1] == 0: L[i][-1] = 1 simple(L) show(L) ######^### #0###0000# #0#000##0# #0000#000# ##0#0#0#0# ##0###0#0# #0000##### #0##0##00# #00#0##00# ##v####### Шаг 3. Простой алгоритм разметки лабиринта заключается в том, что мы будем просматривать внутренность лабиринта (циклом внутри цикла) в поисках еще не размеченных клеток (там, где хранится 0). Для каждой нулевой клетки посмотрим соседние («оглядимся по сторонам»). Если найдем соседнюю клетку со стрелочкой


122 Глава 4 (значениями 1, 2, 3, 4), то нарисуем стрелку в сторону этой соседней клетки (листинг 4.1.4). Листинг 4.1.4 Результат ... def simple(L): for i in range(len(L)): if L[0][i] == 0: L[0][i] = 2 if L[-1][i] == 0: L[-1][i] = 4 if L[i][0] == 0: L[i][0] = 3 if L[i][-1] == 0: L[i][-1] = 1 for i in range(1, len(L) - 1): for j in range(1, len(L) - 1): if L[i][j] == 0: if 1<=L[i][j + 1]<=4: L[i][j] = 1 elif 1<=L[i - 1][j]<=4: L[i][j] = 2 elif 1<=L[i][j - 1]<=4: L[i][j] = 3 elif 1<=L[i + 1][j]<=4: L[i][j] = 4simple(L) simple(L) show(L) ######^### #0###0^<<# #0#000##^# #0000#00^# ##0#0#0#^# ##0###0#^# #0000##### #0##0##00# #0v#0##00# ##v####### Шаг 4. Проанализируем результат выполнения программы на предыдущем шаге. Мы видим, что стрелки появились далеко не во всех нужных клетках. Почему же этого не произошло? Дело в том, что лабиринт мы просматривали справа налево сверху вниз (как мы читаем книги), и многие клетки с нулями просмотрели еще до того, как в соседних клетках появились стрелки. Получается, что одного просмотра лабиринта мало. Нужно несколько просмотров. Оценим максимально возможное количество просмотров. Если предположить, что все пустые клетки вытянуты в одну длинную цепочку и за один просмотр лабиринта мы рисуем только одну стрелку, то количество просмотров будет равно количеству клеток во всем лабиринте. Поместим циклы просмотра лабиринта внутрь еще одного цикла — получим цикл внутри цикла внутри цикла (листинг 4.1.5).


ГЛАВА 5 Объектно-ориентированное программирование В этой главе мы перейдем к объектно-ориентированному программированию, т. е. к использованию языковой конструкции «класс». Но начнем мы с задачи на графах, которую на других языках программирования решают либо с помощью таблиц (двумерных списков), что весьма неизящно, либо с помощью классов. А мы по контрасту с другими языками решим ее без того и другого — только с помощью словарей. Следующей мы решим задачу о поиске родственников в родословном древе. Здесь мы напишем полноценный класс со множеством возможностей. Изложение объектно-ориентированного программирования будет идти «с нуля», хотя и лаконично. Затем наступит очередь задачи о поиске повторяющегося фрагмента в списке. Казалось бы, мы уже решали похожую задачу при сжатии строки (см. разд. 4.4), и можем прекрасно обойтись без классов. Мы и напишем несколько версий этой программы без классов. А потом покажем алгоритм, где классы пригодятся. До сих пор мы имели дело с двумерными списками. Сейчас же мы попробуем обратиться к многомерным спискам. Вы ведь уже познакомились с бесконечномерным пространством в разд. 4.8? Но там пространство было бесконечномерным, а список — одномерным. А здесь мы встретимся с многомерным списком, количество измерений которого заранее неизвестно. И самое удобное — поместить его в класс. А потом решить с его помощью обобщенный вариант задачи поиска максимального квадрата, заполненного одними нулями (мы ее уже решали двумя способами в этой книге, а также в книге «Python. 12 уроков для начинающих»). То есть найдем гиперкуб. 5.1. Графы с помощью словарей Есть много разновидностей и определений графов. В простейшем случае графом называется множество вершин, соединенных ребрами (рис. 5.1). Графы находят много применений. Типичным примером практического их применения являются логистика и транспортные пути. На графах решаются задачи подсчета стоимости пути, поиска оптимального пути, задачи построения области достижимости.


220 Глава 5 x y Рис. 5.1. Пример графа: A, B, C, D — вершины, x, y, z, v — ребра В программах графы обычно хранят в виде таблиц смежности или создают для графовых структур класс. В Python же есть коллекция, которая хорошо подходит для графов. Это словарь. Мы решим несколько задач на графы различного вида, постепенно усложняя модель данных. Задача 1 Пусть вершины — это города, и задается рекомендуемое время для посещения каждого города. Пользователь выбирает маршрут, вводя города. Нужно вывести время путешествия. Ход программирования Для этой задачи граф не нужен. Сохраним в словаре названия городов и время посещения, а в цикле сложим время выбранных городов (листинг 5.1.1). Листинг 5.1.1 Результат V={'A':4,'B':2,'C':1,'D':3} print(V) s=input().split() s=sum([V[i] for i in s]) print(s) {'A': 4, 'B': 2, 'C': 1, 'D': 3} A B C D 10 Задача 2 Усложним задачу. Допустим, мы можем перемещаться между городами не произвольным образом, а только по дорогам. Граф, показанный на рис. 5.1, можно рассматривать как пример дорожной сети. Пусть вершины — это города, а ребра — дороги. Пользователь выбирает маршрут, задавая дороги. Надо подсчитать время пребывания в городах. Ход программирования Шаг 1. Зададим дороги в виде словаря, в котором ключами будут имена дорог, а значениями — список вершин, которые соединяет дорога (листинг 5.1.2).


Объектно-ориентированное программирование 221 Листинг 5.1.2 vertex={'A':4,'B':2,'C':1,'D':3} graph={'x':['A','B'],'y':['B','C'],'z':['C','A'],'v':['C','D']} Шаг 2. Спросим пользователя о дорогах и составим список списков вершин, которые соединят маршрут (листинг 5.1.3). Листинг 5.1.3 ... s=input().split() p1=[graph[i] for i in s] print(p1) Для введенного маршрута: x y v мы получим последовательность ребер: [['A', 'B'], ['B', 'C'], ['C', 'D']] Шаг 3. Соединим список списков в один список (листинг 5.1.4). Листинг 5.1.4 p2=p1[0] for i in range(1,len(p1)): p2=p2+p1[i] print(p2) Для нашего примера получим результат: ['A', 'B', 'B', 'C', 'C', 'D'] Шаг 4. Составим последовательность вершин, удалив дубликаты (листинг 5.1.5). Листинг 5.1.5 path=[p2[0]] for i in range(1,len(p2)): if path[-1]!=p2[i]: path.append(p2[i]) print(path) Для нашего примера получим: ['A', 'B', 'C', 'D'] Шаг 5. Теперь сложим числовые характеристики вершин, как мы это делали в предыдущей задаче: s=sum([vertex[i] for i in path])


222 Глава 5 Мы получим программу (листинг 5.1.6). Листинг 5.1.6 vertex={'A':4,'B':2,'C':1,'D':3} graph={'x':['A','B'],'y':['B','C'],'z':['C','A'],'v':['C','D']} print(vertex) print(graph) s=input().split() p1=[graph[i] for i in s] print(p1) p2=p1[0] for i in range(1,len(p1)): p2=p2+p1[i] print(p2) path=[p2[0]] for i in range(1,len(p2)): if path[-1]!=p2[i]: path.append(p2[i]) print(path) s=sum([vertex[i] for i in path]) print(s) Результат: {'A': 4, 'B': 2, 'C': 1, 'D': 3} {'x': ['A', 'B'], 'y': ['B', 'C'], 'z': ['C', 'A'], 'v': ['C', 'D']} x y v [['A', 'B'], ['B', 'C'], ['C', 'D']] ['A', 'B', 'B', 'C', 'C', 'D'] ['A', 'B', 'C', 'D'] Шаг 6. Тестируя программу, обнаружим, что она работает плохо для очень многих путей. Например, если мы идем в обратную сторону. Это связано с тем, что дубликаты вершин не удаляются. Например, прямой путь: x y z [['A', 'B'], ['B', 'C'], ['C', 'A']] ['A', 'B', 'B', 'C', 'C', 'A'] ['A', 'B', 'C', 'A'] Обратный путь: z y x [['C', 'A'], ['B', 'C'], ['A', 'B']] ['C', 'A', 'B', 'C', 'A', 'B'] Напрашивается решение: создать для каждого пути обратный путь, переставив вершины в списках (листинг 5.1.7).


ГЛАВА 6 Функциональное программирование Функциональному программированию была посвящена глава в книге «Python. 12 уроков для начинающих». Кроме того, отдельные языковые конструкции и приемы этого стиля встречались в главе 3 этой книги, где мы только начали использовать функции. В табл. 6.1 сделан обзор разделов этой главы с точки зрения приемов функционального программирования. Таблица. 6.1. Алгоритмы главы 3 и приемы функционального программирования Раздел Приемы функционального программирования 3.1. Решето Эратосфена и числа-близнецы Удаление нулей из списка. Фильтрация осуществлялась с помощью встроенного в Python стандартного функционала (функции, принимающей в качестве аргумента другие функции) filter. Условие фильтрации задавалось с помощью анонимной функции lambda 3.2. Решето Сундарама Отображение одного списка в другой (был список [0,1,2,3,4,...,n], стал [3,5,7,9,...,2n+1]) с помощью встроенного в Python функционала map 3.4. Линейный и медианный фильтры Передача функции в качестве параметра другой функции (функция фильтрации принимала в качестве аргументов исходное изображение, форму апертуры и функцию обработки) 3.5. Алгоритм Евклида Стягивание списка в одно число с помощью библиотечного функционала reduce (вычисление НОД списка осуществлялось путем последовательного применения функции с двумя аргументами) 3.6. Гипероператоры (третья версия программы) Стягивание списка с помощью reduce, причем в качестве функции стягивания рекурсивно передавался гипероператор более низкого порядка 3.8. Отображения списков Этот раздел уже целиком посвящен функциональному программированию: мы заменили библиотечную функцию map на собственную и с помощью таких приемов, как «частичное применение» и «карринг», подготовили функцию для ее использования в отображении Таким образом, мы применили функциональное программирование для задач всех разделов, кроме двух. Практически всегда можно было обойтись без приемов функционального программирования. Но с ним программы становились изящнее. Перед прочтением этой главы рекомендую освежить в памяти функциональное


288 Глава 6 программирование, просмотрев последние версии программ из разделов, которые приведены в табл. 6.1. В этой главе в первом разделе, посвященном вычислению интеграла, мы вновь повторим функциональное программирование: основные языковые конструкции, приемы и встроенные в Python функционалы. Остальные же разделы будут развивать разд. 3.8, посвященный отображению списков. Там мы на основе исходного списка получали новый список путем применения функции к каждому элементу списка. В этой главе мы сделаем отображение более сложных структур (например, вложенных списков с неограниченным количеством уровней вложенности). Наше отображение будет сохранять внутреннюю структуру. Кроме того, отображение будет осуществляться не одной функцией, а последовательностью функций. Здесь мы познакомимся с такими приемами программирования, как монады, функторы и карринг. Эти приемы пришли в программирование из очень абстрактного раздела математики, который называется теория категорий. В этой книге функциям посвящены три главы: 3, 4 и 6. Получается, что они разделены главой 5. «Объектно-ориентированное программирование». Это связано с тем, что для функторов нам понадобятся языковые конструкции из объектноориентированного программирования. 6.1. Интеграл Задача Написать функционал приблизительного вычисления интеграла. В функционал передаются аргументы: функция для интегрирования и пределы интегрирования. Геометрическим смыслом интеграла ∫ = right left )( dxxfS является площадь криволинейной трапеции под функцией (рис. 6.1). Рис. 6.1. Геометрический смысл интеграла


Функциональное программирование 289 Существуют точные методы вычисления интеграла по заданной функции, которые составляют целый раздел математики. Мы же будем вычислять интеграл примерно. Зададим шаг изменения переменной x как step и разобьем трапецию на n прямоугольников, где step leftright n − = . Чем меньше шаг step, тем значение суммы площадей прямоугольников ближе к площади криволинейной трапеции (рис. 6.2 и 6.3). x y left right step n прямоугольников Рис. 6.2. Приближенное вычисление интеграла Рис. 6.3. Более точное вычисление интеграла Заметим, что мы выбрали высоты прямоугольников так, чтобы длины их левых сторон равнялись значению функции. Таким образом, мы можем примерно подсчитать значение интеграла как сумму площадей этих прямоугольников: ∫ ∑ ∑ − = − = = ≈ ⋅+⋅=⋅⋅+ 1 0 1 0 ()( ) ( ) n i right left n i istepleftfstepstepistepleftfdxxfS . Ход программирования Шаг 1. Напишем функцию вычисления интеграла в соответствии с изложенным математическим методом (листинг 6.1.1). Листинг 6.1.1. Интеграл. Функция высшего порядка def integral(f, left, right, step): s=0 n=int((right-left)/step) for i in range(n): s=s+f(left+step*i) return s*step


290 Глава 6 def square(x): return x*x def cube(x): return x**3 print(integral(square,0,2,0.001)) print(integral(cube,0,2,0.001)) Обратите внимание, что при передаче square и cube в integral после них нет круглых скобок. Круглые скобки с параметрами ставятся при непосредственном вызове функции. А здесь мы просто передаем имена этих функций, которые вызываются внутри кода интеграла. В функции «интеграл» три аргумента: left, right, step — числовые, а один аргумент, f — это функция. Функция integral, поскольку она принимает в качестве аргумента другую функцию, называется функционалом, или функцией высшего порядка. Шаг 2. В нашем примере мы вычислили интегралы от двух функций от x: квадрата и куба. Мы задали функции с помощью ключевого слова def. Но есть и вторая форма задания функций — c помощью ключевого слова lambda (листинг 6.1.2). Листинг 6.1.2 def integral(f, left, right, step): s=0 n=int((right-left)/step) for i in range(n): s=s+f(left+step*i) return s*step square = lambda x: x*x cube = lambda x: x**3 print(integral(square,0,2,0.001)) print(integral(cube,0,2,0.001)) Заметим, что в форме функции с lambda ключевое слово return не нужно. Шаг 3. Какое преимущество дает использование lambda вместо def? С помощью lambda-выражения имя функции отделяется от ее кода. А это значит, что в случае, если не предполагается повторного использования функции в разных частях программы, мы можем код функции без имени сразу прописывать там, где он нужен. То есть мы используем анонимные функции (листинг 6.1.3). Листинг 6.1.3. Интеграл. Анонимные функции def integral(f, left, right, step): s=0


Функциональное программирование 291 n=int((right-left)/step) for i in range(n): s=s+f(left+step*i) return s*step print(integral(lambda x: x*x,0,2,0.001)) print(integral(lambda x: x**3,0,2,0.001)) Шаг 4. Вычислим интеграл от функции x n , предварительно задав значение n = 10 (листинг 6.1.4). Листинг 6.1.4. Интеграл. Захват переменной def integral(f, left, right, step): s=0 n=int((right-left)/step) for i in range(n): s=s+f(left+step*i) return s*step n=10 print(integral(lambda x: x**n,0,2,0.001)) Заметим, что в коде анонимной функции используется переменная x, которая передается в анонимную функцию как аргумент, а переменная n берется из внешнего окружения (это называется захват переменной). Шаг 5. Допустим, что, помимо вычисления интеграла, нам нужна функция возведения в степень: def power(x,n): return x**n В этом случае попытка передать ее в вызов интеграла, как это мы делали с функциями возведения в куб и квадрат: print(integral(power,0,2,0.001)) приведет к ошибке. Ведь в интеграле используются функции с одним аргументом, а у возведения в степень их два. Правильно будет использовать анонимную функцию (листинг 6.1.5). Листинг 6.1.5. Интеграл. Захват функции def integral(f, left, right, step): s=0 n=int((right-left)/step) for i in range(n): s=s+f(left+step*i) return s*step


292 Глава 6 def power(x,n): return x**n print(integral(lambda x: power(x,10),0,2,0.001)) Заметим, что в этой программе из внешнего окружения в анонимную функцию берется уже не число, а другая функция, т. е. происходит захват функции. Шаг 6. Вынесем код анонимной функции из вызова интеграла, присвоив этой функции имя decpower. Так мы на основе функции возведения в степень создадим новую функцию — возведения в степень 10 (листинг 6.1.6). Листинг 6.1.6 def integral(f, left, right, step): s=0 n=int((right-left)/step) for i in range(n): s=s+f(left+step*i) return s*step def power(x,n): return x**n decpower = lambda x: power(x,10) print(integral(decpower,0,2,0.001)) Шаг 7. В этой программе мы на основе функции возведения в степень с помощью анонимной функции получили новую функцию — «возведение в десятичную степень». Но что если нам на основе функции возведения в степень понадобятся и другие именованные функции — например, квадрат и куб? Логично код с анонимной функцией вынести в отдельную функцию partpower, которая будет порождать эти новые функции (листинг 6.1.7). Листинг 6.1.7 def integral(f, left, right, step): s=0 n=int((right-left)/step) for i in range(n): s=s+f(left+step*i) return s*step def power(x,n): return x**n def partpower(n): return lambda x: power(x,n)


ГЛАВА 7 Сюрреализм Решив задачу поиска гиперкуба в многомерном пространстве, причем решив ее с использованием почти всего арсенала языковых конструкций Python, и сделав отображения сложных коллекций с помощью функторов, можно было бы и закончить эту книгу. Но в конце я хочу показать вам абсурдные, непонятно зачем нужные, но вполне работоспособные программы. Программисты тоже умеют шутить. 7.1. Фрактальные списки Создадим список из двух элементов: left и right: left=int(input()) right=int(input()) L=[left,right] Добавим в середину списка новый элемент — этот же самый список! L.insert(1,L) Запустим программу (листинг 7.1.1) и посмотрим, что получится. Заметим, что к граничным элементам мы всегда имеем доступ. Листинг 7.1.1. Фрактальный список Результат left=int(input()) right=int(input()) L=[left,right] L.insert(1,L) print(L) print(L[1]) print(L[1][1]) print(L[1][1][1]) print(L[0]) print(L[1][0]) print(L[1][1][0]) print(L[1][1][1][0]) 1 2 [1, [...], 2] [1, [...], 2] [1, [...], 2] [1, [...], 2] 1 1 1 1


346 Глава 7 7.2. Фрактальный словарь Мы уже знаем, что значением в словаре может быть сложная структура данных — например, список или даже другой словарь. Поместим в словарь этот же самый словарь (листинг 7.2.1)! Листинг 7.2.1. Фрактальный словарь Результат V={'V':'V'} V['V']=V print(V) print(V['V']) print(V['V']['V']) print(V['V']['V']['V']) {'V': {...}} {'V': {...}} {'V': {...}} {'V': {...}} 7.3. Бесконечные вызовы функции В функциональном программировании мы имеем дело с функциями высших порядков, которые принимают в качестве аргумента другие функции или возвращают функции в качестве ответа. Сделаем функцию, которая возвращает как ответ саму себя: def f(): return f f()()() Теперь мы можем ее вызывать бесконечное число раз подряд. Чтобы функция не была совсем бесполезной, пусть она помещает свой аргумент в список (листинг 7.3.1). Листинг 7.3.1. Функция с неограниченным количеством вызовов Результат L=[] def f(n): L.append(n) return f f(1) f(2)(3) f(4)(5)(6) print(L) [1, 2, 3, 4, 5, 6] Просто хранить число в списке не интересно. Добавим в функцию еще один аргумент — другую функцию, которую мы будем применять к первому аргументу (листинг 7.3.2).


Сюрреализм 347 Листинг 7.3.2 Результат L=[] def f(n=0,g=lambda n:n): L.append(g(n)) return f f()(2,lambda n:n**2)(2,lambda n:n**3)(2,lambda n:n**4) print(L) [0, 4, 8, 16] 7.4. Функтор с бесконечными вызовами Доработаем функтор из разд. 5.4 так, чтобы его можно было запускать с неограниченным количеством квадратных и круглых скобок. Пусть функция square возвращает саму себя, а результат сохраняет в списке, как это делалось в предыдущем разделе (листинг 7.4.1). Листинг 7.4.1. Функтор с неограниченным количеством вызовов Результат class functor: def __init__(self,f=lambda n:n): self.f=f def __call__(self,n): return self.f(n) def __getitem__(self,n): return self.f(n) Square=[] @functor def square(n): Square.append(n*n) return square square(1) square[2] square(3)(4) square[5][6] square(7)[8](9)[10] print(Square) [1, 4, 9, 16, 25, 36, 49, 64, 81, 100] Так что же такое square: список, функция или класс?


348 Глава 7


Заключение Если вы перед чтением этой книги познакомились с моей книгой «Python. 12 уроков для начинающих» или занимались этим уже с некоторым предварительным опытом изучения Python, то вы, можно сказать, учились программировать на Python второй раз. Мы вновь прошлись по всем разделам языковых конструкций, вспомнили старые приемы программирования и изучили несколько новых. Мы решили старые задачи новыми методами и новые задачи старыми методами. Как я уже упоминал, процесс обучения итеративен. И мы с вами сделали второй заход в изучении программирования, пройдя большой путь: от поиска корней квадратного уравнения до поиска гиперкуба в многомерном пространстве, от расчета, можно ли кирпич просунуть в дыру, до многоэтапных отображений коллекций, сохраняющих внутреннюю структуру. Сколько же нужно сделать заходов, чтобы научиться программировать на Python? Я назвал эту книгу «Python. Красивые задачи для начинающих» и упомянул, какие же задачи я считаю красивыми — те, которые имеют несколько вариантов решений и предлагают несколько вариантов усложнений, чем формируют алгоритмическое мышление. Вы сможете воспользоваться приобретенными навыками в дальнейшем. Какую же задачу я считаю самой красивой? А вот ее я в этой книге привел только мимоходом (это числа Фибоначчи)... потому что ей целиком будет посвящена моя третья книга по программированию на Python. В ней на примере всего одной задачи мы сделаем третий заход изучения программирования. Она тем и уникальна, что ее можно решить множеством способов с использованием разнообразных приемов программирования и практически всех языковых конструкций. Это действительно самая красивая программистская задача.


Предметный указатель А Абстракция 231 Агрегация 239 Анонимная функция 63, 67, 112, 290, 342 Апертура линейного фильтра 73 Аргументы 231 Атрибут 234 Б Бустрофедон 77 Бэктрекинг 119, 125, 128, 134, 143, 155, 180, 186 В Взаимная рекурсия 173 Вложенная функция 293 Г Гексация 92 Гиперкуб 266 Гипероператоры 91, 92 Гиперсфера 208 Глобальный объект 260 Граф 219 Д Декоратор 261, 264, 315, 322 Динамика по подотрезкам 164, 173 Динамическое программирование 173, 176 З Замыкание 114 ◊ функции 115 Захват ◊ переменной 114, 291 ◊ функции 292 И Индексатор 269 Инкапсуляция 234 Интроспекция 328 К Карринг 321 Каррирование (карринг) 116 Класс 231 Коллекция 23 Комментарий 34 Комплексные числа 16 Конструктор 231 Кортеж 20 Л Лог 106 Лямбда-выражение 331 М Мемоизация (кеширование) 174, 261 Метод 234 ◊ бинарного поиска 163 ◊ линейного поиска 162


352 Предметный указатель Множество 53 Монада 319, 338 Н Накапливающаяся сумма 31 Наследование 256 О Обратная индексация 30 Объект 231 Ориентированный граф 225 Отступы 15 П Палиндром 199 Парадигма программирования 63, 113 Пентация 92 Переключатель 78 Р Рекуррентная формула 26 Рекурсивная структура данных 239 Рекурсия 87, 119, 130, 132, 134, 141, 143, 156, 160, 176, 180, 186 ◊ высшего порядка 99 Решето ◊ Сундарама 65 ◊ Эратосфена 57 С Свойство 231 Седловина матрицы 40 Сигнатура функции 306 Синтаксический сахар 20, 26 Словарь 47, 220 Список 21 Списочное выражение 37 Среда разработки IDLE Python 14 Срез 29 Стек 52 ◊ LIFO 52 Структурное программирование 23 Счетчик со сбросом 26 Т Теория категорий 288, 319, 342 Тетрация 92 Треугольник Паскаля 257 Ф Факториал 331 Флаг 45, 253 Функтор 262, 332, 347 Функционал 85, 111, 290 Функциональное программирование 297 Функция 63 ◊ высшего порядка 85, 111, 290 ◊ с переменным числом аргументов 306 Х Ханойские башни 102 Хеш-функция 256 Ц Цикл 24 Ч Частичное применение функции 114, 294 Числа Фибоначчи 331 Э Экземпляр класса 231


Click to View FlipBook Version