Текст
                    1


Валерий Рубанцев Решение задач на языке Python 3.x 2
Бесплатное издание Все права защищены. Никакая часть этой книги не может быть воспроизведена в любой форме без письменного разрешения правообладателей. Автор книги не несёт ответственности за возможный вред от использования информации, составляющей содержание книги и приложений. Copyright 2014 Валерий Рубанцев Лилия Рубанцева 3
От автора Python, удушающий приверженцев других языков программирования Во многих странах мира Питон используется в учебных целях, в том числе и как первый язык программирования для начинающих. В России же Питон в этом качестве явно недооценён. На уровне процедурного программирования Питон не имеет себе равных среди современных языков. Программы, написанные на Питоне, как правило короче и яснее, чем, например, на более популярном в России Сишарпе. В Питоне значительно меньше типов данных, что позволяет избежать многих проблем с их выбором и приведением. Тип int в Питоне даёт возможность работать с целыми числами произвольной длины, а тип Decimal – с вещественными числами любой точности. С их помощью можно разрабатывать простые, но эффективные приложения для решения математических, физических, химических и других задач, связанных с обработкой числовой информации. К несомненным достоинствам Питона следует отнести также: • он бесплатен • популярен во всём мире • прост в изучении, но используется профессиональными программистами • универсален 4
• один и тот же исходный код можно запустить на компьютерах с любой из распространённых операционных систем: Windows, Mac OS X, Linux • поддерживается фирмой Майкрософт, поэтому программы на Питоне можно писать непосредственно в Visual Studio В этой книге подробно рассматривается решение 90 задач: математических, комбинаторных, вероятностных, игровых. Большая часть задач взята из двух книг по математике для школьников: • Нагибин Ф.Ф., Канин Е.С. - Математическая шкатулка • Кордемский Б.А., Ахадов А.А.- Удивительный мир чисел Эти книги - одни из лучших книг по математике на русском языке. Они выдержали несколько изданий и до сих пор пользуются заслуженной популярностью. Я надеюсь, что вам будет интересно решать классические задачи на компьютере. При написании книги использовались и другие «авторитетные» источники (полный список литературы см. в конце книги): • В. А. Дагене, Г. К. Григас, К. Ф. Аугутис - 100 задач по программированию • Брудно А. Л. Каплан Л. И. - Олимпиады по программированию для школьников • Ehrhard Behrends - Fünf Minuten Mathematik: 100 Beiträge der Mathematik-Kolumne der Zeitung DIE WELT • Абрамов С.А. и др. - Задачи по программированию • Журнал Техника – молодёжи Кроме собственно решения задач, мы разработаем и «вспомогательные» проекты: • • • Делимость чисел Наибольший общий делитель Наименьшее общее кратное 5
• • • • • • • • • Простые числа, Решето Эратосфена Факторизация чисел Совершенные числа Числовые ребусы Факториал Числа Фибоначчи Генерирование перестановок Генерирование сочетаний Разбиение числа на слагаемые Несмотря на сравнительно небольшой объём книги, она охватывает все ключевые элементы языка Питон. В самом её начале вы найдёте Тематический указатель, который поможет вам ориентироваться во всех проектах и легко находить нужный. В конце многих глав имеются задания для самостоятельного решения. Все проекты разрабатывались в Microsoft Visual Studio 2010 и 2013 (не Express!) с установленными IronPython 2.7.4, Python 3.3.3 и Python Tools for Visual Studio 2010 и 2013. Поскольку исходный код программ с расширением *.py не содержит никакой специфической информации, то может быть запущен в любой другой среде разработки программ на Питоне, хотя бы в IDLE, которую мы также будем использовать при разработке проектов, так как исходный код в ней выполняется быстрее, чем в Visual Studio. Чтобы пользоваться русскими буквами, после создания нового проекта вставьте в его начало строку: # -*- coding: Windows-1251 -*- Редактор кода укажет вам на ошибку: Сохраните файл программы, выполнив команду Файл > Сохранить как…: 6
В диалоговом окне щёлкните по стрелке в правой части кнопки Сохранить, чтобы открылся список, и выберите из него строку Сохранить с кодировкой…: В следующем окне просто нажмите кнопку Да, 7
а затем – кнопку ОК: Ошибка исчезнет: Вы можете сократить эти процедуры, если сразу выполните в меню Файл пункт Дополнительные параметры сохранения… Валерий Рубанцев 8
Условные обозначения, принятые в книге: Дополнение или замечание Ненавязчивое требование или указание Исходный код Задание для самостоятельного решения Папка с исходным кодом программы. Исходные коды всех проектов находятся в папке _Projects 9
Оглавление Решение задач на языке Python 3.x .......................... 2 От автора .................................................................... 4 Оглавление ................................................................. 10 Тематический указатель .................................................. 14 Глава #1. Числа, числа, числа… ........................................ 16 Признаки делимости чисел .................................................................................. 20 Проект Делится - не делится? ............................................................................. 26 Проект Назойливый остаток ................................................................................ 32 Проект Первый числовой фокус ........................................................................ 37 Проект Второй числовой фокус .......................................................................... 40 Проект Шестизначное число ................................................................................ 43 Проект Тройка, семёрка и... только .................................................................... 46 Проект Любопытное свойство чисел ................................................................. 51 Проект Как определил ошибку Чохбилмиш?.................................................. 55 Проект Шестизначный перенос ........................................................................... 57 Задания для самостоятельного решения .......................................................... 59 Глава #2. НОД, НОК и компания ...................................... 60 Проект Проект Проект Проект Проект Проект Проект Проект Проект Наибольший общий делитель .............................................................. 60 Наименьшее общее кратное ................................................................... 67 НОК нескольких чисел ............................................................................ 69 Всезнающая статистика ........................................................................... 73 Восстановите потерянную цифру ........................................................ 76 Снимите маску с одной цифры ............................................................ 78 На одно делится, на другое нет ............................................................ 80 Кто где живёт?............................................................................................ 82 Три велосипедиста................................................................................... 85 Глава #3. Простые числа ................................................ 87 Проект Чудеса в решете Эратосфена ................................................................. 90 Проект Простые числа........................................................................................... 94 Проект Простые числа 2 ....................................................................................... 97 Взаимно простые числа ........................................................................................ 101 Проект Разложение числа на простые множители ..................................... 102 10
Проект Совершенные числа ................................................................................ 105 Задания для самостоятельного решения ........................................................ 108 Глава #4. Числовые ребусы ............................................. 109 Проект Каковы жуки? ............................................................................................. 110 Проект Четыре "пари" ............................................................................................ 113 Проект Тайна трёх слагаемых ............................................................................ 115 Проект Меняем четыре буквы на четыре цифры .......................................... 119 Проект Коварная задача папы ............................................................................ 122 Задания для самостоятельного решения ........................................................ 125 Глава #5. Степени и корни............................................. 127 Проект Кубическое число.................................................................................... 127 Проект И «хвост», и «грива» .............................................................................. 130 Проект Возведение в квадрат без операции умножения ..................... 133 Проект Возведение в куб без операции умножения .............................. 135 Проект Зашифрованные жуки ............................................................................ 140 Проект Ж-Ж-Ж! ........................................................................................................ 143 Проект Девять в квадрате .................................................................................... 145 Проект Пара чисел: 3149 и 3151 ....................................................................... 147 Проект Число 698 896.......................................................................................... 150 Проект Числа 11 826, 12 363, 14 676 ............................................................... 153 Проект Числа 32 043 и 99 066 .......................................................................... 159 Проект Число 117 649 ........................................................................................... 160 Проект Красивые цепочки равенств ................................................................. 164 Задания для самостоятельного решения ........................................................ 167 Глава #6. Числовые ряды и другие задачи .......................... 168 Проект Факториал .................................................................................................. 168 Проект Факториальные нули ............................................................................... 171 Проект Числа Фибоначчи ..................................................................................... 173 Проект Числа Фибоначчи 2 ................................................................................. 177 Проект «Избранные» числа................................................................................. 180 Проект Безошибочный прогноз ......................................................................... 185 Проект Ошибочный прогноз ............................................................................... 190 Проект Нумерация страниц ................................................................................ 193 Проект Сколько страниц в книге? ..................................................................... 195 Проект И такие есть числа .................................................................................. 197 Проект Трёхзначное число ................................................................................. 199 11
Проект Таких чисел только два ........................................................................ 201 Проект Ещё два числа........................................................................................... 203 Проект Отгадать число, ничего не спрашивая ............................................. 205 Проект Три лягушки .............................................................................................. 208 Проект Гаусс ............................................................................................................. 211 Проект Плюс-минус ............................................................................................. 215 Проект Минус-плюс .............................................................................................. 217 Проект Дробный ряд ............................................................................................. 219 Проект Ещё один дробный ряд .......................................................................... 221 Проект Трёхзначное число 2 .............................................................................. 223 Проект Вычисляем пи и е ................................................................................... 225 Проект Тригонометрические функции ........................................................... 238 Проект Сотая цифра .............................................................................................. 245 Задания для самостоятельного решения ........................................................ 248 Глава #7. Диофантовы уравнения и линейное программирование 250 Проект На ферме .................................................................................................... 253 Проект Решите систему уравнений .................................................................. 255 Проект Сооружение для лаборатории ............................................................. 257 Проект И такие есть числа 3 .............................................................................. 260 Проект И такие есть числа 4 .............................................................................. 264 Проект Ящики .......................................................................................................... 266 Проект Путёвки ...................................................................................................... 269 Проект На базаре .................................................................................................... 272 Проект Дедушка и внучка .................................................................................... 274 Проект Сколько у мамы дочерей и сыновей?............................................... 276 Проект Из жизни Дефурнеля .............................................................................. 278 Проект Счётные палочки ..................................................................................... 281 Задания для самостоятельного решения ........................................................ 283 Глава #8. Компьютерные игры ......................................... 284 Проект Игра Охота на Скалоеда ........................................................................ 284 Проект Игра Охота на Скалоедов ..................................................................... 303 Головоломка Космический охотник ................................................................ 315 Проект Игра Угадай число .................................................................................. 317 Проект Игра Крестики-нолики........................................................................... 324 Задания для самостоятельного решения ........................................................ 341 Глава #9. Занимательная комбинаторика и теория вероятностей ... 344 12
Проект Генерируем перестановки .................................................................... 346 Проект «Правильные» перестановки .............................................................. 350 Проект Премия за изобретение ......................................................................... 353 Проект Массивные перестановки ..................................................................... 359 Проект Сумма пятизначных чисел ................................................................... 363 Проект Как собрать Дрим тим? .......................................................................... 366 Проект Разменный пункт ..................................................................................... 370 Проект Спортлото .................................................................................................. 374 Проект Жребий брошен! ....................................................................................... 388 Задания для самостоятельного решения ........................................................ 391 Ответы ..................................................................... 394 Космический охотник ........................................................................................... 394 Литература................................................................. 395 13
Тематический указатель Элементы языка Проекты Идентификаторы * Выражения Операторы * Комментарии # * Целый тип int * Вещественный тип float Проект Дробный ряд Проект Ещё один дробный ряд Проект Вычисляем пи и е Проект Тригонометрические функции Списки Проект Делится - не делится? Проект Тройка, семёрка и... только . . . Списки списков Проект Игра Охота на Скалоеда Проект Игра Охота на Скалоедов Проект Игра Крестики-нолики Проект Массивные перестановки Проект Сумма пятизначных чисел Переменные Локальные переменные * * Операторы и операции Проект Тройка, семёрка и... только, присваивания Проект Наибольший общий делитель. . . = =+ =- *= /= %= Операции отношения < > == != <= >= * Логические операции Проект Три велосипедиста, and, or и not Проект Чудеса в решете Эратосфена . . . Арифметические опера- * ции + - * // / % 14
* Условный оператор if Условный оператор if-else Вложенные условные операторы Цикл for Проект Делится - не делится? . . . Цикл while Проект Тройка, семёрка и... только, Проект Наибольший общий делитель . . . Вложенные циклы Проект Назойливый остаток, Проект Простые числа . . . Бесконечные циклы Проект Делится - не делится? Проект Назойливый остаток . . . Оператор break Проект Делится - не делится? . . . Оператор continue Проект Делится - не делится? . . . Классы class Конструкторы Проект Игра Угадай число, Проект Игра Крестики-нолики Поля Методы * Ключевое слово return * Класс math Проект Делится - не делится?. . . Класс random Проект Безошибочный прогноз; Проект Ошибочный прогноз. . . Форматированный вывод Проект Ящики, Проект Путёвки Если после название проекта стоит многоточие . . ., значит элемент многократно используется и в следующих проектах. Звёздочка * в графе Проекты означает, что соответствующий элемент языка используется во многих проектах. 15
Глава #1. Числа, числа, числа… Говорят, что числа правят миром. Нет, они только показывают, как правят миром. Иоганн Гёте Детский компьютер Как вы знаете из уроков математики, чисел бесконечно много, но их можно разбить на отдельные подмножества по тем или иным признакам. Самые первые числа, которые придумали ещё первобытные люди, называются натуральными. Они используются для подсчёта различных предметов, например, яблок или палочек, на которых вы и сами учились считать в первом классе. Папа спрашивает у сына: - Скажи, сколько будет, если к трём грушам прибавить ещё две груши? Сын отвечает: - Не знаю, папа, мы в школе решаем задачи только про яблоки! Множество натуральных чисел обозначается большой латинской буквой N, поэтому само множество можно записать так: N = {1, 2, 3, ...}. Иногда к множеству натуральных чисел относят и нуль (отсутствие предметов вообще): N0 = {0, 1, 2, 3, ...}. Множество натуральных чисел является подмножеством всех чисел и также бесконечно. Если к натуральным числам добавить отрицательные числа (и нуль), то получится множество целых чисел. Оно обозначается большой латин- 16
ской буквой Z = {... -2, -1, 0, 1, 2, ...}. Нетрудно догадаться, что и целых чисел бесконечно много. В арифметике обычно используют именно целые числа, но встречаются алгебраические и геометрические задачи, которые невозможно решить без дробных чисел. Рациональные числа можно представить в виде простой (обыкновенной) дроби: m/n где: • m - целое число; • n - натуральное число, не равное нулю (вы, конечно, помните, что на нуль делить нельзя!). Множество рациональных чисел обозначается буквой Q. Если знаменатель дроби равен 1, то вся дробь равна числителю, то есть целому числу n. Таким образом, все целые числа являются в то же время и рациональными (множество целых чисел - это подмножество рациональных). Но не наоборот! Рациональные числа можно представить также в виде конечной десятичной дроби (1/2 = 0,5) или бесконечной периодической десятичной дроби (1/7 = 0,1428571...). Иррациональные числа не могут быть представлены в виде простой дроби (а также в виде конечной или бесконечной десятичной периодической дроби). Таким образом, иррациональным числом называют любое число, представимое в виде бесконечной непериодической десятичной дроби. Примером такой дроби служит корень квадратный из двойки. Иррациональность этого числа была известна уже древним математикам, которые доказали несоизмеримость стороны и диагонали квадрата. Иррациональные числа обозначают буквой I. Множество действительных, или вещественных чисел объединяет множества рациональных и иррациональных чисел. Их принято наглядно представлять в виде точек на числовой прямой (Рис. 1.1). 17
Рис. 1.1. Числовая прямая Множество действительных чисел обозначают буквой R (от их латинского названия numerus realis). К иррациональным числам относятся знаменитые числа - π (пи, отношение длины окружности к диаметру) и е (основание натуральных логарифмов). В программах на языке Питон чаще всего используют такие числовые типы данных: int – для целых чисел произвольной длины float – для вещественных чисел В третьей версии Питона появился класс Decimal, помощью которого легко манипулировать вещественными числами произвольной точности. Кроме собственно чисел, нам понадобятся и два «числовых» класса. Класс math нужен для математических вычислений, а класс random – для генерирования случайных чисел. На следующей странице вы можете снять учебный стресс, выполнив (если желаете – на время) забавный числовой тест. 18
Последовательно найдите числа от 1 до 100! 19
Признаки делимости чисел Целые числа и их свойства изучает теория чисел, или высшая арифметика. В математических задачах (да и в жизни тоже) нередко нужно быстро определить, делится ли одно число на другое или нет. При этом сам результат деления неважен. У каждого натурального числа имеется, по крайней мере, два делителя - это единица и само число (у единицы они совпадают!). Если других делителей нет, то число называется простым. К ним мы вернёмся немного позже, а сейчас давайте вспомним признаки (то есть правила) делимости. Если делитель – натуральное число, но на него можно разделить любое другое натуральное число – либо нацело, либо с остатком: делимое : делитель = частное + остаток Нас интересует только деление чисел, при котором остаток от деления равен нулю. Признак делимости на 2 Самый простой признак: число делится на 2 только тогда, когда его последняя цифра равна 0, 2, 4, 6 или 8. Если число делится на 2, то оно называется чётным, если не делится - нечётным. Примеры чётных чисел: 2012, 92, 4, 76, 58. Нечётных: 2013, 91, 5, 77, 61. Иногда этот признак формулируют проще: число делится на 2 тогда и только тогда, когда его последняя цифра чётная. Признак делимости на 3 Число делится на 3 тогда и только тогда, когда сумма его цифр делится на 3. Например, число 2013 кратно 3, поскольку сумма его цифр равна 6: 2 + 1 + 3 = 6 (при подсчёте нули не учитываем). Если сумма цифр также выражается не однозначным числом, то следует найти сумму его цифр. Если в результатае сложения цифр получится одно из чисел 3, 6 или 9, то число делится на 3. В противном случае – не делится. Например, сумма цифр числа 123456789 равняется 45. Число двузначное – 20
опять находим сумму его цифр: 4 + 5 = 9. Получили девятку – значит, исходное число 123456789 делится на три. Признак делимости на 4 Очевидно, что числа, кратные четырём, должны быть чётными. Но этого мало, поэтому мы оставляем от числа только две последние цифры и рассматриваем получившееся двузначное число. Если число сразу двузначное, то ничего отбрасывать не нужно. А если однозначное, то достаточно вспомнить таблицу умножения. Если это двузначное число делится на 4, то и всё число также делится на четыре. Почему достаточно рассмотреть только последние две цифры числа? – На этот вопрос легко ответить, если вспомнить, что сотня делится на 4 без остатка. Естественно, любое число сотен также разделится на 4, поэтому разряды сотен, тысяч и так далее в проверяемом числе можно не учитывать. Чтобы ещё упростить проверку, сложите число десятков с половиной единиц. Если сумма чётная, то исходное число делится на 4, в противном случае не делится. Опять проверим год 2013. Число из последних двух цифр равно 13. Оно на 4 не делится, значит, 2013 не кратно четырём. Возьмём другое число 4567896. Оставляем две цифры - 96. Складываем 9 с половиной от 6, то есть тройкой и получаем 12. Это число кратно четырём, значит, число 4567896 делится на 4. Признак делимости на 5 Это правило очень похоже на признак делимости на двойку. Число делится на 5, если оно оканчивается на 0 или 5. Признак делимости на 6 Число делится на 6, если одновременно выполняются признаки делимости на 2 и 3. 21
Признак делимости на 7 Хорошего признака делимости чисел на 7 не существует, зато имеется немало достаточно сложных и запутанных. Из них мы выберем один – самый простой и «универсальный». Разбиваем заданное число, начиная с конца, на группы, состоящие из трёх цифр. Например, если мы проверяем число 4567896, то получим три группы цифр: 4 567 896 3 + 2 - 1 + Кстати говоря, в книгах так зачастую и печатают длинные числа, чтобы легче было распознать разряды сотен, тысяч и так далее. Теперь первое (считаем сзади!) число мы берём со знаком плюс, второе со знаком минус, третье – снова о знаком плюс. То есть знаки плюс и минус чередуются. Составляем из чисел с их знаками арифметическое выражение и вычисляем его значение: 896-567+4 = 333 Если результат делится на 7, то и всё число также делится на 7. В противном случае не делится. В нашем примере число 333 на 7 не делится, значит, этот вывод относится и к исходному числу 4567896. Признак делимости на 8 Число делится на 8, если оно чётное, а число, составленное из трёх последних цифр, делится на 8. Так как делить трёхзначное число на 8 тоже нелегко, то можно воспользоваться тем же приёмом, что и в признаке делимости на 4. Тысяча делится на 8 без остатка. Любое число тысяч также разделится на 8, поэтому разряды тысяч и далее в проверяемом числе можно не учитывать. 22
Рассмотрим три последние цифры. К числу, образованному первыми двумя цифрами, добавьте половину единиц, а затем к числу десятков добавьте половину единиц получившегося числа. Если результат - чётное число, то исходное число делится на 8. Проясним этот алгоритм на примере. Начнём с того же числа 2013. Последние три цифры дают двузначное число 13, которое на 8 не делится. Следовательно, не делится и число года. Возьмём другое число 123457928. Оставляем для проверки трёхзначное число 928. Число из первых двух цифр равно 92. Складываем его с половиной единиц - 4 - и получаем 96. Дальше действуем, как в признаке делимости на 4: 9 + 3 = 12. Это число кратно двум, поэтому всё число 123457928 делится на 8. Признак делимости на 9 Этот признак напоминает правило для тройки. Число делится на 9 тогда и только тогда, когда сумма его цифр делится на 9. Раньше мы установили, что число 2013 делится на 3, а сумма его цифр равна шести. Поэтому, согласно этому признаку делимости, на 9 оно не делится. Если сумма цифр выражается не однозначным числом, то следует найти сумму его цифр. То есть действовать так же, как и в признаке делимости на 3. Признак делимости на 10 Ещё проще, чем признак делимости на 5. Число делится на 10 тогда и только тогда, когда оно заканчивается на 0. Например, число 2010 делится на 10, а число 2013 не делится. Признак делимости на 11 Самое любопытное правило; не все его знают, но оно помогает очень просто определить, делится ли, например, номер автобусного билета на 11. Чтобы узнать, делится ли число на 11, нужно подсчитать отдельно сумму цифр, стоящих на нечётных и чётных местах в исходном числе. Если они равны, то число кратно 11. 23
В противном случае нужно из первой суммы вычесть вторую. Если разность делится на 11, то и всё число делится на 11. Если сумма первых трёх цифр равна сумме трёх последних, то такой билет называется счастливым. Здесь мы для краткости употребляем слово цифры, но вы должны понимать, что речь идёт об однозначных числах, которые записываются этими цифрами. Например, число 123453 делится на 11, так как 1 + 3 + 5 = 2 + 4 + 3 = 9. А число 123456 не делится (проверьте сами!). Другой признак делимости на 11 полностью совпадает с признаком делимости на 7, но делить сумму чисел нужно на 11. Признак делимости на 12 Число делится на 12 тогда и только тогда, когда одновременно выполняются признаки делимости на 3 и 4. Признак делимости на 13 Признак делимости на 13 тот же самый, что для чисел 7 и 11 (второй способ), но делить сумму чисел нужно на 13. Поэтому я недаром назвал этот признак универсальным. Интересно, что наименьшее число, которое одновременно делится на 7, 11 и 13, равняется 7 х 11 х 13 = 1001, то есть сказочному числу арабских ночей. Признак делимости на 19 Признак делимости чисел на 19 хорошо описан в книге Якова Перельмана Занимательная алгебра. 24
Число делится без остатка на 19 тогда и только тогда, когда число его десятков, сложенное с удвоенным числом единиц, кратно 19. Опять проверим большое число 123457928. Оно содержит 12345792 десятка и 8 единиц. По правилу, получаем: 12345792 + 16 = 12345808 Опять находим число десятков и единиц: 1234580 и 8 – и сумму: 1234580 + 16 = 1234596 И так продолжаем дальше: 123459 + 12 = 123471 12347 + 2 = 12349 1234 + 18 = 1252 1252 + 4 = 1256 125 + 12 = 137 13 + 14 = 27 Вывод: число 123457928 на 19 не делится. Добавим к исходному числу шестёрку и проверим сумму на делимость: 123457934 12345793 + 8 = 12345801 1234580 + 2 = 1234582 123458 + 4 = 123462 12346 + 4 = 12350 123 + 10 = 133 13 + 6 = 19 Вывод: число 123457934 на 19 делится. Интересные математические фокусы, связанные с признаками делимости, вы найдёте в книге Мартина Гарднера Математические досуги [ГМ72], Глава 19. Если вы серьёзно интересуетесь свойствами чисел, то прочитайте книгу Н.Н. Воробьёва Признаки делимости [ВНН88]. 25
Проект Делится - не делится? Бесконечный цикл while Функции с параметрами Оператор деления по модулю % Оператор деления // Цикл for Условныq операторы if Оператор return Метод для извлечения квадратного корня math.sqrt Списки Метод печати в консольном окне print Метод ввода строки в консольном окне input Преобразование строки в число типа int с помощью метода int Компьютер должен считать, а человек - думать. Программистская пословица Мы вспомнили признаки делимости чисел, без которых человеку обойтись трудно, а вот компьютеру они совсем не нужны, потому что он и без них считает охотно и быстро. Начните новый проект и сохраните его в папке Делимость. C помощью операции деления по модулю можно легко проверить, делится ли одно число на другое нацело или нет. Например, мы хотим узнать, делится ли число 2014 на 7. Пишем: Рис. 1.2. Делится – не делится 26
Запускаем программу и тут же узнаём, что не делится (Рис. 1.2⬆). То есть нам только и нужно, что проверить остаток от деления. Если он равняется нулю, то первое число делится на второе. Вот и вся премудрость! В функции main пользователь самостоятельно выбирает число, для которого программа найдёт все его делители: # -*- coding: Windows-1251 -*#Делимость чисел #ГЛАВНАЯ ФУНКЦИЯ def main(): print('Делимость чисел') print() #бесконечный цикл ввода данных #пока пользователь не закроет программу #или не введет 0: while True: print("Введите натуральное число > num = int(input()) if (num == 0): return Solve1(num) print() ", end = '') После того как пользователь ввёл число, мы передаём его функции Solve1, которая в цикле for пытается поделить его на числа из диапазона 1.. num и печатает в консольном окне все делители заданного числа (Рис. 1.3): def Solve1(num): #печатаем результаты в консольном окне: print() print("Число " + str(num) + " делится на:") for i in range(1, num+1): if (num % i == 0): print(str(i) + " ", end = '') print() print() 27
Рис. 1.3. Программа в действии Задавая параметры цикла, мы исходим из того, что любое натуральное число делится на единицу и само на себя. На нуль делить нельзя, отрицательных натуральных чисел нет, а на числа, большие заданного, делить нет смысла. Таким образом, наша программа не только проверяет делимость заданного числа на все числа из диапазона 1..num, но и находит все его делители в порядке возрастания. Для проверки очень больших чисел можно оптимизировать нашу функцию. Достаточно заметить, что заданное число num не может делиться на числа, большие num/2, исключая, естественно, само число. Немного подправим код – и пользователь может находить делители огромных чисел (Рис. 1.4): def Solve2(num): #печатаем результаты в консольном окне: print() print("Число " + str(num) + " делится на:") for i in range(1, num//2+1): if (num % i == 0): print(str(i) + " ", end = '') print(num) print() 28
Рис. 1.4. Оптимизированная программа Мы ещё во много раз ускорим поиск делителей, если заметим, что произведение симметричных относительно середины ряда делителей равно заданному числу. Например, для числа 64 мы получили такой ряд делителей: 1 2 4 8 16 32 64 Проверяем утверждение: 1 х 64 = 2 x 32 = 4 x 16 = 8 x 8 = 64 Всё верно! Единственное «неудобство» причиняют восьмёрки – они дважды входят в произведение, поэтому нам следует подумать, как оставить только одну из них. Теперь легко заметить, что нам достаточно найти делители заданного числа в диапазоне 1..√𝑛𝑢𝑚. Вторую половину делителей мы найдём, поделив заданное число на очередной делитель. Для хранения делителей мы заведём список res: 29
def Solve3(num): #список для записи делителей: res = [] for i in range(1, round(math.sqrt(num))+1): if (num % i == 0): res.append(i) #не допускаем повторов делителей: if (i*i != num): res.append(num // i) #печатаем результаты в консольном окне: print() print("Число " + str(num) + " делится на:") print(*res, sep=' ', end='') print() И вот почему. Для первого делителя – единицы – мы найдём парный делитель, равный заданному числу num, для второго парный делитель равен (num / второй делитель). То есть делители мы напечатаем не по порядку (Рис. 1.5). Рис. 1.5. Неупорядоченные делители Чтобы выправить ситуацию, мы до печати результатов отсортируем список res с помощью метода sort списочного класса, а затем напечатаем делители строго по ранжиру: def Solve4(num): #список для записи делителей: res = [] for i in range(1, round(math.sqrt(num))+1): if (num % i == 0): res.append(i) #не допускаем повторов делителей: if (i*i != num): 30
res.append(num // i) #печатаем результаты в консольном окне: res.sort() print() print("Число " + str(num) + " делится на:") print(*res, sep=' ', end='') print() Теперь даже для огромных чисел мы мгновенно выписываем все делители (Рис. 1.6). Рис. 1.6. Молниеносное нахождение делителей Исходный код программы находится в папке Делимость. 31
Проект Назойливый остаток Вложенные циклы for Бесконечный цикл while Условный оператор if Оператор % Оператор break Оператор continue В книге Бориса Кордемского и Аскера Ахадова Удивительный мир чисел [КА86] вы найдёте немало интересных задач, в том числе и на делимость. На странице 86 авторы предлагают решить задачу Назойливый остаток: Некоторые числа, кратные числу 7, при делении на 2, на 3, на 4, на 5 и на 6 дают остаток 1. Найдите наименьшее из таких чисел. Эта же задача напечатана в книге Фёдора Нагибина и Евгения Канина Математическая шкатулка [Нагибин88], задача 42, страницы 18-19, но в более занимательной форме: Колхозница привезла на рынок для продажи корзину яиц. Продавала она их по одной и той же цене. После продажи яиц колхозница пожелала проверить, верно ли она получала деньги. Но вот беда: она забыла, сколько у неё было яиц. Вспомнила она только, что когда перекладывала яйца по 2, то оставалось одно яйцо; одно яйцо оставалось также при перекладывании яиц по 3, по 4, по 5, по 6. Когда же она перекладывала яйца по 7, то не оставалось ни одного. Помоги колхознице сообразить, сколько у неё было яиц. Бросаемся на помощь бестолковой колхознице и в функции main вызываем функцию Solve для решения этой головоломки: # -*- coding: Windows-1251 -*#Кордемский, с.86, Задача 8 #ГЛАВНАЯ ФУНКЦИЯ def main(): print("Назойливый остаток") 32
print() Solve() . . . if __name__ == "__main__": main() Поскольку речь идёт о натуральных числах, то мы можем начать наши поиски с единицы: #РЕШАЕМ ЗАДАЧУ def Solve(): #мин. число: minnum = 1 Если число 1 не является решением задачи, то мы переходим к двойке, к тройке, и так далее – пока не найдётся искомое число num. Так как мы всякий раз добавляем к начальному значению переменной minnum единицу, то вполне разумно делать это в цикле for, в котором переменная num и будет играть роль переменной цикла. Обычно к циклу for прибегают тогда, когда число повторов точно известно. В нашем же случае, мы знаем только начало цикла (min = 1), но не знаем, на каком числе он закончится. Впрочем, мы вполне разумно можем предположить, что у колхозницы было не очень много яиц, и задать заведомо большее значение для верхней границы цикла. Так мы организуем почти бесконечный цикл, который прервём сразу же, как только искомое число будет обнаружено. Для этого мы воспользуемся флажком – логической переменной flg. Если очередное число num выдержит все проверки, значение флага останется верным (True), а мы с помощью оператора break прервём цикл for и напечатаем решение задачи в консольном окне: for num in range(minnum, 1000): flg = True #число не кратно семи: if num % 7 != 0: continue; Проверку очередного числа на остаток, равный единице, также можно проводить в цикле, чтобы не выписывать несколько одинаковых условий. 33
Если число num при делении хотя бы на одно из чисел 2..6 не даёт в остатке 1, то мы сбрасываем флажок и прерываем внутренний цикл for: for d in range(2, 6+1): if (num % d != 1): flg = False break if flg: break print("Искомое число равно " + str(num)) print() На Рис. 1.7 вы видите ответ на эту задачу. Рис. 1.7. Яиц было немало! Настоящий бесконечный цикл легко получить с помощью конструкции while True: def SolveWhile(): #мин. число: num = 0 while True: flg = True num += 1 #число не кратно семи: if num % 7 != 0: continue; for d in range(2, 6+1): if (num % d != 1): flg = False break if flg: break print("Искомое число равно " + str(num)) print() 34
Не знаю, как вам, но мне интересно, а есть ли ещё и другие числа, которые имеют назойливый остаток? Имея компьютер, мы легко утолим своё любопытство – достаточно написать новую функцию Solve2, которая поразительно напоминает первую версию: def Solve2(num): #печатаем результаты в консольном окне: print() print("Число " + str(num) + " делится на:") for i in range(1, num//2+1): if (num % i == 0): print(str(i) + " ", end = '') print(num) print() Тут, конечно, следует учесть, что бесконечный цикл уже не годится, потому что он действительно станет бесконечным, если искомых чисел очень много (а это нетрудно предвидеть). Запускаем приложение и видим, что среди первой десятки тысяч натуральных чисел довольно много подходящих под условие задачи (Рис. 1.8). Рис. 1.8. Неназойливый список назойливых чисел 35
Также нетрудно подметить такую закономерность. Если обозначить через n номер искомого числа, то все числа с назойливыми остатками можно легко найти по формуле: num = 301 + 420(n-1) Любопытно, что если решать задачу для чисел, кратных не 7, а большим числам, то это непременно должны быть простые числа. Например, решения для чисел 11 и 13 показаны на Рис. 1.9. Рис. 1.9. Исследования продолжаются Чтобы понять, почему так происходит, нам нужно научиться вычислять НОД, НОК и находить простые числа! Исходный код программы находится в папке Кордемский 086 08. 36
Проект Первый числовой фокус Метод int Оператор return Условный оператор if Функция с параметром Оператор деления по модулю % Оператор деления // Операторы < > or| != А вот и фокус из книги Удивительный мир чисел [КА86], страница 34, задача 2: Скажите другу: «Любое трёхзначное число умножь на 37, потом на 27. К полученному шестизначному числу прибавь удвоенное первоначальное число. Покажи мне результат, и я угадаю задуманное трёхзначное число». Секрет фокуса. Пусть задумано трёхзначное число 100x + 10y + z, где х, y, z - цифры сотен, десятков и единиц соответственно. Выполнив указанные действия, получим: 100 100x + 10 010у + 1001z= 1001 ⦁ (100x + 10у + z). Теперь ясно, что достаточно разделить результат на 1001, чтобы получилось задуманное трёхзначное число. Пример. Пусть задумано число 173. После выполнения указанных действий получилось 173 173. Наблюдаем, что в его записи трёхзначное число 173 повторяется. Вычеркнем 173; это равносильно тому, что 173 173 разделили на 1001; в результате остается задуманное число 173. Замечание. При повторении фокуса легко обнаружится, что если задумано число abc, то результат, показываемый фокуснику, имеет вид abcabc . Чтобы это скрыть, надо добавить еще одно действие в конце фокуса, например потребовать прибавить 1111. Тогда фокуснику скажут не 173 173, а 174 284. Теперь закономерность скрыта, а фокуснику ничего не стоит в уме вычесть 1111, а затем угадать задуманное число. 37
В функции main мы предлагаем всем желающим загадать трёхзначное число и произвести с ним требуемые манипуляции: # -*- coding: Windows-1251 -*#Кордемский, страница 34, задача 2 #ГЛАВНАЯ ФУНКЦИЯ def main(): print('Первый числовой фокус') print() #бесконечный цикл ввода данных #пока пользователь не закроет программу #или не введет 0: while True: print("Любое трёхзначное число умножь на 37, \nпотом на 27. \nК полученному шестизначному числу прибавь \nудвоенное первоначальное число. \nВведи результат, \nи я угадаю задуманное трёхзначное число. > ", end = '') num = int(input()) if (num == 0): return Solve(num) print() Сам фокусник притаился в функции Solve, которая получает от загадчика 6-значное число. Но эта кио или акопяно не шибко доверчива, поэтому, прежде всего, проверяет число загадчика и отказывается фокусничать, если ей пытаются подсунуть негодное число: def Solve(num): min6 = 100000 max6 = 999999 print() if (num < min6 or num > max6): print("Число должно быть шестизначным!") print() return if (num % 1001 != 0): print("Ты ошибся в вычислениях!") print() return А добросовестному загадчику наш виртуальный фокусник безошибочно сообщает загаданное трёхзначное число: 38
print("Ты задумал число %i!" %(num//1001)) print() Например, моё число не поставило фокусника в тупик (Рис. 1.10)! Рис. 1.10. Компьютерные фокусы Фокус интересный, но очень простой. Постарайтесь учесть замечание и улучшить программу! Исходный код программы находится в папке Кордемский 034 02. 39
Проект Второй числовой фокус Цикл while Конкатенация строк Метод int Управляющие последовательности Условный оператор if Функция с параметром Оператор деления по модулю % Оператор return Второй фокус из книги Удивительный мир чисел [КА86], страница 35, задача 3 немного сложнее первого: Двое участников задумывают одно трёхзначное число. Первый умножает его на 27, потом на 37. Второй умножает задуманное число на 13, потом умножает на 77. Затем фокусник предлагает им сложить получившиеся два шестизначных числа и результат показать ему. Фокусник сообщает, какое трёхзначное число было задумано. Секрет фокуса. Пусть задумано трёхзначное число 100x + 10y + z. Выполнив указанные действия, получим: 200 000x + 20 000y + 2000z = 2000 ⦁ (100x + 10y + z). Становится ясным, что достаточно разделить результат на 2000, чтобы получить задуманное число. Сначала - для убедительности - проверим действие фокуса на контрольном примере: 123 * 27 * 37 = 123 * 13 * 77 = 246000 : 2000 = 122877 123123 246000 123 40
Всё сходится – фокус удался! Вторую фокусную программу мы легко получим из первой. Главное - грамотно написать речь фокусника: # -*- coding: Windows-1251 -*#Кордемский, страница 35, задача 3 #ГЛАВНАЯ ФУНКЦИЯ def main(): print('Второй числовой фокус') print() #бесконечный цикл ввода данных #пока пользователь не закроет программу #или не введет 0: while True: s = "Задумай трёхзначное число." s += "\nУмножь его на 37, а потом на 27." s += "\nЗапомни получившееся шестизначное число." s += "\nТеперь умножь это же трёхзначное число на 13, а потом на 77." s += "\nК получившемуся шестизначному числу" s += "\nприбавь первое шестизначное число." s += "\nСообщи мне результат," s += "\nи я угадаю задуманное трёхзначное число. > " print(s, end = '') num = int(input()) if (num == 0): return Solve(num) print() def Solve(num): if (num % 2000 != 0): print("Ты ошибся в вычислениях!") print() return print() print("Ты задумал число %i!" %(num//2000)) print() if __name__ == "__main__": main() 41
Кто бы сомневался: конечно, компьютер запросто разделит любое число на 2000 (Рис. 1.11), а вот загадчику придётся хорошенько потрудиться, умножая и складывая числа! Рис. 1.11. И тут без промаха! Исходный код программы находится в папке Кордемский 035 03. 42
Проект Шестизначное число Функция без параметров Цикл for Оператор деления по модулю % Оператор continue Оператор break Задача 4 из книги Удивительный мир чисел [КА86], страница 44: Ученику понадобилось написать наибольшее из шестизначных чисел, кратных 11 и чтобы цифра 6 была первой слева. Как надо действовать ученику для быстрого выполнения задания, если признаков делимости на 11 он ещё не знает? Сообщим, что искомое число обладает забавной особенностью: если каждую его цифру повернуть на 180° в плоскости бумаги, оставляя её на прежнем месте, то образовавшееся число окажется дважды кратным 11 (делится на 11 и частное также делится на 11). Выявите ещё одну особенность чисел: найденного и с повёрнутыми цифрами. # -*- coding: Windows-1251 -*#Кордемский, с.44, Задача 4 #ГЛАВНАЯ ФУНКЦИЯ def main(): print("Шестизначное число") print() Solve() . . . if __name__ == "__main__": main() Легко догадаться, что нужно искать решение задачи среди шестизначных чисел, начинающихся с шестёрки: 43
#РЕШАЕМ ЗАДАЧУ def Solve(): #мин. число: minnum = 600000 #макс. число: maxnum = 699999 Причём перебирать числа следует задом наперёд, то есть от большего к меньшему, чтобы сразу же найти наибольшее из всех возможных чисел в заданном диапазоне: for num in range(maxnum, minnum-1, -1): #чиcло должно быть кратно 11: if num % 11 != 0: continue s = "Наибольшее число равно " + str(num) print(s) print() break А вот и ответ (Рис. 1.12). Рис. 1.12. Крути-верти По условию задачи, поворачиваем все цифры найденного числа: 699996 → 966669 Проверяем, делится ли перевёрнутое число на 11 * 11 = 121. Это легко сделать в интерактивной консоли python.exe. Рис. 1.13 подтверждает: перевёрнутое число делится на 121. 44
Рис. 1.13. Поделилось! А ещё одной особенностью этих чисел является их палиндромичность – они одинаково читаются и в прямом, и в обратном направлении. Исходный код программы находится в папке Кордемский 044 04. 45
Проект Тройка, семёрка и... только Функция без параметров Функция с параметром Цикл for Оператор continue Условный оператор if Оператор деления по модулю % Цикл while Комбинированные операторы присваивания Оператор return Списки Задача 8 (13) из книги Удивительный мир чисел [КА86], страница 45: Найдите наименьшее число, обладающее следующими свойствами: • состоит только из цифр 7 и 3, • оно само и сумма его цифр делятся на 7 и на 3. В отличие от Пиковой дамы, туза в этой задаче нет, но тройка и семёрка присутствуют в полном объёме! # -*- coding: Windows-1251 -*#Кордемский, с.45, Задача 8 #ГЛАВНАЯ ФУНКЦИЯ def main(): print('Тройка, семёрка и... только') print() Solve() print() Предположим, что у нас имеется функция Get37, которая возвращает числа, составленные из цифр 3 и 7, и мы запоминаем в переменной min37 самое маленькое из них: #РЕШАЕМ ЗАДАЧУ def Solve(): min = 1 max = 2400 46
#минимальное число: min37 = 10**10 for num in range(min, max+1): ds = Get37(num) if (ds == -1): continue Каждое такое число и сумма его цифр должны одновременно делиться на 3 и на 7, то есть на 21: #число должно делиться на 3 и 7: if (ds % 21 != 0): continue #сумма цифр числа должна делиться на 3 и 7: if (Summa(ds) % 21 != 0): continue Если это так, то мы печатаем найденное число на экране и, если оно меньше текущего значения переменной min37, то запоминаем его: s = "Десятичное число равно " + str(num) print(s) s = "Двоичное число равно " + str(ds) print(s) print() #запоминаем наименьшее число: if (min37 > ds): min37 = ds Просмотрев все числа в заданном диапазоне, мы печатаем ответ на задачу: print("Наименьшее число равно " + str(min37)) print() Функцию Summa нам нужно срочно написать: #НАХОДИМ СУММУ ЦИФР ЗАДАННОГО ЧИСЛА def Summa(num): sum = 0; while (num > 0): sum += num % 10 num //= 10 return sum 47
Таким образом, нам осталось разработать функцию Get37, которая умеет генерировать число из троек и семёрок. Поскольку во всех «подозреваемых» числах только 2 цифры, то вполне естественно представить их в двоичной системе. Путь тройку в двоичной записи будет символизировать нуль, а семёрку – единица, тогда несколько первых 3,7-чисел можно записать в двоичной системе так: 37 73 337 373 377 733 737 773 ... 01 10 001 010 011 100 101 110 ... Так как между двоичными и 3,7-числами существует взаимнооднозначное соответствие (точно так же, как и между десятичными числами и их двоичным представлением), то на вход функции Get37 нужно подавать числа, начиная с нуля, и проверять в функции Solve, не является ли очередное 3,7-число решением задачи. Но поскольку нас интересует не любое число, а именно наименьшее, а они генерируются не последовательно, то необходимо в функции Solve перебрать числа в некотором диапазоне, в котором заведомо окажется искомое число. Перевести десятичное число в двоичное нетрудно, однако мы должны учесть, что нас интересует не оно, а соответствующее ему 3,7-число, поэтому каждый нуль в десятичном числе нужно заменить тройкой, а единицу – семёркой. Это легко сделать с помощью списка idigs, в который следует записать нужные цифры. def Get37(num): if (num < 1): return -1 #цифры числа: idigs = [3, 7] Также мы должны проследить, чтобы в числе обязательно присутствовали обе цифры – и тройка, и семёрка. Например, двоичным числам 00, 11, 000, 111 соответствуют 3,7-числа 33, 77, 333 и 777, которые целиком состоят из одинаковых цифр. 48
Для этого можно завести логический список, подобный idigs, а можно просто объявить 2 логические переменные: #число из троек и семёрок: num37 = 0 #флажки для цифр 3 и 7: flg3 = False flg7 = False И последняя тонкость, которую необходимо «утолстить»: при конвертировании входного десятичного числа в двоичную форму мы никогда не получим ведущих нулей, поскольку число обратится в нуль, и деление на двойку закончится: #формируем число из троек и семёрок: dec = 1 while (num > 2): #очередная цифра: dig = num % 2 #отмечаем 3 и 7: flg3 |= idigs[dig] == 3 flg7 |= idigs[dig] == 7 num37 = num37 + idigs[dig]*dec dec *= 10 num //= 2 #строка должна включать и тройку, и семёрку: if (not flg3 or not flg7): return -1 return num37 Из этого следует, что нужно пожертвовать первой единицей в двоичном числе, тогда оставшиеся цифры дадут все комбинации нулей и единиц в двоичном числе и троек и семёрок – в 3,7-числе. Решение задачи получилось не очень коротким, но зато программа нашла ответ за одно мгновение (Рис. 1.14)! 49
Рис. 1.14. Немаленькое число! Как и в книге, это число – 3333377733. Кроме этого, минимального 3,7-числа, существует бесконечно много 3,7чисел, которые больше его. Можно формировать 3,7-число сзади, но тогда нужно отбросить последнюю единицу – см. функцию Get37R. Исходный код программы находится в папке Кордемский 045 08. 50
Проект Любопытное свойство чисел Функция без параметров Цикл for Оператор деления по модулю % Оператор деления // Условный оператор if Оператор break Задача 15 (16 – номер задачи в издании 1996 года) из книги Удивительный мир чисел [КА86], страница 102: Возьмите какое-либо б-значное число, делящееся на 7, например 325 836. Перенесите последнюю цифру в начало записи числа. Образуется новое число 632 583. Оно также делится на 7. Докажите самостоятельно, что таким свойством обладает любое 6значное число, делящееся на 7. Рекомендация. Представьте заданное число так: 7k = = 10а+ 6 (1). Тогда новое число примет вид 100 0006 +а (2). Используя (1), докажите делимость (2) на число 7. Доказать - значит, убедиться, что все 6-значные числа обладают указанным свойством. Это можно сделать «теоретически», то есть с помощью математических выкладок, а можно и «практически» - с помощью метода грубой силы, или полного перебора. Оба способа доказательства можно считать строгими, ведь они исчерпывают все возможные варианты. Поскольку 6-значных чисел совсем немного, то метод грубой силы здесь вполне уместен. Тем более что алгоритмы, построенные на этом методе почти всегда достаточно простые. Традиционно из функции main мы вызываем функцию Solve для непосредственного решения задачи: # -*- coding: Windows-1251 -*#Кордемский, с.102, Задача 15 #ГЛАВНАЯ ФУНКЦИЯ def main(): print("Любопытное свойство чисел") print() 51
Solve() В функции Solve мы обозначаем переменными минимальное и максимальное 6-значные числа, которые нам и надлежит проверить: #РЕШАЕМ ЗАДАЧУ def Solve(): #мин. и макс. min6 = 100000 max6 = 999999 flg = True 6-значные числа: Флаг flg призван сигнализировать о (не)соблюдении условия задачи: поначалу он установлен в True, но если по ходу проверки нам встретится число, которое нарушает условие задачи, то мы с чистой совестью сбрасываем флаг и прекращаем дальнейшие проверки – гипотеза опровергнута. Если же после испытания всех 6-значных чисел флаг так и останется установленным в True, значит, гипотеза верна. Поскольку диапазон значений проверяемых чисел точно очерчен, то перебирать числа удобнее всего в цикле for: for num in range(min6, max6 + 1): Все числа, не кратные 7, мы пропускаем: #число не кратно семи: if (num % 7 != 0): continue Всем остальным назначаем проверки. Чтобы перенести последнюю цифру числа в начало, нужно её выделить. Это легко сделать, применив к числу операцию деления по модулю: #последняя цифра числа: end = num % 10 52
Число из первых 5 цифр найти ещё проще – нужно просто разделить исходное число на 10: #5-значное число из первых 5 цифр числа num: start5 = num // 10 При формировании нового 6-значного числа мы переносим последнюю цифру в начало, что соответствует умножению на 100000. К этому произведению следует добавить 5-значное число из первых пяти цифр исходного числа: #новое 6-значное число: newnum = end * 100000 + start5 Эту операцию можно записать более наглядно: 123456 исходное 6-значное число 612345 новое 6-значное число Проверяем условие задачи: #если это число не кратно 7, #то утверждение неверное: if (newnum % 7 != 0): flg = False break Печатаем на экране результаты проверки (Рис. 1.16): #все числа проверены: if (flg): print("Утверждение верное") else: print("Утверждение неверное") print() 53
Рис. 1.16. Доказали! Так как мы проверили все 6-значные числа, то можем уверенно утверждать, что любое 6-значное число удовлетворяет условиям задачи. Исходный код программы находится в папке Кордемский 102 15. 54
Проект Как определил ошибку Чохбилмиш? Функция без параметров Цикл for Оператор деления по модулю % Оператор деления // Условный оператор if Оператор break Задача 23 (25) из книги Удивительный мир чисел [КА86], страница 57: Чохбилмиш предложил каждому из двух учеников задумать какое-либо шестизначное число и переставить первую цифру в конец записи числа. Одному сказал: «Найди сумму получившихся чисел». Другому сказал: «Найди разность». Ученики выполнили действия и написали: 913 485 и 167 860. Чохбилмиш не знал, какие числа были задуманы учениками, но сразу определил: «Вы оба ошиблись». Как рассуждал Чохбилмиш? В ответе на задачу утверждается, что сумма любого 6-значного числа с другим 6-значным числом, полученным из исходного переносом первой цифры в конец числа, кратна 11 и аналогично полученная разность – кратна 9. Мы можем проверить эти утверждения с помощью полного перебора, тем более что самая трудная часть задачи – перенос цифры – нами уже решена в предыдущем проекте. # -*- coding: Windows-1251 -*#Кордемский, с.57, Задача 23 #ГЛАВНАЯ ФУНКЦИЯ def main(): print("Как определил ошибку Чохбилмиш?") print() Solve() 55
#РЕШАЕМ ЗАДАЧУ def Solve(): #мин. и макс. min6 = 100000 max6 = 999999 flg = True 6-значные числа: for num in range(min6, max6 + 1): #число не кратно семи: if (num % 7 != 0): continue #последняя цифра числа: end = num % 10 #5-значное число из первых 5 цифр числа num: start5 = num // 10 #новое 6-значное число: newnum = end * 100000 + start5 #сумма и разность чисел num и newnum: sum = num + newnum razn = num - newnum #это число не кратно 11: if (sum % 11 != 0 or razn % 9 != 0): flg = False break #все числа проверены: if (flg): print("Утверждения верные") else: print("Утверждения неверные") print() if __name__ == "__main__": main() Проверив все 6-значные числа, мы можем с уверенностью констатировать факт: оба утверждения верные (Рис. 1.17)! Рис. 1.17. И здесь всё верно Исходный код программы находится в папке Кордемский 057 23. 56
Проект Шестизначный перенос Функция без параметров Цикл for Оператор деления по модулю % Оператор деления // Задача 557 из книги Математическая шкатулка [Нагибин88], страница 94: Первая слева цифра шестизначного числа – 1. Если её перенести с первого места в конец числа, сохранив порядок остальных цифр, то вновь полученное число будет втрое больше первоначального. Восстановите первоначальное число. Эта задача отличается от двух предыдущих только тем, что переносить нужно не последнюю цифру, а первую: # -*- coding: Windows-1251 -*#Нагибин, с.94, Задача 557 #ГЛАВНАЯ ФУНКЦИЯ def main(): print("Шестизначный перенос") print() Solve() #РЕШАЕМ ЗАДАЧУ def Solve(): #мин. и макс. min6 = 100000 max6 = 299999 flg = True 6-значные числа: for num in range(min6, max6 + 1): #первая цифра числа: start = num // 100000 #5-значное число из последних 5 цифр числа num: end5 = num % 100000 #новое 6-значное число: newnum = end5 * 10 + start #это число должно быть втрое больше исходного: if (newnum != 3 * num): 57
continue print("Число равно " + str(num)) print() if __name__ == "__main__": main() Среди 6-значных чисел, начинающихся с единицы, первоначальное число – единственное (Рис. 1.18, слева). Есть ещё одно подобное число, но оно начинается с двойки (Рис. 1.18, справа). Рис. 1.18. Перенос закончен Исходный код программы находится в папке Нагибин 557. 58
Задания для самостоятельного решения Вы ошиблись в подсчёте Удивительный мир чисел.. Задача 4 (2.2) ], страница 50 Ученик покупает 18 карандашей, 6 тетрадей, 12 ластиков, 9 блокнотов и несколько тетрадей для рисования по 15 к. Девушка-продавец выписала чек на 1 р. 52 к. Взглянув на чек, мальчик сразу же сказал продавцу: «Вы ошиблись в подсчёте». Девушка пересчитала и исправила свою ошибку. Как удалось пареньку так быстро обнаружить просчёт? Ответ: Стоимость каждой покупки, а значит и общая сумма кратна трём, но 1 р. 52 к. на три не делится. Задача #24 Математическая шкатулка Какое целое число делится (без остатка) на любое целое число, отличное от 0? Ответ: 0 Задача #25 Математическая шкатулка Сумма каких двух натуральных чисел равна их произведению? Ответ: 2 + 2 = 2 x 2- 59
Глава #2. НОД, НОК и компания А теперь мы напишем две родственные программы – для вычисления НОД (наибольшего общего делителя двух чисел) и НОК (наименьшего общего кратного). Проект Наибольший общий делитель Функция с параметром Фуекция без параметров Цикл for Оператор деления по модулю % Оператор деления // Бесконечный цикл while Метод int Оператор return Условный оператор if Условный оператор if-else Цикл while Комбинированные операторы присваивания Для вычисления НОД мы воспользуемся простым и алгоритмами древнегреческого математика Евклида (Рис. 2.1). быстрым Рис. 2.1. Евклид (Εὐκλείδης, ок. 325 года до н.э. - до 265 года до н.э.) 60
По-английски НОД называется gcd – greatest common divisor. Простой алгоритм Евклида основан на следующих свойствах: НОД(m,m) = m НОД(m,n) = НОД(n,m) НОД(m,n) = НОД(m-n, n) при m > n Из них следует: • Если m > n, то из m нужно вычесть n; • Если m < n, то числа нужно поменять местами; • Когда значения m и n сравняются, вычисление НОД заканчивается, и НОД = m = n. Для вычисления НОД нам нужны два числа - number1 и number2. Чтобы не обременять пользователя лишними заботами, мы позволим ему вводить числа в произвольном порядке, хотя для алгоритма важно, чтобы первое число было больше второго, поэтому выправляем ситуацию, если это необходимо. При равенстве чисел алгоритм сработает правильно. Затем мы передаём оба числа в функцию Euklid, которая и реализует простой алгоритм Евклида. # -*- coding: Windows-1251 -*#ПРОГРАММА ДЛЯ НАХОЖДЕНИЯ НАИБОЛЬШЕГО #ОБЩЕГО ДЕЛИТЕЛЯ ДВУХ НАТУРАЛЬНЫХ ЧИСЕЛ (НОД) #ГЛАВНАЯ ФУНКЦИЯ def main(): print('НОД двух чисел') print() #бесконечный цикл ввода данных #пока пользователь не закроет программу #или не введёт отрицательные числа: while True: print("Введите первое число > ", end = '') number1 = int(input()) print("Введите второе число > ", end = '') 61
number2 = int(input()) if (number1 + number2 < -1): return #если первое число меньше второго, #то меняем их значения: if (number1 < number2): number1, number2 = number2, number1 #находим НОД: if (number2 == 0): nod = number1 else: nod = Euklid(number1, number2) #печатаем НОД: print("НОД = (" + str(number1) + "," + str(number2) + ") = " + str(nod)) print() Если меньшее из чисел (или оба) равны нулю, то за НОД следует принять первое число. Если же оба числа положительные, то начинаем действовать по простому алгоритму Евклида: #ПРОСТОЙ АЛГОРИТМ ЕВКЛИДА def Euklid(n1, n2): while (n2 != n1): if (n1 >= n2): n1 -= n2 else: n2 -= n1; return n1 Тут, конечно, следует учитывать значения чисел: если они очень большие, то лучше пользоваться быстрым, а не простым алоритмом. В цикле while мы на каждой итерации вычитаем из большего числа меньшее – до тех пор, пока значения переменных n1 и n2 не сравняются. Поскольку с каждым разом одно из чисел уменьшается, то рано или поздно это условие будет выполнено. Вычисленное значение НОД мы возвращаем методу main, который и публикует его в окне консоли. При небольших числах алгоритм работает очень быстро (Рис. 2.2). 62
Рис. 2.2. Вычисляем НОД двух чисел Нагнетаем обстановку, задавая всё более «солидные» числа, и простой алгоритм начинает «буксовать» (Рис. 2.3). А при дальнейшем увеличении чисел он попросту впадает в кому! И тут нам на выручку приходит быстрый алгоритм Евклида: #находим НОД: if (number2 == 0): nod = number1 else: #nod = Euklid(number1, number2) nod = SpeedEuklid(number1, number2) #БЫСТРЫЙ АЛГОРИТМ ЕВКЛИДА def SpeedEuklid(n1, n2): while (n2 > 0): n1, n2 = n2, n1 % n2 return n1 63
Рис. 2.3. Вычисляем НОД больших чисел Он отличается от простого алгоритма тем, что на каждой итерации мы находим остаток от деления первого числа на второе, значение второго числа присваиваем первому числу, а значение остатка - второму. Так мы продолжаем до тех пор, пока остаток от деления не обратится в нуль. Как и в первом случае, это условие обязательно будет выполнено. Быстрый алгоритм Евклида основан на свойстве: НОД(m,n) = НОД(n, m%n) при m > n Этот алгоритм не собьёшь с толку никакими числами (Рис. 2.4)! Вы можете взять любые числа и убедиться, что алгоритм действительно работает практически мгновенно. 64
Рис. 2.4. Наша программа легко справляется и с очень большими числами! Для большего понимания быстрого алгоритма давайте вручную найдём НОД чисел 42 и 14: number1 = 42 number2 = 14 Так как второе число больше нуля, то находим остаток от их деления: n = 42 % 14 = 0 И присваиваем новые значения переменным: number1 = 14 number2 = 0 Второе число теперь равно нулю, значит, НОД найден – он равен второму числу, то есть 14. Рассмотрим другой пример: 65
number1 = 56 number2 = 42 n = 56 % 42 = 14 number1 = 42 number2 = 14 Уже после одного цикла мы пришли к первому примеру. Исходный код программы находится в папке НОД. 66
Проект Наименьшее общее кратное Бесконечный цикл while Метод int Оператор return Условный оператор if Функция с параметрами Зная наибольший общий делитель двух чисел, мы очень просто вычислим их наименьшее общее кратное по такой формуле: НОК = число1 * число2 / НОД (число1, число2) (1) По-английски НОК называется lcd – least common denominator. Для нахождения НОД мы воспользуемся быстрым алгоритмом Евклида, а НОК вычислим по формуле (1): # -*- coding: Windows-1251 -*#ПРОГРАММА ДЛЯ ВЫЧИСЛЕНИЯ НАИМЕНЬШЕГО ОБЩЕГО #КРАТНОГО ДВУХ ЧИСЕЛ (НОК) #ГЛАВНАЯ ФУНКЦИЯ def main(): print('НОК двух чисел') print() #бесконечный цикл ввода данных #пока пользователь не закроет программу #или не введёт отрицательные числа: while True: print("Введите первое число > ", end = '') number1 = int(input()) print("Введите второе число > ", end = '') number2 = int(input()) if (number1 + number2 < -1): return #если первое число меньше второго, #то меняем их значения: if (number1 < number2): number1, number2 = number2, number1 67
#находим НОK: if (number1 * number2 == 0): nok = 0 else: #вычисляем НОК: nok= number1 * number2 // SpeedEuklid(number1, number2) #печатаем НОK: print("НОK = (" + str(number1) + "," + str(number2) + ") = " + str(nok)) print() Поскольку НОД вычисляется очень быстро, то и НОК мы получаем в считанные мгновения (Рис. 2.5). Рис. 2.5. Вычисляем НОК Исходный код программы находится в папке НОК. 68
Проект НОК нескольких чисел Функция с параметром Цикл for Оператор return Цикл while Оператор деления по модулю % При решении задачи о назойливом остатке необходимо знать НОК последовательных чисел от 2 до 6 (см. [КА86], решение на странице 133). Найти НОК нескольких чисел можно так. Сначала вычисляем НОК2 первых двух чисел, затем вычисляем НОК3 для НОК2 и третьего числа и так далее. Но давайте решим эту задачу для ряда чисел произвольной длины – от 2 до maxnok. В функции main мы вызываем функцию NOK2, которой и передаём верхнюю границу ряда: # -*- coding: Windows-1251 -*#ВЫЧИСЛЕНИЕ НОК НЕСКОЛЬКИХ ЧИСЕЛ #ГЛАВНАЯ ФУНКЦИЯ def main(): print('НОК НЕСКОЛЬКИХ ЧИСЕЛ') print() NOK2(30) В функции NOK2 мы задаём начальное значение переменной nok, равное 1, что вполне разумно для наименьшего общего кратного чисел 1 и 1. Далее мы находим НОК для 1 и 2, затем для полученного НОК и тройки – как и было описано выше: #НАХОДИМ НОК РЯДА ЧИСЕЛ def NOK2(maxnok): nok = 1 for i in range(2, maxnok+1): nok = NOK(nok,i) print("НОК чисел 2..%i равно %i" %(i, nok)) 69
print() #НАХОДИМ НОK ДВУХ ЧИСЕЛ def NOK(number1, number2): if (number1 * number2 == 0): nok = 0 else: #вычисляем НОК: nok= number1 * number2 // SpeedEuklid(number1, number2) return nok Все промежуточные значения НОК мы печатаем на экране (Рис. 2.6). Рис. 2.6. Вычисляем НОК ряда чисел Из полученного списка видно, что НОК ряда чисел 2..6 равно 60. При делении чисел вида 60n на числа 2..6 в остатке мы всегда будем получать 0. А при делении чисел вида (60n + 1) – единицу, что и требуется в условии задачи. Но это число (60n + 1) должно делиться на 7 без остатка, то есть: 70
(60n + 1) = 7k (1) Поскольку мы умеем вычислять НОК любого ряда чисел, то можем обобщить задачу о назойливых остатках для любых рядов, записав формулу так: (nok * n + 1) = maxd * k (2) Решаем задачу с помощью новой функции Solve: def main(): print('НОК НЕСКОЛЬКИХ ЧИСЕЛ') print() #NOK2(30) Solve(17) Этой функции мы должны передать то число maxd, на которое делится без остатка искомое число. В задаче это семёрка, но нас, конечно, интересуют более крупные числа! На Рис. 2.6⬆ хорошо видно, что наименьшее искомое число быстро растёт с увеличением maxd, поэтому нужно либо задавать значение переменной max достаточно большим, либо искать только заданное количество искомых чисел. В функции Solve мы находим НОК ряда чисел 2..(maxd-1), а для этого нам нужен модифицированная функция для вычисления НОК, которая возвращает, а не печатает значения: def NOK3(maxnok): nok = 1 for i in range(2, maxnok+1): nok = NOK(nok,i) return nok По формуле (2) мы ищем в цикле while числа, которые отвечают условиям задачи и печатаем их в консольном окне: def Solve(maxd): #макс. число: max = 200000000 71
print("Решаем задачу для чисел, кратных %i: " %maxd) nok = NOK3(maxd-1) print("НОК чисел 2..%i равно %i" %(maxd-1, nok)) #искомые числа имеют вид: #(nok * n + 1) n=1 num = nok*n + 1 while(num <= max): #число не кратно maxd: if (num % maxd == 0): print("Искомое число равно " + str(num)) n += 1 num = nok*n + 1; print() Например, взяв вместо числа 7 на десяток больше, мы получим такие решения задачи (Рис. 2.7). Рис. 2.7. Обобщили Конечно, без компьютера решать такие задачи было бы крайне затруднительно! А теперь поэкспериментируйте и подумайте, почему для составных чисел maxd задача не решается. Исходный код программы находится в папке НОК2. 72
Проект Всезнающая статистика Функция без параметров Оператор continue Цикл for Оператор деления по модулю % Задача 14 (16) из книги Удивительный мир чисел [КА86], страницы 86-87: Попал как-то мне в руки обрывок прошлогодней газеты. Моё внимание привлекло чернильное пятно, закрывшее последние три цифры шестизначного числа (Рис. 2.8). По сохранившемуся кусочку текста я вспомнил: это была заметка, в которой сообщалось, что к концу минувшего года население нашего города возросло до этого числа. В заметке говорилось также о том, что это шестизначное число уникально среди шестизначных: оно делится на 2, 3, 4, 6, 7, 8 и 9. Ого! Не правда ли? Чтобы восстановить все цифры этого числа, нет нужды обращаться в реставрационную лабораторию. Собственная сообразительность подскажет вам математический метод быстрого решения этой задачи. Рис. 2.8. Обрывочные сведения 73
Опять всё решение задачи переносим в функцию Solve: # -*- coding: Windows-1251 -*#Кордемский, с.86-87, Задача 14 #ГЛАВНАЯ ФУНКЦИЯ def main(): print("Всезнающая статистика") print() Solve() Эта задача очень простая, поскольку нам предстоит проверить всего одну тысячу чисел. Действительно, на обрывке газеты вы видите, что первые три цифры 6-значного числа – 566. Последние три – от 000 до 999: #РЕШАЕМ ЗАДАЧУ def Solve(): #мин. и макс. min6 = 566000 max6 = 566999 6-значные числа: В цикле for мы перебираем все числа-кандидаты и проверяем их: • Они должны нацело делиться на числа 2,3,4,6,7,8 и 9 for num in range(min6, max6 + 1): #число не кратно 2,3,4,6,7,8,9: if (num % 2 != 0): continue if (num % 3 != 0): continue if (num % 4 != 0): continue if (num % 6 != 0): continue if (num % 7 != 0): continue if (num % 8 != 0): continue if (num % 9 != 0): continue #печатаем искомое число: print("Искомое число равно " + str(num)) print() Ответ вы можете видеть на Рис. 2.9. 74
Рис. 2.9. Информация восстановлена Исходный код программы находится в папке Кордемский 086 14. 75
Проект Восстановите потерянную цифру Функция без параметров Цикл for Оператор деления по модулю % Задача 7 из книги Удивительный мир чисел [КА86], страница 85: Числа вида Мр = 2р -1, где р - простое число, называются числами Мерсенна. При некоторых значениях р Мр - простое число. Так, первые одиннадцать простых чисел Мерсенна получаются при значениях р = 2, 3, 5, 7, 13, 17, 19, 31, 61, 89, 107. Двенадцатое простое число Мерсенна равно М127 = 2127 - 1. В его десятичной записи одна цифра стёрлась. Мы пока заменили её буквой х. Получилась такая запись: 170 141 183 460 469 231 хЗ1 687 303 715 884 105 727. Восстановите цифру, замещенную буквой х, если известно, что М127 + 3 делится на 13. Задача решается очень просто, если вспомнить признак делимости чисел на 13: # -*- coding: Windows-1251 -*#Кордемский, с.85, Задача 7 #ГЛАВНАЯ ФУНКЦИЯ def main(): print("Восстановите потерянную цифру") print() Solve() Цифра, обозначенная буквой x, может принимать значения от 0 до 9. Их легко перебрать в цикле for: #РЕШАЕМ ЗАДАЧУ def Solve(): for x in range(0, 9+1): 76
Для каждого значения х мы находим арифметическую сумму трёхзначных чисел – как и предписывает нам признак делимости на 13: #арифметическая сумма чисел: summa = 170 - 141 + 183 - 460 + 469 - 231 + x*100+31 - 687 + \ 303 - 715 + 884 - 105 + 727 + 3 К получившейся сумме нужно добавить тройку. Если при этом переменная summa делится на 13, то задача решена: if (summa % 13 != 0): continue #печатаем искомое число: print("Искомое число равно 170 141 183 460 469 231 %i31 687 303 715 884 105 727" %x) print() На Рис. 2.10 вы можете видеть, что x = 7. Рис. 2.10. Нашли х Исходный код программы находится в папке Кордемский 085 07. 77
Проект Снимите маску с одной цифры Функция без параметров Цикл for Оператор целочисленного деления % Условный оператор if Задача 6 из книги Удивительный мир чисел [КА86], страница 84: В записи знаменитого «шахматного» числа М64= 1y446 744 073 709 551 615 на его вторую цифру накинута маска у. Сняв маску, расшифруйте значение у, зная, что достаточно увеличить заданное число на 3 единицы, как оно становится кратным числу 19. Примечание. По легенде, именно такое число пшеничных зёрен следовало выдать в награду изобретателю шахмат, попросившему положить всего одно зерно на первую клетку шахматной доски, а на каждую следующую клетку вдвое большее число зёрен, чем на предыдущую. Эта задача сродни предыдущей, но признак делимости чисел на 19 не очень удобен: # -*- coding: Windows-1251 -*#Кордемский, с.84, Задача 6 #ГЛАВНАЯ ФУНКЦИЯ def main(): print("Снимите маску с одной цифры") print() Solve() Поэтому в функции Solve мы просто находим остаток от деления числа num + 3 на 19: #РЕШАЕМ ЗАДАЧУ def Solve(): for y in range(0, 9+1): num = (10 + y)*1000000000000000000 + 446744073709551615 + 3; 78
if (num % 19 != 0): continue #печатаем искомое число: print("Искомое число равно 1%i 446 744 073 709 551 615" %y) print() if __name__ == "__main__": main() Других сложностей в задаче нет, и мы быстро находим, что y = 8 (Рис. 2.11). Рис. 2.11. Нашли зерно задачи Исходный код программы находится в папке Кордемский 084 06. 79
Проект На одно делится, на другое нет Функция без параметров Бесконечный цикл for Оператор целочисленного деления % Оператор break Задача 2 из книги Удивительный мир чисел [КА86], страница 84: Найдите наименьшее число, которое делится на 77, а при делении на 74 даёт в остатке 48. Простая переборная задача – как раз для решения на компьютере: # -*- coding: Windows-1251 -*#Кордемский, с.84, Задача 2 #ГЛАВНАЯ ФУНКЦИЯ def main(): print("На одно делится, на другое нет") print() Solve() Поскольку искомое число кратно 77, то его можно представить в виде: num = 77*n Значение переменной n нужно изменять от 1 до … - пока задача не будет решена. Здесь вполне уместно использовать бесконечный цикл while, прерываемый оператором break, как только искомое число будет найдено: #РЕШАЕМ ЗАДАЧУ def Solve(): n = 1 while True: #искомое число кратно 77: num = 77*n n += 1 if (num % 74 != 48): continue #печатаем искомое число: print("Искомое число равно %i" %num) 80
break print() if __name__ == "__main__": main() Ответ на задачу показан на Рис. 2.12. Рис. 2.12. Лёгкая задача Исходный код программы находится в папке Кордемский 084 02. 81
Проект Кто где живёт? Функция без параметров Списки Цикл for Цикл while Оператор return Задача 3 из книги Удивительный мир чисел [КА86], страница 50: Лайне, Майму, Юта, Белла, Елена и Элеонора живут в одном блоке здания. Возвращаясь из школы домой, Лайне проходит внутри здания 84 ступеньки, Майму -126, Юта -147, Белла - 189, Елена -210, а Элеонора 231 ступеньку. До квартиры на первом этаже ступенек нет, а между этажами одинаковые количества ступенек. Кто на каком этаже живёт? # -*- coding: Windows-1251 -*#Кордемский, с.50, Задача 3 #ГЛАВНАЯ ФУНКЦИЯ def main(): print('Кто где живёт?') print() Solve() print() Число ступенек, ведущих на заданный этаж, можно представить в виде: ЧИСЛО СТУПЕНЕК = ПРОЛЁТ*(ЭТАЖ-1) (1) Откуда: ЭТАЖ = (ЧИСЛО СТУПЕНЕК) / ПРОЛЁТ + 1 (2) Единицу мы добавляем потому, что на первый этаж можно подняться без ступенек. 82
Число ступенек для каждого этажа нам известно из условия задачи. Эти данные, равно как и странные имена девочек, можно поместить в список, которым пользоваться удобнее, чем несколькими отдельными переменными: #РЕШАЕМ ЗАДАЧУ def Solve(): #число степенек: stupeni = [ 84, 126,147,189,210,231 ] #имена: names = [ "Лайне", "Майму", "Юты", "Беллы", "Елены", "Элеоноры"] Число ступенек между всеми этажами (ПРОЛЁТ) одинаково во всём здании, а номер этажа выражается целым числом. Это значит, что число ступенек для каждого этажа должно делиться на одно и то же число ПРОЛЁТ. Все числа кратны их наибольшему общему делителю, который мы легко найдём с помощью функции NOD: #находим НОД всех чисел: nod = 84 for i in stupeni: nod = NOD(nod,i) print("НОД всех чисел = " + str(nod)) print() Этажи, на которых находятся квартиры, теперь легко определить по формуле (2): #решение задачи: for i in range(0, len(stupeni)): print("Этаж %s = %i" %( names[i], print() stupeni[i]//nod + 1)) def NOD(n1, n2): while (n2 > 0): n1, n2 = n2, n1 % n2 return n1 Ответ на задачу представлен на Рис. 2.13. 83
Рис. 2.13. Нашли этажи Так как 21 = 3 х 7, то число ступенек успешно можно разделить и на 3, и на 7. Номера этажей значительно увеличатся, но здравый смысл нам всё-таки подсказывает, что вряд ли между этажами всего 3 или 7 ступенек. Хотя в доме, в котором живут эти странные девочки, всё возможно… Исходный код программы находится в папке Кордемский 050 03. 84
Проект Три велосипедиста Функция без параметров Функция с параметрами Оператор return Условный оператор if Цикл while Оператор целочисленного деления % Два вилисипидиста виихали навстричу Бесконечный цикл while друг другу из пунктов А и В… Оператор and Типичная задача по физике и в исполнении нашего учителя Задача 16 из книги Удивительный мир чисел [КА86], страница 55: Три велосипедиста начали с общего старта движение по круговой дорожке. Первый делает полный круг за 21 мин, второй - за 35 мин, а третий - за 15 мин. Через сколько минут они ещё раз окажутся вместе в начальном пункте? # -*- coding: Windows-1251 -*#Кордемский, с.55, Задача 16 #ГЛАВНАЯ ФУНКЦИЯ def main(): print('Три велосипедиста') print() Solve() print() Задача решается элементарно, Ватсон, как выразился бы Шерлок Холмс, если догадаться, что велосипедисты окажутся в точке старта через целое число кругов. А это случится, когда пройдёт время t (в минутах), которое нацело делится на 21, 35 и 15. Из этого следует, что искомое время в точности равно НОК этих чисел: #РЕШАЕМ ЗАДАЧУ def Solve(): #находим НОК трёх чисел: 85
nok = NOK(NOK(21,35),15) print("Велосипедисты встретятся через %i мин." % nok) print() def NOD(n1, n2): while (n2 > 0): n1, n2 = n2, n1 % n2 return n1 def NOK(n1, n2): return n1*n2//NOD(n1, n2) Если вы хотите решить задачу КОРОЧЕ, как выразилась бы уже Сборная СНГ по вольной борьбе, то нужно просто запустить часы и ждать, пока не пройдёт столько времени, чтобы оно делилось без остатка на означенные числа: #РЕШАЕМ ЗАДАЧУ def Solve2(): t = 0 while True: t += 1 if (t % 21 == 0 and t % 35 == 0 and t % 15 == 0): break print("Велосипедисты встретятся через %i мин." % t) print() Но, как ни решай и ни выражайся, а встреча произойдёт через 105 минут (Рис. 2.14). Рис. 2.14. Время встречи изменить нельзя Исходный код программы находится в папке Кордемский 055 16. 86
Глава #3. Простые числа Будьте проще – и к вам потянутся люди. Кредо простых чисел Простыми называются натуральные числа, имеющие в точности два разных делителя. Из этого определения следует, что ни нуль, ни единица к простым числам не относятся. Также очень легко установить, что первое простое число - это двойка, потому что она делится на единицу и на саму себя (двойка – единственное чётное простое число!). Дальше вы легко найдёте тройку, пятёрку, семёрку (кто посмелее, доберётся и до туза). Вам не составит труда продолжить этот ряд: 11, 13, 17, 23, 29, 31. Чтобы отбросить многие составные (то есть не простые) числа, достаточно воспользоваться признаками делимости. Но проверять большие числа таким способом «опасно», потому что легко ошибиться при делении (да и вообще делить большие числа затруднительно). На этот случай греческий математик Эратосфен (Рис. 3.1) придумал надёжный способ поиска простых чисел, который в его честь назвали решетом Эратосфена. Рис. 3.1. Ἐρατοσθένης ὁ Κυρηναῖος (276 - 194 до н.э.) 87
Действует он так [ЗП88, Задача 557]. Поиск простых чисел начинается с засыпки натуральных чисел в «решето», которое удобно представить в виде прямоугольной таблицы. Наименьшее число - это двойка, поскольку единица к простым числам не относится. А наибольшее – любое, по вашему выбору. Мы ограничимся первой сотней чисел (Рис. 3.2). Рис. 3.2. Числа - в решете Находим в таблице двойку – первое простое число – и обводим её кружком. Очевидно, что все последующие числа, кратные двойке, простыми быть не могут, поэтому мы их вычёркиваем. В данном примере мы будем просто закрашивать клетки с составными числами (Рис. 3.3). Как видите, уже после первого, грубого просеивания в решете осталась только половина чисел. Продвигаемся дальше по таблице и находим первое после двойки невычеркнутое число. Это тройка – второе простое число. Вычёркиваем все ещё не вычеркнутые числа, кратные тройке (Рис. 3.4). Ну, а затем всё повторяется. Находим следующее простое число – пятёрку - и вычёркиваем числа, кратные пяти. Переходим к семёрке, затем к числам 11 и 13. И так далее, пока не дойдём до конца таблицы. Числа в кружках - простые, все прочие – составные (Рис. 3.5). 88
Рис. 3.3. Вычеркнули чётные числа Рис. 3.4. И кратные тройке 89
Рис. 3.5. Просеивание закончено! Проект Чудеса в решете Эратосфена Бесконечный цикл while Метод int Условный оператор if Оператор return Функция с параметром Списки Цикл for Цикл while Как вы убедились, просеивание чисел – занятие необременительное и даже весёлое, но всё-таки требует некоторого внимания и сноровки, поэтому лучше воспользоваться компьютером, тем более что алгоритм поиска простых чисел незамысловатый, и мы без труда переведём его на язык Питон. Нам достаточно знать только конец диапазона чисел end, ведь поиск всегда начинается с двойки: # -*- coding: Windows-1251 -*#Решето Эратосфена import math 90
#ГЛАВНАЯ ФУНКЦИЯ def main(): print('Решето Эратосфена') print() #бесконечный цикл ввода данных #пока пользователь не закроет программу #или не введет 0: while True: s = "Введите конец диапазона > " print(s, end = '') end = int(input()) #если конец диапазона равен 0, #то программу закрываем: if (end == 0): return #конец диапазона не меньше двойки: if (end < 2): end = 2 #ищем простые числа: Primes(end) print() Здесь мы дополнительно позаботились о том, чтобы пользователь мог закрыть программу, введя в качестве конца диапазона нуль. Это необязательно, но и не помешает. Пользователь может и не знать об этой особенности вашего приложения, поэтому сообщите ему о ней. Непосредственно поиск простых чисел мы поместим в функцию Primes, которой передаём число – конец диапазона end: #ИЩЕМ ПРОСТЫЕ ЧИСЛА def Primes(end): print() print("Простые числа в заданном диапазоне:") Решето вполне естественно представить списком number. А дальше мы действуем по алгоритму Эратосфена: 1. Записываем в список number числа, начиная с двойки и заканчивая концом диапазона: 91
#создаём список натуральных чисел 2..end: number = list(range(2,end+1)) 2. Переменную prime мы отведём под текущее простое число. Сначала это будет двойка: prime = 2 3. «Вычёркиваем» из списка число prime*prime, а затем все числа, начиная с этого числа через prime: 4 – 6 – 8 - … для prime= 2, 9 – 12 – 15 - … для prime= 3. И так далее: while (prime <= math.sqrt(end)): for i in range(prime * prime, end+1, prime): number[i-2] = 0 Конечно, мы не можем реально зачеркнуть число в списке, поэтому присваиваем соответствующему элементу списка значение нуль, которое будет означать, что это число составное. Из индекса элемента списка нужно вычесть двойку, поскольку нулевой индекс принадлежит этому числу! 4. Ищем первое «невычеркнутое» число – его значение в списке должно отличаться от нулевого: #ищем следующее простое число: prime += 1 while (prime <= math.sqrt(end) and number[prime-2] == 0): prime += 1 Теперь переменная prime содержит следующее простое число. 5. Если prime не превосходит корня квадратного из максимального числа end, то мы переходим к пункту 3. 92
В противном случае все простые числа в заданном диапазоне уже найдены, их значения в списке - ненулевые. Перебираем весь список, находим по этому признаку простые числа и печатаем их на экране: print() #печатаем простые числа: for i in number: if (i != 0): print(str(i) + " ", end='') print() print() Поскольку в методе main действует бесконечный цикл while, то пользователь может и дальше «решетить» простые чисел (Рис. 3.6). Несмотря на «древность» алгоритма, он действует довольно быстро, но очень жаден до памяти компьютера. Этот пример наглядно показывает нам, что для написания эффективной программы нужен хороший алгоритм. А если алгоритм имеется, то перевести его на любой язык программирования совсем несложно. Рис. 3.6. Решето Эратосфена в действии! Исходный код программы находится в папках Решето Эратосфена. 93
Проект Простые числа Бесконечный цикл while Метод int Условный оператор if Оператор return Функция с параметрами Вложенные циклы for Оператор целочисленного деления % Всем хорош алгоритм Эратосфена, но не годится для больших чисел - слишком неэкономно он расходует память компьютера. Мы, конечно, и с помощью решета легко узнаем, что 2011-й год - простой (естественно, только с математической точки зрения), а следующим простым годом будет 2017-й. Но вот с числами 514229 или 39916801 нам уже придётся повозиться! В таких случаях правильнее один раз написать программу, чем каждый раз считать вручную. Чтобы сделать программу более универсальной, мы добавим ещё одну переменную - begin, чтобы пользователь мог задавать и нижнюю, и верхнюю границу диапазона для поиска простых чисел: # -*- coding: Windows-1251 -*#ПРОГРАММА ДЛЯ ПОИСКА ПРОСТЫХ ЧИСЕЛ #В ЗАДАННОМ ДИАПАЗОНЕ import math #ГЛАВНАЯ ФУНКЦИЯ def main(): print('Простые числа') print() #бесконечный цикл ввода данных #пока пользователь не закроет программу #или не введет 0: while True: s = "Введите начало диапазона > " print(s, end = '') begin = int(input()) #если начало диапазона равно 0, то программу закрываем: if (begin == 0): return #начало диапазона не меньше двойки: if (begin < 2): begin = 2 94
s = "Введите конец print(s, end = '') end = int(input()) диапазона > " #если конец диапазона равен 0, #то программу закрываем: if (end == 0): return #конец диапазона не меньше двойки: if (end < 2): end = 2 #ищем простые числа: Primes(begin, end) print() Функция main действует почти так же, как и «эратосфеновский», но вызывает функцию Primes с двумя параметрами: #ИЩЕМ ПРОСТЫЕ ЧИСЛА def Primes(n1, n2): print() print("Простые числа в заданном диапазоне:") #считаем простые числа: n = 0 #создаем новый файл для записи результатов поиска: w = open('primes.txt', 'a') w.write("\nПростые числа:\n\n") #просматриваем все числа из заданного диапазона: for j in range(n1, n2+1): flg = True #проверяем, делится ли число j #на числа 2..корень квадратный из числа j: for i in range(2, round(math.sqrt(j))+1): #если делится, значит, число составное: if (j % i == 0): flg = False break #если нашли простое число, if (flg): n += 1 #то печатаем его в консольном окне: print(str(j) + " ", end='') #и записываем в файл: w.write(str(j) + " ") w.write("\n") w.close() 95
print() print("Всего " + str(n)) print() Здесь мы в цикле for последовательно перебираем числа от n1=begin до n2=end и проверяем их на простоту. Проверка проходит так: очередное число j мы поочерёдно делим на все числа, начиная с двойки и кончая корнем квадратным из самого числа j. Так как любое натуральное число делится на единицу и само на себя, то два разных делителя у него имеются в любом случае (кроме, единицы, разумеется). Если мы обнаружим хотя бы ещё один делитель, то это будет перебор: число заведомо составное, и проводить испытания дальше нет смысла - мы переходим к проверке следующего числа. Если же число простое, то мы печатаем его на экране (Рис. 3.7) и записываем в файл. Рис. 3.7. Простые числа найдены! Исходный код программы находится в папке Простые числа. 96
Проект Простые числа 2 Бесконечный цикл while Метод int Условный оператор if Оператор return Функция с параметром Списки Оператор break Вложенные циклы for Метод math.sqrt Оператор целочисленного деления % Недостаток нашего алгоритма кроется в делении каждого числа из заданного диапазона begin..end на все числа, начиная с двойки и заканчивая корнем квадратным. Но мы-то знаем, что делить следует только на уже найденные простые числа, которых заведомо меньше. Это значит, что мы должны их где-то хранить. Для этого вполне сгодится и обычный список. В этом случае начало диапазона лучше закрепить за двойкой, чтобы список простых чисел заполнялся правильно. Для разнообразия мы вместо верхней границы диапазона будем требовать от пользователя ввода общего количества простых чисел num, которые он желает найти: # -*- coding: Windows-1251 -*#ПРОГРАММА ДЛЯ ПОИСКА ПРОСТЫХ ЧИСЕЛ import math #ГЛАВНАЯ ФУНКЦИЯ def main(): print('Простые числа') print() #бесконечный цикл ввода данных #пока пользователь не закроет программу #или не введет 0: while True: s = "Введите количество простых чисел > " print(s, end = '') num = int(input()) #если пользователь ввёл 0, то программу закрываем: if (num == 0): return 97
#ищем простые числа: Primes(num) print() В функции Primes мы создаём список prime для хранения найденных простых чисел и сразу же помещаем в него двойку: #ИЩЕМ ПРОСТЫЕ ЧИСЛА def Primes(endNumber): print() print("Простые числа:") #список для хранения простых чисел: prime = [] #первое простое число - двойка: prime.append(2) print(str(prime[0]) + " ", end='') #всего простых чисел: nPrimes = 1 Цикл while мы начинаем со следующего числа, то есть с тройки. Поскольку все остальные простые числа нечётные, то это избавляет нас от необходимости проверять все числа – мы можем ограничиться только нечётными, что мы и делаем, добавляя в цикле while двойку к переменной num: num = 3 while True: #если нашли заданное простое число, #сообщаем об этом пользователю: if (nPrimes == endNumber): print() print("Простое число номер " + str(endNumber) + " равно " + str(prime[endNumber-1])) print() break; Обратите внимание, что цикл while - бесконечный. Дело в том, что мы наперёд не знаем, какое из чисел num окажется по счёту endNumber, поэтому мы прекращаем цикл, когда найдём nPrimes = endNumber простых чисел. 98
Проверку очередного числа num мы выполняем так. Поочерёдно извлекаем из списка prime и ограничиваем проверку условием, что очередное простое число i не больше math.sqrt(num). Дальше действуем, как обычно: #проверяем, делится число num #простые числа из массива prime: flg = True for i in prime: #поиск закончен: if (i > math.sqrt(num)): break #если число num делится, #значит, число составное: if (num % i == 0): flg = False break И если окажется, что число num – простое, то мы увеличиваем счётчик простых чисел на единицу и добавляем num в список: #если нашли простое число, if (flg): #подсчитываем: nPrimes += 1 #добавляем новое простое число в список: prime.append(num) #и печатаем его в консольном окне: print(str(num) + " ", end='') num += 2 print() По ходу поиска простых чисел функция Primes печатает все простые числа на экране, а в конце «выступления» сообщает, что простое число с заданным номером такое-то (у нас оно всегда последнее из найденных). Например, 101-ое простое число равно 547 (Рис. 3.8). 99
Рис. 3.8. Простая задача с простыми числами Исходный код программы находится в папках Простые числа 2. 100
Взаимно простые числа Если наибольший общий делитель двух чисел m и n равняется единице, то такие числа называются взаимно простыми: НОД(m,n) = 1 → числа m и n - взаимно простые Для простых чисел это условие выполняется всегда, поскольку у них не может быть общих делителей, больших единицы: НОД(7,11) = 1 Составные числа, естественно, взаимно просты также только тогда, когда у них нет общих простых делителей: НОД(10,9) = 1 → числа 10 и 9 - взаимно простые НОД(10,8) = 2 → числа 10 и 8 – не взаимно простые: у них имеется общий делитель двойка. Более подробно о взаимно простых числах вы можете прочитать в книге [ОО80], на страницах 49-50. В книге Дагене, Григаса и Аугутиса [100], в Задаче 22 рассматриваются шестерни с взаимно простыми числами зубьев, что уменьшает их износ. 101
Проект Разложение числа на простые множители Бесконечный цикл while Метод int Условный оператор if Оператор return Функция с параметром Вложенные циклы Цикл while Цикл for Оператор целочисленного деления % Основная теорема арифметики утверждает, что любое натуральное число, большее единицы, можно представить в виде произведения простых чисел, причём единственным способом (если не учитывать их порядок). Например: 21 = 3 * 7 23 = 1 * 23 125 = 5 * 5 * 5 Единичный (со)множитель в разложении можно и не указывать, поскольку он имеется у всех чисел. Разложение числа на произведение простых множителей, называется его факторизацией. В этом проекте мы используем самый простой метод для разложения чисел – полный перебор всех чисел от двух до квадратного корня из заданного числа. Он очень простой, но довольно быстро справляется с задачей даже для очень больших чисел (Рис. 3.9). Однако при встрече с большими простыми числами может и спасовать. # -*- coding: Windows-1251 -*#Факторизация 102
#ПРОГРАММА ДЛЯ РАЗЛОЖЕНИЯ НАТУРАЛЬНЫХ ЧИСЕЛ #НА ПРОСТЫЕ МНОЖИТЕЛИ import math #ГЛАВНАЯ ФУНКЦИЯ def main(): print('Факторизация числа') print() #бесконечный цикл ввода данных #пока пользователь не закроет программу #или не введет 0: while True: s = "Введите натуральное число (больше единицы) > " print(s, end = '') number = int(input()) #если пользователь ввёл 0, то программу закрываем: if (number == 0): return #находим разложение: Razl(number) В функцию Razl мы передаём испытуемое число, а само разложение числа даётся нам очень просто: #Находим разложение заданного числа на простые множители def Razl(num): print() print("Разложение числа:") print(str(num) + " = ", end='') #проверяем, делится ли num на числа 2..заданное числo: for i in range(2, num+1): #если делится, выписываем делитель: while (num % i == 0): print(str(i), end='') num //= i if(num >= i): print(" * ", end='') print() print() 103
Рис. 3.9. Разлагаем на простые множители большие числа! Исходный код программы находится в папке Факторизация. 104
Проект Совершенные числа Бесконечный цикл while Метод int Оператор return Условный оператор if Функция с параметром Вложенные циклы for В книге Брудно и Каплана Олимпиады по программированию для школьников [БК85] имеется такая задача: 84.3. Совершенные числа. Совершенным называется натуральное число, равное сумме своих делителей, исключая само число. Первое совершенное число равно шести: 6 = 1 + 2 + 3. Второе равно 28 = 1 + 2 + 4 + 7 + 14. Третье – 496, четвёртое – 8128. Следующие совершенные числа уже гораздо больше. Напечатать все совершенные числа, меньшие, чем заданное М. По-английски совершенные числа называются perfect numbers. На сайте The OEIS Foundation http://oeis.org/A000396 вы найдёте последовательность совершенных чисел: 6 28 496 8128 33 550 336 8 589 869 056 137 438 691 328, 23 05 843 008 139 952 128 2 658 455 991 569 831 744 654 692 615 953 842 176 191 561 942 608 236 107 294 793 378 084 303 638 130 997 321 548 169 216 Легко заметить, что первые 4 совершенных числа достаточно маленькие, а затем они стремительно увеличиваются! 105
Из этого наблюдения следует, что первые совершенные числа можно искать методом грубой силы: # -*- coding: Windows-1251 -*#Совершенные числа import math #ГЛАВНАЯ ФУНКЦИЯ def main(): print('Совершенные числа') print() #бесконечный цикл ввода данных #пока пользователь не закроет программу #или не введет 0: while True: s = "Введите верхнюю границу > " print(s, end = '') num = int(input()) #если пользователь ввёл 0, то программу закрываем: if (num == 0): return PerfectNumbers(num); print() Практически такой же алгоритм поиска совершенных чисел предлагается и в книге Брудно и Каплана: def PerfectNumbers(max): #сумма делителей: summa = 0 for num in range(2, max+1): summa = 1 for divisor in range(2, round(math.sqrt(num))+1): if (num % divisor == 0): summa += divisor n = num // divisor if (divisor != n): summa += n #печатаем число в консольном окне: if (summa == num): print("Число " + str(num) + " совершенное") print() 106
Рис. 3.10. Несовершенный алгоритм для поиска совершенных чисел Увы, дальше четвёртого числа с методом PerfectNumbers вы не продвинетесь! Исходный код программы находится в папке Совершенные числа. 107
Задания для самостоятельного решения Простые числа 1. Напишите метод, который определяет, являются ли несколько чисел взаимно простыми. 2. Найдите четвёрки простых чисел, принадлежащих одному десятку. Например, 11, 13, 17, 19. [ЗП88, Задача 558]. 3. Натуральное число называется полусовершенным, если оно равно сумме всех или некоторых своих делителей, исключая само число: 6, 12, 18, 20, 24, 28, 30, 36, 40. Например, 12 = 1 + 2 + 3 + 6 или 12 = 2 + 4 + 6. Из этого определения следует, что всякое совершенное число является и полусовершенным, то есть полусовершенных чисел в заданном диапазоне не меньше, чем совершенных. Напишите программу, которая находила бы несколько полусовершенных чисел. 4. Два (различных) натуральных числа называются дружественными, если сумма всех делителей (исключая само число) первого числа равна второму числу, и наоборот. Первая пара дружественных чисел была найдена несколько тысячелетий тому назад. Это числа 220 и 284. Следующая пара отыскалась только в 1860 году – 1184 и 1210. Сейчас известно несколько миллионов дружественных чисел. Напишите программу для их поиска [ЗП88, Задача 560]. Учитывайте, что числа в паре либо оба чётные, либо оба нечётные. 108
Глава #4. Числовые ребусы Первый числовой ребус составил Генри Дьюдени: SEND + MORE = MONEY В числовых ребусах словами зашифрованы числа, причём одинаковые буквы обозначают одинаковые цифры, и наоборот, одинаковые цифры обозначены одинаковыми буквами. Задача состоит в том, чтобы заменить буквы соответствующими им цифрами так, чтобы получилось верное равенство. Обычно числовые ребусы решаются с помощью логических рассуждений, но практически никогда не удаётся обойтись без предположений, то есть без перебора вариантов. А с такой работой даже самый захудалый компьютер справится быстрее любого из нас. А от вас требуется только одно – написать простую программу. Как решать числобус Генри Дьюдени, я уже много раз рассказывал в своих книгах, поэтому мы отдадим должное другим криптарифмам. 109
Проект Каковы жуки? Функция без параметров Вложенные циклы for Условный оператор if Оператор continue Оператор return Оператор or Задача 9 из книги Удивительный мир чисел [КА86], страница 100: Решите уравнение в целых неотрицательных числах: 520⦁(Ж⦁У⦁К⦁И + Ж⦁У + Ж⦁И + К⦁И + 1) = 577⦁(У⦁К⦁И + У + И). Числовые ребусы вполне успешно можно решать методом полного перебора. В них каждая цифра заменена буквой, но, поскольку цифр только десять, то каждая буква может принимать всего 10 разных значений - от 0 до 9. В любом ребусе не более десятка разных букв, то есть в худшем случае нам придётся проверить 10 000 000 000 вариантов. Современным компьютерам это вполне по силам. Впрочем, так бездумно компьютер не используют, поэтому даже при полном переборе следует разумно ограничивать число вариантов. Обычно сделать это очень просто, поскольку некоторые способы решения криптарифмов лежат на поверхности и не требуют глубоких размышлений. Например, для уменьшения числа вариантов достаточно учесть тот очевидный факт, что все буквы должны иметь разное значение. Это следует из условия самой головоломки: разным буквам соответствуют разные цифры. Вот теперь можно смело браться за любой числовой ребус. Функция main просто вызывает функцию Solve, а затем печатает число найденных решений: # -*- coding: Windows-1251 -*#Кордемский, с.100, Задача 09 #ГЛАВНАЯ ФУНКЦИЯ def main(): print("Каковы жуки?") print() nVar = Solve() 110
print("Найдены все варианты решения - " + str(nVar)) print() Функция Solve, как это обычно и бывает при полном переборе, очень простая. Записываем столько вложенных циклов for, сколько разных букв в ребусе. В нашем примере всего 4 разные буквы, поэтому и циклов тоже будет 4. Начиная со второй буквы, делаем проверку: её цифровое представление не должно совпадать с предыдущими буквами. Найдя значение каждой буквы, проверяем условие: 520*(Ж*У*К*И + Ж*У + Ж*И + К*И + 1) == 577*(У*К*И + У + И) Если оно выполняется (то есть левая часть выражения равна правой), то мы выписываем найденное решение в консольном окне, а затем ищем другие решения: #РЕШАЕМ ЗАДАЧУ def Solve(): result = 0 for Ж in range(0, 9+1): for У in range(0, 9+1): if (У == Ж): continue for К in range(0, 9+1): if (К == У or К == Ж): continue for И in range(0, 9+1): if (И == К or И == У or И== Ж): continue if (520*(Ж*У*К*И + Ж*У + Ж*И + К*И + 1) == 577*(У*К*И + У + И)): result += 1 print("Вариант # " + str(result)) s = "Ж = " + str(Ж) print(s) s = "У = " + str(У) print(s) s = "К = " + str(К) print(s) s = "И = " + str(И); print(s) print() return result В итоге мы найдём единственное решение этого ребуса (Рис. 4.1). 111
Рис. 4.1. Задача решена! Точно так же вы можете решить любой числобус, НО – каждый раз вам придётся писать новую функцию Solve! Такова плата за простоту программы… Исходный код программы находится в папке Кордемский 100 09. 112
Проект Четыре "пари" Функция без параметров Вложенные циклы for Условный оператор if Оператор continue Оператор return Оператор or Задача 8 из книги Удивительный мир чисел [КА86], страница 100: Как велико ПАРИ, если: В этой системе числовых ребусов также необходимо найти цифровые значения четырёх букв, поэтому большую часть кода мы можем скопировать из предыдущего проекта. # -*- coding: Windows-1251 -*#Кордемский, с.100, Задача 08 #ГЛАВНАЯ ФУНКЦИЯ def main(): print('Четыре "пари"') print() nVar = Solve() print("Найдены все варианты решения - " + str(nVar)) print() Однако теперь нам предстоит проверить выполнение не одного, а сразу четырёх условий! Лучше их проверять последовательно, и если какоелибо условие окажется невыполненным, то с помощью оператора continue сразу переходить к проверке следующих значений переменных цикла П,А,Р,И: 113
#РЕШАЕМ ЗАДАЧУ def Solve(): result = 0 for П in range(0, 9+1): for А in range(0, 9+1): if (А == П): continue for Р in range(0, 9+1): if (Р == А or Р == П): continue for И in range(0, 9+1): if (И == А or И == П or И== Р): continue if (П*А*Р*И != 5*(П + А + Р)): continue if (П*А*Р*И != 3*(И + Р + А)): continue; if (3*П*А*Р*И != 10*(П + И + Р)): continue; if (4*П*А*Р*И != 15*(П + А + И)): continue result += 1 print("Вариант # " + str(result)) s = "П = " + str(П) print(s) s = "А = " + str(А) print(s) s = "Р = " + str(Р) print(s) s = "И = " + str(И); print(s) print() return result Когда все равенства станут верными, мы печатаем решение задачи на экране (Рис. 4.2). Рис. 4.2. Мы выиграли ПАРИ! Исходный код программы находится в папке Кордемский 100 08. 114
Проект Тайна трёх слагаемых Функция без параметров Вложенные циклы for Условный оператор if Оператор continue Оператор return Оператор or Задача 11 из книги Удивительный мир чисел [КА96], страница 79: Переведите на язык арифметики чисел: Первое слагаемое (БОРЯ) должно быть возможно максимальным. Этот ребус с БОРЕЙ – вполне классический: все цифры заменены буквами, и нужно узнать сумму нескольких чисел. Вы найдёте в книгах огромное множество таких ребусов, и все они решаются одинаково – с помощью вложенных циклов for. Решая классические ребусы, вы должны учитывать, что первая цифра числа не может быть нулём. Это значит, что буквы Б, И, Д могут принимать значения от 1 до 9, но не нуль. Это требование легко учесть в циклах for для соответствующих переменных, что мы и сделали в функции Solve. # -*- coding: Windows-1251 -*#Кордемский, с.79, Задача 11 #ГЛАВНАЯ ФУНКЦИЯ def main(): print('Тайна трёх слагаемых') print() nVar = Solve() print("Найдены все варианты решения - " + str(nVar)) 115
print() #РЕШАЕМ ЗАДАЧУ def Solve(): result = 0 for Б in range(1, 9+1): for О in range(0, 9+1): if (О == Б): continue for Р in range(0, 9+1): if (Р == Б or Р == О): continue for Я in range(0, 9+1): if (Я == Б or Я == О or Я == Р): continue for И in range(1, 9+1): if (И == Б or И == О or И == Р or И == Я): continue for Д in range(1, 9+1): if (Д == Б or Д == О or Д == Р or Д == Я or Д == И): continue for У in range(0, 9+1): if (У == Б or У == О or У == Р or У == Я or У == И or У == Д): continue for Ь in range(0, 9+1): if (Ь == Б or Ь == О or Ь == Р or Ь == Я or Ь == И or Ь == Д or Ь == У): continue if (Б*1000 + О*100 + Р*10 + Я + И*100 + Д*10 + И + Б*1000 + У*100 + Д*10 + Ь != Д*1000 + О*100 + Б*10 + Р): continue result += 1 print("Вариант # " + str(result)) s = "БОРЯ = " + str(Б) + str(О) + str(Р) + str(Я) print(s) s = " ИДИ = " + str(И) + str(Д) + str(И) print(s) s = "БУДЬ = " + str(Б) + str(У) + str(Д) + str(Ь) print(s) s = "ДОБР = " + str(Д) + str(О) + str(Б) + str(Р) print(s) print() return result if __name__ == "__main__": main() 116
Кроме большого числа циклов и длинных проверок, ничего сложного в подобного рода задачах нет, поэтому вы решите любую из них без проблем! Рис. 4.3 показывает 72 ответа. Рис. 4.3. 72 БОРИ Обычно хороший числобус должен иметь единственное решение, что в этой задаче достигается дополнительным требованием, чтобы число БОРЯ было максимальным. Конечно, можно просто вручную найти в списке решений то, в котором это условие выполняется, но всякий перебор лучше перепоручать компьютеру. Введём переменную max, которая поначалу имеет нулевое значение (или любое отрицательное): max = 0 Перед строчкой result += 1 117
вставляем код для проверки и запоминания максимального значения для БОРИ: if (max > Б*1000 + О*100 + Р*10 + Я): continue #запоминаем новое максимальное значение: max = Б*1000 + О*100 + Р*10 + Я result += 1 Теперь функция Solve будет печатать только «рекордные» результаты для числа БОРЯ. Самое большое значение будет последним в списке. На Рис. 4.4 вы видите любопытную картину – число БОРЯ увеличивается с каждой итерацией и достигает своего максимума только в самом конце списка решений. Рис. 4.4. Самый большой БОРЯ Мы можем предположить, что автор задачи решал её так же, как и мы, то есть на компьютере. Исходный код программы находится в папке Кордемский 079 11. 118
Проект Меняем четыре буквы на четыре цифры Функция без параметров Вложенные циклы for Условный оператор if Оператор continue Оператор return Оператор or Задача 10 из книги Удивительный мир чисел [КА86], страница 73: В равенстве 7 * (a2 + b)3 = 23с2k (1) и независимо от него в равенстве 37037 * ab =19019 * ck (2) замените буквы а, b, с, k на подходящие цифры так, чтобы оба равенства подтвердились. Очень простая задача – всего на 4 вложенных цикла! # -*- coding: Windows-1251 -*#Кордемский, с.73, Задача 10 #ГЛАВНАЯ ФУНКЦИЯ def main(): print("Меняем четыре буквы на четыре цифры") print() nVar = Solve() print("Найдены все варианты решения - " + str(nVar)) print() В функции Solve, пожалуй, стоит обратить внимание только на выделенные строчки, в которых проверяется условие первой задачи: #РЕШАЕМ ЗАДАЧУ def Solve(): result = 0 for a in range(0, 9+1): for b in range(0, 9+1): 119
if (b == a): continue for c in range(0, 9+1): if (c == a or c == b): continue for k in range(0, 9+1): if (k == a or k == b or k == c): continue a2b = a*10 + 2 + b if (7*a2b*a2b*a2b != 2*10000 + 3*1000 + c*100 + 20 + k): continue result += 1 print("Вариант # " + str(result)) s = "a = " + str(a) print(s) s = "b = " + str(b) print(s) s = "c = " + str(c) print(s) s = "k = " + str(k); print(s) print() return result if __name__ == "__main__": main() Первая часть задачи решена (Рис. 4.5)! Рис. 4.5. Первая половина задачи Чтобы решить вторую часть задачи, следует изменить условие: #a2b = a*10 + 2 + b #if (7*a2b*a2b*a2b != 2*10000 + 3*1000 + c*100 + 20 + k): # continue ab= a*10 + b ck= c*10 + k; 120
if (37037*ab != 19019*ck): continue Вторая часть задачи имеет 2 решения (Рис. 4.6)! Рис. 4.6. И вторая половина Исходный код программы находится в папке Кордемский 073 10. 121
Проект Коварная задача папы Функция без параметров Вложенные циклы for Условный оператор if Оператор continue Оператор return Оператор or Задача 11 из книги Удивительный мир чисел [КА86], страницы 22-23: Мой папа - шофёр - часто ездит по трассе, вдоль которой расположены пять небольших поселков: А, В, С, D, Е. Папа знал точно, сколько домов в каждом из этих посёлков, и составил для меня такую задачу: «Сколько всего домов в этих пяти посёлках, если • • • • • в А и В вместе 13 домов, в В и С вместе 31 дом, в С и Е - 17 домов, в Е и D - 26 домов, в А и D - 23 дома?» Я подметил, что число домов в каждом посёлке подсчитывалось папой дважды (по тексту задачи), поэтому моё решение было молниеносным: (13 + 31 + 17 +26+ 23) / 2 = 55 (домов в пяти поселках вместе). Но... вот уж действительно: поспешишь — людей насмешишь! Оказалось, что задачу папа сознательно составил так, что она не может быть решена. И я должен был это доказать. Помогите! В каждом посёлке не меньше 0 домов и не больше 31 дома – эти числа легко получить из условия задачи. Достаточно перебрать все варианты числа домов в этом диапазоне для посёлков А, В, С, D, Е, чтобы найти все решения задачи. # -*- coding: Windows-1251 -*#Кордемский, с.22-23, Задача 11 122
#ГЛАВНАЯ ФУНКЦИЯ def main(): print('Коварная задача папы') print() nVar = Solve() print("Найдены все варианты решения - " + str(nVar)) print() Выписываем 5 вложенных циклов for и по ходу итераций проверяем папины фантазии: #РЕШАЕМ ЗАДАЧУ def Solve(): result = 0 min = 0 max = 31 for A in range(min, max+1): for B in range(min, max+1): if (A + B != 13): continue for C in range(min, max+1): if (B + C != 31): continue for D in range(min, max+1): if (A + D != 23): continue for E in range(min, max+1): if (C + E != 17): continue #if (C + E != 27): continue if (E + D != 26): continue result += 1 print("Вариант # " + str(result)) s = "A = " + str(A) print(s) s = "B = " + str(B) print(s) s = "C = " + str(C) print(s) s = "D = " + str(D) print(s) s = "E = " + str(E) print(s) print() return result if __name__ == "__main__": main() 123
Как и отметил торопливый сынуля, папа его дезинформировал (Рис. 4.7). Рис. 4.7. С полным перебором не поспоришь! Правда, если в условии задачи нежно подправить условие: C + E = 17 → C + E = 27 то папина задача (а я подозреваю, что это был либо папа Карло, либо Валдис Пельш) успешно разрешится единственным способом (Рис. 4.8). Рис. 4.8. Поправили папу Из лучших педагогических побуждений будем считать, что папа просто ошибся, а вовсе не глумился над своим ч(а/у)дом. Исходный код программы находится в папке Кордемский 022 11. 124
Задания для самостоятельного решения Поиграем в прятки Удивительный мир чисел. Задача 8 (9) ], страница 73 Все 10 цифр спрятались под буквами в каждом из пяти равенств: 1) 2) 3) 4) 5) Д ⦁ Г У В А = Б Ж Я И Е Е ⦁ Д А И Г = Б ⦁ У В Ж Я Е Ж ⦁ Г В А = Б У Я Д И ЖИ ⦁ ДАГ = ЕУВБЯ УА ⦁ ВБГ = ИЯ ⦁ ЖДА Задача несложная, но трудоёмкая: чтобы решить её, придётся выписывать 10 вложенных циклов! ЛОБ ТРИ САМ Удивительный мир чисел. Задача 2, страница 70 Это трёхзначные числа, такие, что ЛОБ +ТРИ = САМ Расшифруйте сложение, обходясь без цифры нуль. В любом возможном решении должна обнаружиться определенная закономерность в числе «САМ». Какая? Семейство, спрятавшееся в «БАКУ» Удивительный мир чисел. .Задача 5, страница 77 а) Расшифруйте произведение чисел БАКУ и §§§§, зная, что в каждом сомножителе цифры образуют (слева направо) строго убывающую последовательность. Какое семейство цифр спряталось за буквами Б, А, К, У? б) Расшифруйте в этом ребусе ещё и второй множитель (§§§§), если строго убывающую последовательность образуют цифры только перво- 125
го сомножителя (БАКУ) и если в произведении вторая цифра слева нуль. 126
Глава #5. Степени и корни Зри в корень! Козьма Прутков Из множества занимательных и поучительных задач со степенями и корнями мы решим всего 42-√𝟗, но зато очень интересных! Проект Кубическое число Функция с параметром Функция round Метод math.pow Цикл for Условный оператор if Оператор break Задача 1 из книги Удивительный мир чисел [КА86], страница 89: Найдите число, куб которого - данное число: 1) М = 996 703 628 669; 2) N = 1 011 443 374 872. Самый простой способ – извлечь из заданных чисел кубический корень. При этом мы должны учесть, что метод pow не всегда возвращает точное значение, хотя по смыслу задачи оба корня должны быть целыми числами. В этом случае достаточно округлить корень до ближайшего целого числа встроенной функцией round: # -*- coding: Windows-1251 -*#Кордемский, с.89, Задача 1 import math #ГЛАВНАЯ ФУНКЦИЯ def main(): print('Кубическое число') 127
print() Solve(996703628669) Solve(1011443374872) print() #РЕШАЕМ ЗАДАЧУ def Solve(num): print("Кубическое число = " + str(round(math.pow(num,1/3.0)))) print() На Рис. 5.1 показано решение задачи: Рис. 5.1. Извлекли корни Поскольку кубические корни даже из очень больших чисел невелики, то можно найти их простым перебором, даже начиная с нуля! Пишем вторую версию функции для решения задачи: def Solve2(num): i = 0 while True: if (i*i*i > num): break if (i*i*i == num): print("Кубический корень из числа %i = %i" %(num, i)) break i += 1 print() И получаем точные значения искомых корней (Рис. 5.2). 128
Рис. 5.2. И перебор не в тягость! При этом нам не понадобился метод pow класса math! Исходный код программы находится в папке Кордемский 089 01. 129
Проект И «хвост», и «грива» Функция без параметров Функция с параметром Оператор целочисленного деления % Оператор return Цикл while Вложенные циклы for Оператор or Задача 8 из книги Удивительный мир чисел [КА86], страница 72: Если есть четырёхзначные числа, первые две и последние две цифры каждого из которых совпадают соответственно с первыми двумя и последними двумя цифрами квадрата и куба искомого числа, то какое из них наименьшее и какое - наибольшее? Числа, оканчивающиеся единицей и двумя нулями, исключаем. В зашифрованном виде: xyzt2= xy...zt и xyzt3= xy...zt. В правой и левой частях этих равенств буквой х зашифрована одна и та же цифра, буквой у - также, буквой z - также и буквой t - также, но эти цифры могут быть и одинаковыми. # -*- coding: Windows-1251 -*#Кордемский, с.72, Задача 8 #ГЛАВНАЯ ФУНКЦИЯ def main(): print('И "хвост", и "грива"') print() nVar = Solve() print("Найдены все варианты решения - " + str(nVar)) print() Чтобы получить из квадратов и кубов две первые и две последние цифры (точнее, двузначные числа, составленные из этих цифр), мы напишем 2 вспомогательных функции. 130
Две последние цифры легко получить как остаток от деления исходного числа на сотню: def Last2(num): return num % 100 С первыми двумя цифрами сложнее: мы не знаем «длину» числа, поэтому и не сможем сразу разделить его на нужную степень числа 10. В этом случае нужно последовательно делить заданное число на 10, пока оно не станет двузначным: def First2(num): while (num >= 100): num //= 10 return num Решить задачу можно так же, как мы решали числовые ребусы – с помощью четырёх вложенных циклов for. При этом мы учитываем, что переменные x, y, z, t могут иметь одинаковые значения, что в числовых ребусах недопустимо. Также из условия задачи следует, что t не равно 0 и 1, а z не равно 0. В остальном функция Solve достаточно проста: #РЕШАЕМ ЗАДАЧУ def Solve(): result = 0 for x in range(1, 9+1): for y in range(0, 9+1): for z in range(1, 9+1): for t in range(2, 9+1): xyzt = x*1000 + y*100 + z*10 + t xyzt2 = xyzt * xyzt xyzt3 = xyzt * xyzt * xyzt; if (First2(xyzt2) != x*10 + y or First2(xyzt3) != x*10 + y or Last2(xyzt2) != z*10 + t or Last2(xyzt3) != z*10 + t): continue 131
result += 1 print("Вариант # " + str(result)) print("xyzt = " + str(xyzt)) print("xyzt2 = " + str(xyzt2)) print("xyzt3 = " + str(xyzt3)) print() return result Запустив программу, мы тут же получаем ответ (Рис. 5.3). Рис. 5.3. Решили задачу игриво: и в хвост и в гриву Так как функции First2 и Last2 позволяют извлекать нужные цифры из любых чисел, то задачу можно решить, проверяя в одном цикле for все четырёхзначные числа. Попробуйте! Исходный код программы находится в папке Кордемский 072 08. 132
Проект Возведение в квадрат без операции умножения Бесконечный цикл while Метод int Оператор return Условный оператор if Функция с параметром Цикл for Задача 6 из книги В. А. Дагене, Г. К. Григас, К. Ф. Аугутис 100 задач по программированию [100], страница 27: Квадрат любого натурального числа n равен сумме n первых нечётных чисел: 12 = 1 22 = 1 + 3 32 = 1 + 3 + 5 42 = 1 + 3 + 5 + 7 52 = 1 + 3 + 5 + 7 + 9 ... Основываясь на данном свойстве, составьте программу, позволяющую напечатать квадраты натуральных чисел от 1 до n. В главной функции мы организуем традиционный бесконечный цикл ввода данных от пользователя, которому предлагаем ввести число, квадрат которого он хотел бы получить: # -*- coding: Windows-1251 -*#Дагене, #6, с.27 import math #ГЛАВНАЯ ФУНКЦИЯ def main(): print('Квадраты чисел') print() #бесконечный цикл ввода данных #пока пользователь не закроет программу #или не введет 0: while True: s = "Введите число > " 133
print(s, end = '') num = int(input()) #если пользователь ввёл 0, то программу закрываем: if (num == 0): return #вычисляем квадрат заданного числа: q = Quadrat(num) print("Квадрат числа " + str(num) + " = " + str(q)) print() Функция Quadrat вычисляет квадрат заданного числа num по алгоритму, описанному выше: #ВЫЧИСЛЯЕМ КВАДРАТ def Quadrat(num): #нечётное число: nech = 1 #квадрат: q = 0 #вычисляем квадрат как сумму нечётных чисел: for i in range(1, num+1): q += nech nech += 2 return q Несмотря на множество операций сложения, квадраты даже очень больших чисел функция Quadrat находит очень быстро (Рис. 5.4). Рис. .5.4. Решение квадратной задачи Исходный код программы находится в папке Квадраты. 134
Проект Возведение в куб без операции умножения Бесконечный цикл while Метод int Условный оператор if Оператор return Функция с параметром Цикл for Вложенные циклы for Задача 6⨀ из книги В. А. Дагене, Г. К. Григас, К. Ф. Аугутис 100 задач по программированию [100], страницы 27-28: Куб любого натурального числа n равен сумме n нечётных чисел, следующих по порядку за числами, сумма которых составила куб числа n - 1: 13= 1 23= 3 + 5 33= 7 + 9 +11 43= 13 + 15 +17 +19 53= 21 + 23 + 25 + 27 + 29 Основываясь на этом свойстве, создайте программу, позволяющую напечатать таблицу кубов натуральных чисел. Эта задача посложнее «квадратной»! Хотя мы и знаем, сколько нечётных чисел входит в сумму, но не знаем, с какого числа начинается ряд слагаемых. Выпишем номера нечётных чисел, с которых начинается суммирование (верхняя строка): 1 2 4 7 11 1 2 3 4 1 1 1 Затем найдём разность между соседними числами (средняя строка) и повторим эту операцию ещё раз. В нижней строке все числа одинаковые, это значит, что номер нечётного числа можно вычислить по формуле: a⦁num2 + b⦁num + c 135
Для нахождения коэффициентов этого выражения, следует воспользоваться методом исчисления конечных разностей. Он описан, например, в моей книге Как решать комбинаторные задачи на компьютере. В итоге мы получим вот такую замечательную формулу: n = num (num-1) / 2 + 1 (1) По номеру n мы легко найдём и само нечётное число. Оно равняется: 2n – 1 (2) Эти две формулы можно объединить в одну: Первое нечётное число = num (num-1) + 1 Если ряд чисел не очень сложный, то можно поискать для него формулу в Интернете. Например, для нашей последовательности 1, 2, 4, 7, 11 я сразу нашёл общую формулу: http://znanija.com/task/141487 http://answers.yahoo.com/question/index?qid=20080903051936AAUvpkC http://michaelnela.hubpages.com/question/87442/what-is-the-general-term-of-1-2-4-7-11-1622 Функция main требует только небольшой правки: # -*- coding: Windows-1251 -*#Дагене, #6, с.27-28 import math #ГЛАВНАЯ ФУНКЦИЯ def main(): print('Кубы чисел') print() #бесконечный цикл ввода данных #пока пользователь не закроет программу #или не введет 0: 136
while True: s = "Введите число > " print(s, end = '') num = int(input()) #если пользователь ввёл 0, то программу закрываем: if (num == 0): return #вычисляем куб заданного числа: q = Qube(num) print("Куб числа " + str(num) + " = " + str(q)) print() В функции Qube мы сначала вычисляем номер n первого нечётного числа суммы, а затем по формуле (2) и само нечётное число. Остальная часть метода ничем не отличается функции Quadrat: #ВЫЧИСЛЯЕМ КУБ def Qube(num): #номер нечётного числа: n = num*(num-1)//2 + 1 #нечётное число: nech = 2*n - 1 #куб: q = 0 #вычисляем куб как сумму нечётных чисел: for i in range(1, num+1): q += nech nech += 2 return q Начинаем осторожно проверять работу нового метода на небольших числах, кубы которых нам точно известны. Но, разохотившись, вы можете заняться вычислением БОЛЬШИХ кубов (Рис. 5.5). 137
Рис. 5.5. Это вам не в кубики играть! Кубическая задача успешно решена! Пользуясь функцией Qube, можно составить таблицу кубов, о которой идёт речь в задаче, но лучше написать отдельную функцию, тем более что она не отнимет у вас много сил: def PrintTable(num): print() nech = 1 #для всех чисел 1..num: for i in range(1, num+1): q = 0 for j in range(1, i+1): q += nech nech += 2 print("Куб числа " + str(i) + " = " + str(q)) Алгоритм, заложенный в эту функцию, буквально повторяет условие задачи. Мы последовательно находим сумму нечётных чисел, продолжая их ряд с каждым новым числом, для которого находим куб. На Рис. 5.6 вы можете видеть отчёт о проделанной работе. 138
Рис. 5.6. Кубическая таблица Исходный код программы находится в папке Кубы. 139
Проект Зашифрованные жуки Функция без параметров Вложенные циклы for Оператор continue Условный оператор if Оператор or Задача 4 из книги Удивительный мир чисел [КА86], страница 71: Эту задачу вполне можно считать числовым ребусом и потому решать точно так же – с помощью вложенных циклов. Все буквы, входящие в равенства а) и б), должны быть различными, а буква Ж не может равняться нулю. Значение степени И заведомо не меньше трёх и не больше шести. # -*- coding: Windows-1251 -*#Кордемский, с.71, Задача 4 #ГЛАВНАЯ ФУНКЦИЯ def main(): print("Зашифрованные жуки") print() nVar = Solve() print("Найдены все варианты решения - " + str(nVar)) print() С задачей а) мы справляемся без особых хлопот и затей: 140
#РЕШАЕМ ЗАДАЧУ def Solve(): result = 0 for Ж in range(1, 9+1): for У in range(0, 9+1): if (У == Ж): continue for К in range(0, 9+1): if (К == У or К == Ж): continue for И in range(3, 6+1): if (И == К or И == У or И== Ж): continue ЖУК = Ж*100 + У*10 + К num = ЖУК for i in range(2, И+1): num *= ЖУК if (num != 244140000 + Ж*100 + У*10 + К): continue result += 1 print("Вариант # " + str(result)) print("ЖУК = " + str(Ж) + str(У) + str(К)) print("И = " + str(И)) print() return result Она имеет единственное решение, показанное на Рис. 5.7. Рис. 5.7. Первый ЖУК выявлен Для решения задачи б) нужно только изменить проверку: #if (num != 244140000 + Ж*100 + У*10 + К): # continue 141
if (num != 19987173000 + Ж*100 + У*10 + К): continue Она также имеет единственное решение (Рис. 5.8). Рис. 5.8. И второй ЖУК тоже Исходный код программы находится в папке Кордемский 071 04. 142
Проект Ж-Ж-Ж! Функция без параметров Цикл for Оператор return Условный оператор if Оператор continue Задача 3 из книги Удивительный мир чисел [КА86], страница 71: Убрав из жучиной программы всё лишнее, мы легко берём очередную задачу: # -*- coding: Windows-1251 -*#Кордемский, с.71, Задача 3 #ГЛАВНАЯ ФУНКЦИЯ def main(): print("Ж-Ж-Ж!") print() nVar = Solve() print("Найдены все варианты решения - " + str(nVar)) print() #РЕШАЕМ ЗАДАЧУ def Solve(): result = 0 for Ж in range(2, 9+1): Ж1 = Ж-1 num = Ж1 for i in range(2, 5+1): num *= Ж1 if (num != Ж*1000 + Ж*100 + Ж*10 + Ж1): continue result += 1 print("Вариант # " + str(result)) 143
print() print("Ж = " + str(Ж)) print() return result if __name__ == "__main__": main() На Рис. 5.9 вы видите, что Ж = 7, то есть полное решение такое: (7-1)5 = 7776 Рис. 5.9. И с жужжелицей справились Исходный код программы находится в папке Кордемский 071 03. 144
Проект Девять в квадрате Функция без параметров Цикл for Оператор целочисленного деления % Оператор return Задача 1 из книги Удивительный мир чисел [КА86], страница 70: Найдите шестизначное число, зашифрованное в ребусе: ДЕВЯТЬ2=§§§§§§ДЕВЯТЬ Мы легко найдём, что наименьшее 6-значное число, состоящее из разных цифр, равно 102345, а наибольшее – 987654. Таким образом, нужно перебрать все числа в этом диапазоне, возвести их в квадрат и сравнить последние 6 цифр квадрата с исходным числом. По условию задачи, они должны совпадать. # -*- coding: Windows-1251 -*#Кордемский, с.70, Задача 1 #ГЛАВНАЯ ФУНКЦИЯ def main(): print("Девять в квадрате") print() nVar = Solve() print("Найдены все варианты решения - " + str(nVar)) print() В функции Solve мы и выполняем означенные действия: #РЕШАЕМ ЗАДАЧУ def Solve(): minnum = 102345 maxnum = 987654 result = 0 for num in range(minnum, maxnum+1): num2 = num*num if (num2 % 1000000 != num): 145
continue result += 1 print("Вариант # " + str(result)) s = "num = " + str(num) s += " num2 = " + str(num*num) print(s) print() return result Задача имеет 2 решения (Рис. 5.10). Рис. 5.10. Расквадратили Исходный код программы находится в папке Кордемский 070 01. 146
Проект Пара чисел: 3149 и 3151 Функция без параметров Цикл for Условный оператор if Оператор continue Задача 3.2 из книги Удивительный мир чисел [КА86], страница 43: Десятичная запись куба каждого из чисел 3149 и 3151 начинается двумя его первыми цифрами, а оканчивается двумя его последними цифрами: 31493 = 31 226 116 949 31513 = 31 285 651 951 Есть ещё два последовательных четырёхзначных числа, обладающих таким же свойством. Какие это числа? Задача решается полным перебором всех четырёхзначных чисел: # -*- coding: Windows-1251 -*#Кордемский, с.43, Задача 3-2 #ГЛАВНАЯ ФУНКЦИЯ def main(): print("Пара чисел: 3149 и 3151") print() nVar = Solve() print("Найдены все варианты решения - " + str(nVar)) print() def Last2(num): return num % 100 def First2(num): while (num >= 100): num //= 10 return num #РЕШАЕМ ЗАДАЧУ 147
def Solve(): minnum = 1000 maxnum = 9999 result = 0 for num in range(minnum, maxnum+1): Для каждого четырёхзначного числа мы находим его куб. Затем сравниваем в этих числах 2 первые и 2 последние цифры. Для этого мы привлекаем функции First2 и Last2, разработанные нами в проекте И «хвост», и «грива»: num3 = num*num*num #две последние цифры: if (Last2(num) != Last2(num3)): continue #две первые цифры: if (First2(num) != First2(num3)): continue result += 1 print("Вариант # " + str(result)) s = "num = " + str(num) s += " num3 = " + str(num*num*num) print(s) print() return result На Рис. 5.11 приведён полный список чисел, выполняющих условие задачи. В книге указана пара чисел 3200 и 3201, но вы видите, что есть и другие пары чисел: 1000 и 1001 1024 и 1025 9975 и 9976 148
Рис. 5.11. Уточнили ответ Исходный код программы находится в папке Кордемский 043 03-2. 149
Проект Число 698 896 Функция без параметров Цикл for Функция с параметром Цикл while Оператор return Функция max Оператор целочисленного деления % Задача 3.3 из книги Удивительный мир чисел [КА86], страница 43: Число 698 896 квадратное (8362), палиндромическое. Предполагают, что оно - наименьшее квадратное палиндромическое число с чётным количеством цифр. Подтвердить это или опровергнуть можно, только покопавшись в таблице квадратов чисел, меньших 836. И правда, давайте покопаемся! # -*- coding: Windows-1251 -*#Кордемский, с.43, Задача 3-3 #ГЛАВНАЯ ФУНКЦИЯ def main(): print("Число 698 896") print() nVar = Solve() print("Найдены все варианты решения - " + str(nVar)) print() Например, можно докопаться до чисел, гораздо больших, чем 836: #РЕШАЕМ ЗАДАЧУ def Solve(): minnum = 4 maxnum = 1000000 result = 0 for num in range(minnum, maxnum+1): num2 = num*num 150
Чтобы распознать квадратный палиндром, мы задействуем функцию IsPalindromicNumber: #если не палиндром: if (not IsPalindromicNumber(num2)): continue def IsPalindromicNumber(num): #избавляемся от чисел, заканчивающихся на нуль: if (num != 0 and num % 10 == 0): return False rev = 0 while (num >= rev): rev = 10 * rev + num % 10 if (num == rev): return True num //= 10 if (num == rev): return True return False А вот новая функция для подсчёта цифр в квадрате: #НАХОДИМ ЧИСЛО ЦИФР В ЗАДАННОМ ЧИСЛЕ def NumDigit(num): num = abs(num) nd = 0 while (num > 0): nd += 1 num //= 10 return max(1,nd) Как и полагается по условию задачи, мы рассматриваем только числа с чётным набором цифр: #число цифр: if (NumDigit(num2) % 2 != 0): continue #s = "num = " + str(num) #s += " num2 = " + str(num*num) #print(s) result += 1 print("Вариант # " + str(result)) s = "num = " + str(num) s += " num2 = " + str(num*num) 151
print(s) print() return result За несколько секунд мы, покопавшись и порывшись, установили, что первое квадратное палиндромическое число именно 698 896, а второе – гораздо больше (Рис. 5.12). Рис. 5.12. Квадратные палиндромы Если искать квадратные палиндромы с любым числом цифр, то можно откопать и накопать весьма любопытные экземпляры (Рис. 5.13)! Рис. 5.13. Тоже квадратно Исходный код программы находится в папке Кордемский 043 03-3. 152
Проект Числа 11 826, 12 363, 14 676 Функция без параметров Цикл for Условный оператор if Оператор continue Оператор return Функция с параметрами Списки Цикл while Оператор or Задача 3.4 из книги Удивительный мир чисел [КА86], страница 43: Числа 11 826, 12 363, 14 676. В десятичной записи квадрата каждого из них содержатся цифры от 1 до 9, по одному разу каждая: 11 8262= 139 854 276 12 3632 = 152 843 769 14 6762 = 215 384 976 Есть ещё пятизначное число с таким же свойством. Его запись содержит цифры 1, 2, 3, 4, 5, каждую по одному разу. Из пяти разных цифр (без нуля) можно сформировать 5! = 120 разных пятизначных чисел (убедитесь!), но, пользуясь калькулятором, вы без большого труда выделите из них требуемое число. Компьютер именно без труда большого труда переберёт все пятизначные числа: # -*- coding: Windows-1251 -*#Кордемский, с.43, Задача 3-4 #ГЛАВНАЯ ФУНКЦИЯ def main(): print('Числа 11 826, 12 363, 14 676') print() nVar = Solve() print("Найдены все варианты решения " + str(nVar)) print() 153
#РЕШАЕМ ЗАДАЧУ def Solve(): min = 10000 max = 99999 result = 0 for num in range(min, max+1): num2 = num*num Единственная проблема – выяснить, что цифры в числе не повторяются и что среди них нет нуля. Нам ничего не остаётся делать, как написать новую функцию для этого: #если цифры повторяются: if (not IsNoRepDigit(num2)): #if (not IsNoRepDigit(num2, False)): #if (not IsNoRepDigit(num2, False) or num2 < 1000000000): continue result += 1 print("Вариант # " + str(result)) s = "num = " + str(num) s += " num2 = " + str(num2) print(s) print() return result Можно придумать много способов, как проверить, что в числе цифры не повторяются. Самый простой из них – конвертировать число в строку и проверять в ней символы. Но правильнее сделать так. Создадим для цифр числа num список digs: #ОПРЕДЕЛЯЕМ, ЧТО ВСЕ ЦИФРЫ В ЗАДАННОМ ЧИСЛЕ #РАЗНЫЕ def IsNoRepDigit(num, noNull = True): #список цифр: digs = [] 154
Поведение функции IsNoRepDigit мы регулируем с помощью параметра noNull. По умолчанию он равен True, то есть числа, содержащие нуль, будут отвергаться. Если же мы допускаем в числе присутствие нуля, то значение параметра noNull должно быть False. Если заданное число равно нулю, то функция IsNoRepDigit возвращает True или False в зависимости от того, допускается ли в заданном числе нуль: if (num == 0 and not noNull): return True num = abs(num) Если число отличается от нуля, то мы начинаем извлекать из него по одной цифре, начиная с конца: while (num > 0): dig = num % 10 И вот здесь мы должны убедиться, что такая цифра ещё не встречалась, то есть её нет в списке digs: if dig in digs: return False if (noNull and dig == 0): return False digs.append(dig) num //= 10 #print(digs) return True Если вы раскомментируете строчку #print(digs) то увидите в консольном окне распечатку списка digs для всех чисел с неповторяющимися цифрами (Рис. 5.14). 155
На этом подготовительные работы заканчиваются, и мы приступаем к решению задачи. На Рис. 5.15 вы можете видеть часть списка пятизначных чисел, квадраты которых состоят из разных цифр и не содержат нуля. Всего таких чисел ровно 30. Рис. 5.14. Разноциферные числа Рис. 5.15. Весь пятизначный список Что касается числа, состоящего из цифр 1,2,3,4,5, то оно в списке занимает третье место. Это число 12543. Если вы пожелаете найти разноциферные числа с нулём, то перенесите комментарий на строчку выше: 156
#если цифры повторяются: #if (not IsNoRepDigit(num2)): if (not IsNoRepDigit(num2, False)): Как и следовало ожидать, чисел с неповторяющимися цифрами стало заметно больше, но в некоторых из этих чисел отсутствует, например, цифра 8, так что ряд цифр получается разорванным (Рис. 5.16). Рис. 5.16. Не все цифры присутствуют Тут, пожалуй, лучше изменить условие проверки чисел так, чтобы были напечатаны только 10-значные числа: if (not IsNoRepDigit(num2, False) or num2 < 1000000000): На удивление их оказалось довольно много (Рис. 5.17). 157
Рис. 5.17. Самые длинные квадраты с неповторяющимися цифрами Исходный код программы находится в папке Кордемский 043 03-4. 158
Проект Числа 32 043 и 99 066 Задача 3.8 из книги Удивительный мир чисел [КА86], страница 44: Числа 32 043 и 99 066. В запись квадрата каждого из них входят все 10 цифр, по одному разу каждая: 32 0432= 1 026 753 849 99 0662 = 9 814 072 356. Предполагают, что первое - наименьшее, а второе - наибольшее из пятизначных чисел с таким свойством. Подтвердить или опровергнуть это утверждение под силу только ЭВМ или энтузиасту - любителю счёта. Как верно отмечено в условии задачи, это нам под силу! Более того, мы ненароком уже решили эту проблему в предыдущем проекте, и на Рис. 5.17 отчётливо видно, что указанные числа действительно наименьшее и наибольшее из всех пятизначных чисел. 159
Проект Число 117 649 Функция без параметров Цикл for Условный оператор if Оператор continue Функция round Функция с параметром Задача 3.6 из книги Удивительный мир чисел [КА86], страница 44: Число 117 649 существует одновременно в трёх качествах. Оно: • квадратное • кубическое • кратное семи: 117 649 = 3432 = 493 = 7k, k ∈ N. Более того, на отрезке от единицы до миллиона оно единственное с таким свойством. Докажите! Чтобы доказать это утверждение, нужно проверить все числа в заданном диапазоне (но мы расширим диапазон, чтобы найти и другие числа). # -*- coding: Windows-1251 -*#Кордемский, с.43, Задача 3-6 import math #ГЛАВНАЯ ФУНКЦИЯ def main(): print('Число 117 649') print() Solve() print() #РЕШАЕМ ЗАДАЧУ def Solve(): minnum = 7 maxnum = 10000000 result = 0 160
Чтобы не проверять делимость чисел на 7, можно начать поиск с семёрки и в каждой следующей итерации добавлять к переменной цикла также семёрку, тогда все проверяемые числа будут сразу кратны семи, и одна проверка отпадает сама собой. for num in range(minnum, maxnum+1, 7): #чиcло должно быть полным квадратом и кубом: Проверку числа на квадратность и кубичность мы доверим двум новым функциям: if (not IsQuadrat(num)): continue if (not IsQube(num)): continue Если очередное число num выдержало все проверки, то оно является решением задачи, и мы печатаем его на экране: result += 1 print("Вариант # " + str(result)) s = "num = " + str(num) print(s) s = "Квадратный корень = " + str(round(math.pow(num, 1.0/2.0))) print(s) s = "Кубический корень = " + str(round(math.pow(num, 1.0/3.0))) print(s) print() return result Проверку числа на квадратность и кубичность можно выполнить с помощью методов sqrt или pow класса math: #ОПРЕДЕЛЯЕМ ЯВЛЯЕТСЯ ЛИ ЗАДАННОЕ ЧИСЛО #ПОЛНЫМ КВАДРАТОМ def IsQuadrat(num): r = round(math.sqrt(num)) return r * r == num 161
#ОПРЕДЕЛЯЕМ ЯВЛЯЕТСЯ ЛИ ЗАДАННОЕ ЧИСЛО #ПОЛНЫМ КУБОМ def IsQube(num): r = round(math.pow(num, 1.0/3.0)) return r * r * r == num Рис. 5.18 показывает, что первое число с указанными в задаче свойствами, - 117 649, а второе – 7 529 536 – значительно больше 1 миллиона. А всего среди первого миллиарда чисел только 4 кратны 7 и являются полными квадратами и кубами. Если слегка подправить функцию Solve, то можно найти ещё несколько любопытных чисел (Рис. 5.19): #for num in range(minnum, maxnum+1, 7): for num in range(1, 500000000): Рис. 5.18. Семикратные корни 162
Рис. 5.19. Квадратно-кубические числа Или в более наглядной форме: • • • 33752 = 2253 = 11390625 49132 = 2893 = 24137560 И так далее. Исходный код программы находится в папке Кордемский 044 03-6. 163
Проект Красивые цепочки равенств Функция без параметров Бесконечный цикл while Условный оператор if Оператор continue Оператор break Оператор return Функция с параметром Цикл while Задача 9 (14) из книги Удивительный мир чисел [КА86], страница 45: Пусть символ Cц(N) выражает сумму цифр числа N в десятичной записи. Например, Сц(53) = Сц(125)= 1 +2 + 5 = 8. Мы утверждаем, что при одном и том же целом значении х имеют место две красивые цепочки тождеств: 1) Сц (х6) = Сц (х7) = Сц (х8) = Сц (х12) = 6х 2) Сц(х9) = Сц(х10) = Сц(х11) = Сц(х13) = Сц(х17) = Сц(х21) = хх Найдите подходящее значение х. # -*- coding: Windows-1251 -*#Кордемский, с.45, Задача 9 #ГЛАВНАЯ ФУНКЦИЯ def main(): print('Красивые цепочки равенств') print() Solve() print() Так как в условии задачи участвуют очень большие степени, то можно предположить, что число х должно быть, наоборот, очень маленьким. В бесконечном цикле while мы перебираем значения х, начиная с единицы, и при этом проверяем выполнение условий задачи: 164
#РЕШАЕМ ЗАДАЧУ def Solve(): min = 1 x = min-1 while True: x += 1 x6 = x*x*x*x*x*x if (Summa(x6) != 6*x): continue x7 = x6*x if (Summa(x7) != 6*x): continue x8 = x7*x; if (Summa(x8) != 6*x): continue x9 = x8*x x12 = x6*x6; if (Summa(x12) != 6*x): continue xx = x for i in range(2, x+1): xx *= x; if (Summa(x9) != xx): continue x10 = x9*x if (Summa(x10) != xx): continue x11 = x10*x if (Summa(x11) != xx): continue x13 = x12*x if (Summa(x13) != xx): continue x17 = x7*x10 if (Summa(x17) != xx): continue x21 = x11*x10 if (Summa(x21) != xx): continue Как только все равенства станут верными, мы прерываем цикл оператором break и печатаем найденное число: break print("Число х равно " + str(x)) print() Для подсчёта суммы цифр чисел мы используем функцию Summa: #НАХОДИМ СУММУ ЦИФР ЗАДАННОГО ЧИСЛА def Summa(num): sum = 0 165
while (num > 0): sum += num % 10 num //= 10 return sum Как мы и предполагали, значение х совсем небольшое (Рис. 5.20). Рис. 5.20. Нашли икс Исходный код программы находится в папке Кордемский 045 09. 166
Задания для самостоятельного решения Задача-ребус Удивительный мир чисел, страницы 69-70 Представим задачу в форме такого ребуса: Эта задача практически ничем не отличается от той, что мы решили в проекте Девять в квадрате. Задача #33 Математическая шкатулка Какую последнюю цифру может иметь квадрат натурального числа? Куб его? Четвёртая степень? Задача #34 Математическая шкатулка Могут ли числа 458, 523, 652 быть квадратами или кубами целого числа? 167
Глава #6. Числовые ряды и другие задачи Без вычисления факториалов и чисел Фибоначчи не обходится, пожалуй, ни одна книга по программированию. А всё потому, что вычислять их легко и весело! Проект Факториал Бесконечный цикл while Метод int Функция с параметром Условный оператор if Оператор return Цикл for Произведение натуральных чисел от единицы до заданного (пусть это будет n) называется факториалом. Обозначается факториал восклицательным знаком после числа: n! (читается: эн-факториал) Чтобы его вычислить, нужно, как и следует из определения, просто перемножить все числа от единицы до этого числа, включительно: n! = 1 х 2 х . . . х (n-1) х n (1) По определению, 0! = 1. Формула очень простая, но нетрудно догадаться, что для больших значений n факториал будет выражаться огромным числом. Обычно для вычисления факториала используют два алгоритма - рекурсивный и итерационный. Нас интересует только итерационный - он работает быстрее. 168
В функции main пользователь вводит число, после чего вызывается функция factorial, которая возвращает вычисленное значение факториала: # -*- coding: Windows-1251 -*#ПРОГРАММА ДЛЯ ВЫЧИСЛЕНИЯ ФАКТОРИАЛОВ #ГЛАВНАЯ ФУНКЦИЯ def main(): print('Факториал') print() #бесконечный цикл ввода данных #пока пользователь не закроет программу #или не введет отрицательное число: while True: s = "Введите число > " print(s, end = '') num = int(input()) #если пользователь ввёл отрицательное число, #то программу закрываем: if (num < 0): return #находим факториал заданного числа: fact = factorial(num) #печатаем факториал: print(str(num) + "! = " + str(fact)) print() Функция factorial действует строго по формуле (1): #ВЫЧИСЛЯЕМ ФАКТОРИАЛ ЗАДАННОГО ЧИСЛА def factorial(num): if (num == 0): return 1 fact = num for i in range(2, num): fact *= i return fact Умножать на единицу, конечно, смысла нет. Здесь мы учитываем, что факториал нуля равен единице, это особый случай и его нужно учесть отдельно (Рис. 6.1)! 169
Рис. 6.1. Маленькие и большие факториалы Исходный код программы находится в папке Факториал. 170
Проект Факториальные нули Функция без параметров Оператор break Бесконечный цикл while Задача 840 из книги Математическая шкатулка [Нагибин88], страница 128: Сколько нулей в конце записи числа, выражающего произведение 1 ⦁ 2 ⦁ 3 ⦁ 4 ⦁ 5 ⦁ 6 ⦁ . . . ⦁14 ⦁ 15? Произведение чисел 1..15 равно факториалу числа 15: # -*- coding: Windows-1251 -*# Нагибин, с.128, Задача 840 #ГЛАВНАЯ ФУНКЦИЯ def main(): print('Факториальные нули') print() Solve() print() Факториалы мы вычислять умеем, поэтому достаточно пересчитать нули в конце записи числа 15! В данном случае можно просто напечатать значение факториала на экране и посмотреть, сколькими нулями заканчивается запись. Но вполне вероятно, что вам встретится и другая задача на подсчёт хвостовых нулей, поэтому мы добросовестно пересчитаем их в функции Solve: #РЕШАЕМ ЗАДАЧУ def Solve(): num = 15 #находим факториал заданного числа: fact = factorial(num) #печатаем факториал: print(str(num) + "! = " + str(fact)) #считаем нули: 171
n = 0 while True: #очередная цифра сзади: dig = fact % 10 if (dig != 0): break fact //= 10 n += 1 print("Число нулей в заданном числе = " + str(n)) print() Последнюю цифру легко отделить от числа с помощью оператора %. Чтобы предпоследняя цифра числа обратилась в последнюю, нужно разделить его на 10. Как только последняя цифра будет отличаться от нуля, подсчёт нужно закончить. На Рис. 6.2 очень хорошо видно, что факториал числа 15 заканчивается тремя нулями. Рис. 6.2. Нулевая считалка Исходный код программы находится в папке Нагибин 840. 172
Проект Числа Фибоначчи Бесконечный цикл while Метод int Списки Условный оператор if Цикл for Оператор or Оператор return Числа Фибоначчи, как нетрудно догадаться, открыл Фибоначчи (он же Леонардо Пизанский), средневековый математик (Рис. 6.3), автор Книги абака (Liber Abaci), которую он написал в 1202 году. Рис. 6.3. Леонардо Пизанский (Leonardo Pisano, 1170 - 1250), по прозвищу Фибоначчи (Fibonacci) Впрочем, дотошные историки утвеждают, что этот ряд чисел был известен в Индии задолго до Фибоначчи, где он использовался при стихосложении. В трактате Книга абака Фибоначчи рассмотрел математическую модель, связанную с кроликами. Он взял пару взрослых кроликов (точнее, кролика и крольчиху) и предположил, что они могут производить на свет потомство каждый месяц. Причём у них всегда рождается пара крольчат разного пола, у которых через два месяца также рождаются крольчата. Фибоначчи решил подсчитать, сколько будет кроликов через год, если за это время ни один 173
кролик не умрёт. Числа Фибоначчи как раз и отражают рост популяции кроликов. В книгах по программированию принято находить числа Фибоначчи с помощью красивого рекурсивного алгоритма, который основан на рекуррентном определении самих чисел: Первое число Фибоначчи = 0 Второе число = 1 Все последующие равны сумме двух предыдущих, то есть: Третье число = 0 + 1 = 1 Четвёртое = 1 + 1= 2 Пятое = 1 + 2 = 3 и так далее, до бесконечности. Рекурсивные алгоритмы обычно довольно короткие, но не всегда быстрые. Поэтому для ускорения вычислений мы воспользуемся методом динамического программирования, то есть просто запомним уже найденные числа Фибоначчи в списке. Этим мы убьём сразу двух зайцев (или кроликов) – и заданное число получим, и все предыдущие числа сохраним в массиве! В функции main пользователь вводит любое неотрицательное число, после чего функция Fibo находит числа Фибоначчи от нулевого до заданного.: # -*- coding: Windows-1251 -*#ПРОГРАММА ДЛЯ ВЫЧИСЛЕНИЯ ЧИСЕЛ ФИБОНАЧЧИ #ГЛАВНАЯ ФУНКЦИЯ def main(): print('Числа Фибоначчи') print() #бесконечный цикл ввода данных #пока пользователь не закроет программу #или не введет отрицательное число: while True: s = "Введите число > " print(s, end = '') num = int(input()) 174
#если пользователь ввёл отрицательное число, #то программу закрываем: if (num < 0): return #находим все числа Фибоначчи от 0 до num: fibo = Fibo(num) #и печатаем их: for i in range(0, num+1): print("Число Фибоначчи " + str(i) + " = " + str(fibo[i])) print() Чтобы сохранить все промежуточные числа, мы создадим список чисел f и сразу же поместим в него три первых числа Фибоначчи, чтобы можно было найти следующие. Далее мы передаём функции Fibo номер числа Фибоначчи, которое желает узнать пользователь. Сам алгоритм следует определению чисел Фибоначчи: #ВЫЧИСЛЯЕМ ЗАДАННОЕ ЧИСЛО ФИБОНАЧЧИ def Fibo(n): f = [] #помещаем в список первые числа Фибоначчи: f.append(0) f.append(1) f.append(1) if (n == 1 or n == 2): return n for i in range(3, n+1): f.append(f[i - 1] + f[i - 2]) return f В итоге мы получаем список f, до краёв наполненный числами Фибоначчи, которые и распечатываем в функции main. Последнее из найденных чисел соответствует заданному пользователем номеру числа Фибоначчи. Из этого следует, что если пользователю нужно только оно, то можно возвращать пользователю только последнее из найденных чисел. Наша уловка со списком позволяет практически мгновенно находить совершенно невероятные числа Фибоначчи (Рис. 6.4)! 175
Рис. 6.4. Вычисляем мгновенно! Исходный код программы находится в папке Числа Фибоначчи. 176
Проект Числа Фибоначчи 2 Бесконечный цикл while Метод int Функция с параметром Цикл for Оператор return Легко заметить, что из всего списка чисел Фибоначчи нам для вычисления следующего числа нужны только два последних элемента. И если все числа Фибоначчи от первого до заданного сохранять не нужно, то можно обойтись и вообще без списка. И при этом мы не потеряем скорость вычислений! Вот как это делается. «Клонируйте» предыдущий проект и исправьте функцию main: # -*- coding: Windows-1251 -*#ПРОГРАММА ДЛЯ ВЫЧИСЛЕНИЯ ЧИСЕЛ ФИБОНАЧЧИ #ГЛАВНАЯ ФУНКЦИЯ def main(): print('Числа Фибоначчи') print() #бесконечный цикл ввода данных #пока пользователь не закроет программу #или не введет отрицательное число: while True: s = "Введите число > " print(s, end = '') num = int(input()) #если пользователь ввёл отрицательное число, #то программу закрываем: if (num < 0): return #находим число Фибоначчи: f = Fibo2(num) #и печатаем его: print("Число Фибоначчи " + str(num) + " = " + str(f)) print() 177
Как видите, кода стало меньше, но теперь мы получим из функции Fibo2 только одно число. Впрочем, вы можете вызывать эту функцию в цикле и получить все числа Фибоначчи от нулевого до заданного. Конечно, придётся выполнить лишнюю работу, но скорость программы такова, что вы ничего не заметите. Функция Fibo2 для нахождения чисел Фибоначчи тоже упростилась по стравнению с первой версией: #НАХОДИМ ЧИСЛО ФИБОНАЧЧИ БЕЗ СПИСКА def Fibo2(n): f1 = 0 f2 = 1 f = 1 for i in range(1, n+1): f = f1 + f2 f2 = f1 f1 = f return f Здесь нам понадобились всего три переменные f1, f2 и f. Первые два числа Фибоначчи мы задаём сразу, а все последующие вычисляем как сумму двух предыдущих. То есть третье (если нулевое число Фибоначчи считать первым (нередко последовательность чисел Фибоначчи начинается с единицы, а не с нуля, поэтому и возникают подобные «разночтения») число 0 + 1 = 1, четвёртое 1 + 1= 2, пятое 1 + 2 = 3 и так далее: f = f1 + f2 f2 = f1 f1 = f Проверка подтверждает наши ожидания – новый алгоритм считает быстро и аккуратно (Рис. 6.5). 178
Рис. 6.5. Тоже неплохо! Исходный код программы находится в папке Числа Фибоначчи 2. 179
Проект «Избранные» числа Функция без параметров Цикл for Условный оператор if Оператор continue Оператор целочисленного деления % Функция с параметром Цикл while Задача 12 из книги Удивительный мир чисел [КА86], страница 65: Есть 80 четырёхзначных и 800 пятизначных чисел, не оканчивающихся нулем, и таких, что если от любого из них вычтем 999 в случае четырёхзначного числа и 9999 в случае пятизначного числа, то всякий раз получим обращённое число, т. е. записанное теми же цифрами, но в обратном порядке. Какие это числа? Найдите способ быстрого вычисления суммы этих чисел (без привлечения компьютера). Мы проигнорируем замечание в скобках и решим сначала задачу для 4значных чисел: # -*- coding: Windows-1251 -*#Кордемский, с.65, Задача 12 #ГЛАВНАЯ ФУНКЦИЯ def main(): print('Избранные числа') print() nVar = Solve() print("Найдены все варианты решения - " + str(nVar)) print() Так как условие задачи запрещает числам оканчиваться нулём, то минимальное 4-значное число равно 1001, а максимальное – 9999: #РЕШАЕМ ЗАДАЧУ def Solve(): 180
result = 0 min = 1001 max = 9999 minus = 999 Перебираем все 4-значные числа в цикле for: for num in range(min, max+1): #число не заканчивается нулём: if (num % 10 == 0): continue Вычитаем из каждого числа 999: num2 = num - minus И сравниваем его с обращённым исходным числом: if (num2 != ReverseNum(num)): continue Если они совпадают, то найдено ещё одно решение задачи: result += 1 print("Вариант # " + str(result)) s = "num = " + str(num) s += " > " + str(num2) print(s) print() return result Для обращения чисел мы используем функцию ReverseNum: def ReverseNum(num): rev = 0 while (num > 0): rev = rev*10 + num % 10 num //= 10 return rev 181
Как и указано в книге Удивительный мир чисел, всего существует 80 таких чисел (Рис. 6.6). Рис. 6.6. Четырёхзначные числа-перевёртыши Для поиска 5-значных чисел необходимо изменить значения локальных переменных в функции Solve: #min = 1001 #max = 9999 #minus = 999 min = 10001 max = 99999 minus = 9999 5-значных чисел ровно в 10 раз больше, чем 4-значных, что и сообщает нам Рис. 6.7. 182
Рис. 6.7. Пятизначные числа-перевёртыши Поскольку мы решаем задачу на компьютере, то было бы странно, если бы мы принялись сумму найденных чисел вычислять вручную! Поэтому объявляем переменную для подсчёта суммы: #сумма чисел: sum = 0 И каждое найденное число добавляем к общей сумме: ++result sum += num В конце метода Solve печатаем эту сумму: print("Сумма всех чисел = " + str(sum)) print() return result Вот решение задачи для 4- и 5-значных чисел (Рис. 6.8). 183
Рис. 6.8. Числовой сумматор Исходный код программы находится в папке Кордемский 065 12. 184
Проект Безошибочный прогноз Класс random Функция с параметром Условный оператор if Оператор return Операторы or и and Списки Цикл for Функция с параметром-массивом Цикл while Функция abs Задача 16 (17) из книги Удивительный мир чисел [КА86], страница 67: Расположите по кругу 4 произвольных натуральных числа а1, а2, а3, а4. Замените их абсолютными значениями разностей (Рис. 6.9): |а1 — а2|, |а2 — а3|, |а3 — а4|, |а4 — а1|. Рис. 6.9. Иллюстрация к задаче С получившимися разностями поступите так же, как с исходными числами. Повторите процедуру вычисления разностей несколько раз, и на некотором шаге все разности одновременно станут нулями. Можете начать вычисления не с 4, а с 8 или с 16, вообще с 2k (k = 2, 3, ...) чисел - финал будет таким же. Докажите хотя бы для 4 исходных чисел, что прогноз безошибочен. Дополнительную информацию об этой задаче смотрите на странице 6. Задача очень интересная, и мы за неё возьмёмся! 185
Поскольку нам потребуются «произвольные», то есть случайные числа, то без генератора таких чисел нам не обойтись: # -*- coding: Windows-1251 -*#Кордемский, с.67, Задача 16 import random В функции main мы передаём функции Solve количество чисел, которые мы желаем испытать: #ГЛАВНАЯ ФУНКЦИЯ def main(): print('Безошибочный прогноз') print() Solve(4) print() По условию задачи, это число не меньше 4 и должно быть степенью двойки, поэтому, прежде всего, следует проверить значение параметра n: #РЕШАЕМ ЗАДАЧУ def Solve(n): #число n должно быть степень двойки: if (n < 4 or n & (n - 1) != 0): return Если условие задачи нарушено, то следующую часть кода выполнять нельзя, и мы возвращаемся в главную функцию программы. Если все числа изначально равны нулю, то условие задачи будет выполнено, но, скорее всего, нам потребуется сделать для этого несколько шагов, которые мы, ради интереса, подсчитываем в переменной step: #номер шага: step = 0 В задаче могут участвовать не только 4, но и 8, и 16 чисел, поэтому для их хранения потребуется список, а не n отдельных переменных: 186
#список чисел: a = [] a.append(0) Как вы помните, нумерация элементов в списке начинается с нуля, а в задаче индексы чисел начинаются с 1. Мы сразу добавляем в список нуль, и тогда в списке а окажутся элементы с индексами 0..n, причём нулевой элемент в задаче не используется, но мы знаем, что его значение равно 0. Теперь мы заполняем список случайными числами, что соответствует присвоению значений числам a1..a4 в задаче: #заполняем список случайными числами: max = 20 for i in range(1, n+1): a.append(random.randint(0,max)) Тут же можно полюбопытствовать, что у нас получилось: print("Шаг %i:" % step) #печатаем список: print(*a[1:], sep=" ", end="") print() В задаче утверждается, что через некоторое число шагов все элементы списка одновременно станут нулями. На самом деле это не так: некоторые элементы массива могут обратиться в нули раньше других, поэтому мы будем контролировать процесс по сумме всех элементов в массиве. Как только сумма обратится в нуль, цикл while будет закончен: #сумма элементов списка: sum = -1 #заменяем элементы списка #абсолютными значениями разностей: while (sum != 0): a1 = a[1] for i in range(1, n): a[i] = abs(a[i] - a[i+1]) a[n] = abs(a[n] - a1) 187
step += 1 print("Шаг %i:" %step) print(*a[1:], sep=" ", end="") print() #находим сумму элементов списка: sum = 0 for i in a: sum += i; Важно учесть, что последнюю разность a4 – a1 нельзя вычислить в цикле for. Действительно, значение первого элемента списка будет заменено абсолютным значением разности a1 = a1 – a2 и последняя разность будет вычислена неверно! Поэтому мы перед началом цикла изменения значений элементов списка запоминаем во вспомогательной переменной a1 текущее значение первого элемента. Его мы и используем для замены значения последнего элемента списка. Результаты проверки «гипотезы» для четырёх чисел показаны на Рис. 6.10. Рис. 6.10. Прошагали 188
Как вы видите, для обнуления элементов списка может потребоваться разное число шагов – в зависимости от начальных значений элементов массива. С числами 8, 16 и далее поэкспериментируйте самостоятельно! Исходный код программы находится в папке Кордемский 067 16. 189
Проект Ошибочный прогноз Класс random Функция без параметров Списки Цикл for Цикл while Функция abs Операторы and и or Пример из книги Удивительный мир чисел [КА86], страница 6: Иной результат наблюдается для серии разностей в случае комплекта из трёх произвольных натуральных чисел: в финале всегда получаются две единицы и нуль в том или ином чередовании. Пример. Пусть исходная тройка чисел R0 = (7, 12, 1). Тогда последовательность разностей будет: R1 = (5, 11, 6) R2 = (6, 5, 1) R3 = (1, 4, 5) R4 = (3, 1, 4) R5 = (2, 3, 1) R6 = (1, 2, 1) R7 = (1, 1, 0) Поскольку один пример ничего не доказывает, то мы напишем программу и подвергнем серьёзному сомнению и проверке это утверждение. # -*- coding: Windows-1251 -*#Кордемский, с.6, Пример import random #ГЛАВНАЯ ФУНКЦИЯ def main(): print('Безошибочный прогноз') print() Solve() print() 190
Небольшие изменения в функции Solve – и можно приниматься за научноисследовательские работы! #РЕШАЕМ ЗАДАЧУ def Solve(): #тройка чисел: n = 3 #номер шага: step = 0 #список чисел: a = [] a.append(0) #заполняем список случайными числами: max = 20 for i in range(1, n+1): a.append(random.randint(0,max)) #a.append(7) #a.append(12) #a.append(1) print("Шаг %i:" % step) #печатаем список: print(*a[1:], sep=" ", end="") print() flg = False #заменяем элементы списка #абсолютными значениями разностей: while (not flg): a1 = a[1] for i in range(1, n): a[i] = abs(a[i] - a[i+1]) a[n] = abs(a[n] - a1) step += 1 print("Шаг %i:" %step) print(*a[1:], sep=" ", end="") print() if (a[1] * a[2] * a[3] == 0 and (a[1] - a[2] - a[3] == 0 or a[2] == a[3])): flg = True Раскомментируйте строчки с условиями примера, закомментируйте заполнение списка случайными значениями и запустите программу. Рис. 191
6.11 подтверждает, что на 7 шаге одно число превратится в нуль, а остальные два – в единицу. Рис. 6.11. Пока всё верно Теперь закомментируйте раскомментированное и пуститесь в свободное плавание по числовому океану! Сначала было тихо и штильно (Рис. 6.12, слева), но вскоре заштормило (Рис. 6.12, в середине). И не на шутку (Рис. 6.12, справа и ещё правее)! Рис. 6.12. А дальше скверно Как вы видите, элементы списка а всегда превращаются в элегантные числа: 1 нуль и 2 «ненуля». Причём ненули это не всегда пара единиц! Вывод: Не говори гоп, пока не проверишь решение на компьютере! Исходный код программы находится в папке Кордемский 067 16-2. 192
Проект Нумерация страниц Функция без параметров Цикл for Условный оператор if-elif-else Задача 6 из книги Удивительный мир чисел [КА86], страница 64: Я спросил наборщика типографии: - Сколько отдельных литер с цифрами потребуется для нумерации 1000 страниц книги? - Легко вычислить,- ответил наборщик. - Вот моё решение: 3000 - 18 – 90 +1= 2893. Я решил иначе, но получил такой же результат. Придумайте свой план решения и найдите объяснение способу, предложенному наборщиком. # -*- coding: Windows-1251 -*#Кордемский, с.64, Задача 6 #ГЛАВНАЯ ФУНКЦИЯ def main(): print('Нумерация страниц') print() Solve() print() Число литер легко найти так: • • • • для записи чисел 1..9 нужно по 1 литере для записи чисел 10..99 нужно по 2 литеры для записи чисел 100..999 нужно по 3 литеры для записи чисел 1000..9999 нужно по 4 литеры В функции Solve мы «пролистываем» книгу от 1 до 1000 страницы и считаем литеры, как описано выше: 193
#РЕШАЕМ ЗАДАЧУ def Solve(): #число литер: sum = 0 for i in range(1, 1000+1): if (i < 10): sum += 1 elif (i < 100): sum += 2 elif (i < 1000): sum += 3 else: sum += 4 s = "Потребуется литер: " + str(sum) print(s) print() Все правы: потребуется 2893 литеры (Рис. 6.13). Рис. 6.13. Литерная задача Исходный код программы находится в папке Кордемский 064 06. 194
Проект Сколько страниц в книге? Функция без параметров Бесконечный цикл for Вложенные условные операторы if-else Оператор break Задача 5 из книги Удивительный мир чисел [КА86], страница 64: Для нумерации страниц книги наборщик типографии использовал 2529 литер с цифрами. На каждой литере одна цифра. Сколько страниц в этой книге? Задача очень похожа на предыдущую, поэтому нам нужно только подправить её код: # -*- coding: Windows-1251 -*#Кордемский, с.64, Задача 5 #ГЛАВНАЯ ФУНКЦИЯ def main(): print('Сколько страниц в книге?') print() Solve() print() #РЕШАЕМ ЗАДАЧУ def Solve(): #число использованных литер: SUMMA = 2529 #число литер: sum = 0 i = 1 Мы знаем, что первая страница обозначена числом 1, вторая – числом 2, и так далее. Если мы будем последовательно находить сумму литер, затраченных на нумерацию страниц, то рано или поздно она сравняется с заданной – SUMMA (а если задача составлена неверно, то это событие может и не произойти!). На этом бесконечный цикл while заканчивается, и мы печатаем ответ на задачу: 195
#перелистываем страницы и считаем литеры: while True: if (i < 10): sum += 1 elif (i < 100): sum += 2 elif (i < 1000): sum += 3 else: sum += 4 if (sum == SUMMA): s = "В книге страниц: " + str(i) print(s) break elif (sum > SUMMA): s = "Задача решения не имеет!" print(s) break i += 1 print() В книге оказалось 879 страниц (Рис. 6.14), а задачу автор составил верно! Рис. 6.14. Посчитали все страницы Исходный код программы находится в папке Кордемский 064 05. 196
Проект И такие есть числа Функция без параметров Цикл for Условный оператор if Оператор continue Оператор return Задача 4-1 из книги Удивительный мир чисел [КА86], страница 63: Какое двузначное число в 19 раз больше числа его единиц? # -*- coding: Windows-1251 -*#Кордемский, с.63, Задача 4-1 #ГЛАВНАЯ ФУНКЦИЯ def main(): print("И такие есть числа") print() nVar = Solve() print("Найдены все варианты решения - " + str(nVar)) print() Чтобы решить задачу, достаточно перебрать все двузначные числа и проверить условие задачи: #РЕШАЕМ ЗАДАЧУ def Solve(): result = 0 for n in range(10, 99+1): if (n != n%10*19): continue result += 1 print("Вариант # " + str(result)) s = "Число = " + str(n) print(s) print() 197
return result Как вы видите на Рис. 6.15, задача имеет единственное решение: искомое число равно 95. Рис. 6.15. Число найдено Исходный код программы находится в папке Кордемский 063 04-1. 198
Проект Трёхзначное число Функция без параметров Цикл for Условный оператор if Оператор continue Оператор целочисленного деления % Оператор деления // Задача 3 из книги Удивительный мир чисел [КА86], страница 63: Найдите трёхзначное число, обладающее следующими свойствами: • число десятков на 4 меньше числа единиц, но на 4 больше числа сотен; • если цифры этого числа разместить в обратном порядке, то новое полученное число будет на 792 больше искомого. # -*- coding: Windows-1251 -*#Кордемский, с.63, Задача 3 #ГЛАВНАЯ ФУНКЦИЯ def main(): print('Трёхзначное число') print() Solve() print() Чтобы найти трёхзначное число с требуемыми свойствами, необходимо перебрать все трёхзначные числа и выбрать те из них, которые удовлетворяют условиям задачи. #РЕШАЕМ ЗАДАЧУ def Solve(): min3 = 100 max3 = 999 for n in range(min3, max3+1): 199
Прежде всего, нужно найти, сколько единиц, десятков и сотен содержит текущее число. Это нетрудно сделать с помощью двух операций – целочисленного деления и деления с остатком: for n in range(min3, max3+1): #число единиц: e = n % 10 #число десятков: d = n // 10 % 10 if (d != e - 4): continue #число сотен: s = n // 100 % 10 if (d != s + 4): continue Выполнить проверки и того проще. И вот мы уже получаем ответ на эту задачу (Рис. 6.16): s = "Число = " + str(n) print(s) print() Рис. 6.16. Нашли трёхзначное число Поскольку мы перебрали все трёхзначные числа и нашли только одно, которое удовлетворяет условиям задачи, то последняя проверка на разность с перевёрнутым числом оказалась бы лишней! Исходный код программы находится в папке Кордемский 063 03. 200
Проект Таких чисел только два Функция без параметров Цикл for Оператор целочисленного деления % Оператор деления / / Условный оператор if Оператор continue Задача 1 из книги Удивительный мир чисел [КА86], страница 63: Есть только два двузначных числа, каждое из которых равно неполному квадрату разности своих цифр. Найдите эти числа. Чтобы облегчить решение, подскажем, что одно число на 11 больше другого. Так как двузначных чисел очень мало, то мы и без подсказки найдём оба искомых числа! # -*- coding: Windows-1251 -*#Кордемский, с.63, Задача 1 #ГЛАВНАЯ ФУНКЦИЯ def main(): print('Таких чисел только два') print() Solve() print() Выделяем из каждого числа его единицы и десятки и сравниваем само число с неполным квадратом разности его цифр: #РЕШАЕМ ЗАДАЧУ def Solve(): min2 = 10 max2 = 99 for n in range(min2, max2+1): #число единиц: 201
e = n % 10 #число десятков: d = n // 10 % 10 if (e * e + d * d - e * d != n): continue s = "Число = " + str(n) print(s) print() Задача решена (Рис. 6.17)! Рис. 6.17. Искомая парочка Исходный код программы находится в папке Кордемский 063 01. 202
Проект Ещё два числа Функция без параметров Условный оператор if Цикл for Оператор continue Оператор деления // Оператор целочисленного деления % Задача 2 из книги Удивительный мир чисел [КА86], страница 63: Сходным свойством обладают ещё два двузначных числа: каждое равно неполному квадрату суммы своих цифр. Найдите эти числа, зная, что одно число на 50 больше другого. Эта задача решается точно так же, как предыдущая: # -*- coding: Windows-1251 -*#Кордемский, с.63, Задача 2 #ГЛАВНАЯ ФУНКЦИЯ def main(): print('Ещё два числа') print() Solve() print() Только в функции Solve нужно исправить одну строчку: #РЕШАЕМ ЗАДАЧУ def Solve(): min2 = 10 max2 = 99 for n in range(min2, max2+1): #число единиц: e = n % 10 #число десятков: d = n // 10 % 10 if (e * e + d * d + e*d != n): 203
continue s = "Число = " + str(n) print(s) print() Правда, к нашему удивлению, чисел оказалось не два, а три (Рис. 6.18)! Рис. 6.18. А таких чисел три! Да, с полным перебором состязаться невозможно! Исходный код программы находится в папке Кордемский 063 02. 204
Проект Отгадать число, ничего не спрашивая Функция без параметров Вложенные циклы for Условный оператор if Оператор continue Оператор деления // Оператор целочисленного деления % Ради счастья, ради нашего, Если хочешь ты его, Ни о чем меня не спрашивай, Не расспрашивай, не выспрашивай, Не выведывай ничего. Песня из комедии Свадьба в Малиновке Задача 4 из книги Удивительный мир чисел [КА86], страница 36: Пусть кто-нибудь задумает двузначное число в виде 10x + y, где х — цифра десятков, у - цифра единиц, х – у ≥ 2, у ≠ 0. Потребуйте теперь, чтобы он переставил цифры в обратном порядке и вычел меньшее число из большего. Полученную разность пусть сложит с нею же, но написанной в обратном порядке следования цифр. Ничего не спрашивая у загадавшего, вы сообщаете, что у него получилось 99. Пример. Пусть задумано 75. Загадавший должен выполнить следующие действия: 75 - 57=18, 18 + 81=99. В общем виде: пусть задумано 10x + y, где х – у ≥ 2. При обратном расположении цифр число имеет вид 10у + х. Абсолютная величина разности: R =|10x + y — (10у + х)| = 9⦁|х – у|. Так как х – у ≥ 2, то R = 9⦁(х – у) = 10⦁(х – у) - (х – у) + 10 - 10. То есть разность можно представить в виде: R = 10(x – y - 1) + (10 - (x - y)). (1) Запишем число с обратным расположением цифр: 10(y – x + 10) + (x – y - 1) (2) Сложив (1) и (2), получим: 10(x - y - 1) + (10 - (x - y)) + 10(y – x + 10) + (x – y - 1) = 99. Итак, независимо от выбора цифр х и у двузначного числа при х – у ≥ 2 и у ≠ 0 всегда получается число 99. 205
Если вышеуказанные действия будут применены к любому трёхзначному числу 100x +10y + z при соблюдении условий х – у > 2, х - у = y - z, то в результате всегда получится 1089. Для четырёхзначного числа l000x+ l00y + 10z + t при условии x > y >z > t > 0, x - y = y - z = z - t в результате всегда получится число 10 890 и т. д. В книге это не задача, а фокус, но мы обойдёмся без фокусов! Наша задача: с помощью компьютера и полного перебора подтвердить (или опровергнуть) доказательство, приведённое в книге. Из условия задачи следует, что цифра x изменяется в диапазоне 3..9, а цифра y – в диапазоне 3..7, при этом цифра x как минимум на двойку больше цифры y. # -*- coding: Windows-1251 -*#Кордемский, с.36, Задача 4 #ГЛАВНАЯ ФУНКЦИЯ def main(): print('Отгадать число, ничего не спрашивая') print() Solve() print() #РЕШАЕМ ЗАДАЧУ def Solve(): min2 = 10 max2 = 99 for x in range(3, 9+1): for y in range(1, 7+1): if (x - y < 2): continue Получив пару верных цифр, мы составляем из них число, переворачиваем его, находим абсолютную разность прямого и обратного чисел, а затем сумму полученной разности и её «зеркального отражения»: #прямое число: num = 10*x + y #обратное число: numr = 10*y + x 206
#разность чисел: r = abs(num - numr) #число единиц в разности: e = r % 10 #число десятков в разности: d = r // 10 #сумма: sum = r + e*10 + d Для контроля печатаем каждое двузначное число, удовлетворяющее условиям задачи, а также результат наших действий над ним: print("Исходное число равно: " + str(num)) print("Полученное число равно: " + str(sum)) print() print() Часть списка представлена на Рис. 6.19. Из него видно, что все двузначные числа обращаются в 99. Рис. 6.19. Удачный фокус Трёх- и четырёхзначные числа проверьте самостоятельно! Исходный код программы находится в папке Кордемский 036 04. 207
Проект Три лягушки Функция без параметров Списки Бесконечный цикл for Вложенные циклы for Условный оператор if Оператор break Оператор continue Комбинированные операторы присваивания Задача 6 из книги Удивительный мир чисел [КА86], страница 51: Три лягушки находятся на дне колодца глубиной 60 м. За день они поднимаются на 18 м каждая, а потом спускаются первая на 12 м, вторая на 16 м, третья на 17 м и остаются на своих местах до следующего дня. На следующий день каждая лягушка проделывает снова такой же маршрут и т. д. Через сколько дней лягушки выйдут из колодца? # -*- coding: Windows-1251 -*#Кордемский, с.51, Задача 6 #ГЛАВНАЯ ФУНКЦИЯ def main(): print('Три лягушки') print() Solve() print() Для начала объявим необходимые переменные и списки: #РЕШАЕМ ЗАДАЧУ def Solve(): #глубина колодца: GLUBINA = 60 #высота подъёма: UP = 18 #глубина опускания: DOWN = [12, 16, 17] #текущее расстояние до поверхности: frogs = [GLUBINA, GLUBINA, GLUBINA] 208
#освободившиеся лягушки: free = [False, False, False] Приключения начинаются! По значению элементов списка free мы определяем, все ли лягушки-квакушки выбрались из колодца. Если это радостное событие состоялось, значит, все элементы в списке обратились в True: n = 1 while True: #все лягушки свободны: if not False in free: print() print("Все лягушки свободны!") print() break На этом решение задачи заканчивается, и мы с чистой совестью отпускаем их на свободу, а бесконечный цикл while прерываем оператором break. Каждый день лягушки поднимаются на UP метров: #лягушки поднялись вверх: for i in range(0, len(frogs)): #эта лягушка уже свободна: if (free[i]): continue frogs[i] -= UP Если текущее значение лягушки в списке frogs не больше нуля, значит, лягушка выкарабкалась из колодца: #лягушка выбралась из колодца? free[i] = frogs[i] <= 0 if (free[i]): print("Лягушка %i свободна! День: %i" %(i+1, n)) Если лягушкам выбраться не удалось, то они опускаются вниз – каждая на свой «размер»: #лягушки опускаются: for i in range(0, len(frogs)): #эта лягушка уже свободна: 209
if (free[i]): continue frogs[i] += DOWN[i] n += 1 print() Запускаем программу и освобождаем лягушек (Рис. 6.20)! Рис. 6.20. Свободу французским лягушкам! Ура-ква-ква! Квак преквасен этот мир! Исходный код программы находится в папке Кордемский 051 06. 210
Проект Гаусс Функция с параметрами Цикл for Задача 5 из книги Математическая шкатулка [Нагибин88], страница 15: Рассказывают, что в начальной школе, где учился мальчик Карл Гаусс, ставший потом знаменитым математиком, учитель, чтобы занять класс на продолжительное время самостоятельной работой, дал детям такое задание - вычислить сумму всех натуральных чисел от 1 до 100. Но маленький Гаусс это задание моментально выполнил. Попробуй и ты быстро выполнить это задание. Нетрудно в этом ряде чисел увидеть арифметическую прогрессию, начинающуюся с единицы и насчитывающую 100 членов. Разность арифметической прогрессии равна 1. Зная все параметры этого ряда, мы быстро найдём его сумму: Сумма = (1 + 100)*100/2 = 5050 # -*- coding: Windows-1251 -*#Нагибин, с.15, Задача 5 #ГЛАВНАЯ ФУНКЦИЯ def main(): print('Гаусс') print() Solve(1,100,1) print() Учитель же, конечно, предполагал, что школяры будут вычислять сумму ряда последовательным прибавлением очередного члена ряда к текущей сумме. 211
Давайте в функции Solve потакнём недалёкому учителю, тем более что компьютер – не Гаусс и быстро, а главное – с удовольствием найдёт сумму именно так. Пусть сумма ряда вначале равна нулю: #РЕШАЕМ ЗАДАЧУ def Solve(start, num, r): #сумма ряда: sum = 0 Текущий член ряда – это сначала первый член прогрессии, который мы назовём start: #текущий член ряда: n = start Если в последовательности num членов, то мы их друг за другом добавляем к сумме: for i in range(0, num): sum += n При этом не забываем вычислять следующий член последовательности, прибавляя к текущему разность арифметической прогрессии: n += r print("Сумма ряда равна %i" % sum) print() В нашей задаче: • • • start = 1 num = 100 r=1 212
Наша глуповатая, но прилежная функция правильно подсчитала сумму заданного ряда (Рис. 6.21). Рис. 6.21. Методический сумматор Зато наша функция получилась универсальной! Подставляя нужные значения в функцию Solve, вы быстро найдёте сумму любой арифметической прогрессии. Например, давайте решим такую задачу. Задача 7.1 из книги Математическая шкатулка [Нагибин88], страница 15: Как быстро вычислить сумму 1 + 3+ 5+7 + 9 + ... + 997 +999? Добавьте в функцию main выделенную строку: Solve(1,100,1) Solve(1,999//2+1,2) И получите ответ на задачу (Рис. 6.22). Рис. 6.22. Гауссиана продолжается! Здесь главное – не ошибиться со значениями параметров функции Solve! • start = 1 213
• • num = 999//2+1 r=2 При вычислении num следует добавить 1, так как при целочисленном делении результат будет округлён отбрасыванием целой части, поэтому число членов окажется на 1 меньше действительного. Задача 838 из книги Математическая шкатулка [Нагибин88], страница 128: Вычислите: 5 + 10+ 15+20 + 25 + ... + 100. И опять одна строка программы решает задачу (Рис. 6.23): Solve(1,100,1) Solve(1,999//2+1,2) Solve(5,100//5,5) Рис. 6.23. Арифметике нас учить не надо! В этой задаче: • • • start = 5 num = 100/5 r=5 Исходный код программы находится в папке Нагибин 005. 214
Проект Плюс-минус Функция без параметров Цикл for Комбинированные операторы присваивания Задача 7-2 из книги Математическая шкатулка [Нагибин88], страница 15: Как быстро вычислить: 99 - 97 + 95 - 93 + 91 - 89 + ... + 7 - 5 + 3 – 1? Конечно, быстро вычислить можно на компьютере! # -*- coding: Windows-1251 -*#Нагибин, с.15, Задача 7-2 #ГЛАВНАЯ ФУНКЦИЯ def main(): print('Плюс-минус') print() Solve() print() По сравнению с обычной арифметической прогрессией (здесь она убывающая) знак членов ряда чередуется: плюс-минус-плюс-минус… Но достаточно ввести переменную sign, чтобы решить и эту задачу (Рис. 6.24): #РЕШАЕМ ЗАДАЧУ def Solve(): #сумма ряда: sum = 0 start = 99 #текущий член ряда: n = start r = -2; num = 99//2+1 #знак алгебраической суммы: sign = 1 215
for i in range(0, num): sum += n*sign n += r #меняем знак: sign *= -1 print("Сумма ряда равна %i" % sum) print() Рис. 6.24. Плюсы и минусы – это наши плюсы! К сожалению, в издании 1988 года отсутствует и ответ, и решение этой задачи, а вот в более раннем издании 1958 года приведено решение – и весьма остроумное! Исходный код программы находится в папке Нагибин 007-2. 216
Проект Минус-плюс Функция без параметров Цикл for Задача 482-8 из книги Математическая шкатулка [Нагибин88], страница 88: Найдите простой приём вычисления: 1002 – 992 + 982 – 972 + 962 – 952 + ... + 42 – 32 + 22 – 12 Здесь мы видим практически тот же ряд, что и в предыдущей задаче, только ещё проще! # -*- coding: Windows-1251 -*#Нагибин, с.88, Задача 482-8 #ГЛАВНАЯ ФУНКЦИЯ def main(): print('Минус-плюс') print() Solve() print() Задача решается без труда, но довольно интересно, что ответ на неё полностью совпадает с тем ответом, который мы получили, решая задачу Гаусса (Рис. 6.25). #РЕШАЕМ ЗАДАЧУ def Solve(): #сумма ряда: sum = 0 #знак алгебраической суммы: sign = 1 for i in range(100, 1-1, -1): sum = sum + i*i*sign #меняем знак: sign *= -1 print("Сумма ряда равна %i" % sum) 217
print() Рис. 6.25. Гаусс вечен! Исходный код программы находится в папке Нагибин 482-8. 218
Проект Дробный ряд Функция без параметров Тип данных float Цикл for Комбинированные операторы присваивания Оператор деления / Задача 9-1 из книги Математическая шкатулка [Нагибин88], страница 16: Найдите простой приём вычислений и воспользуйтесь им для вычисления суммы: # -*- coding: Windows-1251 -*#Нагибин, с.16, Задача 9-1 #ГЛАВНАЯ ФУНКЦИЯ def main(): print('Дробный ряд') print() Solve() print() Этот ряд – почти арифметическая прогрессия: в знаменателе первый сомножитель изменяется от 1 до 9 с приращением 1. Второй сомножитель равен первому, плюс 1. Произведение этих сомножителей, а значит и дробей, мы легко найдём. Осталось найти сумму дробей: #РЕШАЕМ ЗАДАЧУ def Solve(): #сумма ряда: sum = 0.0 #текущий член ряда: n = 1 r = 1 num = 9 219
for i in range(0, num): sum += 1.0/(n*(n+1)) n += r print("Сумма ряда равна %f" % sum) print() Рис. 6.26 показывает, что сумма равна 0,9, и подсказывает нам, что у этой задачи имеется и красивое некомпьютерное решение. Найдите его! Рис. 6.26. Дробовая задача! Исходный код программы находится в папке Нагибин 009-1. 220
Проект Ещё один дробный ряд Функция без параметров Тип данных float Цикл for Комбинированные операторы присваивания Оператор деления / Задача 9-2 из книги Математическая шкатулка [Нагибин88], страница 16: Найдите простой приём вычислений и воспользуйтесь им для вычисления суммы: # -*- coding: Windows-1251 -*#Нагибин, с.16, Задача 9-2 #ГЛАВНАЯ ФУНКЦИЯ def main(): print('Ещё один дробный ряд') print() Solve() print() Задача очень похожа на предыдущую, поэтому решается исправлением двух строк в коде (Рис. 6.27): #РЕШАЕМ ЗАДАЧУ def Solve(): #сумма ряда: sum = 0.0 #текущий член ряда: n = 10 r = 1 num = 90 for i in range(0, num): sum += 1.0/(n*(n+1)) 221
n += r print("Сумма ряда равна %f" % sum) print() Рис. 6.27. Дробим всё! Исходный код программы находится в папке Нагибин 009-2. 222
Проект Трёхзначное число 2 Функция с параметрами Цикл for Вложенные операторы if Оператор деления // Оператор целочисленного деления % Оператор break Задача 597 из книги Математическая шкатулка [Нагибин88], страницы 97-98: Сколько слагаемых суммы 1 + 2 + 3 + 4 + 5 + … надо взять, чтобы получить трёхзначное число, состоящее из одинаковых цифр? # -*- coding: Windows-1251 -*#Нагибин, с.97-98, Задача 597 #ГЛАВНАЯ ФУНКЦИЯ def main(): print('Трёхзначное число') print() Solve(1,100,1) print() Поскольку числовой ряд представляет собой арифметическую прогрессию, сумму которой мы умеем находить, то нам нужно только проследить, когда эта сумма превратится в трёхзначное число с одинаковыми цифрами. Искать заданное число можно в бесконечном цикле while, который следует прервать по исчерпании всех трёхзначных сумм: #РЕШАЕМ ЗАДАЧУ def Solve(start, num, r): #сумма ряда: sum = 0 #текущий член ряда: n = start i = 1 while True: sum += n; if (sum >= 111): 223
#число единиц: e = sum % 10 #число десятков: d = sum // 10 % 10 #число сотен: s = sum // 100 if (e == d and e == s): print("Число слагаемых равно %i" %i) print("Трёхзначное число равно %i" %sum) #трёхзначные числа закончились: if (sum > 999): break n += r i += 1 print() Правда, по условиям задачи, цикл можно закончить сразу, как только будет найдено первое трёхзначное число с одинаковыми цифрами, но мы проверим все трёхзначные суммы – в надежде, что среди них найдутся и другие подходящие. Ан нет: Рис. 6.28 разочаровывает нас – задача имеет единственное решение! Рис. 6.28. Поиски оказались бесплодными Исходный код программы находится в папке Нагибин 597. 224
Проект Вычисляем пи и е Функция без параметров Тип данных float Цикл for Оператор деления / Комбинированные операторы присваивания Метод math.pow Ряды используются не только в занимательных задачах, но и при решении вполне серьёзных проблем. Например, с их помощью можно вычислить одни из самых знаменитых иррациональных чисел – π и е (поскольку они выражаются бесконечной десятичной дробью, то мы сможем найти только приближённое значение). Современное обозначение этого числа греческой буквой пи предложил в 1706 году английский математик У.Джонсон. Он воспользовался первой буквой греческого слова periferia (конечно, в оригинальном написании, а не в более удобном - латинскими буквами), что значит окружность. Но общепризнанным в научном мире этот символ стал после того как в 1736 году Леонард Эйлер использовал его в своих работах. История вычислений числа пи, которое равно отношению длины окружности к её диаметру, началась много тысячелетий назад. Так, египетские математики считали пи равным (16/9)2, то есть примерно равно 3,1604938…, а индийские – √10 = 3,16227766… В третьем веке до нашей эры Архимед установил, что пи меньше, чем 3 1/7 и больше, чем 3 10/71 → 3,1428 … 3,1408. Среднее значение этого диапазона равно 3,14185, что больше пи уже в четвёртом десятичном знаке. Но ещё до Архимеда, в пятом веке до нашей эры китайский математик Цзу Чунчжи нашёл более точное значение - 3,1415927... В первой половине пятнадцатого века алКаши, астроном и математик из Самарканда вычислил пи с точностью до 16 знаков после запятой. В 1615 году голландский математик Лудольф ван Цейлен довёл точность вычислений до 32 знаков. В 1873 году Вильям Шенкс после 20 лет расчётов нашёл 707 знаков числа пи, но в 1944 году Д.Фергюсон с помощью механического калькулятора выяснил, что верны только первые 527 знаков числа Шенкса. Для своих расчётов Шенкс использовал формулу Дж. Мачина: 225
А арктангенс вычислял по формуле Сам Мачин (Рис. 6.29) ещё в 1706 году по этой формуле вычислил 100 знаков числа пи. Рис. 6.29. John Machin (1686? - 1751) С появлением ЭВМ скорость вычислений значительно выросла. В 1949 году электронная машина ЭНИАК за 70 часов работы вычислила более двух тысяч десятичных знаков числа пи. Через некоторое время были найдены 3000 знаков всего за 13 минут. В 1959 году компьютеры преодолели рубеж десяти тысяч знаков, а сейчас известно несколько десятков миллионов знаков числа пи. Чтобы их напечатать, потребуется несколько толстенных книг. Число пи с точностью 500 знаков после запятой: π ≈ 3,141 592 653 589 793 238 462 643 383 279 502 884 197 169 399 375 105 820 974 944 592 307 816 406 286 208 998 628 034 825 342 117 067 982 148 086 513 282 306 647 093 844 609 550 582 231 725 359 408 128 481 117 450 284 102 701 938 521 105 559 644 622 948 954 930 381 964 428 810 975 665 933 446 128 475 648 233 786 783 165 271 201 909 145 648 566 923 460 348 610 454 326 648 213 393 607 260 249 141 273 724 587 006 606 315 588 174 881 520 920 962 829 254 091 715 364 367 892 590 360 011 330 530 548 820 466 521 384 146 951 941 511 609 433 057 270 365 759 591 953 092 186 117 381 932 611 793 105 118 548 074 462 379 962 749 567 351 885 752 724 891 227 938 183 011 949 12… 226
Конечно, все эти знаки не упомнить (да и не надо!), а вот несколько первых знать совсем не помешает. Помню в школьной стенной газете был такой стишок для запоминания первых цифр числа пи: Чтобы нам не ошибаться, Надо правильно прочесть: Три, четырнадцать, пятнадцать, Девяносто два и шесть. Я его запомнил и надеюсь, что и вам это удастся! Формула Валлиса Английский математик Джон Валлис (Рис. 6.30) вывел эту красивую формулу в 1655 году, когда вычислял площадь круга. Она представляет собой бесконечное произведение дробей. Рис. 6.30. John Wallis by Sir Godfrey Kneller (1616 - 1703) 227
К сожалению, чтобы вычислить даже несколько правильных знаков числа пи по этой формуле, нужно затратить немало времени и сил. Однако, имея компьютер, мы можем облегчить себе задачу. Итак, пишем программу: # -*- coding: Windows-1251 -*#Вычисляем пи и е #ГЛАВНАЯ ФУНКЦИЯ def main(): print('Вычисляем пи и е') print() Wallis() print() #ВЫЧИСЛЕНИЕ ПИ ПО ФОРМУЛЕ ВАЛЛИСА def Wallis(): pi=1.0 i = 1.0 for i in range(1, 10000000+1): pi *= 4 * i * i / (2 * i - 1) / (2 * i + 1) print("i = " + str(i) + " pi = " + str(2*pi)) print() После десяти миллионов итераций получаем ответ (Рис. 6.31). Рис. 6.31. Да! Уже седьмой знак после запятой неверный Ряд Лейбница Попробуем зайти с другого конца и воспользуемся знакочередующимся рядом, который предложил немецкий математик Лейбниц (Рис. 6.32). 228
Рис. 6.32. Gottfried Wilhelm von Leibniz (1646 – 1716) А ряд вот такой: Он хорош тем, что его легко приспособить под наши нужды: #ВЫЧИСЛЯЕМ ПИ - Ряд Лейбница #pi/4 = 1 - 1/3 + 1/5 - 1/7 + ... def CalcPi(): pi = 0.0 neg = 1 for i in range(0, 1000000+1): pi += neg/(i*2+1) neg = -neg; print("i = " + str(i) + " pi = " + str(4*pi)) print() После миллиона итераций число пи найдено (Рис. 6.33). 229
Рис. 6.33. К сожалению, результат не лучше первого! Вообще говоря, точность вычислений по этим формулам и не может быть высокой из-за того что тип float грубоват для этих целей. Вычисляем число е Число е –основание натуральных логарифмов - равно: е ≈ 2,7182818284 5904523536 0287471352 6624977572 9574966967 6277240766 3035354759 4571382178 5251664274 2003059921 8174135966 2904357290 0334295260 5956307381 3490763233 8298807531 9525101901 1573834187 9307021540 4167509244 7614606680 8226480016 8477411853 7423454424 7744992069 5517027618 3860626133 1384583000 7520449338 6737113200 7093287091 2744374704 7230696977 2093101416 5515108657 4637721112 5238978442 5056953696 7707854499 4454905987 9316368892 3009879312 7736178215 4249992295 8269895193 6680331825 2886939849 6465105820 9392398294 2509443117 3012381970 6841614039 7019837679 3206832823 5311802328 7825098194 5581530175 6717361332 0698112509 3041690351 5988885193 4580727386 6738589422 8792284998 5749279610 4841984443 6346324496 8487560233 6248270419 2160990235 3043699418 4914631409 3431738143 6405462531 0888707016 7683964243 7814059271 4563549061 3031072085 0115747704 1718986106 8739696552 1267154688 9570350354… 4709369995 2746639193 3232862794 8914993488 3710753907 2656029760 9283681902 6996794686 7635148220 8879332036 7646480429 9618188159 9208680582 7862320900 5209618369 1038375051 Его можно вычислить как сумму бесконечного ряда: e = 1 + 1/1! + 1/2! + 1/3! + ... Очень удобная формула для вычисления на компьютере: знаменатель каждой следующей дроби равен знаменателю предыдущей дроби, умно230
женному на 1, 2, 3, … Из этого следует, что достаточно предыдущую дробь последовательно делить на эти числа и добавлять к общей сумме: #ВЫЧИСЛЯЕМ e #e = 1 + 1/1! + 1/2! + 1/3! + ... def CalcE(): e = 1.0 f = 1.0 i = 1 for i in range(1, 17+1): f /= i e += f print("i = " + str(i) + " e = " + str(e)) print() print("i = " + str(i) + " e = " + str(e)) print() Уже через 27 итераций мы получим 15 правильных десятичных знаков числа е после запятой (Рис. 6.34). В той же школьной стенной газете было и такое прозаическое правило для запоминания первых цифр числа е: 2 – 7 – 1828 – 1828 (год рождения Льва Толстого) – 45 - 90 – 45 (углы прямоугольного равнобедренного треугольника). Вполне достаточно, чтобы очаровать учительницу математики (на учителей математики этот приём, увы, не действует)! Эти же формулы используются в книге [100], страницы 24-26 для нахождения чисел пи и е. 231
Рис. 6.34. Неплохое е! В книге [100] предлагается вычислить пи и по такой формуле: Новый метод легко получить из метода Лейбница: #ВЫЧИСЛЯЕМ ПИ def CalcPi2(): pi = 0.0 for i in range(0, 10000000+1): n = i*2+1 pi += 1/(n*n) print("i = " + str(i) + " pi = " + str(math.sqrt(8*pi))) print() Как и следовало ожидать, точность вычислений невысокая (Рис. 6.35). 232
Рис. 6.35. Тоже пи Другая формула для вычисления пи из этой книги: Здесь мы опять наталкиваемся на ограничение при выборе типа данных (Рис. 6.36): #ВЫЧИСЛЯЕМ ПИ def CalcPi3(): pi = 0.0 neg = 1.0 for i in range(0, 10000000+1): n = i*2+1 pi += neg/n/n/n neg = -neg print("i = " + str(i) + " pi = " + str(math.pow(32*pi, 1.0/3.0))) print() Рис. 6.36. Точности не хватает! 233
В приведённой выше формуле из книги допущена опечатка – степень пи не 2, а 3, поэтому мы должны извлечь кубический корень из суммы ряда. Следующий ряд состоит как бы из двух рядов, сумму которых можно вычислить отдельно, а затем найти пи: В целом ряд напоминает ряд Лейбница, поэтому новую функцию написать не очень сложно (Рис. 6.37): #ВЫЧИСЛЯЕМ ПИ def CalcPi4(): sum1 = 0 sum2 = 0 neg = 1 n5 = 5 n239 = 239 for i in range(0, 100000+1): sum1 += neg*4/n5/(i*2+1) sum2 += neg/n239/(i*2+1) neg = -neg n5 *= 25 n239 = n239*239*239 print("i = " + str(i) + " pi = " + str(4*(sum1-sum2))) print() Рис. 6.37. Повышаем точность Здесь все знаки числа пи (кроме последнего) верные! 234
И наконец, вычислите самостоятельно пи по такой формуле: Поскольку здесь участвуют квадратные корни, то точность вычислений также будет невысокой. Учительница математики спрашивает у Вовочки: - Сколько будет пи в квадрате? Вовочка не растерялся: - Пи-пи! Для вычислений с произвольной точностью имеется в языке Питон специальный модуль decimal, который необходимо подключить к проекту: from decimal import * Давайте с его помощью перепишем функцию CalcPi4: #ВЫЧИСЛЯЕМ ПИ def CalcPi4(): getcontext().prec = 1000 sum1 = Decimal(0) sum2 = Decimal(0) neg = 1 n5 = Decimal(5) n239 = Decimal(239) #for i in range(0, 100000+1): for i in range(0, 1000+1): sum1 += neg*4/n5/(i*2+1) sum2 += neg/n239/(i*2+1) neg = -neg n5 *= 25 n239 = n239*239*239 print("i = " + str(i) + " pi = " + str(4*(sum1-sum2))) print() 235
Как вы видите, прежде всего, нужно задать желаемую точность вычислений: getcontext().prec = 1000 Затем нужно создать все переменные, которые должны иметь высокую точность, как объекты класса Decimal. Сами же вычисления проводятся точно так же, как и раньше. Зато уже после 1000 итераций мы получаем 1000 правильных знаков числа пи (Рис. 6.38). Рис. 6.38. Хорошая точность Аналогично вы можете вычислить и около 2000 десятичных знаков числа е (Рис. 6.39). #ВЫЧИСЛЯЕМ e def CalcE2(): getcontext().prec = 2000 e = Decimal(0) i = 0 while True: fact = math.factorial(i) e += Decimal(1)/fact i += 1 if fact > 10**2000: break print() print("i = " + str(i) + " e = " + str(e)) print() 236
Рис. 6.39. Отличная точность Но вычисления с высокой точностью выполняются значительно медленнее обычных, и это следует учитывать при разработке программ! Исходный код программы находится в папке Вычисляем пи и е. 237
Проект Тригонометрические функции Функция с параметром Оператор return Тип данных float Оператор деления / Константа math.pi Бесконечный цикл while Метод math.sin Метод math.cos Метод math.atan Цикл for Оператор return Условный оператор if Комбинированные операторы присваивания Задача 5⨀⨀ из книги 100 задач по программированию [100], страница 26: Составьте функции для вычисления с указанной точностью значений тригонометрических функций путём сложения членов следующих рядов: В последней формуле |x| < 1. Ряд для вычисления синуса должен смутно напомнить вам ряд для вычисления основания натуральных логарифмов е: 1 + 1/1! + 1/2! + 1/3! + ... 238
А поскольку это так, то вам остаётся только учесть, что ряд знакопеременный, а в числителе x каждый раз увеличивается в х2 раз. Но сначала нужно решить проблему с вводом угла х. Так как пользователю удобнее вводить угол в градусах, а формула предпочитает радианы, то мы должны позаботиться о переводе градусов в радианы. Вспомним, что полной окружности соответствует угол 360˚, или 2π радианов. Чтобы найти, сколько радианов содержится в а градусах, составим несложную пропорцию: 2π радианов = 360˚ x радианов = a˚ Откуда находим: 2π a˚ = 360˚ x → x радианов = a˚* π /180˚ , или x радианов = a˚ /180˚* π Переводим эту формулу на язык Питон и получаем функцию Grad2Rad: def Grad2Rad(a): return a / 180.0 * math.pi В функции main мы вызываем функцию Sin для вычисления заданного угла и сравниваем полученное значение с тем, что выдаёт метод sin класса math: # -*- coding: Windows-1251 -*#Тригонометрические функции import math from decimal import * #ГЛАВНАЯ ФУНКЦИЯ def main(): print('Тригонометрические функции') print() #бесконечный цикл ввода данных 239
#пока пользователь не закроет программу: while True: print("Градусы: ", end='') #угол в градусах: grad = float(input()) #переводим в радианы: rad = Grad2Rad(grad) print("Синус " + str(grad) + " = ", end='') sin = Sin(rad) #sin = Sin(Decimal(rad)) print(str(sin)) print("math.sin = " + str(math.sin(rad))) print() Приведённая в книге формула для вычисления синуса – это ряд Тейлора. Так как числитель дробей постоянно и резко уменьшается, а знаменатель очень быстро растёт, то точности встроенного типа float хватает всего на пару десятков итераций: #ВЫЧИСЛЯЕМ СИНУС ЗАДАННОГО УГЛА def Sin(x): sin = 0.0 xn = x f = 1.0 neg = 1 for i in range(0, 22+1, 2): if (i > 0): f = f /i/(i+1) sin += neg*xn*f neg = -neg xn = xn*x*x return sin Запускаем программу и задаём хорошо известные углы в градусах. Рис. 6.40 убеждает нас, что наша небольшая функция Sin почти не уступает в точности встроенному методу класса math! 240
Рис. 6.40. Отсинусились Переходим к вычислению косинусов: #ГЛАВНАЯ ФУНКЦИЯ def main(): . . . #print("Синус " + str(grad) + " = ", end='') #sin = Sin(rad) ##sin = Sin(Decimal(rad)) #print(str(sin)) #print("math.sin = " + str(math.sin(rad))) #print() print("Косинус " + str(grad) + " = ", end='') cos = Cos(rad) print(str(cos)) print("math.cos = " + str(math.cos(rad))) print() Чтобы получить функцию Cos, нужно в функции для вычисления синуса исправить всего несколько строчек: 241
#ВЫЧИСЛЯЕМ КОСИНУС ЗАДАННОГО УГЛА def Cos(x): cos = 1.0 xn = x*x f = 1.0 neg = -1 for i in range(2, 22+1, 2): f = f /i/(i-1) cos += neg*xn*f neg = -neg xn = xn*x*x return cos Здесь мы иногда наблюдаем расхождение с методом cos класса math в последнем знаке (Рис. 6.41), но в целом и этот метод можно признать успешным! Ряд для вычисления арктангенса заданного угла мало отличается от ряда для синуса, он даже проще: def main(): . . . #print("Косинус " + str(grad) + " = ", end='') #cos = Cos(rad) #print(str(cos)) #print("math.cos = " + str(math.cos(rad))) #print() print("Арктангенс " + str(grad) + " = ", end='') atan = Atan(rad) print(str(atan)) print("math.atan = " + str(math.atan(rad))) print() 242
Рис. 6.41. И откосинусились И мы быстро справляемся с новой функцией: #ВЫЧИСЛЯЕМ АРКТАНГЕНС ЗАДАННОГО УГЛА def Atan(x): atan = 0.0 xn = x neg = 1 for i in range(0, 1000+1, 2): atan += neg*xn/(i+1) neg = -neg xn = xn*x*x return atan В данном ряду знаменатели увеличиваются довольно медленно, поэтому для достижения максимально возможной точности потребуется значительно больше итераций. Также следует учитывать, что ряд правильно работает только для углов, которые меньше 1 радиана (Рис. 6.42)! 243
Рис. 6.42. Вполне точные арктангенсы Исходный код программы находится в папке Тригонометрические функции. 244
Проект Сотая цифра Функция без параметров Цикл while Вложенные циклы while Условный оператор if Оператор break Списки Комбинированные операторы присваивания Задача 300 из книги Математическая шкатулка [Нагибин88], страница 45: Все натуральные числа, начиная с 1, записаны в порядке их возрастания: 1 2 3 4 5 6 7 8 9 10 11… Какая цифра в этой записи стоит на сотом месте? Проще всего решить задачу «строковым» способом: # -*- coding: Windows-1251 -*#Нагибин, с.45, Задача 300 #ГЛАВНАЯ ФУНКЦИЯ def main(): print('Сотая цифра') print() Solve() print() #РЕШАЕМ ЗАДАЧУ def Solve(): s = '' i = 1 while(len(s) < 100): s += str(i) i += 1 print("s = %s" %s) print("На сотом месте стоит цифра: %s" % s[99]) print() 245
Пока в строке меньше 100 знаков, мы добавляем к ней следующее число. Поскольку при этом длина строки может оказаться больше 100 символов, то мы просто интересуемся, какая цифра стоит на сотом месте (её индекс 99). На Рис. 6.43 мы видим, что сотое место в числовой записи занимает цифра 5. Рис. 6.43. Даёшь сотню! Но строковый способ решения задачи не совсем честный, поэтому давайте обойдёмся без строк - только числами! Функция Solve при этом, конечно, станет длиннее и сложнее, но зато всё по-честному! #РЕШАЕМ ЗАДАЧУ def Solve(): num = 1 nd = 0 anum = [] while (len(anum) <= 100): n = num anum.append(n % 10) n //= 10 while (n > 0): if (nd >= 100): break #очередная цифра: anum.insert(len(anum)-1, n % 10) n //= 10 nd += 1 num += 1 if (nd >= 100): break print(anum) print("На сотом месте стоит цифра: %i" % anum[99]) print() 246
Здесь число num увеличивается от 1 и до тех пор, пока число цифр в списке anum не достигнет сотни. Теперь нам нужно самостоятельно выделять из каждого числа его цифры и заносить в список anum. Здесь важно учесть, что цифры мы выделяем с конца числа, а заносить их в список нужно в обратном порядке. Ответ мы получим, естественно, тот же самый, что и раньше (Рис. 6.44). Рис. 6.44. Даёшь сотню! Исходный код программы находится в папке Нагибин 300. 247
Задания для самостоятельного решения Задача #297 Математическая шкатулка Чтобы пронумеровать страницы некоторой книги, понадобилось 1164 цифры. Сколько в этой книге страниц? Ответ: 424 Задача #298 Математическая шкатулка Сколько цифр нужно употребить для нумерации книги, в которой 634 страницы? Ответ: 1794 Формула Бине [ЗП88]. Задача 556 n-ное число Фибоначчи можно вычислить по формуле Бине: В ней буквой фи обозначено золотое сечение: Из неё следует, что Fn равно ближайшему к . целому числу. 248
Так как абсолютная величина выражения меньше единицы, то при больших n числа Фибоначчи можно вычислять по приближённой формуле: Напишите метод, вычисляющий заданное число Фибоначчи по формуле Бине. 249
Глава #7. Диофантовы уравнения и линейное программирование Под диофантовыми понимаются неопределённые уравнения, которые решаются в целых числах (часто – в натуральных). Они названы в честь древнегреческого математика Диофанта, жившего в третьем веке нашей эры (Рис. 7.1). В честь Диофанта назван и кратер на Луне (на рисунке – в правом нижнем углу). Рис. 7.1. Диофант Александриийский Διόφαντος ὁ Ἀλεξανδρεύς, Diophantus 250
В тринадцатитомном труде Арифметика (до нас дошли только 6 первых книг) он показывает, как решать подобные уравнения. В 1974 году была издана книга Арифметика и книга о многоугольных числах, содержащая перевод на русский язык всех трудов Диофанта (Рис. 7.2, слева). В 2007 году книга была переиздана (Рис. 7.2, справа). Рис. 7.2. Диофантовы книги Более подробно о Диофанте вы можете прочитать в книге Якова Перельмана Занимательная математика (Рис. 7.3, слева) и Изабеллы Башмаковой (Рис. 7.3, справа). Рис. 7.3. И книги о Диофанте Впрочем, диофантовы уравнения появились задолго до самого Диофанта. Первое из таких уравнений было известно ещё в Древнем Вавилоне: 251
x2 + y 2 = z 2 Оно было решено пифагорейцами. Второе уравнение: x2 – ay2 = 1 решил в целых числах Евклид. О способах решения диофантовых уравнений можно узнать в книге Башмаковой, а также в Справочном пособии к решению задач: Диофантовы уравнения Дмитрия Базылева (Рис. 7.4). Рис. 7.4. Справочник по диофантовым уравнениям Самая известная занимательная задача, связанная с диофантовыми уравнениями, это, безусловно, Кролики и фазаны. 252
Проект На ферме Функции без параметров Вложенные циклы for Условный оператор if Оператор continue Задача 12 (13) из книги Удивительный мир чисел [КА86], страница 53: На ферме выращивают кроликов и фазанов. В настоящее время их столько, что у всех вместе 740 голов и 1980 ног. Сколько же в настоящее время находится на ферме кроликов и фазанов? # -*- coding: Windows-1251 -*#Кордемский, с.53, Задача 12 #ГЛАВНАЯ ФУНКЦИЯ def main(): print('На ферме') print() Solve() print() Поскольку и число голов, и число ног у кроликов и фазанов выражается целыми числами, то достаточно перебрать все варианты распределения 740 голов по кроликам и фазанам: #РЕШАЕМ ЗАДАЧУ def Solve(): GOLOVY = 740 NOGI = 1980 for kroliki in range(0, GOLOVY+1): for fazany in range(0, GOLOVY+1): if (kroliki + fazany != GOLOVY): continue При этом мы должны учитывать, что у кроликов по 4 ноги, а у фазанов – только по 2: 253
if (kroliki*4 + fazany*2 != NOGI): continue print("Кроликов = %i \nФазанов = %i" % (kroliki, fazany)) print() И вмиг кролики и фазаны пересчитаны и занесены в консольное окно нашей программы (Рис. 7.5). Рис. 7.5. Пересчитали поголовье и поножье Исходный код программы находится в папке Кордемский 053 12. 254
Проект Решите систему уравнений Функция без параметров Вложенные циклы for Условный оператор if Оператор continue Оператор return Задача 7 из книги Удивительный мир чисел [КА86], страница 100: Решите систему уравнений: Далеко неочевидно, но значения переменных x и y должны быть целыми, иначе верхнее равенство вряд ли выполнится. Его лучше записать в другом виде: x + y = 2x # -*- coding: Windows-1251 -*#Кордемский, с.100, Задача 7 import math #ГЛАВНАЯ ФУНКЦИЯ def main(): print("Решите систему уравнений") print() nVar = Solve() print("Найдены все варианты решения - " + str(nVar)) print() 255
Пределы изменения переменных x и y можно взять достаточно большими, но если решение не будет найдено, то верхнюю границу следует ещё увеличить. Больше никаких трудностей в решении задачи не наблюдается, поэтому в двух вложенных циклах for мы перебираем значения переменных x и y и проверяем выполнение условий задачи: #РЕШАЕМ ЗАДАЧУ def Solve(): result = 0 for x in range(0, 100+1): for y in range(0, 100+1): if (x+y != math.pow(2,x)): continue if ((x+y)*math.pow(6,x) != 248832): continue result += 1 print("Вариант # " + str(result)) s = "x = " + str(x) print(s) s = "y = " + str(y) print(s) print() return result На Рис. 7.6 вы видите ответ на задачу. Значения переменных оказались совсем небольшими! Рис. 7.6. Системная задача Исходный код программы находится в папке Кордемский 100 07. 256
Проект Сооружение для лаборатории Функция без параметров Вложенные циклы for Условный оператор if Оператор continue Оператор return Задача 7 из книги Удивительный мир чисел [КА86], страница 64: Для одной лаборатории изготовили конструкцию из двух спаянных пустотелых параллелепипедов (см. рисунок). Длины всех рёбер (в дециметрах) - целые числа. Основания параллелепипедов — квадраты. Высота первого параллелепипеда равна стороне основания второго, а высота второго равна стороне основания первого параллелепипеда. Поместится ли эта конструкция, объём которой 3900 дм3, в лаборатории, если расстояние от пола лаборатории до потолка равно 2 м 75 см? # -*- coding: Windows-1251 -*#Кордемский, с.64, Задача 7 #ГЛАВНАЯ ФУНКЦИЯ def main(): print("Сооружение для лаборатории") print() nVar = Solve() print("Найдены все варианты решения - " + str(nVar)) print() 257
Поскольку x и y – целые числа, то мы можем просто перебрать все их возможные комбинации. Например, можно сразу заключить, что оба эти числа больше нуля. Из уравнения x2*y + x*y2 = 3900 (1) следует, что ни одно из этих чисел не больше корня квадратного из 3900. Точное значение корня нам не нужно, пусть это будет 70. Осталось в двух вложенных циклах for составить все пары чисел x и y и для каждой пары проверить выполнение условия (1): #РЕШАЕМ ЗАДАЧУ def Solve(): result = 0 for x in range(1, 70+1): for y in range(1, 70+1): if (x*x*y + x*y*y != 3900): continue Если оно выполняется, значит, размеры конструкции найдены: result += 1 print("Вариант # " + str(result)) s = "x = " + str(x) print(s) s = "y = " + str(y) print(s) Общая высота сооружения равна x+y. Значение этой суммы мы печатаем на экране: s = "Высота сооружения равна = " + str(x+y) print(s) print() return result 258
Как показывает Рис. 7.7, либо x = 12, y = 13, либо y = 12, x = 13, но в обоих случаях высота конструкции составляет 12 + 13 = 25 дециметров = 2,5 метра, что меньше высоты лаборатории. Значит, конструкция в ней поместится. Рис. 7.7. Конструкторская задача Исходный код программы находится в папке Кордемский 064 07. 259
Проект И такие есть числа 3 Функция без параметров Вложенные циклы for Условный оператор if Оператор continue Функция с параметром Оператор return Оператор целочисленного деления % Оператор and Задача 4-3 из книги Удивительный мир чисел [КА86], страница 63: Два простых числа обладают свойством: если от каждого из них вычесть половину другого, то одна разность будет в 5 раз больше другой. Найдите эти числа. # -*- coding: Windows-1251 -*#Кордемский, с.63, Задача 4-3 #ГЛАВНАЯ ФУНКЦИЯ def main(): print('И такие есть числа') print() Solve() print() Пусть первое число n1 меньше второго числа n2. Тогда n2 как минимум на 1 больше n1, но не более, чем в 2 раза. Первое число мы ищем в бесконечном цикле while, начиная с двойки первого простого числа. Второе число мы ищем в цикле for, начиная со следующего числа и заканчивая числом, вдвое большим, чем первое: #РЕШАЕМ ЗАДАЧУ def Solve(): n1 = 2-1 while True: n1 += 1 260
for n2 in range(n1+2, 2*n1+1): Для каждой пары чисел проверяем условие задачи: if (2*n2 - n1 != 5*(2*n1 - n2)): continue Чтобы избежать дробных чисел, мы оба числа умножили на двойку. Как только оно выполнится, мы печатаем решение на экране и заканчиваем поиски: s = "Первое число = " + str(n1) print(s) s = "Второе число = " + str(n2) print(s) return Обратите внимание, что для решения этой задачи нам даже не потребовалось проверять числа n1 и n2 на простоту (Рис. 7.8). Рис. 7.8. Парочисловая задача решена Но если вы закомментируете строку: #return то найдёте ещё множество пар чисел, которые удовлетворяют условиям задачи: 261
если от каждого из них вычесть половину другого, то одна разность будет в 5 раз больше другой. Но при этом не являются простыми (Рис. 7.9). Рис. 7.9. Непростое решение простой задачи Подкорректируем функцию Solve: #РЕШАЕМ ЗАДАЧУ def Solve(): n1 = 2-1 while True: n1 += 1 if (not IsPrime(n1)): continue for n2 in range(n1+2, 2*n1+1): if (not IsPrime(n2)): continue if (2*n2 - n1 != 5*(2*n1 - n2)): continue s = "Первое число = " + str(n1) print(s) s = "Второе число = " + str(n2) print(s) return 262
Теперь каждое число n1 и n2 мы дополнительно проверяем на простоту с помощью функции IsPrime: #ОПРЕДЕЛЯЕМ ЯВЛЯЕТСЯ ЛИ ЗАДАННОЕ ЧИСЛО #ПРОСТЫМ def IsPrime(num): if (num < 2): return False if (num > 2 and num % 2 == 0): return False end = round(math.sqrt(num)) for i in range (3, end+1, 2): if (num % i == 0): return False return True Ответ на задачу мы получим тот же самый, но функция Solve теперь «правильная». Исходный код программы находится в папке Кордемский 063 04-3. 263
Проект И такие есть числа 4 Функция без параметров Вложенные циклы for Условный оператор if Оператор continue Оператор or Задача 4-4 из книги Удивительный мир чисел [КА86], страница 63: Сумма первого числа и квадрата второго равна 57, а сумма второго числа и квадрата первого равна 71. Найдите эти числа. # -*- coding: Windows-1251 -*#Кордемский, с.63, Задача 4-3 #ГЛАВНАЯ ФУНКЦИЯ def main(): print('И такие есть числа') print() Solve() print() Из условия задачи следует, что оба числа не меньше 1 и не больше 9. Этого вполне достаточно, чтобы во вложенных циклах for проверить все возможные пары чисел: #РЕШАЕМ ЗАДАЧУ def Solve(): for n1 in range(1, 9+1): for n2 in range(1, 9+1): if (n1 + n2*n2 != 57 or n1*n1 + n2 != 71): continue s = "Первое число = " + str(n1) print(s) s = "Второе число = " + str(n2) print(s) 264
return Перебор оказался очень небольшим, поэтому ответ получаем практически мгновенно (Рис. 7.10). Рис. 7.10. И такие числа мы нашли Исходный код программы находится в папке Кордемский 063 04-4. 265
Проект Ящики Функция без параметров Вложенные циклы for Условный оператор if Оператор continue Форматированный вывод Задача 424 из книги Математическая шкатулка [Нагибин88], страница 81: Завод должен переслать заказчику 1100 деталей, которые для пересылки упаковываются в ящики трёх типов. Один ящик первого типа вмещает 70 деталей, второго типа – 40 деталей, третьего типа – 25 деталей. Стоимость пересылки одного ящика первого, второго и третьего типов равна соответственно 20, 10 и 7 р. Какие ящики должен использовать завод, чтобы стоимость пересылки бала наименьшей? Недогрузка ящиков не допускается. Эта типичная задача линейного (точнее - целочисленного) программирования может быть успешно решена простым перебором вариантов. Примеры задач целочисленного программирования: • • • • задача о назначениях задача коммивояжера задача почтальона задача о максимальном паросочетании # -*- coding: Windows-1251 -*#Нагибин, с.81, Задача 424 #ГЛАВНАЯ ФУНКЦИЯ def main(): print('Ящики') print() Solve() print() 266
Обозначим через k1, k2 и k3 число ящиков каждого типа. Минимальное число этих ящиков равно 0. Верхнюю границу легко определить, поделив общее число деталей на вместимость каждого ящика: #РЕШАЕМ ЗАДАЧУ def Solve(): DETALI = 1100 K1 = DETALI//70 K2 = DETALI//40 K3 = DETALI//25 Для хранения текущего значения минимальной стоимости пересылки мы заведём переменную minCost, которой сначала необходимо присвоить какое-либо большое значение: #мин. стоимость пересылки: minCost = 1000000 Во вложенных циклах for мы перебираем все варианты «расфасовки» деталей по ящикам, учитывая, что в сумме в них должно оказаться 1100 деталей: for k1 in range(0, K1+1): for k2 in range(0, K2+1): for k3 in range(0, K3+1): if (k1*70 + k2*40 + k3*25 != DETALI): continue Для каждой удачной комбинации ящиков мы находим стоимость пересылки и, если она окажется меньше текущего значения переменной minCost, то печатаем текущую рекордную упаковку: #стоимость пересылки: cost = k1*20 + k2*10 + k3*7 if (minCost >= cost): minCost = cost print("k1 = %i k2 = %i k3 = %i" %(k1, k2, k3)) print("Минимальная стоимость = %i" % minCost) print() 267
Если разные расфасовки дадут одинаковую стоимость, мы напечатаем все, чтобы узнать общее число решений задачи. Поскольку результаты нашей упаковочной деятельности не ухудшаются по ходу работы программы, то минимальная стоимость окажется в самом низу списка, который вы видите на Рис. 7.11. Итак, решение задачи единственное: Детали нужно упаковать в 25 ящиков второго типа и в 4 ящика третьего типа. При этом минимальная стоимость пересылки составит 278 рублей. Рис. 7.11. Экономная упаковка Исходный код программы находится в папке Нагибин 424. 268
Проект Путёвки Функция без параметров Вложенные циклы for Условный оператор if Оператор continue Форматированный вывод Задача 425 из книги Математическая шкатулка [Нагибин88], страница 81: Предполагается использовать 2000 р. на путёвки в дома отдыха. Путёвки имеются на 15, 27 и 45 дней, стоимость их соответственно 21, 40 и 60 р. Сколько и каких путёвок нужно купить, чтобы общее число дней отдыха было наибольшим? Эту задачу можно решить за пару минут, просто слегка изменив предыдущий проект: # -*- coding: Windows-1251 -*#Нагибин, с.81, Задача 425 #ГЛАВНАЯ ФУНКЦИЯ def main(): print('Путёвки') print() Solve() print() #РЕШАЕМ ЗАДАЧУ def Solve(): SUMMA = 2000 K1 = SUMMA//21 K2 = SUMMA//40 K3 = SUMMA//60 #макс. число дней: maxDays = 0 for k1 in range(0, K1+1): for k2 in range(0, K2+1): for k3 in range(0, K3+1): 269
if (k1*21 + k2*40 + k3*60 != SUMMA): continue #число дней отдыха: days = k1*15 + k2*27 + k3*45 if (maxDays <= days): maxDays = days print("k1 = %i k2 = %i k3 = %i" %(k1, k2, k3)) print("Максимальное число дней = %i" % maxDays) print() if __name__ == "__main__": main() Замечательно, что, решив одну задачу, потом можно использовать её код как заготовку при решении множества других подобных задач! Наша программа советует не покупать путёвки на 15 дней, а сэкономленные деньги потратить на приобретение двух 27- и тридцати двух 45дневных путёвок. Они позволят трудящимся заслуженно отдыхать 1494 дня (Рис. 7.12). Рис. 7.12. Отдыхай с умом! В книге приведён другой ответ. Вероятно, автор не учёл, что можно вообще не покупать путёвки какого-либо вида. Чтобы проверить эту догадку, закомментированную строку замените другой: #if (maxDays <= days): if (maxDays <= days and k1*k2*k3 != 0): Теперь нулевые значения числа путёвок будут проигнорированы, и программа выдаст «книжные» результаты (Рис. 7.13). 270
Рис. 7.13. Наши люди отдыхают дольше! У автора задачи получилось на 15 дней меньше, чем у нас. Да, без компьютера толком не отдохнёшь! Исходный код программы находится в папке Нагибин 425. 271
Проект На базаре Функция без параметров Вложенные циклы for Условный оператор if Оператор continue Форматированный вывод Управляющие последовательности Задача 7 (2.3) из книги Удивительный мир чисел [КА86], страница 51: На базаре за 9 кг орехов и 2 кг фейхоа заплатили столько же денег, сколько за 6 кг гранатов. А за 6 кг орехов, 5 кг фейхоа и 4 кг гранатов заплатили 43 р. Сколько стоит 1 кг орехов, фейхоа и гранатов отдельно, если известно, что стоимость каждого продукта выражается целым числом рублей? # -*- coding: Windows-1251 -*#Кордемский, с.51, Задача 7 #ГЛАВНАЯ ФУНКЦИЯ def main(): print('На базаре') print() Solve() print() Эту базарную задачу легко решить по аналогии с упаковочной - достаточно учесть её фруктовый контекст: #РЕШАЕМ ЗАДАЧУ def Solve(): COST = 43 OREHI = COST//6 FEIHOA = COST//5 GRANAT = COST//4 for orehi in range(0, OREHI+1): for feihoa in range(0, FEIHOA+1): for granat in range(0, GRANAT+1): 272
if (orehi*6 + feihoa*5 + granat*4 != COST): continue if (orehi*9 + feihoa*2 != granat*6): continue print("Орехи = %i \nФейхоа = %i \nГранаты = %i" %(orehi, feihoa, granat)) print() Теперь вы можете смело отправляться на рынок 1986 года – вас никто не обманет (Рис. 7.14)! Рис. 7.14. Деньги любят счёт в банке Исходный код программы находится в папке Кордемский 051 07. 273
Проект Дедушка и внучка Функция без параметров Цикл while Форматированный вывод Оператор деления // Задача 10 (10.1) из книги Удивительный мир чисел [КА86], страница 52: Сколько дедушке лет, столько месяцев внучке. Дедушке с внучкой вместе 91 год. Сколько лет дедушке и сколько внучке? # -*- coding: Windows-1251 -*#Кордемский, с.52, Задача 10 #ГЛАВНАЯ ФУНКЦИЯ def main(): print('Дедушка и внучка') print() Solve() print() Самое сложное в этой задаче – сформулировать проверку в операторе while: #РЕШАЕМ ЗАДАЧУ def Solve(): #возраст внучки в месяцах: x = 0 while (x + 12*x != 91*12): x += 1 print("Возраст внучки в годах = %i" % (x // 12)) print("Возраст дедушки в годах = %i" % (91 - x // 12)) print() 274
Обозначив через х возраст внучки в месяцах, мы найдём возраст дедушки в годах – тоже х, а в месяцах – 12х. Дальше задача решается – как сыр по маслу (Рис. 7.15). Рис. 7.15. Внучка совсем ещё зелёная Исходный код программы находится в папке Кордемский 052 10. 275
Проект Сколько у мамы дочерей и сыновей? Функция без параметров Вложенные циклы for Условный оператор if Оператор continue Оператор деления // Форматированный вывод Управляющие последовательности Это и есть фейхоа! Задача 15 (4) из книги Удивительный мир чисел [КА86], страница 55: В семье 12 детей. Мама принесла для них 70 штук фейхоа. Половину всех фейхоа она раздала дочерям поровну, остальные отдала сыновьям, которые разделили их между собой также поровну. Каждый мальчик получил на 2 фейхоа больше, чем каждая девочка. Сколько было у этой мамы дочерей и сыновей? Оne day англичанин и шотландец нашли клад и решили его поделить. - Давай поделим его по-честному, - предложил англичанин. - Нет, давай лучше поровну! – возразил шотландец. С точки зрения математики, мальчики и девочки ничем не отличаются от кроликов и фазанов, поэтому эту задачу мы решаем так же, как и про животных на ферме: # -*- coding: Windows-1251 -*#Кордемский, с.55, Задача 15 #ГЛАВНАЯ ФУНКЦИЯ def main(): print('Сколько у мамы дочерей и сыновей?') print() Solve() print() #РЕШАЕМ ЗАДАЧУ def Solve(): 276
#число фейхоа: FEIJOA = 70 #число детей: CHILDREN = 12 #число мальчиков в семье: m = 0 #число девочек в семье: d = 0 for m in range(1, CHILDREN+1): for d in range(1, CHILDREN+1): if (m + d != CHILDREN): continue Единственная закавыка этой задачи - в написании вот этой проверки: if (FEIJOA//2//m - FEIJOA//2//d != 2): continue Впрочем, при внимательном чтении условия задачи легко сообразить, что девочки и мальчики получили странных фруктов фейхоа поровну, то есть по FEIJOA//2 штук. Всё остальное – дело техники, то есть компьютера. print("Мальчиков = %i print() \nДевочек = %i" %(m, d)) А на Рис. 7.16 представлены плоды раздела плодов и полов в семье плодовитой мамы. Рис. 7.16. Честный делёж Исходный код программы находится в папке Кордемский 055 15. 277
Проект Из жизни Дефурнеля Функция без параметров Цикл while Комбинированные операторы присваивания Оператор and Задача 24 (26) из книги Удивительный мир чисел [КА86], страница 57: Некогда в отрывном календаре были приведены любопытные факты из биографии француза Пьера Дефурнеля. Он был отцом трёх сыновей, родившихся в разных веках. Он сам и старший сын родились в XVII веке. Женился он на девятнадцатом году жизни. Через год стал отцом первого сына. Спустя 37 лет женился второй раз. Через год стал отцом второго сына. А ещё через 43 года родилась его будущая третья жена. Через 19 лет после этого они поженились. Через год родился третий сын. Спустя 8 лет Пьер Дефурнель умер. Установите годы рождения Пьера Дефурнеля, сыновей, третьей жены и годы, когда женился Дефурнель и когда он умер? # -*- coding: Windows-1251 -*#Кордемский, с.57, Задача 24 #ГЛАВНАЯ ФУНКЦИЯ def main(): print('Из жизни Дефурнеляа') print() Solve() print() Поскольку XVII век начинается с 1601 года, то мы можем в цикле while начать перебор с предыдущего года и добавлять по 1 к текущему числу year – году рождения Дефурнеля: #РЕШАЕМ ЗАДАЧУ def Solve(): #год рождения Дефурнеля: year = 1600 #годы рождения сыновей: 278
son1 = 0 son2 = 0 son3 = 0 flg = False while (not flg): year += 1 И так мы продолжаем итерации до тех пор, пока не выполнятся все условия задачи касательно года рождения каждого из трёх его сыновей. Все условия легко формируются при внимательном чтении условия задачи: #год рождения первого сына: son1 = year + 19 flg = son1 < 1701 #год рождения второго сына: son2 = son1 + 38 flg &= son2 >= 1701 and son2 < 1801 #год рождения третьего сына: son3 = son2 + 63 flg &= son3 >= 1801 and son3 < 1901 Когда год рождения Дефурнеля будет установлен, мы печатаем полезную и занимательную статистическую информацию на экране: print("Год рождения Дефурнеля: = %i" % year) print("Год рождения первого сына: = %i" % son1) print("Год рождения второго сына: = %i" % son2) print("Год рождения третьего сына: = %i" % son3) print("Год рождения третьей жены: = %i" %(son2 + 43)) print("Год первой женитьбы: = %i" % (son1-1)) print("Год второй женитьбы: = %i" %(son2-1)) print("Год третьей женитьбы: = %i" %(son3-1)) print("Год смерти Дефурнеля: = %i" %(son3 + 8)) print("Дефурнель прожил: %i" %(son3 + 8 - year)) print() Она представлена в полном объёме на Рис. 7.17. 279
Рис. 7.17. Компьютерный ЗАГС в действии Исходный код программы находится в папке Кордемский 057 24. 280
Проект Счётные палочки Функция без параметров Вложенные циклы for Условный оператор if Оператор continue Форматированный вывод Управляющие последовательности Задача 594 из книги Математическая шкатулка [Нагибин88], страница 97: Из 36 счётных палочек построили треугольники, квадраты и домики (Рис. 7.18) – всего 10 фигур. Рис. 7.18. Палочные фигуры Найдите число фигур каждого вида. # -*- coding: Windows-1251 -*#Нагибин, с.97, Задача 594 #ГЛАВНАЯ ФУНКЦИЯ def main(): print('Счётные палочки') print() Solve() print() Ясно, что из 36 палочек можно построить от 0 до 36//3 треугольников, от 0 до 36//4 квадратов и от 0 до 36//6 домиков. Все варианты строительства мы, как обычно, перебираем во вложенных циклах for: 281
#РЕШАЕМ ЗАДАЧУ def Solve(): PALOCHKI = 36 FIGUR = 10 T = PALOCHKI//3 K = PALOCHKI//4 D = PALOCHKI//6 for t in range(0, T+1): for k in range(0, K+1): for d in range(0, D+1): if (t + k + d != FIGUR): continue if (t*3 + k*4 + d*6 != PALOCHKI): continue print("Треугольники = %i \nКвадраты = %i \nДомики = %i" %(t, d, k)) print() Рис. 7.19 уверенно демонстрирует нам 3 решения задачи, хотя в книге указано только второе. Рис. 7.19. Строительство закончено Мы опять можем предположить, что следовало построить все фигуры хотя бы по одному разу, хотя в книге это требование отсутствует. Исходный код программы находится в папке Нагибин 594. 282
Задания для самостоятельного решения Задача 11, страница 52 Удивительный мир чисел Овчарка погналась за лисой, когда между ними было расстояние 99 м. Скачок лисы 1,1 м, скачок овчарки 2,2 м. Когда овчарка делает 19 скачков, лиса делает 29 скачков. Сколько метров проскачут они, пока овчарка догонит лису? 283
Глава #8. Компьютерные игры Типичная современная компьютерная игра – жуть и гадость! Несмотря на некоторую легковесность названия, программирование компьютерных игр – задача совсем непростая! И даже игры без графики, которыми мы будем здесь заниматься, потребуют от вас недюжинного ума и мощных усилий! Проект Игра Охота на Скалоеда Бесконечный цикл while Условный оператор if Оператор break Цикл for Вложенные циклы for Класс random Операторы or и and Оператор return Форматированный вывод Условный оператор if-else Вложенные операторы if-else Бесконечный цикл while «Компьютер» докомпьютерной эры В компьютерные игры сражались уже тогда, когда даже самые простые компьютеры были недоступны большинству граждан тогдашнего Советского Союза. Их место занимали программируемые калькуляторы (ПМК): БЗ-34, МК-61, МК-52. В журнале Наука и жизнь регулярно публиковались статьи о программировании, а в журнале Техника – молодёжи несколько лет существо284
вал Клуб электронных игр (Рис. 8.1), где читатели делились своими программами. Рис. 8.1. Эмблема рубрики В первом номере журнала за 1987 год я нашёл интересную игру Охота на Скалоеда, которую мы вполне можем «портировать» на современные компьютеры. Где-то в глубоком космосе, на неведомой планете жило-было-не-тужило суровое существо – Скалоед. Скалоед непрерывно буровит землю и гранит, выгрызая в них подземные ходы, что роднит его со Студентами, которые тоже постоянно чего-нибудь грызут. По этим ходам Скалоеда может преследовать отважный Охотник - с вполне естественной целью – уничтожить Скалоеда. Поле битвы, или - по-научному – Лабиринт можно представить в виде таблицы (Рис. 8.2). Нумерация горизонталей противоречит нашим представлениям о верхе и низе, поэтому мы будем нумеровать их сверху вниз и начиная с нуля. Естественно, сам чужеземный мир от этого не перевернётся, а продолжит своё бренное существование в полном объёме. 285
Рис. 8.2. План Лабиринта В начале охоты Скалоед занимает место в середине Лабиринта. На Рис. 8.2⬆ его клетка отмечена штриховкой. Охотник затаился с подветренной стороны в нижнем левом углу, который у нас неминуемо превратится в верхний левый – с координатами (0,0). На Рис. 8.2⬆ Охотник никак не отмечен, но память о нём навсегда останется в наших сердцах, душах и извилинах. Клетки Лабиринта могут быть двух видов: • • Проходимые (свободные ходы) – обозначены единицами Непроходимые (скальная порода) – обозначены нулями Начальная позиция Охотника должна находиться в проходимой клетке, и двигаться Охотник может только по проходимым клеткам. А вот Скалоед может перемещаться по любым клеткам. При этом он, выгрызая в скальной породе ходы, делает непроходимые клетки проходимыми. Но, наткнувшись на проходимую клетку, он заваливает её скальной породой и превращает в непроходимую. Начальная позиция игры может отличаться от той, что показана на Рис. 8.2⬆, но Охотник обязательно должен занимать проходимую клетку. 286
Ходы Охотник и Скалоед выполняют поочерёдно: сначала ходит Охотник, затем – Скалоед. Он может переместиться в соседнюю свободную клетку слева, справа, сверху или снизу. Скалоед также может переместиться на соседнюю клетку по этим же направлениям, но он не ограничен в выборе клеток, поскольку для него они все – проходимые. К сожалению, Скалоед ограничен в другом – он сильно обделён интеллектом, поэтому бродит по Лабиринту совершенно случайно, целиком полагаясь на свою планиду, то есть на судьбу. Если в соседней клетке паче чаяния окажется Скалоед, то Охотник торжественно вступает в эту клетку, наносит сопернику удар в темя и отшибает оба рога, из чего можно сделать поспешный вывод, что Охотника зовут Шурик, а Скалоед живёт вовсе не на неведомой планете, а на Кавказе. Шурик записывает тосты по случаю победы Журнальная статья умалчивает о драматической развязке истории, когда Скалоед первым оказывается в клетке с незадачливым Охотником и пожирает его глазами. Мы исправим эту оплошность, чтобы усимметрить и гармонизировать взаимоотношения особей и сущностей в природе. Если Скалоед опережает Охотника в борьбе за жизненное пространство, то мы будем вынуждены признать торжество случая над трезвым расчётом Охотника. 287
Охота начинается! Из этой долгой истории следует, что, в первую очередь, нам потребуется Лабиринт, в коем и разворачиваются эти самые исторические события. Поскольку Лабиринт не простой, а с клетками двух видов – проходимыми и непроходимыми, то вполне и очень правильно использовать для него символьный список списков: #лабиринт: Labyrinth = [[символ]*Size for i in range(Size)] Хотя в оригинальной программе клетки обозначены числами, но символы покажут нам состояние клеток более понятно, чем малоосмысленные числа – нуль и единица: #непроходимые и прoходимые клетки: SKALA_SYM = ':' HOD_SYM = '.' Мы не можем ожидать, что прямоугольные лабиринты окажутся занимательнее квадратных, поэтому будем считать, что наш Лабиринт имеет квадратную форму заданного размера. Если места для инопланетного сафари окажется мало, то игрок-Охотник, сможет самопроизвольно выбрать размеры Лабиринта из ограниченного разумным смыслом диапазона: #мин. макс. размеры лабиринта: MIN_SIZE = 8 MAX_SIZE = 30 288
В функции main жаждущий приключения Охотник создаёт игру желаемого размера и запускает игровой цикл: # -*- coding: Windows-1251 -*#Игра Охота на Скалоеда #Журнал Техника - моложёжи, 1987 гол, №1, сс.50-51 #ГЛАВНАЯ ФУНКЦИЯ def main(): print('Охота на Cкалоеда') print() #создаём игру: game = Game(9) #начинаем игровой цикл: game.Play() Как вы видите, здесь почти ничего нового для вас нет, поэтому переходим в конструктор класса игры, который принимает размер Лабиринта: #КЛАСС ИГРЫ class Game(): #КОНСТРУКТОР def __init__(self, Size): #контролируем ввод пользователя: if (Size >= MIN_SIZE and Size <= MAX_SIZE): self.Size = Size else: self.Size = MIN_SIZE #создаём лабиринт: self.Labyrinth = [[HOD_SYM]*self.Size for i in range(self.Size)] Счёт по партиям мы будем хранить в списке Result: #счёт игры: Result = [0]*2 Result[OHOTNIK] = 0 Result[SKALOED] = 0 print("Счёт Охотник: %i Скалоед: %i" %(0,0)) 289
Как обычно, мы скрупулёзно контролируем действия пользователя, который в предстартовой горячке может задать несуразные размеры игрового поля! Игровой цикл начинается с подготовки к новой игре в методе NewGame, а затем Охотник и Скалоед выполняют свои ходы, преисполненные охотничьего азарта и вдохновения: #НОВАЯ ИГРА def NewGame(self): #готовимся к новой игре: self.Prepare() print() print("Новая игра") print() #ИГРОВОЙ ЦИКЛ def Play(self): while True: self.NewGame() while True: #ход Охотника: self.PrintPosition() #return self.OhotnikMove() if (self.isGameOver(OHOTNIK)): break #ход Скалоеда: self.PrintPosition() self.SkaloedMove() if (self.isGameOver(SKALOED)): break Сколько Лабиринту ни виться, а конец у игры будет: либо Охотник завалит Скалоеда, либо сам попадёт в зубы более удачливому сопернику. Поскольку игра идёт понарошку, то Охотник может – даже будучи съеденным без остатка - продолжить свои похождения и всё-таки добыть р(б)огатые трофеи: #игра окончена - следующая партия? print() print("Играем дальше? (1 - да) > ", end ='') 290
s = input() if (s != "1"): break print() Подготовка к игре должна быть весьма основательной, поэтому её нужно вынести в отдельный метод Prepare. По заданным размерам мы создаём лабиринт. Если вы взглянете на оригинальную картинку из журнала Техника – молодёжи, то увидите, что большую часть его занимают свободные проходы, а скальная порода образует аккуратные горизонтальные полосы. Именно расположение и длина этих полос будут разнообразить игру. Сначала мы заполняем Лабиринт свободными проходами: #ГОТОВИМСЯ К ИГРЕ def Prepare(self): #создаём лабиринт: Labyrinth = [[HOD_SYM]*self.Size for i in range(self.Size)] Затем в каждой горизонтали заваливаем скальной породой несколько последовательно расположенных клеток, начинающихся в клетке с горизонтальной координатой begin и заканчивающихся в клетке end: #скальная порода: for j in range(0, self.Size): #начало: begin = random.randint(0, self.Size-1) #конец: end = random.randint(begin, self.Size-1) for i in range(begin, end+1): self.Labyrinth[i][j] = SKALA_SYM Так как эти координаты мы задаём случайно, то массив скальной породы может занимать от 0 до Size клеток в каждой горизонтали. Для генерации случайных чисел мы используем модуль random: import random 291
Координаты наших героев удобно хранить в экземплярах класса Coords: #координаты: class Coords(): #КОНСТРУКТОР def __init__(self, x , y): self.X = x self.Y = y #координаты Скалоеда: SkaloedCoods = Coords(0,0) #координаты Охотника: OhotnikCoords = Coords(0,0) Согласно классическим правилам, Скалоед изначально занимает центральную клетку Лабиринта: #координаты Скалоеда: x = self.Size//2 y = self.Size//2 SkaloedCoods.X = x SkaloedCoods.Y = y А Охотник – верхнюю левую. Причём эта клетка должна быть проходимой: #координаты Охотника: x = 0 y = 0 OhotnikCoords.X = x OhotnikCoords.Y = y self.Labyrinth[x][y] = HOD_SYM Для большего удобства и взаимопонимания мы обозначили Охотника и Скалоеда переменными: #игроки: OHOTNIK = 0 SKALOED = 1 292
То же самое относится и к символам, которые показывают положение Охотника и Скалоеда на игровом поле: OHOTNIK_SYM = 'O' SKALOED_SYM = 'C' Распечатать текущую позицию нам не составит труда – достаточно пройтись по всем клеткам Лабиринта и напечатать соответствующий символ – свободного прохода или скальной породы. Также мы должны помнить и о наших героях: #ПЕЧАТАЕМ ТЕКУЩУЮ ПОЗИЦИЮ def PrintPosition(self): #лабиринт: for y in range(0, self.Size): for x in range(0, self.Size): if (x == SkaloedCoods.X and y == SkaloedCoods.Y): print(SKALOED_SYM, end='') elif (x == OhotnikCoords.X and y == OhotnikCoords.Y): print(OHOTNIK_SYM, end='') else: print(self.Labyrinth[x][y], end='') print() print() На Рис. 8.3, слева хорошо видно, что скальная порода образует непрерывные полосы, которые иногда могут полностью блокировать Охотника в углу Лабиринта (Рис. 8.3, справа). Рис. 8.3. Скальная порода – непроходимые клетки 293
Поэтому в методе Prepare лучше оставить верхнюю горизонталь свободной для прохода: #скальная порода: for j in range(1, self.Size): Теперь в игровом цикле пришла очередь Охотнику сделать свой первый ход. Он, конечно, волен выбрать любое направление, которое согласуется с правилами игры. Однако в спешке он может стать на неверный путь или нажать неправильную клавишу, поэтому метод ввода хода игрока не столь прост, как это может показаться с первого взгляда. В первую очередь, мы обозначим направление ходов буквами: • • • • L(eft) - влево R(ight) - вправо U(p) - верх D(own) – вниз Возможен ли тот или иной ход, мы проверяем в методе TestOhotnik. Здесь мы вычисляем координаты той клетки, в которую Охотник желает перейти: #ПРОВЕРЯЕМ ХОД ОХОТНИКА def TestOhotnik(self, dir): if (dir == 'L'): #новые координаты Охотника: x = OhotnikCoords.X - 1 y = OhotnikCoords.Y if (dir == 'R'): x = OhotnikCoords.X + 1 y = OhotnikCoords.Y if (dir == 'U'): x = OhotnikCoords.X y = OhotnikCoords.Y - 1 if (dir == 'D'): x = OhotnikCoords.X y = OhotnikCoords.Y + 1 294
Если она выходит за рамки дозволенного, то гипотетический ход невозможен, и метод возвращает Falsе: #выход за граница лабиринта: if (x < 0 or x >= self.Size or y < 0 or y >= self.Size): return False Ход допускается только в клетку со Скалоедом и в проходимую клетку: #клетка со Скалоедом: if (x == SkaloedCoods.X and y == SkaloedCoods.Y): return True #свободный ход: if (self.Labyrinth[x][y] == HOD_SYM): return True #хода нет: return False Метод OhotnikMove получает от метода TestOhotnik заключение по каждому из четырёх возможных направлений и формирует строку smoves, которую мы используем и для подсказки, и для проверки действий игрока: #ХОД ОХОТНИКА def OhotnikMove(self): #допустимые ходы: smoves = "" if (self.TestOhotnik('L')): smoves += "L " if (self.TestOhotnik('R')): smoves += "R " if (self.TestOhotnik('U')): smoves += "U " if (self.TestOhotnik('D')): smoves += "D " smoves = smoves.strip() Но если строка smoves так и осталась пустой, значит, Охотник временно замурован и вынужден ждать раскупорки со стороны Скалоеда: #некуда ходить: if (smoves == ""): 295
return Если у Охотника имеются ходы, то программа подсказывает ему допустимые направления (Рис. 8.4): #есть ходы: move = "" while True: print("Ваш ход [%s]> " %smoves, end='') Рис. 8.4. Подсказка для Охотника По крайней мере, на первом ходу у него всегда есть ход вправо и, возможно, вниз. Игрок должен нажать одну из предложенных ему букв (в любом регистре). Если же он попытается ввести компьютер в заблуждение, то метод OhotnikMove, не обнаружив его буквы в строке smoves, вежливо, но настойчиво попросит его скорректировать свой ход: #ход игрока: move = input().upper() #неверный ход: if (not move in smoves): print("Повторите Ваш ход!\r\n") else: break 296
Практика охоты показывает, что Охотник должен иметь возможность пропустить ход, просто нажав клавишу ВВОД: #пропускаем ход: if (move == ""): return С другой стороны, подумайте, как наказывать Охотника за такое бездействие на протяжении нескольких ходов! Так он может подкараулить Скалоеда, спрятавшись за соседним камешком. Если же игрок нажмёт верную буквенную клавишу, а затем и клавишу ВВОД, чтобы перейти в соседнюю клетку, то мы вычисляем его новые координаты: #новые координаты Охотника: dir = move[0] if (dir == 'L'): OhotnikCoords.X -= 1 if (dir == 'R'): OhotnikCoords.X += 1 if (dir == 'U'): OhotnikCoords.Y -= 1 if (dir == 'D'): OhotnikCoords.Y += 1 Ход игрока может быть тактическим, при котором он просто перемещается на новое место в Лабиринте, но может быть и победным – если Охотник попадает в клетку со Скалоедом, который тут же отбрасывает рога и копыта от неожиданности: #ПРОВЕРЯЕМ, НЕ ЗАКОНЧЕНА ЛИ ИГРА def isGameOver(self, player): #победил Охотник? if (player == OHOTNIK): #клетка со Скалоедом: if (OhotnikCoords.X == SkaloedCoods.X and OhotnikCoords.Y == SkaloedCoods.Y): return self.GameOver(OHOTNIK) 297
Контора по приёму рогов и копыт Скалоеда и её дружный коллектив Немного забегая вперёд по тропе войны, мы проверяем и ход Скалоеда, который также одерживает победу, если огорошивает игрока своим стремительным вторжением в его клетку: #победил Скалоед? else: if (OhotnikCoords.X == SkaloedCoods.X and OhotnikCoords.Y == SkaloedCoods.Y): return self.GameOver(SKALOED) Во всех остальных случаях игра продолжается дальше: #игра продолжается: return False И тогда Скалоед наносит ответный удар, то есть делает ход конём. Поскольку ума у него нет, то ходит он наудачу. Это значит, что мы должны составить для него список возможных ходов, из которых он милостиво выберет один. Для этого мы пишем метод TestSkaloed, аналогичный методу TestOhotnik, но учитываем, что Скалоед может перейти в любую соседнюю клетку – в пределах Лабиринта: def TestSkaloed(self, dir): if (dir == 'L'): #новые координаты Скалоеда: x = SkaloedCoods.X - 1 y = SkaloedCoods.Y 298
if (dir x = y = if (dir x = y = if (dir x = y = == 'R'): SkaloedCoods.X + 1 SkaloedCoods.Y == 'U'): SkaloedCoods.X SkaloedCoods.Y - 1 == 'D'): SkaloedCoods.X SkaloedCoods.Y + 1 #выход за граница лабиринта: if (x < 0 or x >= self.Size or y < 0 or y >= self.Size): return False return True #ХОД СКАЛОЕДА def SkaloedMove(self): #допустимые ходы: smoves = "" if (self.TestSkaloed('L')): smoves += "L" if (self.TestSkaloed('R')): smoves += "R" if (self.TestSkaloed('U')): smoves += "U" if (self.TestSkaloed('D')): smoves += "D" В отличие от Охотника, который может передвигаться только по свободным ходам, Скалоед выгрызает ходы в скальной породе и, наоборот, засыпает свободные проходы, то есть состояние клетки, из которой он уходит, изменяется на противоположное: непроходимая клетка становится проходимой, а проходимая - непроходимой: if (self.Labyrinth[SkaloedCoods.X][SkaloedCoods.Y] == SKALA_SYM): #делаем проход: self.Labyrinth[SkaloedCoods.X][SkaloedCoods.Y] = HOD_SYM else: #засыпаем проход: self.Labyrinth[SkaloedCoods.X][SkaloedCoods.Y] = SKALA_SYM Направление хода Скалоед выбирает случайно, извлекая букву из строки smoves: #выбираем случайный ход: 299
n = random.randint(0, dir = smoves[n] if (dir == 'L'): SkaloedCoods.X -= if (dir == 'R'): SkaloedCoods.X += if (dir == 'U'): SkaloedCoods.Y -= if (dir == 'D'): SkaloedCoods.Y += len(smoves)-1) 1 1 1 1 print("Ход Скалоеда: " + dir) print() Если ход одного из участников обоюдоострой охоты окажется результативным, то метод GameOver заканчивает их мытарства и злоключения, печатает обычную в таких случаях надпись GAME OVER, публикует имя победителя и изменяет счёт игры: #ЗАКАНЧИВАЕМ ОЧЕРЕДНУЮ ПАРТИЮ def GameOver(self, winner): print() print("GAME OVER") if (winner == OHOTNIK): print("ПОБЕДИЛ ОХОТНИК") SkaloedCoods.X = -1 SkaloedCoods.Y = -1 else: print("ПОБЕДИЛ СКАЛОЕД") print() self.PrintPosition() #общий счёт: Result[winner] += 1 print("Счёт Охотник:%i Скалоед:%i" %(Result[OHOTNIK], Result[SKALOED])) print() return True Тут, конечно, труба зовёт нас сразиться с монстром подземного Лабиринта, и мы начинаем игру! 300
Компас не оставляет нам выбора, строго указывая направо (Рис. 8.5). Рис. 8.5. И вечный бой! Нажимаем букву R (или r) и клавишу ВВОД – Охотник идёт вправо. Скалоед действует случайно, но встречно (Рис. 8.6). Рис. 8.6. Обоюдоострая охота Мы идём на сближение со Скалоедом и через несколько ходов оказываемся в непосредственной близости с ним (Рис. 8.7). Пора задуматься о последствиях! Охотник может отступить, выполнив ход вверх, или устремиться навстречу Скалоеду, рискуя быть съеденным. Любители шампанского и адреналина выбрали бы ход влево, но вы можете 301
отступить и подождать, пока на поле не возникнет более выгодная для вас позиция… Рис. 8.7. Уже чувствуется тяжёлое дыхание зверя! Увы, на этот раз Скалоед оказался проворнее и удачливее Охотника (Рис. 8.8, слева), но у него есть ещё порох в пороховницах и бесконечная тяга к приключениям (Рис. 8.8, справа)! Рис. .8.8 Для кого-то охота всегда удачна Несмотря на незамысловатый сюжет, игра получилась довольно увлекательная, но всё-таки надо, надо добавить зверушке интеллекта, чтобы охотник не дремал! Исходный код программы находится в папке Охота на Скалоеда. 302
Проект Игра Охота на Скалоедов Списки Списки списков Условный оператор if-else Условный оператор if Оператор break Бесконечный цикл while Вложенные циклы for Операторы and и or Оператор return Функция с параметрами Функция без параметров Оператор continue Класс random На ловца и зве(п)рь бежит! Охотницкая поговорка Игру Охота на Скалоеда вполне можно причислить к пошаговым стратегиям. Да вот только персонажей маловато. Давайте углубим и усугубим нашу игру, добавив новых Скалоедов! Например, можно увеличить число Скалоедов до 5: #макс. число Скалоедов: MAX_SKALOEDY = 5 К сожалению, объявление этой переменной никак не отразится на нашей программе, которую нам придётся существенно и основательно передоработать! Вместо одного Скалоеда на игровом поле будет пастись целый табун монстров: #список Скалоедов: Skaloedy = [] Для удобства изменим идентификаторы переменных: #игроки: 303
IOHOTNIK = 0 ISKALOED = 1 OHOTNIK = 'O' SKALOED = 'C' EMPTY = ' ' и можно конструировать новую игру: #КЛАСС ИГРЫ class Game(): #КОНСТРУКТОР def __init__(self, Size, nsk): #контролируем ввод пользователя: if (Size >= MIN_SIZE and Size <= MAX_SIZE): self.Size = Size else: self.Size = MIN_SIZE #создаём лабиринт: self.Labyrinth = [[HOD+EMPTY]*self.Size for i in range(self.Size)] Result[IOHOTNIK] = 0 Result[ISKALOED] = 0 print("Счёт Охотник: %i Скалоед: %i" %(0,0)) #Скалоеды: if (nsk >= 1 and nsk <= MAX_SKALOEDY): self.nSkaloedy = nsk else: self.nSkaloedy = 1 В конструкторе появился ещё один параметр – число Скалоедов. В функции main мы задаём максимальное число этих существ: #создаём игру: game = Game(9, 5) Игровой цикл почти не изменился по сравнению с предыдущей версией, но после Охотника свои ходы будут последовательно выполнять все Скалоеды: #ИГРОВОЙ ЦИКЛ def Play(self): 304
while True: self.NewGame() while True: #ход Охотника: self.PrintPosition() self.OhotnikMove() if (self.isGameOver(OHOTNIK)): break #ход Скалоедов: self.PrintPosition() self.SkaloedyMove() if (self.isGameOver(SKALOED)): break #игра окончена - следующая партия? print() print("Играем дальше? (1 - да) > ", end ='') s = input() if (s != "1"): break print() Метод NewGame можно оставить без изменений – он просто вызывает метод Prepare для подготовки к новой игре: #НОВАЯ ИГРА def NewGame(self): #готовимся к новой игре: self.Prepare() print() print("Новая игра") print() В методе Prepare мы по старому рецепту создаём Лабиринт и выставляем Охотника в его верхний угол. Однако сам массив Labyrinth должен стать более сложным. Действительно, координаты одного Охотника и одного Скалоеда легко проверить, но для 5 Скалоедов эта процедура окажется слишком длинной, поэтому лучше данные для каждой клетки Лабиринта хранить в строке из двух символов. Первый символ – это статус клетки, а второй – персонаж игры, то есть Охотник или Скалоед. Обозначим их номера в строке «говорящими» переменными: STATUS = 0 305
WHO = 1 Символ STATUS может принимать значения: #непроходимые и прoходимые клетки: SKALA = ':' HOD = '.' Так мы сразу определим, сможет ли Охотник перейти на какую-либо клетку поля. Символ WHO принимает значения: OHOTNIK = 'O' SKALOED = 'C' EMPTY = ' ' и показывает, находится ли в клетке Охотник, или Скалоед, или она свободна (EMPTY). При создании Лабиринта мы записываем в каждую клетку Лабиринта символы, обозначающие, что она проходима и пуста: #ГОТОВИМСЯ К ИГРЕ def Prepare(self): #создаём лабиринт: self.Labyrinth = [[HOD+EMPTY]*self.Size for i in range(self.Size)] Затем мы засыпаем часть свободных проходов скальной породой, присваивая символу STATUS значение SKALA: #скальная порода: for j in range(1, self.Size): #начало: begin = random.randint(0, self.Size-1) #конец: end = random.randint(begin, self.Size-1) for i in range(begin, end+1): 306
self.Labyrinth[i][j] = SKALA + EMPTY Охотник занимает верхнюю левую клетку, которую мы объявляем свободной (она уже свободна, но порядок важнее!): #координаты Охотника: x = 0 y = 0 OhotnikCoords.X = x OhotnikCoords.Y = y self.Labyrinth[x][y] = HOD + OHOTNIK Генерация Скалоедов требует осторожности и аккуратности! Мы не можем весь скоп Скалоедов засадить в одну центральную клетку поля – их следует беспорядочно разбросать по просторам Лабиринта. Это несложно сделать, ведь Скалоед прекрасно себя чувствует и в проходимой, и в непроходимой клетке. Но было бы неэтично подселить новорождённого Скалоеда в клетку с Охотником или с другим Скалоедом. Проблема решается просто: если в случайно выбранной клетке пусто, то в неё можно прописать Скалоеда. Сущность этого действа заключается в запоминании координат Скалоеда в списке Skaloedy и в присваивании символу WHO соответствующей клетки Лабиринта значения SKALOED: #координаты Скалоедов: nsk = 0 x = 0 y = 0 Skaloedy.clear() while (nsk < self.nSkaloedy): while (self.Labyrinth[x][y][WHO] != EMPTY): x = random.randint(0, self.Size-1) y = random.randint(0, self.Size-1) Skaloedy.append(Coords(x,y)) self.Labyrinth[x][y] = self.Labyrinth[x][y][STATUS] + SKALOED nsk += 1 На этом ритуальные обряды по подготовке к игре обрываются, и мы можем во всей красе напечатать текущую (а для нас она - исходная) позицию. Метод PrintPosition благодаря символам WHO и STATUS получился простым и изящным: 307
#ПЕЧАТАЕМ ТЕКУЩУЮ ПОЗИЦИЮ def PrintPosition(self): #лабиринт: for y in range(0, self.Size): for x in range(0, self.Size): if (self.Labyrinth[x][y][WHO] != EMPTY): print(self.Labyrinth[x][y][WHO], end='') else: print(self.Labyrinth[x][y][STATUS], end='') print() print() При пяти Скалоедах ситуация в Лабиринте может возникнуть такая – Рис. 8.9. Рис. 8.9. Массированная атака Скалоедов К счастью, начинает игру Охотник, поэтому уже первым ходом вправо он может снести голову Скалоеду… Начало метода OhotnikMove дословно цитирует нашу предыдущую программу, а вот потом появляются новоделы. Поскольку Охотник отмечен в символе WHO занимаемой им клетки, то, прежде чем двинуться в соседнюю клетку, он должен выписаться из текущей клетки: #ХОД ОХОТНИКА def OhotnikMove(self): #допустимые ходы: smoves = "" if (self.TestOhotnik('L')): 308
smoves += "L " if (self.TestOhotnik('R')): smoves += "R " if (self.TestOhotnik('U')): smoves += "U " if (self.TestOhotnik('D')): smoves += "D " smoves = smoves.strip() #некуда ходить: if (smoves == ""): return #есть ходы: move = "" while True: print("Ваш ход [%s]> " %smoves, end='') #ход игрока: move = input().upper() #неверный ход: if (not move in smoves): print("Повторите Ваш ход!\r\n") else: break #пропускаем ход: if (move == ""): return #стираем Охотника в прежней позиции: self.Labyrinth[OhotnikCoords.X][OhotnikCoords.Y] = self.Labyrinth[OhotnikCoords.X][OhotnikCoords.Y][STATUS]+EMPTY При переселении в новую клетку Охотника может ожидать приятный сюрприз в виде жертвенного Скалоеда, присутствие и наличие (или намордие) которого легко определить по значению символа WHO новой клетки: #новые координаты Охотника: dir = move[0] if (dir == 'L'): OhotnikCoords.X -= 1 if (dir == 'R'): OhotnikCoords.X += 1 if (dir == 'U'): OhotnikCoords.Y -= 1 if (dir == 'D'): OhotnikCoords.Y += 1 #клетка со Скалоедом: 309
if (self.Labyrinth[OhotnikCoords.X][OhotnikCoords.Y][WHO] == SKALOED): По закону жизни и игры Охотник и Скалоед не могут мирно существовать на одной клетке – побеждает тот, кто застиг соперника врасплох. В данной ситуации подфартило Охотнику, а неудачнику-Скалоеду пришла фиаска, и его следует вычеркнуть из списка жильцов Лабиринта. В этой версии игры Скалоед представлен только координатами клетки, поэтому мы должны найти по ним самого Скалоеда (его индекс в списке Skaloedy) и задать ему такие координаты, которых у здравствующих особей быть может: #уничтожаем его: for i in Skaloedy: if (i.X == OhotnikCoords.X and i.Y == OhotnikCoords.Y): i.X = -100 i.Y = -100 break Переселив Скалоеда в мир иной – с отрицательными координатами, - мы избавляемся от его присутствия в Лабиринте, поскольку при любой проверке он окажется далеко вне его. Покончив с формальностями, мы переносим Охотника в новую клетку: #риcуем в новой позиции: self.Labyrinth[OhotnikCoords.X][OhotnikCoords.Y] = self.Labyrinth[OhotnikCoords.X][OhotnikCoords.Y][STATUS] + OHOTNIK После каждого хода Охотника следует проверить в методе isGameOver, не извёл ли он уже всех своих врагов. Как вы помните, у живых Скалоедов координаты неотрицательные, а у истреблённых – отрицательные. По этому признаку мы определяем, остались ли в списке Skaloedy здравствующие твари или вся популяция загублена отважным, но безрассудным Охотником: #ПРОВЕРЯЕМ, НЕ ЗАКОНЧЕНА ЛИ ИГРА def isGameOver(self, player): #победил Охотник? if (player == OHOTNIK): #все Скалоеды уничтожены? flg = True 310
for i in Skaloedy: if (i.X >= 0): flg = False break Если Охотник добился абсолютной виктории, то игре приходит полный геймовер: if (flg): return self.GameOver(IOHOTNIK) И чтобы не возвращаться в этот метод, посмотрим, как распорядится злая судьба в случае фатальной неудачи самого Охотника. Если Скалоед окажется шустрее и удачливее Охотника, то мы присвоим ему отрицательные координаты, с которыми он и перейдёт благополучно в тот же метод GameOver, но с другим значением параметра: #победил Скалоед? else: if (OhotnikCoords.X < 0): return self.GameOver(ISKALOED) Ну а если на естественный вопрос: - Есть кто живой? - мы получим в ответ мат и рык, значит, игра находится в самом разгаре: #игра продолжается: return False В методе GameOver мы совершаем традиционные обрядовые действия, уже хорошо вам известные по прошлогоднему сезону охоты: #ЗАКАНЧИВАЕМ ОЧЕРЕДНУЮ ПАРТИЮ def GameOver(self, winner): print() print("GAME OVER") if (winner == IOHOTNIK): print("ПОБЕДИЛ ОХОТНИК") else: print("ПОБЕДИЛИ СКАЛОЕДЫ") 311
print() self.PrintPosition() #общий счёт: Result[winner] += 1 print("Счёт Охотник:%i Скалоед:%i" %(Result[IOHOTNIK], Result[ISKALOED])) print() return True Но до торжеств и народных гуляний ещё далеко, и после хода игрока наступает черёд Скалоедов, которые пытаются пойти куда глаза глядят. Чтобы очередного Скалоеда не вынесло прежде времени за пределы среды обитания, мы вызываем метод TestSkaloed: def TestSkaloed(self, id, dir): if (dir == 'L'): #новые координаты Скалоеда: x = Skaloedy[id].X - 1 y = Skaloedy[id].Y if (dir == 'R'): x = Skaloedy[id].X + 1 y = Skaloedy[id].Y if (dir == 'U'): x = Skaloedy[id].X y = Skaloedy[id].Y - 1 if (dir == 'D'): x = Skaloedy[id].X y = Skaloedy[id].Y + 1 #выход за граница лабиринта: if (x < 0 or x >= self.Size or y < 0 or y >= self.Size): return False #клетка занята Скалоедом: if (self.Labyrinth[x][y][WHO] == SKALOED): return False return True Как видите, ему категорически запрещено покидать Лабиринт или ущемлять жилищные права своих мордастых собратьев. Если Скалоеду верного пути нет, то он остаётся прозябать в старой клетке: 312
#ХОД СКАЛОЕДА def SkaloedyMove(self): for i in range(0, self.nSkaloedy): #допустимые ходы: smoves = "" if (self.TestSkaloed(i,'L')): smoves += "L" if (self.TestSkaloed(i,'R')): smoves += "R" if (self.TestSkaloed(i,'U')): smoves += "U" if (self.TestSkaloed(i,'D')): smoves += "D" #некуда ходить: if (smoves == ""): continue Если же горизонт чист, то Скалоед изменяет состояние текущей клетки: x = Skaloedy[i].X y = Skaloedy[i].Y if (self.Labyrinth[x][y][STATUS] == SKALA): #делаем проход: self.Labyrinth[x][y] = HOD + EMPTY else: #засыпаем проход: self.Labyrinth[x][y] = SKALA + EMPTY И переходит в новую, но совершенно случайную клетку: #выбираем случайный ход: n = random.randint(0, len(smoves)-1) dir = smoves[n] if (dir == 'L'): Skaloedy[i].X -= 1 if (dir == 'R'): Skaloedy[i].X += 1 if (dir == 'U'): Skaloedy[i].Y -= 1 if (dir == 'D'): Skaloedy[i].Y += 1 313
Если в новой клетке ему подвернётся под горячую лапу Охотник, то Скалоед выказывает ему своё отрицательное отношение к охоте в виде новых координат: x = Skaloedy[i].X y = Skaloedy[i].Y; #в этой клетке Охотник? if (self.Labyrinth[x][y][WHO] == OHOTNIK): OhotnikCoords.X = -100; OhotnikCoords.Y = -100; И тут же рисуется в новой клетке: #рисуем: self.Labyrinth[x][y] = self.Labyrinth[x][y][STATUS] + SKALOED Охотник, буде он ещё жив, получает сообщение о направлении хода очередного Скалоеда: print("Ход Скалоеда %i: %s" %((i+1), dir)) print() Отходив, Скалоеды через метод isGameOver узнают, не слопал ли ктонибудь из них невезучего Охотника. А тут и сказочной игре Game Over Процесс стратегической игры стал ещё увлекательнее, особенно если Охотник не прячется в кустиках, а смело бросается навстречу судьбе (Рис. 8.10)! 314
Рис. 8.10. Особенности русской охоты на Скалоедов Исходный код программы находится в папке Охота на Скалоедов. Переведите дух после охоты на привале, а потом крепко, обеими руками беритесь за космическую головоломку! Головоломка Космический охотник Помогите охотнику (он похож на героя компьютерных игр Пакмана) поймать всех толстолапых монстров с планеты Пармезан. Космический охотник может двигаться только по коридорам лабиринта, отмеченным точками. Его путь не должен самопересекаться (то есть вы не можете дважды пройти по одной и той же точке), а закончиться он должен в центре управления, обозначенном стрелкой и звёздочками. Удачной охоты! 315
316
Проект Игра Угадай число Класс: конструктор, методы, поля Игровой цикл Класс random Условный оператор if-else Условный оператор if Оператор break Оператор or Вложенные операторы if-else Оператор return Бабка надвое сказала: либо дождик, либо снег, либо будет, либо нет. Фольклорный пример бинарного предсказания Давайте перейдём к программированию менее кровожадных игр! В первой игре нужно угадать задуманное компьютером число в заданном диапазоне, пользуясь подсказками. Игра имеет долгую историю, но популярна до сегодняшних дней. По-русски игра так и называется Угадай число, по-английски - Guess the Number. Правила игры Компьютер загадывает число от 1 до 100, а игрок должен угадать его за минимальное число попыток. Если число угадано, то компьютер поздравляет его и загадывает следующее. И так до тех пор, пока игроку не надоест. Если названное игроком число меньше задуманного, то компьютер сообщает ему о недоборе, если больше – о переборе. Стратегия игры Игра очень простая – и стратегия игры тоже не сложнее: нужно каждый раз называть число, находящееся посередине интервала оставшихся чисел. 317
Например, на первом ходу игрок должен назвать число 100 : 2 = 50. Если угадал – сразу молодец! Если перебор, то диапазон уменьшается вдвое – 1..49. При недоборе – аналогично: 51..100. Далее действуем по заранее утверждённому плану, без особых затей. Легко подсчитать, что потребуется не более 8 ходов, чтобы угадать любое число от 1 до 100. Если вам этого мало, загадывайте числа побольше. Уточним. Минимальное загаданное число – 1, максимальное – 100. Разность составляет 100 – 1 = 99. Половина разности равна 99 : 2 = 49.5. Поскольку загадано целое число, то дробное нужно округлить: до меньшего – 49 или до большего – 50. Таким образом, на первом ходу можно называть любое из этих чисел. Дальше действуйте аналогично. Несмотря на кажущуюся простоту, наша стратегия является хорошим примером двоичного (бинарного) поиска. Действительно, с каждым ходом число претендентов уменьшается вдвое: вначале их было 100 (Рис. 8.11), после первого хода - 50, затем - 25 – 13 – 7 – 4 – 2 – 1. Обычно этот метод используют для поиска нужного элемента в упорядоченном массиве. Наше применение немного нетрадиционное, но зато очень эффектное (Рис. 8.12)! Рис. 8.11. Первый ход Поскольку все игры, как и телевизионные сериалы, пишутся по одному сценарию, то класс игры можно позаимствовать у Охоты на Скалоеда. Угадайка гораздо проще охоты, поэтому некоторые методы можно упразднить. Чтобы сделать игру более универсальной, мы позволим игроку самостоятельно выбирать верхнюю границу чисел (нижняя всегда равняется единице) из диапазона MIN_NUM.. MAX_NUM: 318
Рис. 8.12. Бинарная угадайка в действии #КЛАСС ИГРЫ class Game(): #мин. загаданное число: MIN_NUM = 15 #макс. загаданное число: MAX_NUM = 1000 В классическом варианте это всегда сотня, в нашей игре верхняя граница хранится в переменной: #макс. загаданное число: MaxNumber = ... Из диапазона 1.. MaxNumber компьютер и загадает число: 319
#загаданное число: Number = ... А число, которое назовёт игрок, мы сохраним в поле: #названное число: Num = ... Хотя число попыток в игре не ограничивается, но статистика неумолимо ведётся: #число попыток: nAttempt = ... Для выбора случайного числа мы, как водится, подключаем модуль random: import random Конструктор игры получает от пользователя верхнюю границу диапазона, проверяет её и сохраняет в поле MaxNumber: #КОНСТРУКТОР def __init__(self, num): #контролируем ввод пользователя: if (num >= self.MIN_NUM and num <= self.MAX_NUM): self.MaxNumber = num else: self.MaxNumber = 100 В функции main мы создаём классический вариант игры: # -*- coding: Windows-1251 -*#Игра Угадай число #ГЛАВНАЯ ФУНКЦИЯ def main(): 320
print('Угадай число') print() #создаём игру: game = Game(100) #начинаем игровой цикл: game.Play() После создания игры начинается игровой цикл: #ИГРОВОЙ ЦИКЛ def Play(self): while True: self.NewGame() #return while True: self.Attempt() if (self.isGameOver()): break #игра окончена - следующая партия? print() print() print("Играем дальше? (1 - да) > ", end ='') s = input() if (s != "1"): break print() Он даже проще, чем в наших охотничьих играх! Сначала компьютер готовится к новой игре: #НОВАЯ ИГРА def NewGame(self): #готовимся к новой игре --> #загадываем число: self.Number = random.randint(1, self.MaxNumber) #обнуляем число попыток: self.nAttempt = 0 print() print("Новая игра") print() print("Отгадайте число от 1 до %i!" % self.MaxNumber) print() 321
Он загадывает число, случайно выбирая его из заданного диапазона, обнуляет число попыток и сообщает игроку диапазон, в котором находится загаданное число. Для классического варианта приглашение к игре выглядит так (Рис. 8.13). Рис. 8.13. Добро пожаловать! Отзывчивый игрок вводит число с клавиатуры и нажимает клавишу ВВОД (Рис. 8.14): #ПЫТАЕМСЯ УГАДАТЬ ЧИСЛО def Attempt(self): #попытка: self.nAttempt += 1 print() print("Попытка #%i > " % self.nAttempt, end='') self.Num = int(input()) Рис. 8.14. Первая попытка По правилам игры, компьютер в ответ не ходит, а только комментирует попытку игрока и следит за окончанием игры: #ПРОВЕРЯЕМ, НЕ ЗАКОНЧЕНА ЛИ ИГРА def isGameOver(self): if (self.Num > self.MAX_NUM or self.Num < 1): 322
print(" Ваще мимо!!!", end='') return False if (self.Num == self.Number): print(" Поздравляю, угадал!", end='') return True else: if (self.Num > self.Number): print(" Перебор!") else: print(" Недобор!") #игра продолжается: return False Если число угадано, то игрок принимает поздравление, во всех остальных случаях игра продолжается, но игрок получает подсказку, которая облегчает ему бремя выбора следующего числа. Если игрок никогда не участвовал в шоу Поле чудес, то очень скоро он число угадает, а затем либо закончит игру, либо продолжит её. Очень простая, но увлекательная игра. Я сгонял пару партеек – и мне понравилось (Рис. 8.15)! Рис. 8.15. Компьютерная угадайка Исходный код программы находится в папке Угадай число. 323
Проект Игра Крестики-нолики Класс: конструктор, методы, поля Игровой цикл Список списков Класс random Список Вложенные операторы if-else Вложенные циклы for Оператор or Бесконечный цикл while Оператор return Мальчик с нашей улицы С девочкой играл На асфальте крестики Мелом рисовал Шла над южным городом Летняя пора Крестики - нолики Детская игра. Детская песня Крестики-нолики – игра, знакомая каждому с детства! На поле 3 х 3 клетки 2 игрока поочерёдно выставляют свой значок: первый игрок – крестик, второй – нолик. Выигрывает тот, кто первым составит из трёх своих значков непрерывный ряд – по горизонтали, по вертикали или по диагонали. Давно известно, что при правильной игре каждая партия в Крестикинолики заканчивается вничью, но игра до сих пор привлекает внимание детей, взрослых и программистов… По-английски игра называется Cross and Naught, Cross & Zero, Tic-Tac-Toe. Поля класса Game отражают правила игры в крестики и нолики. Размеры доски постоянны, поэтому их следует сохранить в переменной: #КЛАСС ИГРЫ class Game(): #размеры доски: SIZE = 3 324
Игровое поле (доска) квадратное, что диктует нам хранение текущей позиции в списке списков: #игровое поле: Board = [] Сначала все клетки поля пустые – IEMPTY, затем на них появляются символы PLAYER игрока (IPLAYER) и компьютера – COMPUTER (ICOMPUTER), соответственно: #игроки: IPLAYER = 1 ICOMPUTER = 10 IEMPTY = 0 PLAYER = 'X' COMPUTER = 'O' EMPTY = '.' Здесь требует пояснения только выбор значения для кода компьютера в списке Board. Казалось бы, это могла быть двойка, но при подсчёте крестиков и ноликов на доске очень важно, чтобы значения кода игрока и кода компьютера существенно отличались (см. дальше). Назначение остальных полей понятны без лишних слов: #номер текущего хода: nMove = 0 #счёт игры: Result = [0]*2 Поскольку выбор параметров игры отсутствует, то в конструкторе мы только обнуляем счёт и показываем его на экране: #КОНСТРУКТОР def __init__(self): #счёт: self.Result[0] = 0 self.Result[1] = 0 print("Счёт Игрок: %i Компьютер: %i" %(0,0)) 325
print() В функции main почти ничего и менять не нужно по сравнению с нашими более ранними проектами: # -*- coding: Windows-1251 -*#Игра Tic-Tac-Toe #Крестики-нолики на доске 3 х 3 клетки import random #ГЛАВНАЯ ФУНКЦИЯ def main(): print('Крестики-нолики') print() #создаём игру: game = Game() #начинаем игровой цикл: game.Play() Как и велено правилами, соперники ходят по очереди. Сначала – Игрок потом – Компьютер. Игра заканчивается, если: • • Один из игроков выстроил непрерывную цепочку из трёх символов Все клетки заняты. В этом случае фиксируется ничья Таким образом, после каждого хода мы печатаем на экране текущую позицию, после чего Игрок и Компьютер ставят свой значок в свободную клетку, а мы проверяем, не закончилась ли игра: #ИГРОВОЙ ЦИКЛ def Play(self): while True: self.NewGame() while True: self.PrintPosition() self.PlayerMove() if (self.isGameOver(IPLAYER)): break self.PrintPosition() self.ComputerMove() 326
if (self.isGameOver(ICOMPUTER)): break #игра окончена - следующая партия? print() print() print("Играем дальше? (1 - да) > ", end ='') s = input() if (s != "1"): break print() Прежде всего, нужно научиться печатать текущую позицию на экране. Как всегда, за это отвечает метод PrintPosition. В нём мы просматриваем все элементы списка Board и по их значениям печатаем соответствующий символ в консольном окне: #ПЕЧАТАЕМ ТЕКУЩУЮ ПОЗИЦИЮ def PrintPosition(self): #оцифровка: print(" ABC"); #доска: for y in range(0, self.SIZE): print(str(y+1) + " ", end='') for x in range(0, self.SIZE): if (self.Board[x][y] == IEMPTY): print(EMPTY, end='') elif (self.Board[x][y] == IPLAYER): print(PLAYER, end='') else: print(COMPUTER, end='') print() print() Здесь же мы должны подумать и о том, как игрок будет вводить свой ход, ведь теперь мы не можем ограничиться одной буквой, как в Скалоедах. Наверно, удобнее всего обозначить горизонтали цифрами, а вертикали – латинскими буквами, как это принято в шахматах или шашках (Рис. 8.16). 327
Рис. 8.16. Шахматная оцифровка Правда, нам удобнее считать горизонтали сверху вниз, а не снизу вверх, но это непринципиально. Тогда начальная позиция, с пустыми клетками будет выглядеть так, как показано на Рис. 8.17. Рис. 8.17. Начальная позиция По-моему, получилось внятно… Теперь Игрок должен выполнить свой ход, для чего мы отправляемся в метод PlayerMove: #ХОД ИГРОКА def PlayerMove(self): LETTERS = ['A','B','C'] NUMBERS = ['1','2','3'] #ход: self.nMove += 1 328
Игрок получает подсказку: сначала нужно напечатать букву (в любом регистре), обозначающую вертикаль, затем цифру – обозначающую горизонталь (Рис. 8.18, слева): #вводим кординаты клетки: while True: print() print("Ход #%i [БукваЦифра] > " % self.nMove, end='') move = input().upper() Рис. 8.18. Ходьба начинается Если Игрок хорошо владеет собой, пальцами и клавиатурой, то он напечатает строку из двух символов (Рис. 8.18⬆, справа). Эти символы мы легко извлекаем из строки пользователя: #координаты клетки игрока: x = move[0] y = move[1] После чего проверяем его: • Если символы из строки Игрока не входят в списки LETTERS и NUMBERS, то Игрок должен повторить ход • Если Игрок пытается поставить свой значок в уже занятую клетку, то мы сообщаем ему, что эта клетка уже занята #проверяем ход: 329
if (not(x in LETTERS) and (y in NUMBERS)): print("Повторите ввод!") continue x = LETTERS.index(x) y = NUMBERS.index(y) if (self.Board[x][y] != IEMPTY): print("Эта клетка уже занята!") else: break В этих неблагоприятных случаях Игрок должен ввести новые координаты клетки. Если же верная рука Игрока не дрогнула, то мы заносим в список доски его код, который при печати позиции будет заменён символом Игрока, то есть крестиком: #ставим крестик: self.Board[x][y] = IPLAYER print() Поскольку элементы в списке Board имеют числовые индексы, которые начинаются с нуля, то необходимо по символам Игрока вычислить координаты клетки в массиве. Мы знаем, что первый символ строки – это буква (латинская!): A, B или C. Им соответствуют индексы 0, 1, 2. Их легко получить с помощью метода index: x = LETTERS.index(x) y = NUMBERS.index(y) Как известно, лучший первый ход – в самую сердцевину игрового поля, в клетку В2 (Рис. 8.19). Наш Игрок не устоял перед искушением и сделал именно такой ход. К сожалению, ход слишком предсказуем и делает игру однообразной, поэтому, возможно, первый ход нужно выбирать случайно… 330
Рис. 8.19. Первый ход – не в бровь, а в глаз Однако пора и Компьютеру поставить свой нолик! Увы, без нашей помощи ему не обойтись, поэтому бросаем все силы на разработку метода ComputerMove. Начало – очевидно: def ComputerMove(self): #ход: self.nMove += 1 Поскольку Игрок уже сделал первый ход, то Компьютеру предстоит достойно ответить на него. Скорее всего, Игрок выбрал центральную клетку. А может быть, и нет – люди коварнее компьютеров! В последнем случае Компьютеру следует немедля занять центральную клетку, и вот почему. Через неё проходит 4 (Рис. 8.20, слева) из 8 трёхклеточных рядов (Рис. 8.20, справа), это значит, что нолик в этой клетке позволит продолжить ряд в наибольшем числе направлений по сравнению с периферийными клетками. Рис. 8.20. Жадный выбор хода 331
Если же Игрок оккупировал центр поля, то Компьютеру следует занять одну из угловых клеток (из соображения симметрии - любую). Опять смотрим на Рис. 8.21, слева – через угловую клетку проходит 3 ряда, а через остальные периферийные клетки – только два (Рис. 8.21, справа). Рис. 8.21. Жадность – не порок! Координаты угловых клеток состоят только из нулей и двоек (Рис. 8.22), что очень удобно при выборе случайного угла: Рис. 8.22. Координаты угловых клеток #если это первый ход компьютера: if (self.nMove == 2): #если Игрок пошёл в центр: if (self.Board[1][1] == IPLAYER): #выбираем один из четырёх углов: 332
x = random.randint(0,1)*2 y = random.randint(0,1)*2 return self.DoMove(x,y) #если Игрок не пошёл в центр, #ставим нолик в центр: else: x = 1 y = 1 return self.DoMove(x,y) #ХОД КОМПЬЮТЕРА def DoMove(self, x,y): LETTERS = ['A','B','C'] NUMBERS = ['1','2','3'] print("Ход #%i Компьютера: %s " % (self.nMove, LETTERS[x] + NUMBERS[y])) #ставим нолик: self.Board[x][y] = ICOMPUTER Проверяем теорию на практике. Когда Игрок занимает центр, Компьютер отвечает ходом в угол (Рис. 8.23). Если Игрок хитрит, то Компьютер ставит нолик в центр поля (Рис. 8.24). Итак, мы научили Компьютер правильно выбирать свой первый ход. Рис. 8.23. Угловой удар головой 333
Рис. 8.24. Занимаем центр поля Забудем на время о втором ходе Компьютера и перейдём в эндшпиль. Если на поле возникнет одна из ситуаций, показанных на Рис. 8.25, слева, то Компьютер должен поставить нолик в свободную клетку, чтобы он дополнил ряд до трёх ноликов. Этот ход принесёт Компьютеру победу! Рис. 8.25. Победные ходы Если же следующий ход может принести победу игроку (Рис. 8.25⬆, справа), то Компьютер должен упредить соперника и разорвать цепочку крестиков (Рис. 8.26). 334
Рис. 8.26. Не забывайте об обороне! Для поиска таких позиций мы напишем специальный метод Test00XX. Он проверяет все 8 рядов игрового поля и ищет 2 символа игрока (нолики или крестики) в ряду, в котором также имеется пустая клетка для совершения хода. Так как в списке Board крестики Игрока имеют значение IPLAYER, а нолики Компьютера – ICOMPUTER, то сумму этих значков легко найти: def Test00XX(self, player): #сумма двух значков заданного игрока: summa = player * 2 Поскольку пустая клетка имеет в массиве нулевое значение IEMPTY, то, найдя заданную сумму summa в массиве, мы смело можем утверждать, что в проверяемом ряду ровно 2 символа заданного игрока и 1 пустая клетка. В эту клетку и следует выполнять ход, поэтому её координаты нужно запомнить по ходу поиска. Как только искомая комбинация будет найдена, дальнейшие поиски прекращаются, и метод возвращает координаты пустой клетки: #ищем ряд из двух значков игрока --> #проверяем горизонтали: for y in range(0, self.SIZE): sum = 0 for x in range(0, self.SIZE): if (self.Board[x][y] == IEMPTY): emptycell = x sum += self.Board[x][y] if (sum == summa): 335
return emptycell, y #проверяем вертикали: for x in range(0, self.SIZE): sum = 0 for y in range(0, self.SIZE): if (self.Board[x][y] == IEMPTY): emptycell = y sum += self.Board[x][y] if (sum == summa): return x, emptycell #проверяем диагонали: sum = 0 for y in range(0, self.SIZE): if (self.Board[y][y] == IEMPTY): emptycell = y sum += self.Board[y][y] if (sum == summa): return emptycell, emptycell sum = 0 for y in range(self.SIZE-1, 0-1, -1): if (self.Board[y][y] == IEMPTY): emptycell = y sum += self.Board[self.SIZE-y-1][y] if (sum == summa): return (self.SIZE-emptycell-1), emptycell Если ни в одном ряду не окажется пары символов с пустой клеткой, то метод вернёт отрицательные координаты: #двух символов нет: return -1, -1 Все ходы, кроме первого, Компьютер выбирает с помощью следующего кода: #последующие ходы: else: #если есть 2 нолика и пустая клетка #в каком-либо ряду, #то ставим нолик: x,y = self.Test00XX(ICOMPUTER) if (x != -1): return self.DoMove(x,y) 336
#если есть 2 крестика и пустая клетка #в каком-либо ряду, #то ставим нолик: x,y = self.Test00XX(IPLAYER) if (x != -1): return self.DoMove(x,y) print() На Рис. 8.27, слева вы видите, что Игрок выстроил в верхней горизонтали 2 крестика, но Компьютер умело защищается, пресекая это ряд ноликом. Рис. 8.27. Хорошая обороноспособность! Легкомысленный Игрок не заметил угрозы со стороны Компьютера и сделал опрометчивый ход, на что Компьютер безжалостно достраивает восходящую диагональ очередным ноликом и уверенно побеждает (Рис. 8.27⬆, справа)! Дебют и эндшпиль этой незамысловатой игры мы разобрали по косточкам. Осталось решить вопрос с миттельшпилем, когда очевидных ходов нет и нужно выбирать одну из пустых клеток. Как мы выяснили, вполне разумно занять пустующую угловую клетку, а ежели это нам не удастся, то любую из свободных: 337
#если нет двух символов, #то ходим в пустой угол: for i in range(0, 100): x = random.randint(0,1)*2 y = random.randint(0,1)*2 if (self.Board[x][y] == IEMPTY): return self.DoMove(x,y) #если не удалось поставить нолик в угол, #ходим в случайную свободную клетку: while True: x = random.randint(0,2) y = random.randint(0,2) if (self.Board[x][y] == IEMPTY): return self.DoMove(x,y) Здесь нам приходится полагаться на случайность, но в целом Компьютер играет довольно разумно. Обычно в подобных играх для выбора лучшего (или хотя бы хорошего) хода используют весьма непростой алгоритм минимакс, но для такой примитивной игры это было бы неоправданной роскошью. После каждого хода игроков нужно проверить, не закончилась ли игра. А это событие может произойти в трёх случаях (Рис. 8.28): • • • Игрок выстроил ряд из трёх крестиков – победа Игрока Компьютер выстроил ряд из трёх ноликов – победа Компьютера На доске не осталось пустых клеток – ничья Рис. 8.28. Исход игры В методе isGameOver мы проверяем, остались ли у игроков ходы, и ищем ряды из трёх одинаковых символов: 338
#ПРОВЕРЯЕМ, НЕ ЗАКОНЧЕНА ЛИ ИГРА def isGameOver(self, player): #ничья? if (self.nMove == self.SIZE*self.SIZE): return self.GameOver(0) #сумма трёх значков заданного игрока: summa = player * self.SIZE #ищем ряд из трёх значков игрока --> #проверяем горизонтали: for y in range(0, self.SIZE): sum = 0 for x in range(0, self.SIZE): sum += self.Board[x][y] if (sum == summa): return self.GameOver(player) #проверяем вертикали: for x in range(0, self.SIZE): sum = 0 for y in range(0, self.SIZE): sum += self.Board[x][y] if (sum == summa): return self.GameOver(player) #проверяем диагонали: sum = 0 for y in range(0, self.SIZE): sum += self.Board[y][y] if (sum == summa): return self.GameOver(player) sum = 0 for y in range(self.SIZE-1, 0-1, -1): sum += self.Board[self.SIZE-y-1][y] if (sum == summa): return self.GameOver(player) #игра продолжается: return False По результатам проверки игра либо продолжается, либо заканчивается в методе GameOver: #ЗАКАНЧИВАЕМ ОЧЕРЕДНУЮ ПАРТИЮ def GameOver(self, winner): print() 339
print("GAME OVER") print() if (winner == IPLAYER): print("ПОБЕДИЛ ИГРОК") self.Result[0] += 1 elif (winner == ICOMPUTER): print("ПОБЕДИЛ КОМПЬЮТЕР") self.Result[1] += 1 else: print("НИЧЬЯ") print() self.PrintPosition() #общий счёт: print("Счёт Игрок:%i Компьютер:%i" %(self.Result[0], self.Result[1])) return True И тут можно поставить на игре жирный Х Исходный код программы находится в папке Tic-Tac-Toe. 340
Задания для самостоятельного решения Волк и овцы Волк и овцы - это старинная игра с шашками. В классическом варианте шашки расставляют на чёрных полях доски 8 х 8 клеток так, как показано на Рис. 8.29. Рис. 8.29. Начальная позиция 4 шашки – это овцы. Они могут ходить только вперёд на соседнюю свободную чёрную клетку (то есть по диагонали). 1 шашка другого цвета (обычно чёрная) – волк. Он выполняет ходы на соседние чёрные клетки – как вперёд, так и назад. Начальная позиция волка чаще находится у края доски, но это не обязательно. Волк ходит первым, затем – одна из овец, затем опять волк, и так далее по очереди. 341
Цель волка – дойти до противоположного края доски (или оказаться позади всех овец, что практически то же самое). Цель овец – оттеснить волка к краю доски, чтобы он не смог выполнить очередной ход. Строгой теории игры нет, но практика свидетельствует, что при правильной игре овцы всегда побеждают. Существуют варианты игры, в которых размеры доски 9 х 9 клеток, а число овец – 4 или 3. В последнем случае легко побеждает волк. Напишите игру Волк и овцы, взяв за основу программу Охота на Скалоедов. Роль Охотника здесь переходит к волку, а Скалоедов – к овцам. Ходы выполняются не по горизонталям и вертикалям, а по диагоналям, но в целом игра Волк и овцы не очень сильно отличается от Охоты на Скалоедов. В журнале Техника – молодёжи, №8 за 1986 год, на страницах 44-46 рассказывается о программировании этой игры на микрокалькуляторах. В №11 за тот же год была напечатана статьяТребуется выигрышная стратегия, которая получила продолжение уже в следующем году. В номерах 6 и 7 обсуждаются приёмы – как программирования, так и собственно процесса игры. Угадай число Напишите программу, в которой вы загадываете число, а компьютер отгадывает его. Крестики-нолики Напишите программу для игры в крестики и нолики вдвоём, то есть один игрок против другого. Задание очень простое. 342
В книге Марка Михаэлиса (Mark Michaelis) Essential C# 4.0 [MM10] программа для игры в Крестики-нолики используется как пример использования различных конструкций языка Си-шарп. На страницах 869-874 приведён полный листинг программы. В другой книге – Брэдли Джонс (Bradley L. Jones) Sams Teach Yourself the C# Language in 21 Days [JB03] – на страницах 617-628 также напечатан листинг программы для игры в Крестики-нолики для двух игроков. Компьютерные игры для двух игроков экономят бумагу и расходуют электроэнергию. Ещё это неплохое упражнение в программировании. Других преимуществ у игр на компьютере без компьютера я не вижу. 343
Глава #9. Занимательная комбинаторика и теория вероятностей Комбинаторика - это раздел математики, который изучает множества (совокупности, наборы) каких-либо элементов. Первая книга по комбинаторике вышла в 1666 году под названием Рассуждения о комбинаторном искусстве. Её написал известный немецкий математик Готфрид Вильгельм фон Лейбниц, который и придумал название комбинаторика этому разделу математики. Как в жизни, так и в программировании комбинаторные задачи встречаются очень часто. Например, сколько различных слов можно составить из букв русского алфавита? Сколько существует различных комбинаций при игре в кости двумя или тремя кубиками? Сколько разных нарядов можно составить из трех юбок и четырёх блузок и так далее. Все комбинаторные задачи решаются с помощью комбинаторных конфигураций: размещений, перестановок, сочетаний, композиций и разбиений. А начнём мы наши комбинаторные забавы с перестановок элементов. Возьмём множество, состоящее из трёх разных элементов, например, русских букв - {К, О, Т}. Всякое «слово», составленное из всех этих букв без повторений, и называется перестановкой элементов множества. Поскольку в множестве элементы не упорядочены, то мы выпишем их сначала в произвольном порядке, например так: 1. КОТ Вот мы и получили первую перестановку, а значит, и первое слово – КОТ. Оно «случайно» совпало с настоящим русским словом. Чтобы найти вторую перестановку, поменяем местами вторую и третью буквы: 2. КТО Тоже получилось неплохо, ведь кто - это русское местоимение. Давайте поменяем теперь первую и вторую буквы: 3. ТКО 344
Такого слова нет (в старой речи была частица –тко, имеющая тот же смысл, что и современная -ка: бери-тко, читай-тко), а вот четвёртая перестановка снова удачная. Чтобы её получить, поменяем местами вторую и третью буквы: 4. ТОК Снова меняем первую и вторую буквы - и получаем пятую перестановку: 5. ОТК Не ахти какое слово, но Отдел технического контроля тоже сгодится. И последнюю перестановку мы найдём, переставив две последние буквы: 6. ОКТ ОКТ – тоже сокращение от Оптическая когерентная томография, так что все наши перестановки из трёх букв К, О, Т оказались не совсем бессмысленными. Конечно, с другими буквами результат был бы другим. Но почему мы можем утверждать, что нашли все перестановки множества из трёх элементов? – Давайте рассуждать логически. На первом месте в слове может стоять любая из трёх букв. На второе место можно поставить любую из двух оставшихся, а для последнего места останется только одна буква. Таким образом, всего можно составить 3 х 2 х 1 = 6 разных слов. Но мы ровно столько и составили, значит, других слов из этих букв составить нельзя (естественно, мы используем каждую букву только один раз!). Если взять множество из четырёх разных элементов, то, рассуждая аналогично, мы придём к выводу, что из них можно составить 4 х 3 х 2 х 1 = 24 разных слова. Этот ряд легко продолжить сколь угодно далеко, а произведение чисел от единицы до заданного называется факториалом. Как его вычислять, вы уже знаете. 345
Проект Генерируем перестановки Бесконечный цикл while Метод int Условный оператор if Оператор return Списки Цикл for Функция с параметрами Условный оператор if-else Рекурсивная функция Мы научились подсчитывать общее число перестановок. Ну, пусть мы знаем, что из трёх разных букв можно составить 3! разных слов, но вопрос в том, как получить эти перестановки? С множеством из трёх элементов мы легко справились, а если взять больше – четыре, пять, а то и восемь? Оказывается, мы не первые, кто задался этим вопросом. Ещё в семнадцатом веке английские звонари научились выбивать на нескольких разных колоколах «мелодии», состоящие из всех перестановок этих колоколов. Например, для трёх колоколов нужно было сыграть такую мелодию: Первый колокол – второй – третий. Второй колокол – первый – третий. Дальше вы и сами продолжите эту музыку. Колоколов, конечно, было больше, а последовательность колокольных ударов нужно было держать в голове. Например, в Книге рекордов Гиннеса рассказывается о том, что в 1963 году за 17 с лишним часов удалось выбить на восьми колоколах все 8! = 40320 перестановок. В семнадцатом веке, конечно, эти музыкально-комбинаторные экзерсисы были короче, но, тем не менее, запомнить многие сотни перестановок было совсем непросто, поэтому звонари придумывали свои способы для «генерирования» всех колокольных перестановок. Один из таких способов мы и положим в основу компьютерной программы, которая быстро и правильно выпишет все перестановки элементов заданного множества. Нам вполне достаточно одной переменной MAX_ELEM для ограничения числа элементов в множестве, иначе их печать на экране займёт очень много времени; одной целой переменной nPerm – для хранения числа пе346
рестановок и целочисленного списка а – для хранения собственно перестановки: # -*- coding: Windows-1251 -*#Рекурсивная генерация перестановок #макс. число элементов в #множестве: MAX_ELEM = 8 #число перестановок: nPerm = 0 В функции main пользователь задаёт число элементов множества в диапазоне 1.. MAX_ELEM, после чего мы создаём список а и заполняем его числами 1.. MAX_ELEM. Так мы получаем первую перестановку: 1 2 3 4 … MAX_ELEM Нам гораздо удобнее переставлять именно числа, а не элементы других типов. Однако вы можете понимать под этими числами индексы каких-либо элементов в списке любого типа, так что наш генератор получится вполне универсальным. #ГЛАВНАЯ ФУНКЦИЯ def main(): print('Генерируем перестановки') print() global nPerm #бесконечный цикл ввода данных #пока пользователь не закроет программу #или не введет 0: while True: s = "Число элементов (1.." + str(MAX_ELEM) +") > print(s, end = '') #число элементов: nElem = int(input()) #если пользователь ввёл 0, #то программу закрываем: if (nElem == 0): return " #число перестановок: 347
nPerm = 0 #список для хранения очередной перестановки --> #начальная перестановка: a = list(range(1, nElem+1)) Генерирование всех остальных перестановок, а также их печать происходит в рекурсивной функции PermutationRec, которой мы передаём три аргумента: 1. всегда 0 2. число элементов в списке nElem 3. список a nPerm = 0 #генерируем перестановки: PermutationRec(0, nElem, a) print("Число перестановок = " + str(nPerm)) print() В функции PermutationRec мы переставляем элементы списка до тех пор, пока не получим новую перестановку при k == n, после чего печатаем её на экране и продолжаем генерировать новые перестановки: #ГЕНЕРИРУЕМ ПЕРЕСТАНОВКИ def PermutationRec(k, n, a): global nPerm #нашли очередную перестановку: if (k == n): nPerm += 1 #печатаем её: print(*a, sep='', end='') print() else: for i in range(k, len(a)): a[k], a[i] = a[i], a[k] PermutationRec(k+1, n, a) a[k], a[i] = a[i], a[k] 348
Запускаем программу и проверяем её работу при различных значениях числа элементов в множестве. Рис. 9.1 убеждает нас, что программа верно генерирует перестановки. Рис. 9.1. Перестановочная программа в работе! Исходный код программы находится в папке Перестановки Рекурс. 349
Проект «Правильные» перестановки Бесконечный цикл while Метод int Функция с параметрами Список Цикл for Цикл while Оператор return Глядя на Рис. 9.1⬆, вы легко заметите, что некоторые перестановки занимают не «свои» места. Например, перестановка 2 4 3 1 оказалась выше перестановки 2 4 1 3. Иногда нужно получить перестановки в лексикографическом порядке, то есть в таком, в каком стоят слова в словаре. Тогда перестановка 2 4 1 3 должна предшествовать перестановке 2 4 3 1, поскольку единица меньше тройки. Точно так же перестановка 4 1 2 3 должна занимать место выше перестановки 4 1 3 2. Перестановки очень часто используются в различных комбинаторных задачах, поэтому нам нужно только подобрать соответствующий алгоритм и перевести его на язык Питон. В отличие от разработки собственно алгоритма, перевод его на любой язык – дело техники. Функцию main мы опять используем для ввода числа элементов в списке и вызова функции Permutation: # -*- coding: Windows-1251 -*#Генерация перестановок в лексикографическом порядке #макс. число элементов в #множестве: MAX_ELEM = 8 #ГЛАВНАЯ ФУНКЦИЯ def main(): print('Генерируем перестановки') print() #бесконечный цикл ввода данных #пока пользователь не закроет программу #или не введет 0: while True: s = "Число элементов (1.." + str(MAX_ELEM) +") > print(s, end = '') " 350
#число элементов: nElem = int(input()) #если пользователь ввёл 0, #то программу закрываем: if (nElem == 0): return #генерируем перестановки: n = Permutation(nElem) print("Число перестановок = " + str(n)) print() Эта функция нерекурсивная, поэтому создание и инициализацию списка можно перенести непосредственно в неё. Как обычно, первую перестановку образуют последовательные числа 1..n, которые затем переставляются иначе, чем в предыдущей программе, чтобы сохранялся лексикографический порядок перестановок: #ГЕНЕРИРУЕМ ПЕРЕСТАНОВКИ def Permutation(n): #число перестановок: nPerm = 0 #список чисел --> #начальная перестановка: a = list(range(0, n+1)) a.append(0) j = ... while (j != 0): nPerm += 1 #печатаем очередную перестановку: print(*a[1:n+1], sep='', end='') print() i = n while (a[i-1] > a[i]): i -= 1 j = i - 1 h = a[j] while (a[i+1] > h): i += 1 a[j] = a[i] a[i] = h i = j+1 k = n while (i < k): h = a[i] a[i] = a[k] a[k] = h i += 1 k -= 1 351
return nPerm На Рис. 9.2 вы видите, что все перестановки заняли свои места. Рис. 9.2. Перестановки по порядку Обратите внимание, что в списке а первый и последний элементы всегда равны нулю, поэтому при печати не используются. Исходный код программы находится в папке Перестановки. 352
Проект Премия за изобретение Список Функция без параметров Функцмя с параметрами Оператор return Цикл for Генерирование перестановок – процесс занимательный и познавательный уже сам по себе, но нам перестановки нужны для решения задач. Задача 15 (16) из книги Удивительный мир чисел [КА86], страница 67: Четыре изобретателя независимо друг от друга придумали 10 различных приспособлений: Андрей - 1, Борис - 2, Виктор - 3 и Григорий - 4. Когда внедрили их в производство, то выяснилось, что каждое приспособление, разработанное одним и тем же изобретателем, даёт одну и ту же годовую экономию: • • • • чьё-то по 25 тыс. р., чьё-то по 125 тыс. р., чьё-то по 625 тыс. р., а чьё-то даже по 3125 тыс. р. В результате годовая экономия составила 8350 тыс. р. 10% этой суммы выделили на премию, которую распределили между изобретателями пропорционально вкладу каждого в общую годовую экономию. Сколько рублей получил каждый из них в качестве премии? Создадим список для хранения годовой экономии по приспособлениям каждого изобретателя: #список годовой экономии приспособлений: eco = [0, 25, 125, 625, 3125] Чтобы используемые значения имели индексы 1..4, мы добавляем нулевой элемент с нулевым же значением. Тогда: 353
eco[1] eco[2] eco[3] eco[4] = 25 = 125 = 625 = 3125 Для нахождения годовой экономии мы должны найти сумму годовых экономии для каждого изобретателя, но мы не знаем авторов изобретений. Если предположить, что eco[1] принадлежит Андрею, то его годовая экономия составит eco[1] * 1 = 25. Аналогично вычисляем для других изобретателей: eco[2] * 2 = 250 eco[3] * 3 = 1875 eco[4] * 4 = 12500 Итого: 14650. Не совпадает с условием задачи, в которой указано, что годовая экономия составила 8350. Это значит, что изобретения распределились иначе! Для удобства сведём все данные в таблицу: eco[1] 25 1 25 eco[2] 125 2 250 eco[3] 625 3 1875 eco[4] 3125 4 12500 Из неё видно, что годовая экономия каждого изобретателя n зависит от годовой экономии каждого изобретения eco[n] и номера самого изобретателя (точнее, от числа его изобретений). Мы предположили, что изобретатель №1 внедрил приспособление с годовой экономией eco[1] = 25, и так далее. Оказалось, что это не так. Теперь мы можем переставить красные числа в таблице иначе и снова посчитать общую годовую экономию: eco[1] 25 1 25 eco[2] 125 2 250 eco[3] 625 4 2500 eco[4] 3125 3 9375 354
25 + 250 + 2500 + 9375 = 12150 Опять не получилось! Мы должны составить новую перестановку изобретателей, найти для неё годовую экономию – и так продолжать до тех пор, пока сумма зелёных чисел в таблице не совпадёт с заданной. Поскольку алгоритм мы придумали, и главную роль в нём играют перестановки чисел 1..4, то вполне разумно обратиться к функции Permutation, который умеет генерировать перестановки. При этом мы должны учесть, что нам нужны не сами перестановки, а общая годовая экономия, так что эту функцию нужно доработать. Начнём новый проект с объявления переменной Summa, значение которой равно общей годовой экономии: # -*- coding: Windows-1251 -*#Кордемский, с.67, Задача 15 #годовая экономия: Summa = 8350 Функция main вызывает функцию Solve для решения поставленной задачи, получает от него число найденных решений и печатает его на экране: #ГЛАВНАЯ ФУНКЦИЯ def main(): print('Премия за изобретение') print() nVar = Solve() print("Найдены все варианты решения " + str(nVar)) print() Функция Solve создаёт список eco, о котором мы говорили выше, и передаёт его функции Permutation: #РЕШАЕМ ЗАДАЧУ def Solve(): #список годовой экономии приспособлений: eco = [0, 25, 125, 625, 3125] 355
return Permutation(4, eco) На этот раз функция Permutation получает не только число элементов в списке, но и список годовой экономии для приспособлений каждого изобретателя. Так мы учитываем специфику конкретной задачи. Мы не знаем наверняка, что задача имеет единственное решение, поэтому будем посчитывать варианты: #ГЕНЕРИРУЕМ ПЕРЕСТАНОВКИ def Permutation(n, eco): #число вариантов: nVar = 0 Для каждой перестановки а мы находим общую годовую экономию и сравниваем её с заданной. Для этого пишем функцию GetSumma, которой передаём очередную перестановку чисел 1..4 и список годовых экономий eco: def GetSumma(a, eco): n = len(a)-2 sum = 0 Найти общую годовую экономию очень просто: перемножаем элементы из списков a и eco с одинаковыми индексами и находим сумму этих произведений: for i in range(1, n+1): sum += a[i] * eco[i] Если полученная сумма совпадёт с указанной в задаче, то мы печатаем решение: if (sum == Summa): PrintResult(a, eco) А в функцию Permutation возвращаем True, если решение найдено, или False в противном случае: 356
return sum == Summa #список чисел --> #начальная перестановка: a = list(range(0, n+1)) a.append(0) j = ... while (j != 0): #нашли решение задачи: if (GetSumma(a, eco)): nVar += 1 Остальная часть кода функции Permutation не претерпела изменений, но возвращает она теперь не общее число перестановок, а число найденных решений задачи: . . . return nVar И последняя функция проекта - PrintResult – обстоятельно информирует нас о решении задачи. Иначе говоря, распечатывает ту таблицу, которую мы заполняли вручную: #ПЕЧАТАЕМ РЕШЕНИЕ ЗАДАЧИ def PrintResult(a, eco): print() n = len(a)-2 print("Число приспособлений: ", end='') for i in range(1, n+1): print(str(a[i]) + " ", end='') print() print("Годовая экономия за каждое приспособление: ", end='') for i in range(1, n+1): print(str(eco[i]) + " ", end='') print() sum = 0 print("Годовая экономия изобретателей: ", end='') for i in range(1, n+1): sum += a[i] * eco[i] print(str(a[i] * eco[i]) + " ", end='') 357
print() print("Сумма = " + str(sum)) print("Премия изобретателей: ", end='') for i in range(1, n+1): print(str(a[i] * eco[i] /10.0) + " ", end='') print() print() Итак, Григорий предложил 4 изобретения по 25 тыс. р., Андрей – 1 за 125, Виктор – 3 по 625 и Борис – 2 по 3125, что составляет как раз 8350 тыс. р. Также вы видите на Рис. 9.3, что каждый изобретатель получил 10% от суммы головой экономии: • • • • Григорий - 10 тыс. р. Андрей – 12,5 тыс. р. Виктор – 187,5 тыс. р. Борис – 625 тыс. р. Рис. 9.3. Изобретатели выявлены А задача имеет единственное решение, напрасно мы сомневались… Исходный код программы находится в папке Кордемский 067 15. 358
Проект Массивные перестановки Функцияс параметрами Оператор return Цикл for Список списков Цикл while Бесконечный цикл while Метод int Последний проект показал несовершенство функции Permutation: при решении каждой новой задачи её приходится изменять. Гораздо лучше иметь такую функцию, которая возвращает все перестановки в вызывающую функцию, а та делает с ними всё, что захочет. Тогда функция Permutation будет совершенно одинакова во всех программах! Итак, функция Permutation должна возвращать все перестановки. Их число легко найти по формуле для вычисления факториала: #ВЫЧИСЛЯЕМ ФАКТОРИАЛ ЗАДАННОГО ЧИСЛА def factorial(num): if (num == 0): return 1 fact = num for i in range(2, num): fact *= i return fact Все перестановки можно хранить в списке. Но каждую перестановку мы также храним в списке. Это значит, что для хранения перестановок нужен список списков, который напоминает двумерный массив. Общее число перестановок, а значит и число элементов в первом списке равно: #ГЕНЕРИРУЕМ ПЕРЕСТАНОВКИ def Permutation(n): #число перестановок: AllPerm = Factorial(n) Число элементов во втором списке в точности равно числу элементов множества (в перестановке). Теперь мы можем создать список списков для хранения всех перестановок: 359
#список перестановок: perms = [[0]*n for i in range(AllPerm)] #число перестановок: nPerm = 0 #список чисел --> #начальная перестановка: a = list(range(0, n+1)) a.append(0) j = ... while (j != 0): Каждую новую перестановку мы копируем в список, причём первый индекс равен номеру текущей перестановки 0..AllPerm-1, а второй изменяется от 0 до n-1. И мы должны учесть, что в списке а индексы на 1 больше: #запоминаем очередную перестановку: for id in range(1, n+1): perms[nPerm][id-1] = a[id] nPerm += 1 i = n while (a[i-1] > a[i]): i -= 1 j = i - 1 h = a[j] while (a[i+1] > h): i += 1 a[j] = a[i] a[i] = h i = j+1 k = n while (i < k): h = a[i] a[i] = a[k] a[k] = h i += 1 k -= 1 Остальная часть функции Permutation осталась без изменений, а возвратить функция Permutation должна список перестановок: return perms 360
Для проверки новой функции отправляемся в функцию main, в которой нужно изменить всего несколько строк: # -*- coding: Windows-1251 -*#Генерируем перестановки #макс. число элементов в #множестве: MAX_ELEM = 8 #ГЛАВНАЯ ФУНКЦИЯ def main(): print('Перестановки') print() #бесконечный цикл ввода данных #пока пользователь не закроет программу #или не введет 0: while True: s = "Число элементов (1.." + str(MAX_ELEM) +") > print(s, end = '') #число элементов: nElem = int(input()) #если пользователь ввёл 0, #то программу закрываем: if (nElem == 0): return " #генерируем перестановки: perms = Permutation(nElem) for perm in perms: print((" %2i"*nElem) % tuple(perm)) print() На Рис. 9.4 отчётливо видно, что новая функция работает без ошибок! 361
Рис. 9.4. Списки перестановок Исходный код программы находится в папке Перестановки Список. 362
Проект Сумма пятизначных чисел Функция без параметров Список списков Вложенные циклы for Применим новый способ генерирования перестановок в следующем проекте. Задача 11 (4-5) из книги Удивительный мир чисел [КА86], страница 65: Предположим, что из цифр 1, 2, 3, 4, 5, 6 составлены всевозможные пятизначные числа, причём все цифры в записи каждого числа различны. Чему равна сумма всех таких пятизначных чисел? Легко посчитать, что пятизначных чисел из цифр 1..6 можно составить столько же, сколько и перестановок из этих цифр. Первое место в пятизначном числе может занимать одна из 6 цифр, второе – одна из 5 оставшихся, и так далее. Всего: 6 * 5 * 4 * 3 * 2 = 6! Следовательно, мы должны сначала получить все перестановки из цифр 1..6, затем составить из них пятизначные числа, и наконец, найти их сумму. Все перестановки генерирует функция Permutation, которая и возвращает их в список perms (Рис. 9.5): # -*- coding: Windows-1251 -*#Кордемский, с.65, Задача 11 #ГЛАВНАЯ ФУНКЦИЯ def main(): print('Сумма пятизначных чисел') print() Solve() print() 363
#РЕШАЕМ ЗАДАЧУ def Solve(): #генерируем перестановки: perms = Permutation(6) Рис. 9.5. Пятизначные числа Поскольку перестановки состоят из 6 цифр, а нам нужны только 5, то мы будем составлять 5-значные числа из первых пяти цифр (или из последних пяти цифр – кто как пожелает). Зная цифры, само число легко вычислить, последовательно умножая число, составленное из 1..n-1 первых цифр на 10 и прибавляя к произведению следующую цифру: #находим сумму всех чисел: sum = 0 for p in range(0, Factorial(6)): num = 0 for n in range(0, 5): num = num*10 + perms[p][n] Полученное 5-значное число добавляем к промежуточной сумме: sum += num print("Сумма пятизначных чисел равна " + str(sum)) Запускаем программу и получаем ответ (Рис. 9.6). 364
Рис. 9.6. Сумма найдена Исходный код программы находится в папке Кордемский 065 11. 365
Проект Как собрать Дрим тим? Список Бесконечный цикл while Метод int Условный оператор if Оператор return Оператор continue Функцияс параметрами Цикл while Оператор break Условный оператор if-else Функция без параметров Давайте решим ещё одну жизненно важную задачу. Представим себе, что из 16 футболистов нам нужно выпустить на поле 11. Общее число футболистов для комбинаторики - это число элементов в множестве, а число игроков в команде – число элементов в подмножестве. Таким образом, нам нужно найти все подмножества заданного множества. Каждое подмножество какого-либо множества иначе называют набором из заданного числа элементов, или сочетанием. В сочетании порядок элементов не играет роли, поэтому мы отберём только 11 игроков в команду, а как между ними поделить номера на футболках (а, значит, и их амплуа на поле), - это уже задача тренера. Объявим новые глобальные переменные программы Сочетания: # -*- coding: Windows-1251 -*#Сочетания #ПРОГРАММА ДЛЯ ГЕНЕРИРОВАНИЯ #ВСЕХ ПОДМНОЖЕСТВ k МНОЖЕСТВА ЧИСЕЛ n=1..nElem #макс. число элементов в #множестве: MAX_ELEM = 30 #число элементов в #подмножестве: k = 0 #список, содержащий #очередное подмножество: a = [] 366
#номер подмножества: nSubset = 0 В функции main пользователь должен ввести два числа. Важно учесть, что в подмножестве элементов не больше, чем в множестве: #ГЛАВНАЯ ФУНКЦИЯ def main(): print('Генерируем сочетания') print() global k #бесконечный цикл ввода данных #пока пользователь не закроет программу #или не введет 0: while True: s = "Число элементов (1.." + str(MAX_ELEM) +") > print(s, end = '') #число элементов: nElem = int(input()) #если пользователь ввёл 0, #то программу закрываем: if (nElem == 0): return " #считываем число элементов подмножества: s = "Число элементов в подмножестве (1.." + str(nElem) +") > " print(s, end = '') k = int(input()) if (k > nElem): print("Повторите ввод!") print() continue #генерируем все подмножества: n = SubSet(nElem) print("Число сочетаний = " + str(n)) print() Затем функция SubSet генерирует все подмножества: //Генерируем все сочетания множества 1..nElem //из k элементов #Генерируем все сочетания множества 1..nElem 367
#из k элементов def SubSet(nElem): global nSubset global a a.append(0) for i in range(1, k+1): a.append(i) nSubset = 0 p = k while (p >= 1): #печатаем очередное подмножество: nSubset += 1 WriteSubset() if (k == nElem): break if (a[k] == nElem): p -= 1 else: p = k if (p >= 1): for i in range(k, p-1, -1): a[i] = a[p] + i - p + 1 return nSubset Функция печати очередного подмножества: #ПЕЧАТАЕМ ОЧЕРЕДНУЮ ПЕРЕСТАНОВКУ #ЭЛЕМЕНТОВ МНОЖЕСТВА def WriteSubset(): if (nSubset > 29): return s = "" if (nSubset < 1000): s += " " if (nSubset < 100): s += " " if (nSubset < 10): s += " " s += str(nSubset) + "> " for i in range(1, k+1): s += str(a[i]) + " " print(s) Запускаем программу и вводим наши данные: 16 элементов в множестве и 11 – в подмножестве. Программа выдаёт огромный список из 4368 разных команд (Рис. 9.7)! Комбинаторную задачу мы решили, а вот какая из этих команд действительно станет командой мечты, тут комбинаторика бессильна! 368
Рис. 9.7. Футбольный комбинатор Иногда полезно заранее знать, сколько же получится сочетаний при тех или иных исходных данных - не печатать же весь список подмножеств, если нам интересно узнать только их число! На этот случай в комбинаторике припасена несложная формула: В нашей программе n = nElem. Как видите, в комбинаторике без факториала не обойтись! Исходный код программы находится в папке Сочетания. 369
Проект Разменный пункт Список Бесконечный цикл while Метод int Функция с параметрами Цикл while Условный оператор if Вложенные циклы for Пусть у нас имеется сколько угодно монет любого достоинства и нам нужно составить из них сумму в n копеек. В комбинаторике подобная задача формулируется так. Сколькими способами можно записать натуральное число n в виде суммы: n = n1 + n2 + … + nk? (1) Порядок слагаемых при этом не учитывается, но принято записывать их в порядке убывания, то есть от больших слагаемых к меньшим. Такая запись называется стандартной формой разбиения числа n на k слагаемых. В англоязычной литературе разбиения чисел называют Integer Partitions. Если речь идёт обо всех вариантах разбиения, то их число обозначают P(n). Мы легко найдем, что: P(1) = 1> 1, потому что единицу невозможно представить иначе. P(2) = 2 > 2 11 P(3) = 3 > 3 21 111 P(4) = 5 > 4 31 22 211 1111 P(5) = 7 > 5 41 32 311 221 2111 11111 370
Эту процедуру можно продолжить, но уже сейчас явно прослеживается её рекурсивная сущность. Однако мы воспользуемся итерационным алгоритмом, описанным Витольдом Липским в книге [ЛВ88], Алгоритм 1.22. Нам нужно только перевести его на язык Питон. Заданное число мы будем, как обычно, получать от пользователя и обозначим его буквой n. Все слагаемые мы поместим в список adds, их общее число в текущем разбиении сохраним в переменной nAdds, а число найденных вариантов – в переменной nVar. И для алгоритма Липского нам потребуется ещё один список r, который показывает число повторов каждого слагаемого в разбиении. Например, для разбиения пятёрки 311 – тройка повторяется 1 раз, а единица – дважды. # -*- coding: Windows-1251 -*#Разменный пункт #слагаемые: adds = [] #число слагаемых: nAdds = 0 #число повторов каждого слагаемого: r = [] #число разбиений: nVar = 0 В функции main пользователь вводит число от 2 до 20, а функция Generate генерирует все его разбиения: #ГЛАВНАЯ ФУНКЦИЯ def main(): print('Разменный пункт') print() #бесконечный цикл ввода данных #пока пользователь не закроет программу #или не введет 0: while True: s = "Введите число 2..20 > " print(s, end = '') num = int(input()) #если пользователь ввёл 0, #то программу закрываем: if (num == 0): return 371
#генерируем все разбиения: Generate(num) print("Число разбиений = " + str(nVar)) print() В функции Generate мы создаём оба списка и переходим в функцию Part: #РАЗБИВАЕМ ЧИСЛО def Generate(n): global nVar, r, adds, nAdds adds = [0]*(n + 1) r = [0]*(n + 1) s = "Все разбиения числа " + str(n) + ": " print(s) Part(n) #Генерируем все разбиения числа n #и печатаем их def Part(n): global nVar, r, adds, nAdds #число разбиений: nVar = 0 #первое разбиение равно числу n: adds[1] = n r[1] = 1 nAdds = 1 Print(nAdds) #находим следующие разбиения: while (adds[1] > 1): sum = 0 if (adds[nAdds] == 1): sum += r[nAdds] nAdds -= 1 sum += adds[nAdds] r[nAdds] -= 1 l = adds[nAdds] - 1 if (r[nAdds] > 0): nAdds += 1 adds[nAdds] = l r[nAdds] = sum // l l = sum % l if (l != 0): nAdds += 1 adds[nAdds] = l r[nAdds] = 1 Print(nAdds) 372
Всякий раз, когда функция part сгенерирует новое разбиение, мы печатаем его на экране: #ПЕЧАТАЕМ РАЗБИЕНИЕ def Print (d): global nVar, r, adds, nAdds nVar += 1 s = "" for i in range(1, d+1): for j in range(1, r[i]+1): s += (str(adds[i]) + " ") print(s) И вот уже без особого напряжения мы получаем полный список разбиений числа (Рис. 9.8). Рис. 9.8. Все разбиения числа 12 Исходный код программы находится в папке Разменный пункт. 373
Проект Спортлото Бесконечный цикл while Метод int Условный оператор if Функция с параметрами Оператор break Условный оператор if-else Цикл for Оператор return В книге Эрхарда Берендса (Ehrhard Behrends) Fünf Minuten Mathematik: 100 Beiträge der Mathematik-Kolumne der Zeitung DIE WELT [BE13], на страницах 1-3 предлагаются вероятностные задачи, которые, как мы сейчас увидим, очень близки к задачам комбинаторным. В некоем большом городе, например, в Берлине или в Гамбурге кто-то забыл в автобусе зонтик. Предположим, что все телефонные номера в этих городах состоят из 7 цифр. Какова вероятность того, что вы (а это ведь вы нашли тот забытый зонтик!), случайно набирая номер, попадёте на того самого бюргера, который потерял зонтик? Скорее всего, не все возможные комбинации из 7 цифр могут быть телефонными номерами, но мы предположим, что это так и есть. Тогда общее число номеров равно 107 = 10 000 000. Если вы позвоните 1 раз, то вероятность составит 1 / 10 000 000. В общем случае вероятность можно вычислить как отношение числа благоприятных случаев (исходов) к их общему числу. По адресу http://www.youtube.com/watch?v=KA-gN1h15Ko вы можете посмотреть ролик о поиске владельца потерянного зонтика (Рис. 9.9). 374
Рис. 9.9. Учебно-познавательный ролик В Германии, точно так же как и в России, многие годы играют в лото 6 из 49 (Рис. 9.10). Рис. 9.10. Карточка немецкого «Спортлото» В книге утверждается, что при покупке 1 билета вы имеете всего 1 шанс из 13 983 816 угадать все 6 номеров. Это даже меньше, чем найти незадачливого владельца зонтика в большом городе. Однако вряд ли кто-нибудь станет звонить по телефону в надежде попасть на него, а вот в лото играют многие и охотно. И это легко понять: выиграть чужое гораздо приятнее, чем его вернуть. Но давайте проверим, правильно ли подсчитал вероятность автор книги. 375
«Клонируйте» проект Сочетания и сохраните его в новой папке под названием Lotto. Поскольку 6 номеров из 49 можно выбрать многими способами, то распечатывать их нет никакого смысла. А вот сохранить все сочетания в списке смысл есть. Действительно, шарики выпадают из барабана совершенно случайно, поэтому все комбинации совершенно равноправны, и это значит, что вам не нужно ломать голову над составлением числовых комбинаций – выбирайте случайно любую из списка и смело зачёркивайте числа! Если учесть, что в каждом билете может быть до 12 комбинаций (Рис. 9.11), то таким нехитрым способом вы быстро избавитесь от головной боли. Рис. 9.11. 12-кратная кароточка лото Хотя, надо признать, этот способ был бы хорош, если бы только вы играли в лото. Но кроме вас, ещё несколько миллионов человек надеются на удачу, поэтому даже в случае выигрыша вам придётся делиться с такими же везунчиками, как и вы. Чтобы избежать дележа, нужно выбирать «редкие», «маловероятные» комбинации чисел, чтобы никто другой их не нашёл. Как 376
вы помните, героиня комедии Спортлото-82 Таня поступила именно так, зачеркнув 6 первых чисел (Рис. 9.12). Рис. 9.12. Удачный выбор чисел И выиграла (Рис. 9.13)! Рис. 9.13. Увы, жизнь – это не кино! Таким образом, наша задача упрощается – мы уже не будем распечатывать сочетания, а ограничимся только их подсчётом: # -*- coding: Windows-1251 -*#Lotto #ПРОГРАММА ДЛЯ ПОДСЧЁТА #ВСЕХ ПОДМНОЖЕСТВ k МНОЖЕСТВА ЧИСЕЛ n=1..nElem #макс. число элементов в #множестве: MAX_ELEM = 49 #число элементов в 377
#подмножестве: k = 0 #список, содержащий #очередное подмножество: a = [] #номер подмножества: nSubset = 0 #ГЛАВНАЯ ФУНКЦИЯ def main(): print('Подсчитываем сочетания') print() global k #бесконечный цикл ввода данных #пока пользователь не закроет программу #или не введет 0: while True: s = "Число элементов (1.." + str(MAX_ELEM) +") > print(s, end = '') #число элементов: nElem = int(input()) #если пользователь ввёл 0, #то программу закрываем: if (nElem == 0): return " #считываем число элементов подмножества: s = "Число элементов в подмножестве (1.." + str(nElem) +") > " print(s, end = '') k = int(input()) if (k > nElem): print("Повторите ввод!") print() continue #считаем все подмножества: n = SubSet(nElem) print("Число сочетаний = " + str(n)) print() Для этого вполне годится готовая функция SubSet из нашего предыдущего проекта, в которой достаточно закомментировать всего одну строку: #Генерируем все сочетания множества 1..nElem #из k элементов def SubSet(nElem): global nSubset 378
global a a.append(0) for i in range(1, k+1): a.append(i) nSubset = 0 p = k while (p >= 1): #печатаем очередное подмножество: nSubset += 1 #WriteSubset() if (k == nElem): break if (a[k] == nElem): p -= 1 else: p = k if (p >= 1): for i in range(k, p-1, -1): a[i] = a[p] + i - p + 1 return nSubset Запускаем программу – и убеждаемся в правоте автора: 6 чисел из 49 можно выбрать 13 983 816 способами (Рис. 9.14). Рис. 9.14. Маловероятно! Если не нужно сохранять комбинации чисел в списке, то можно найти число сочетаний по формуле, которая нам известна из проекта Как нам собрать Дрим тим? 379
К сожалению, для подсчётов она малопригодна, так как в числителе стоит огромный факториал. Но нетрудно догадаться, что первые (n-k) чисел этого факториала сократятся с этими же числами в знаменателе, то есть числитель равен произведению чисел от (n-k+1) до n, а в знаменателе останется только факториал числа k. Изловчившись, мы легко напишем новую функцию для подсчёта сочетаний: def GetNumSubSets(n, k): chisl = 1 for i in range(n, n - k, -1): chisl *= i znam = Factorial(k) return chisl//znam #ВЫЧИСЛЯЕМ ФАКТОРИАЛ ЗАДАННОГО ЧИСЛА def Factorial(num): if (num == 0): return 1 fact = num for i in range(2, num): fact *= i return fact Вызываем функцию GetNumSubSets в функции main и получаем тот же результат, что и раньше: #считаем все подмножества: #n = SubSet(nElem) n = GetNumSubSets(nElem, k) print("Число сочетаний = " + str(n)) print() Аналогично мы можем узнать число раскладов в игре скат (Рис. 9.15). Рис. 9.15. Карточные расклады 380
Или число рукопожатий, которыми обменяются 14 человек, расходясь после праздника (примеры из книги) (Рис. 9.16). Рис. 9.16. Число рукопожатий В Чемпионате России по футболу участвуют 16 команд (Рис. 9.17). Сколько всего матчей они проведут между собой? Задача решается аналогично предыдущей, но следует учесть, что каждая пара команд сыграет дважды – дома и на выезде. Рис. 9.17. Таблица розыгрыша Чемпионата России по футболу 2013-2014 381
Поэтому команды проведут не 120 (Рис. 9.18), а 120 х 2 = 240 матчей. Рис. 9.18. Число матчей В современной России играют также в Гослото 6 из 45 и 5 из 36 (Рис. 9.19). Рис. 9.19. Новое российское лото Подсчитаем число вариантов и для них (Рис. 9.20). По сравнению с иноземными лотереями, российские более обнадёживающие! В Спортлото играют и в Италии – но по другим правилам. В одном из вариантов нужно угадать 5 чисел из 90. Легко подсчитать, что вероятность такого радостного события равна 1 / 43 949 268 (Рис. 9.21). 382
Рис. 9.20. Небольшая надежда всё-таки есть! Рис. 9.21. Итальянское лото Не забудьте поправить переменую: MAX_ELEM = 90 В варианте SuperEnaLotto (Рис. 9.22) нужно угадать уже 6 чисел из 90, что уменьшает шансы игроков до 1 / 622 614 630 (Рис. 9.23). «Нет, я так не играю!» - сказал бы Карлсон. В итальянском лото все неразыгранные деньги переходят в следующий тираж. А поскольку вероятность выигрыша главного приза невелика, то постепенно джек-пот может достигать огромных значений – до 100 миллионов евро (Рис. 9.24)! 383
Рис. 9.22. Попробуй угадай! Рис. 9.23. Везёт же итальянцам! Рис. 9.24. Вот это джекпот! 384
И тогда из соседних стран в Италию движутся караваны автобусов с жаждущими наживы джентльменами удачи. Вот так, сравнительно дёшево можно развивать туризм и познавательный интерес к своей стране… Чтобы показать, насколько мала вероятность выигрыша в немецкое Спортлото, Эрхард Берендс прибегает к такому наглядному примеру. Вы, конечно, понимаете, что в «цивилизованных» странах всё построено на личной наживе, а не на заботе о развитиии спорта, поэтому я употребляю слово Спортлото только по аналогии с советской версией лото. Расстояние между Берлином и Котбусом составляет около 140 км (Рис. 9.25) или в сантиметрах – 14 000 000. Как вы помните, практически столько же разных карточек можно заполнить в немецком лото. Рис. 9.25. Дорога дальняя! Теперь представьте, что вы едете на автомобиле по этой дороге с завязанными глазами (опасаясь побочных эффектов и последствий, автор книги уточняет: вас везут по этой дороге). Где-то на обочине стоит столб диаметром в 1 см (Рис. 9.26). Вы в любой момент времени можете бросить одноцентовую монету в надежде попасть в этот столб (или шест). 385
Рис. 9.26. Иллюстрация из книги Вероятность этого события приблизительно равна вероятности выигрыша в немецкое лото. Для итальянского лото SuperEnaLotto потребуется дорога в 6000 километров: из Рима через Берлин в Москву и обратно. По адресу http://www.youtube.com/watch?v=ODwm29lIt0E вы можете посмотреть ролик с экспериментом по бросанию монеты из автомобиля в шест. Рис. 9.27. Видеоэксперимент 386
Играйте, дорогие люди, в Спортлото, За это не осудит вас никто! (душераздирающий призыв из комедии Спортлото-82) Исходный код программы находится в папке Lotto. 387
Проект Жребий брошен! Цикл for Функция с параметрами Условный оператор if Оператор return Форматированный вывод Кто отжеребился, могут пройти к своим машинам. Кто отжеребился, не мешайте спортсменам, участвующим в жеребьёвке. Из спортивных репортажей Жребием может быть любой предмет, который можно бросить в лицо судьбе. У нас это будет монетка. Она очень удобна, когда, например, нужно распределить ворота команд на футбольном поле. А на третьем Чемпионате Европы по футболу в 1968 году именно монетка определила судьбу сборной СССР в полуфинале. Матч со сборной Италии, которая и принимала чемпионат, закончился вничью, а победителя тогда определяли таким немудрёным способом. Повезло итальянцам… Монета хороша тем, что у неё 2 стороны, на которые она с одинаковой вероятностью может упасть. Впрочем, студенты нашли у монеты и скрытые возможности: - Давайте монетку кинем. Если орлом упадёт, то пивка выпьем, если решка - то водочки. Если ребром упадёт - доспим, ну а если в воздухе зависнет, тогда учиться пойдём. Понятно, что если мы подбросим монетку 1 раз, то выпадет либо орёл, либо решка. Судить о вероятности их выпадения невозможно. Это значит, что число подбросов нужно значительно увеличить. А это уже утомительно, поэтому пусть виртуальный эксперимент проводит компьютер – ему ничего не стоит подбросить монету и 1 миллион, и даже 10 миллионов раз кряду! 388
Естественно, компьютер и одного раза не сможет подбросить настоящую монетку, но зато с помощью генератора псевдослучайных чисел он может имитировать (или симулировать – в хорошем смысле этого слова!) выпадение орлов и решек. В функции main мы задаём параметры эксперимента – число серий из 10 экспериментов и число подбрасываний монет в каждой серии: # -*- coding: Windows-1251 -*#Монета import random #ГЛАВНАЯ ФУНКЦИЯ def main(): print('Подбрасываем монетку') print() #число серий: ns = 10 for i in range(0, ns): #число бросаний: n = 1000000 orlov = Experiment(n) print("Орлов : Решек %i : %i" %(orlov, n - orlov)) print("Вероятность %f : %f" %(orlov/n, 1.0 - orlov/n)) print() Метод randint класса random с аргументами 0, 1 будет выдавать случайную последовательность нулей и единиц. Если мы примем 1 за орла, 0 – за решку, то только успевай считать! def Experiment(n): orlov = 0 for i in range(0, n): if (random.randint(0,1) == 1): orlov += 1 return orlov Я провёл тестирование нашей программы при 1 миллионе подбрасываниях в каждой серии. Рис. 9.28 показывает неплохое качество нашего экспериментального устройства! 389
Рис. 9.28. Подбрасываем виртуальные деньги Конечно, было бы странно, если бы при случайных бросаниях число орлов и решек в точности совпадало, но эти числа должны быть довольно близки друг к другу, что мы и видим на Рис. 9.28⬆. Исходный код программы находится в папке Монета. 390
Задания для самостоятельного решения Перестановки Подумайте, как вместо чисел в перестановках использовать другие элементы, например, буквы. Сочетания Напишите программу, которая вычисляла бы и печатала в списке число сочетаний. Ладейное окончание Научившись генерировать перестановки, мы, сами того не подозревая, решили знаменитую комбинаторную задачу: найти все варианты расстановки восьми ладей на шахматной доске так, чтобы они не били друг друга. Когда речь идёт о поиске всех вариантов, то обычно для решения задачи используют метод перебора с возвратами (по-английски backtracking). Однако число всех расстановок ладей 8! = 40320 невелико, и мы можем решить задачу полным перебором (brute force), учтя, конечно, тот факт, что на одной горизонтали может находиться единственная ладья. Тогда каждая перестановка чисел 1..8 служит решением задачи о ладьях. Например, перестановке 1 2 3 4 5 6 7 8 соответствует решение, представленное на Рис. 9.29. Действительно, если каждую ладью ставить на отдельную горизонталь Г, а в ней – на ту вертикаль, номер которой совпадает с числом, стоящим в Г-той позиции перестановки, то ни одна ладья не будет угрожать другим ладьям - и задача решена. Тысячная перестановка, которую выдаёт наша программа Генерируем перестановки, такая: 1 4 3 7 2 6 5 8. Если мы расставим ладьи согласно этой перестановке, то также получим решение задачи (Рис. 9.30). 391
Рис. 9.29. Перестановка ладей Таким образом, мы умеем находить все решения ладейной задачи, и вам нужно только и всего, что красиво вывести их на экран. Можно ограничиться крестиками X в консольном окне, хотя в картинках было бы лучше… Рис. 9.30. Одно из решений ладейной задачи 392
Ферзевой гамбит Если мы заменим ладей ферзями, то оба наших решения окажутся неверными. Ферзи – более серьёзные шахматные фигуры и могут больно ударить и по диагонали! С другой стороны, совершенно очевидно, что среди 40320 решений для ладей отыщутся и все 92 решения для ферзей. Например, такое (Рис. 9.31). Так что для решения проблемы с ферзями вам необходимо устроить проверку всех перестановок и отобрать только те, в которых ферзи не угрожают друг другу. Конечно, не лучший способ для решения этой задачи, но вполне пригодный. Рис. 9.31. Безобидные ферзи 393
Ответы Космический охотник 394
Литература [Нагибин88] Нагибин Ф.Ф., Канин Е.С. Математическая шкатулка М.: Просвещение, 1988. – 160 с. [100] В. А. Дагене, Г. К. Григас, К. Ф. Аугутис 100 задач по программированию М.:Просвещение, 1993. – 251 с. ISBN: 5-09-003864-3 395
[КА86] [КА96] Б.А. Кордемский, А.А.Ахадов Удивительный мир чисел (МАТЕМАТИЧЕСКИЕ ГОЛОВОЛОМКИ И ЗАДАЧИ ДЛЯ ЛЮБОЗНАТЕЛЬНЫХ) М.: Просвещение, 1986. – 144 с. М.: Просвещение, 1996. – 159 с 396
[ОО80] Оре О. Приглашение в теорию чисел М.: Наука, 1980 г. - 128 с. Библиотечка Квант, Выпуск 3 [ЗП88] Абрамов С.А. и др. Задачи по программированию Наука, 1988. – 224 с. ISBN: 5-02-013774-Х Серия: Библиотечка программиста 397
[ВНН88] Воробьёв Н.Н. Признаки делимости Наука. - 1988, 96 с. ISBN 5-02-013731-6 [БК85] Брудно А. Л. Каплан Л. И. Олимпиады по программированию для школьников Наука. - 1985, 96 с. 398
[BE13] Ehrhard Behrends Fünf Minuten Mathematik: 100 Beiträge der Mathematik-Kolumne der Zeitung DIE WELT Springer Spektrum. - 2013, 272 с. 3-е издание ISBN: 978-3-658-00998-4 [ГМ72] Гарднер Мартин Математические досуги М.: Мир, 1972. – 495 с. 399
[ЛВ88] Липский В. Комбинаторика для программистов Москва: Мир, 1988. – 200 с. [РВ11] Рубанцев Валерий Delphi в примерах, играх и программах. От простых приложений, решения задач и до программирования интеллектуальных игр Наука и Техника, 2011. – 672 с. ISBN: 978-5-94387-664-6 Серия: Самоучитель 400
Книги из серии Программирование на языке C# 5.0: Начальный уровень [CS10] Рубанцев Валерий Программирование на языке C# 5: Начальный уровень RVGames, 2014. – 620 с. Основы программирования на языке Си-шарп. [CS11] Рубанцев Валерий Программирование на языке C# 5: Практикум по решению задач начального уровня RVGames, 2014. – 420 с. Многочисленные проекты для укрепления навыков программирования на языке Си-шарп. 401
[CS12] Рубанцев Валерий Программирование на языке C# 5: Компьютерная графика. Начальный уровень RVGames, 2014. – 460 с. Основы компьютерной графики. [CS13] Рубанцев Валерий Программирование на языке C# 5: Тотальный тренинг по Си-шарпу. Начальный уровень RVGames, 2014. – 400 с. Разнообразные полезные и занимательные проекты на языке Си-шарп. 402