Text
                    1


Валерий Рубанцев Программирование для всех Python. Занимательная математика на Питоне 2
Бесплатное издание Все права защищены. Никакая часть этой книги не может быть воспроизведена в любой форме без письменного разрешения правообладателей. Автор книги не несёт ответственности за возможный вред от использования информации, составляющей содержание книги и приложений. Copyright 2022 Валерий Рубанцев Лилия Рубанцева 3
От автора В России Питон используется в учебных целях, и это вполне объяснимо. На уровне процедурного программирования Питон не имеет себе равных среди современных языков. Программы, написанные на Питоне, как правило, короче и яснее, чем, например, на Си-шарпе. В Питоне значительно меньше типов данных, что позволяет избежать многих проблем с их выбором и приведением. Тип int в Питоне даёт возможность работать с целыми числами произвольной длины, а тип Decimal – с вещественными числами любой точности. С их помощью можно разрабатывать простые, но эффективные приложения для решения математических, физических, химических и других задач, связанных с обработкой числовой информации. К несомненным достоинствам Питона следует отнести также: • он бесплатен • популярен во всём мире • прост в изучении, но используется профессиональными программистами • универсален • один и тот же исходный код можно запустить на компьютерах с любой из распространённых операционных систем: Windows, Mac OS X, Linux В этой книге подробно рассматривается решение почти 200 занимательных математических задач из различных литературных и интернетовских источников: Большая часть задач взята из двух книг по математике для школьников: • Нагибин Ф.Ф., Канин Е.С. - Математическая шкатулка • Кордемский Б.А., Ахадов А.А.- Удивительный мир чисел • В. А. Дагене, Г. К. Григас, К. Ф. Аугутис - 100 задач по программированию • Брудно А. Л. Каплан Л. И. - Олимпиады по программированию для школьников • Ehrhard Behrends - Fünf Minuten Mathematik: 100 Beiträge der Mathematik-Kolumne der Zeitung DIE WELT • Абрамов С.А. и др. - Задачи по программированию 4
• Проект Эйлер Кроме решения задач, мы разработаем и «вспомогательные» проекты и алгоритмы, которые необходимы при решении математических задач – и не только занимательных: • • • • • • • • • • • • Делимость чисел Наибольший общий делитель Наименьшее общее кратное Простые числа, Решето Эратосфена Факторизация чисел Совершенные числа Числовые ребусы Факториал Числа Фибоначчи Генерирование перестановок Генерирование сочетаний Разбиение числа на слагаемые Книга охватывает все ключевые элементы языка Питон. В конце некоторых глав имеются задания для самостоятельного решения. Поскольку исходный код программ с расширением *.py не содержит никакой специфической информации, то может быть запущен в любой среде разработки программ на Питоне. Чтобы пользоваться русскими буквами, в начале проектов вставляйте строку: # -*- coding: Windows-1251 -*- Валерий Рубанцев 5
Оглавление Python. Занимательная математика на Пит оне .................. 2 От автора ............................................................... 4 Оглавление ............................................................. 6 Глава #1. Числа в математике и в программах на Пит оне .... 13 Математические операции с целыми числами ............................................. 19 Математические операции с вещественными числами .............................. 23 Дополнительные операции с числами ............................................................ 24 Преобразование и приведение числовых типов ........................................... 25 Обобщение ................................................................................................................. 27 Глава #2. Практикум по вычислительным задачам .............. 31 Кошки-мышки ............................................................................................................ 31 Шахматная задача ..................................................................................................... 32 Число шахматных партий ..................................................................................... 34 Какая разница? .......................................................................................................... 35 Складывание бумаги ............................................................................................... 36 Три цифры - раз ....................................................................................................... 40 Три цифры - два ....................................................................................................... 42 Гугол ............................................................................................................................ 46 Задача Рачинского .................................................................................................... 47 Вычисление сложных арифметических выражений .................................... 49 Умножение чисел a5 x b5...................................................................................... 50 Умножение чисел, близких к 100........................................................................ 51 Умножение близких чисел .................................................................................... 54 Обобщение ................................................................................................................. 56 Глава #3. Делимость ................................................ 57 Признаки делимости чисел ................................................................................. 57 Проект Делится - не делится? ............................................................................ 63 Проект Таблица Пифагора .................................................................................. 71 Проект Таблица Пифагора 2 ............................................................................... 78 Проект Таблица Пифагора 3 ............................................................................... 83 Проект Таблица Пифагора 4 ............................................................................... 85 Проект Таблица Пифагора 5 ............................................................................... 88 6
Задачи с сайта Проект Эйлер .............................................................................. 90 Проект Задача 1 с сайта Проект Эйлер ............................................................ 92 Проект Четыре числа ............................................................................................. 95 Проект Совершенные числа ................................................................................. 97 Проект Дружественные числа ........................................................................... 100 Проект Дружественные числа 2 ........................................................................ 102 Проект Дружественные числа 3 ........................................................................ 104 Проект Назойливый остаток .............................................................................. 106 Проект Первый числовой фокус .......................................................................112 Проект Второй числовой фокус.........................................................................115 Проект Шестизначное число ...............................................................................117 Проект Тройка, семёрка и... только ..................................................................119 Проект Любопытное свойство чисел ............................................................. 124 Проект Как определил ошибку Чохбилмиш? ............................................... 127 Проект Шестизначный перенос ......................................................................... 129 Проект Наименьшее число ..................................................................................131 Проект Трёхзначное число 3 ............................................................................. 133 Задания для самостоятельного решения ....................................................... 134 Глава #4. НОД, НОК и компания ............................... 136 Проект Проект Проект Проект Проект Проект Проект Проект Проект Проект Наибольший общий делитель ............................................................ 136 Наименьшее общее кратное .................................................................141 НОК нескольких чисел ......................................................................... 144 Всезнающая статистика ........................................................................ 148 Восстановите потерянную цифру...................................................... 150 Снимите маску с одной цифры .......................................................... 152 На одно делится, на другое нет ......................................................... 153 Кто где живёт? ......................................................................................... 155 Три велосипедиста ................................................................................ 157 Задача 5 с сайта Проект Эйлер .......................................................... 159 Глава #5. Простые числа ......................................... 163 Проект Чудеса в решете Эратосфена ............................................................... 167 Проект Sieve .............................................................................................................171 Проект Простые числа ........................................................................................ 173 Проект Простые числа 2 .................................................................................... 177 Проект Prime ............................................................................................................ 180 Проект Простые числа-близнецы ................................................................... 184 Взаимно простые числа ....................................................................................... 187 7
Проект Суперпростые числа .............................................................................. 188 Проект Разложение числа на простые множители .....................................191 Проект Генератор простых делителей .......................................................... 195 Задача 3 с сайта Проект Эйлер ......................................................................... 196 Проект Спираль Улама ........................................................................................ 198 Проект Задача 7 с сайта Проект Эйлер ......................................................... 207 Проект Задача 10 с сайта Проект Эйлер ....................................................... 209 Проект Гипотеза Гольдбаха ................................................................................211 Задания для самостоятельного решения ....................................................... 216 Глава #6. Числовые ребусы....................................... 218 Проект Каковы жуки? ........................................................................................... 218 Проект Четыре "пари" .......................................................................................... 221 Проект Тайна трёх слагаемых ......................................................................... 223 Проект Меняем четыре буквы на четыре цифры ....................................... 227 Проект Коварная задача папы .......................................................................... 229 Проект Универсальный решатель ................................................................... 232 Задания для самостоятельного решения ...................................................... 240 Глава #7. Степени и корни ...................................... 242 Проект Кубическое число .................................................................................. 242 Проект И «хвост», и «грива» ............................................................................ 244 Проект Возведение в квадрат без операции умножения .................... 247 Проект Возведение в куб без операции умножения ............................ 249 Проект Зашифрованные жуки ........................................................................... 253 Проект Ж-Ж-Ж!....................................................................................................... 255 Проект Девять в квадрате ................................................................................... 257 Проект Пара чисел: 3149 и 3151...................................................................... 258 Проект Число 698 896 ......................................................................................... 261 Проект Числа 11 826, 12 363, 14 676 .............................................................. 264 Проект Числа 32 043 и 99 066 ........................................................................ 269 Проект Число 117 649 ......................................................................................... 269 Проект Красивые цепочки равенств ............................................................... 273 Задания для самостоятельного решения ...................................................... 275 Глава #8. Последовательности ................................... 277 Проект Факториал ................................................................................................. 277 Проект Факториальные нули ............................................................................. 279 Проект Факториалы .............................................................................................. 282 8
Проект Последовательность факториалов .................................................. 284 Проект Задача 20 с сайта Проект Эйлер ...................................................... 285 Проект Последовательность двойных факториалов................................ 286 Проект Последовательность простых чисел .............................................. 288 Проект Вьетнамская задача ................................................................................ 290 Проект Задача 14 с сайта Проект Эйлер ....................................................... 292 Числа Фибоначчи ................................................................................................... 297 Проект Числа Фибоначчи..................................................................................... 301 Проект Числа Фибоначчи 2 ................................................................................ 304 Проект Последовательность чисел Фибоначчи .......................................... 306 Проект Задача 2 с сайта Проект Эйлер ......................................................... 307 Проект «Избранные» числа ............................................................................... 309 Проект Безошибочный прогноз ......................................................................... 313 Проект Ошибочный прогноз .............................................................................. 317 Проект Нумерация страниц ............................................................................... 320 Проект Сколько страниц в книге?.................................................................... 323 Проект И такие есть числа ................................................................................ 325 Проект Трёхзначное число ................................................................................ 326 Проект Таких чисел только два ....................................................................... 328 Проект Ещё два числа ......................................................................................... 330 Проект Отгадать число, ничего не спрашивая ............................................. 331 Проект Три лягушки ............................................................................................ 334 Проект Гаусс .......................................................................................................... 337 Проект Ряд натуральных чисел ......................................................................... 341 Проект Ряд целых чисел .................................................................................... 342 Проект Ряд квадратов целых чисел ................................................................ 344 Проект Ряд квадратов целых чисел. Задача 1.............................................. 345 Проект Арифметическая прогрессия ............................................................. 347 Проект Арифметическая прогрессия 2 ......................................................... 349 Проект Нагибин88-15 .......................................................................................... 350 Проект Нагибин88-15 2 ....................................................................................... 351 Проект Нагибин88-128 ....................................................................................... 352 Проект Функция count .......................................................................................... 353 Проект Функция cycle........................................................................................... 353 Проект Функция repeat ........................................................................................ 355 Проект Геометрическая прогрессия ............................................................... 355 Проект Прогрессивные квадраты .................................................................... 358 Проект Плюс-минус ............................................................................................ 362 Проект Минус-плюс ............................................................................................ 364 Проект Дробный ряд ............................................................................................ 365 9
Проект Ещё один дробный ряд ......................................................................... 366 Проект Трёхзначное число 2 ............................................................................ 368 Проект Вычисляем пи и е ................................................................................. 370 Проект Вычисляем пи по методу Архимеда............................................... 385 Проект Тригонометрические функции ........................................................... 391 Проект Сотая цифра ............................................................................................. 396 Проект Случайные ферзи ................................................................................... 399 Задания для самостоятельного решения ...................................................... 404 Глава #9. Диофантовы уравнения и линейное программирование .................................................. 406 Проект На ферме .................................................................................................. 408 Проект Кролики и фазаны .................................................................................. 410 Проект Решите систему уравнений .................................................................. 414 Проект Сооружение для лаборатории ............................................................. 415 Проект И такие есть числа 3 .............................................................................. 418 Проект И такие есть числа 4 ............................................................................. 422 Проект Ящики ........................................................................................................ 423 Проект Путёвки ..................................................................................................... 426 Проект На базаре................................................................................................... 428 Проект Дедушка и внучка ................................................................................... 430 Проект Хитрое уравнение ................................................................................... 431 Проект Сколько у мамы дочерей и сыновей? ............................................. 433 Проект Из жизни Дефурнеля ............................................................................ 435 Проект Счётные палочки ................................................................................... 437 Задания для самостоятельного решения ...................................................... 439 Глава #10. Интересные числа .................................... 440 Проект Римские числа ........................................................................................ 440 Проект Примориалы ........................................................................................... 444 Проект Суперфакториалы .................................................................................. 447 Проект Среднее арифметическое .................................................................... 448 Проект Среднее геометрическое ..................................................................... 450 Проект Совершенные числа 2 ........................................................................... 452 Проект Последовательность простых чисел 2 .......................................... 455 История о том, как Леонард Эйлер открыл числа Каталана ................... 458 Проект Числа Каталана ........................................................................................ 461 Проект Числа Каталана 2 ................................................................................... 463 Проект Квадратные числа .................................................................................. 464 10
Проект Задача 6 с сайта Проект Эйлер ......................................................... 467 Проект Теорема Лагранжа ................................................................................. 469 Проект Задача 142 с сайта Проект Эйлер .................................................... 482 Проект Ряд треугольных чисел ........................................................................ 488 Проект Треугольные числа ............................................................................... 490 Проект Задача 12 с сайта Проект Эйлер ....................................................... 495 Проект Числа Армстронга.................................................................................. 497 Проект Числа Армстронга 2 .............................................................................. 500 Проект Числа Армстронга 3 .............................................................................. 502 Проект Паразитические числа ...........................................................................511 Числа-палиндромы .............................................................................................. 527 Проект Задача 4 с сайта Проект Эйлер ......................................................... 527 Пифагоровы тройки чисел ................................................................................ 533 Проект Пифагоровы тройки ............................................................................. 535 Задания для самостоятельного решения ...................................................... 538 Глава #11. Комбинаторика и теория вероятностей ............. 541 Перестановки .......................................................................................................... 541 Проект Генерируем перестановки................................................................... 543 Проект Функция permutations ........................................................................... 546 Проект «Правильные» перестановки ............................................................. 551 Проект Премия за изобретение ....................................................................... 553 Проект Массивные перестановки .................................................................... 559 Проект Сумма пятизначных чисел .................................................................. 562 Проект Как собрать Команду мечты? ............................................................. 565 Проект Функция combinations ............................................................................ 569 Проект Функция combinations_with_replacement ........................................ 570 Проект Дартс .......................................................................................................... 573 Проект Разменный пункт .................................................................................... 578 Проект Спортлото................................................................................................. 586 Проект Жребий брошен! ...................................................................................... 598 Задания для самостоятельного решения ...................................................... 600 Глава #12. Рекурсия ............................................... 603 Проект Проект Проект Проект Проект Рекурсия, или Сказочка про белого бычка .................................... 603 Нерекурсивная сказочка ....................................................................... 605 Ханойские башни ................................................................................... 606 Рекурсивный факториал ........................................................................611 Рекурсивное умножение ....................................................................... 615 11
Проект Рекурсивный модуль.............................................................................. 616 Проект Рекурсивные перевороты ..................................................................... 617 Проект Рекурсивные палиндромы ................................................................... 621 Проект Нерекурсивные палиндромы ............................................................. 623 Проект Рекурсивная считалка ........................................................................... 625 Проект Рекурсивные разбиения ........................................................................ 628 Проект Двадцать пятёрок ................................................................................... 630 Проект Двадцать пятёрок и больше................................................................ 635 Проект Треугольные числа ............................................................................... 640 Проект Рекурсивные числа Фибоначчи .......................................................... 643 Проект Рекурсивные анаграммы ...................................................................... 645 Проект Числовые рекурсии ............................................................................... 650 Проект Великолепная четвёрка ....................................................................... 654 Проект Рекурсивный тортик .............................................................................. 657 Проект Red and Black ........................................................................................... 662 Литература .......................................................... 670 12
Глава #1. Числа в математике и в программах на Питоне Числа правят миром. Пифагор Говорят, что числа правят миром. Нет, они только показывают, как правят миром. Иоганн Гёте Детский компьютер Как вы знаете из уроков математики, чисел бесконечно много, но их можно разбить на отдельные (под)множества по тем или иным признакам. Самые первые числа, которые придумали ещё первобытные люди, называются натуральными. Они используются для подсчёта различных предметов, например, яблок или палочек, на которых вы и сами учились считать в первом классе. Папа спрашивает у сына: - Скажи, сколько будет, если к трём грушам прибавить ещё две груши? Сын отвечает: - Не знаю, папа, мы в школе решаем задачи только про яблоки! Множество натуральных чисел обозначается большой латинской буквой N, поэтому само множество можно записать так: N = {1, 2, 3, ...}. Иногда к множеству натуральных чисел относят и нуль (отсутствие предметов 13
вообще): N0 = {0, 1, 2, 3, ...}. Множество натуральных чисел является подмножеством всех чисел и также бесконечно. Если к натуральным числам добавить отрицательные целые числа (и нуль), то получится множество целых чисел. Оно обозначается большой латинской буквой Z = {... -2, -1, 0, 1, 2, ...}. Нетрудно догадаться, что и целых чисел бесконечно много. В арифметике обычно используют именно целые числа, но встречаются алгебраические и геометрические задачи, которые невозможно решить без дробных чисел. Рациональные числа можно представить в виде простой (обыкновенной) дроби: m/n где: • m - целое число; • n - натуральное число, не равное нулю (вы, конечно, помните, что на нуль делить нельзя!). Множество рациональных чисел обозначается буквой Q. Если знаменатель дроби равен 1, то вся дробь равна числителю, то есть целому числу n. Таким образом, все целые числа являются в то же время и рациональными (множество целых чисел - это подмножество рациональных). Но не наоборот! Рациональные числа можно представить также в виде конечной десятичной дроби (1/2 = 0,5) или бесконечной периодической десятичной дроби (1/7 = 0,142857142857...). Иррациональные числа не могут быть представлены в виде простой дроби (а также в виде конечной или бесконечной десятичной периодической дроби). Таким образом, иррациональным числом называют любое число, представимое в виде бесконечной непериодической десятичной дроби. Примером такой дроби служит корень квадратный из двойки. Ир14
рациональность этого числа была известна уже древним математикам, которые доказали несоизмеримость стороны и диагонали квадрата. Иррациональные числа обозначают буквой I. Множество действительных, или вещественных чисел объединяет множества рациональных и иррациональных чисел. Их принято наглядно представлять в виде точек на числовой прямой (Рис. 1). Рис. 1. Числовая прямая Множество действительных чисел обозначают буквой R (от их латинского названия numerus realis). К иррациональным числам относятся знаменитые числа - π (пи, отношение длины окружности к диаметру) и е (основание натуральных логарифмов). Все рассмотренные нами числа - это подмножества комплексных чисел. Их можно представить в виде: a + bi где: • a, b - вещественные числа; • i – мнимая единица. Множество комплексных чисел обычно обозначается буквой С (от латинского слова complex). Наглядно связь между множествами чисел показывает Рис. 2. В программах на языке Питон чаще всего используют такие числовые типы данных: int – для целых чисел произвольной длины float – для вещественных чисел 15
Рис. 2 В записи вещественных чисел обязательно должна быть десятичная точка (а не запятая, как в математике!), если даже дробная часть отсутствует: 5.0 3. -7.123 Ноль целых можно не указывать, а сразу ставить точку: .5 В записи целых чисел десятичной точки быть не должно! Типу float соответствует тип double в других языках программирования. Точность вещественных чисел ограничена 17 знаками. Переменные типа float могут хранить числа в диапазоне: 2.2250738585072014e-308…1.7976931348623157e+308 Таким образом, очень маленькие и очень большие по абсолютной величине числа хранить в вещественных переменных нельзя! Для работы с комплексными числами имеется тип complex. 16
Модуль fractions умеет выполнять арифметические рациональными числами (натуральными дробями). действия с В третьей версии Питона появился класс Decimal, помощью которого легко манипулировать вещественными числами произвольной точности. Кроме собственно чисел, нам понадобятся и два «числовых» класса. Класс math нужен для математических вычислений, а класс random – для генерирования случайных чисел. На следующей странице вы можете снять учебный стресс, выполнив (если желаете – на время) забавный числовой тест. 17
Последовательно найдите числа от 1 до 100! 18
Математические операции с целыми числами Целые числа можно складывать и вычитать точно так же, как это делается в арифметике. Простые арифметические вычисления удобно проводить в среде разработки IDLE, которая поставляется вместе с Питоном. Запустите IDLE и вычислите следующие выражения (Рис. 1). Рис. 1 Как видите, знаки операций сложения и вычитания совпадают с математическими. В арифметических выражениях может быть сколько угодно чисел (операндов). Знак операции в программировании называют оператором, а числа, которые участвуют в операции, - операндами. Запись, состоящая из операторов, операндов и круглых скобок, называется выражением. Если в выражение состоит только из арифметических операций, то оно называется арифметическим. Между числами и знаками арифметических операций может быть любое число пробелов, но допустимо печатать их и вплотную друг к другу. Последний способ записи выражений сокращает запись, но делает её трудно читаемой. Лучше между знаками операций и операндами ставить ровно 1 пробел. Результат всегда вы всегда получите верный: Питону пробелы не нужны, а вот вам или пользователям вашей программы небезразлично, как вы оформите исходный текст программы, поэтому всегда разделяйте числа и знаки ровно одним пробелом. 19
Перед положительными числами можно ставить знак +, но этого обычно не делают. А вот перед отрицательными числами обязательно должен стоять знак – (Рис. 2). Рис. 2 Знаки операций умножения и деления отличаются от принятых в математике. Для умножения используют звёздочку (астериск), а для деления – косую дробную черту. Не путайте математическую звёздочку астериск с героем комиксов и мультфильмов Астериксом! В результате умножения целых чисел всегда получается целое число. А в результате деления – вещественное, если даже первое число делится нацело на второе (Рис. 3). Рис. 3 20
Запомните: знак / обозначает вещественное деление. Для целочисленного деления используют пару таких знаков (Рис. 4). Рис. 4 Результат целочисленного деления - целая часть частного - число типа int. Ещё один парный знак (2 звёздочки) обозначает операцию возведения в степень. Показатель степени может быть целым и вещественным (Рис. 5). Рис. 5 В первом случае результат – целое число, во втором – вещественное. И последняя арифметическая операция – деление по модулю. Знак операции тоже может вызвать удивление – это знак %, которым в математике обозначают проценты. В программировании проценты не нужны, поэтому этот знак приспособили под другие цели. Результат выполнения операции деления по модулю - остаток от деления первого числа на второе (Рис. Рис. 6). Рис. 6 Если остаток от деления равен нулю, то первое число нацело делится на второе, то есть кратно ему. Мы знаем, что чётными называются числа, которые кратны двум. Например, число 2022 – чётное (Рис. 7). Рис. 7 21
Если же остаток от деления числа на двойку не равен нулю, то число называется нечётным (Рис. 8). Рис. 8 Все арифметические операции выполняются слева направо согласно их приоритетам: • сначала возведение в степень, • затем умножение и деление • и в последнюю очередь – сложение и вычитание. Последовательность выполнения арифметических операций можно изменить с помощью скобок - точно так же, как в математике. Но в программах на Питоне скобки в арифметических выражениях должны быть только круглые, но не квадратные и не фигурные, которые имеют другое назначение. Также следите, чтобы скобки были парными: для каждой открывающей скобки должна быть и закрывающая. При этом открывающие и закрываюшие скобки должны следовать правилу матрёшки. Если в скобках (внешних) есть другие скобки (внутренние), то они должны целиком находиться внутри внешних скобок. Операция возведения в степень выполняется справа налево! Результат вычисления арифметического выражения 2 ** 2 ** 3 равен не 64, а 256, в чём легко убедиться:. 22
То есть сначала вторая двойка возводится в куб, а затем первая двойка – в восьмую степень. Если вы хотите возводить в степень слева направо, то используйте скобки: Приоритеты арифметических операций вы найдёте в таблице: Операции Действие () Круглые скобки ** Возведение в степень + - Унарный плюс Унарный минус * / // % Умножение Вещественное деление Целочисленное деление Остаток от целочисленного деления + - Сложение Вычитание Математические операции с вещественными числами К вещественным числам можно применять те же арифметические операции, что и к целым, но нужно учитывать особенности вычислений в Питоне: если в выражение входит хотя бы одно вещественное число, результат всегда будет вещественным (Рис. 1). Рис. 1 23
При целочисленном делении вещественных чисел результат – это целая часть частного, но вещественного типа. Дополнительные операции с числами В математике операцию абсолютной величины числа обозначают двумя вертикальными чёрточками: |-2|. В Питоне так не получится – нужно использовать функцию abs, а в круглых скобках указать число. Функция возвращает абсолютную величину (модуль) этого числа (Рис. 1). Рис. 1 Функция divmod(x, y) возвращает пару чисел (кортеж). Первое число – целая часть частного от деления x // y. Второе – остаток от деления x % y (Рис. 2). Рис. 2 Функция pow(x, y) с двумя параметрами возвращает xy – число х в степени y (Рис. 3). Рис. 3 Легко заметить, что целочисленный результат точный, а вещественный округляется до 17 значащих цифр. 24
Функция pow(x, y, z) с тремя параметрами возвращает xy % z, то есть остаток от деления степени xy на z (Рис. 4). Рис. 4 Вызовы функций выполняются раньше арифметических операций (Рис. 5). Рис. 5 Преобразование и приведение числовых типов Операция вещественного деления / возвращает вещественное число, если даже оба операнда целого типа. Если все операнды имеют целый тип и не используется операция вещественного деления, то результат операции целое число. Если хотя бы один операнд вещественного типа, то и результат будет иметь вещественный тип. В этом случае транслятор автоматически преобразует операнды целого типа к вещественному. Иногда нужно самостоятельно преобразовать операнд у другому типу. Такое преобразование типов называется приведением. Для приведения вещественных чисел к целому типу используйте функцию int (Рис. 1). Рис. 1 Функция int без параметров возвращает целочисленный нуль: 25
Эта функция умеет конвертировать строку в число (Рис. 2). Рис. 2 Но в строке должно быть записано правильное целое число, иначе программа завершится с ошибкой. По умолчанию функция int конвертирует строку в десятичное число, но функция int с двумя параметрами позволяет задать основание системы счисления для числа в строке (Рис. 3). Рис. 3 Для записи двоичных, восьмеричных и шестнадцатеричных чисел можно использовать префиксы. Основание системы счисления должно иметь значение в интервале 2..36 (Рис. 4). Рис. 4 Для приведения целых чисел к вещественному типу используйте функцию float (Рис. 5). Рис. 5 26
Функция float без параметров возвращает вещественный нуль: Функция float умеет конвертировать строки, в которых записаны правильные целые или вещественные числа (Рис. 6). Рис. 6 Иногда нужно, наоборот, конвертировать целое или вещественное число в строку. Для этого служит функция str (Рис. 7). Рис. 7 В Питоне имеется функция eval, которая вычисляет выражение, записанное в строке, и возвращает результат (Рис. 8). Рис. 8 Обобщение 1. В интерактивной оболочке IDLE удобно выполнять арифметические вычисления. 2. Все числа в Питоне делятся на 2 вида – целые и вещественные. 3. В записи вещественных чисел обязательно должна быть десятичная точка, которая отделяет целую часть от дробной. В математике вместо точки используют запятую – не перепутайте! 27
4. Числа могут быть положительными и отрицательными. Перед отрицательными числами ставят знак минус. Перед положительными можно ставить знак плюс, но этого обычно не делают. 5. Арифметические операции делятся на бинарные и унарные. В бинарных операциях участвуют два числа, между которыми ставят знак арифметической операции: + - * / // ** %. Числа в арифметических операциях называются операндами. В унарных операциях участвует единственный операнд. Перед ним ставят знак операции. Это может быть унарный плюс или унарный минус: +2 -2. Унарный плюс обычно не ставят, а унарный минус ставят перед отрицательным числом обязательно. Он изменяет значение числа на противоположное. Приоритет унарных операций выше, чем у деления и умножения, поэтому отрицательные числа можно записывать без скобок (Рис. 1). Рис. 1 Приоритет операции возведения в степень выше, чем унарных операций. Значение этого арифметического выражения равно -4, а не 4 (Рис. 2). Рис. 2 Сначала возводим двойку в квадрат: 22 = 4. Затем дописываем унарный минус 4 → -4. В математической записи это свойство арифметических операций видно более наглядно: -22 = 4. 6. Если в арифметической операции участвуют только целые числа и к ним не применяется операция вещественного деления, то результат операции – целое число. 7. Если в арифметическом выражении хотя бы одно число вещественное или применяется вещественное деление, то результат – вещественное число. 8. Порядок выполнения арифметических действий определяется приоритетами арифметических операций. Сначала слева направо выполняются операции с наиболее высоким приоритетом, потом – с более низким, и так далее. Операции возведения в степень выполняются справа налево. 9. Изменить порядок выполнения операций можно с помощью круглых скобок. 28
10. Выражения в скобках вычисляются в соответствии с приоритетами операций. Если в выражении имеются вложенные скобки, то сначала выполняются действия в самых глубоко вложенных скобках. Затем переходят к следующим по вложенности скобкам и так далее: ((1) 3 (2)) 11. Знаки операций умножения *, возведения в степень **, вещественного деления /, целочисленного деления // и деления по модулю % отличаются от математических. А скобки в арифметических выражениях могут быть только круглые. 12. Дробную черту / иногда называют на английский манер слешем (читайте: слэшем). Рассмотрим примеры: Арифметическая операция 6+3 6-3 6*3 6/3 6 // 3 6%3 6 ** 3 (6 – 3) * -3 (2 + 4 - 1) / 5 * 11 (3.1415 – 1.0) / 7.78 * (45 - 7) -2.67 * 3.14 + -3/2 – 7.01 3 * 4 + 5 – 6/2 Результат 9 3 18 2.0 2 0 216 -9 11.0 10.459768637532136 -16.8938 14.0 13. Для приведения вещественных чисел к целому типу и конвертирования строк в целые числа используйте функцию int. 14. Для приведения целых чисел к вещественному типу и конвертирования строк в вещественные числа используйте функцию float. 15. Для конвертирования чисел в строки служит функция str. 16. Вызовы функций имеют более высокий приоритет, чем арифметические операции. 29
17. Функция abs возвращает абсолютную величину числа. 18. Функция divmod(x, y) возвращает целую часть частного от деления числа x на число y и остаток от деления x на y. Задание для самостоятельного решения Числа и арифметические выражения встречаются практически в каждой программе, поэтому хорошенько потренируйтесь на вычислениях. Примеры вы найдёте в школьных учебниках. Но учитывайте различия между математикой и программированием. Особое внимание обращайте на тип результата – целый или вещественный. От этого зависит точность и надёжность ваших будущих программ. Старайтесь проверять свои «школьные» вычисления не по ответам в учебнике, а в программах на Питоне. В простых случаях для этого достаточно возможностей интерактивной оболочки IDLE. 30
Глава #2. Практикум по вычислительным задачам С помощью простых арифметических действий уже можно решать интересные задачи. Давайте попробуем! Кошки-мышки Александр Альбов в интересной и познавательной книге От абака до кубита. История математических символов (Рис. 1) приводит такую задачу из папируса Райнда: В семи домах сидят по семи кошек, и каждая поймала семь мышей. Сколько всего мышей они изловили? Рис. 1 Мы уже неоднократно решали эту задачу в других книгах. Автор приводит «современный способ решения»: семь возвести в третью степень. Со школьных времён мы помним квадрат семи - 49; осталось умножить 49 на 7 и получить 343. Египтянам приходилось семь раз сложить 7, зафиксировать результат 49 и семь раз сложить 49. Египтянам приходилось несладко, а вот современным школьникам значительно легче. Если они, конечно, выучили таблицу умножения. 31
А с Питоном нам и вычислять ничего не нужно. Достаточно записать арифметическое выражение и нажать клавишу ВВОД, чтобы получить ответ (Рис. 2). Рис. 2 Шахматная задача Вам, возможно, известна легенда про изобретателя шахмат, которого правитель Индии, царь Шерам решил отблагодарить за интересную игру и предложил ему самому назначить цену. И легенда, и задача встречаются во многих книгах по занимательной математике. Например, вы можете прочитать о ней в книге В. Литцмана Великаны и карлики в мире чисел, на странице 26. Изобретатель - его звали Сета - попросил заплатить ему зерном. А зёрнышки посчитать так: На первую клетку шахматной доски положить 1 зерно пшеницы, на вторую 2, на третью 4, на четвёртую 8, и так далее, каждый раз увеличивая число зёрен в 2 раза. На первый взгляд кажется, что в итоге получится всего горстка пшеницы. Так и подумал царь Шерам и приказал выдать скромному изобретателю 32
гораздо больше – целый мешок пшеницы. Но тот попросил отмерить ровно столько зёрен, сколько получится в результате вычислений. Шерам не стал спорить с изобретателем и повелел точно вычислить, сколько тому причитается зёрен. Когда придворные математики закончили свои долгие вычисления, то оказалось, что зёрен должно быть так много, что их просто не могло быть ни у Шерама, ни у кого другого на земле. Если отбросить «мелкие подробности», то нам нужно найти сумму чисел: 20 + 21 + 22 + 23 + 24 + … + 263 Эти числа образуют геометрическую прогрессию. Сумма её членов равна 264 – 1. При помощи операции возведения в степень мы без труда решим знаменитую шахматную задачу. На бумаге это трудное и долгое занятие, а вычислить сумму членов геометрической прогрессии на Питоне можно за несколько секунд. В интерактивной оболочке запишите выражение для суммы на питоньем языке, нажмите клавишу ВВОД и получите ответ на задачу (Рис. 1). Рис. 1 Число огромное, а мы нашли его за одно мгновение! В книге Перельмана Живая математика вы можете прочитать эту легенду, откуда заодно узнаете, что число называется: Восемнадцать квинтильонов четыреста сорок шесть квадрильонов семьсот сорок четыре триллиона семьдесят три биллиона семьсот девять миллионов пятьсот пятьдесят одна тысяча шестьсот пятнадцать Якобы так его назвали придворные математики, согласно легенде. Число верное, но квинтильоны и квадрильоны появились гораздо позже. 33
Опять же согласно легенде, царь не смог сдержать своего обещания, но в книге вы найдёте хитроумный способ выполнить его и тем самым не уронить своего царского достоинства. Вы хотите узнать как? – Тогда читайте книгу! Число шахматных партий Ещё одна задача, тесно связанная с шахматами. В книге Перельмана Занимательная алгебра рассматривается вопрос о приблизительном подсчёте всех возможных шахматных партий. При этом автор пользуется способом бельгийского математика М.Крайчика, изложенным в книге Математика игр и математические развлечения. Я не буду пересказывать содержание книги Перельмана, а сразу оглашу результат: (20 x 20)5 x (30 x 30)35 Дальше идут трудоёмкие вычисления, которые нам не нужны, потому что для Питона эти вычисления – детская задача (Рис. 1). Рис. 1 Это число точное, хотя число партий приблизительное. В книге Перельмана приводится более грубая оценка: 2 х 10116. Если вы не поленитесь и пересчитаете все цифры в полученном числе, то их окажется 117 штук. То есть результат в книге Перельмана верный, но округлённый. 34
Только не подумайте, что я воспользовался своим же советом и добросовестно пересчитал цифры. Питон и здесь всё сделает за нас, если его правильно попросить (Рис. 2). Рис. 2 Какая разница? Бен Орлин в книге Math with Bad Drawings. Illuminating the Ideas That Shape Our Reality (Рис. 1) приводит показательный пример математического взгляда на вещи. Рис. 1 Для людей, далёких от математики, выражения x2 и 2x отличаются только переменой мест буквы х и цифры 2, то есть незначительно. А математики знают, что разница между этими выражениями огромная. Если х = 10, то значение первого выражения равно (Рис. 2). Рис. 2 35
А второго? – Гораздо больше (Рис. 3). Рис. 3 Про быстрое возрастание степеней числа 2 мы уже знаем по шахматной задаче. А Бен Орлин приводит подобный пример для х = 100 (Рис. 4). Рис. 4 Если первое число всё ещё относительно небольшое, то второе – огромное! Если первое число можно сравнить с весом грузового автомобиля (в фунтах), перевозящего кирпичи, то второе число – это вес ста тысяч Земель. Складывание бумаги Как иногда удивительным образом стародавние задачи появляются в новом обличии! Казалось бы, вполне современная головоломка со складыванием листа бумаги не имеет с шахматной задачей родственных связей, но это не так. Обе поражающие воображение задачи основаны на свойствах показательной функции. В 28-ом математическом выпуске Академии занимательных наук профессор Круглов наглядно доказывает хомячку Циркулю, что лист бумаги формата А4 нельзя сложить вдвое более 6 раз (Рис. 1). Но в Интернете можно найти ролик, в котором показано, как такой же лист бумаги можно сложить 7 раз (Рис. 2). Фокус в том, что сначала листок нужно складывать по длинной стороне, а не попеременно по длинной – по короткой. 36
Рис. 1 Рис. 2 Нашлись умельцы, которые взяли огромный лист бумаги и свернули его 9 раз (Рис. 3). Давайте разбираться, почему бумага так упорно не желает складываться вдвое! Так как мы не проводим натурные испытания, то удовольствуемся простой математической моделью процесса складывания бумаги вдоль и поперёк. Она несущественно упрощает этот процесс, зато описывается элементарной формулой: d = a · 2n 37
d – общая толщина сложенного листа бумаги а – толщина самого листа n – число складываний Рис. 3 Возьмём достаточно тонкую бумагу толщиной a = 0.1 мм. В начале эксперимента весь «свёрток» имеет такую же толщину (Рис. 4). Пока всё нормально. Рис. 4 После трёх складываний толщина сложенного листа бумаги всё ещё меньше 1 миллиметра (Рис. 5). Но при последующих складываниях толщина листа стремительно растёт (Рис. 6). 38
Рис. 5 Рис. 6 После 9 складываний толщина достигнет 5 сантиметров, а после 12 – почти 410. Теперь легко представить размеры исходного листа бумаги, чтобы его можно было сложить пополам при такой толщине. Если конечная площадь верхней поверхности листа 40 квадратных сантиметров, то исходный лист при 12 складываниях должен быть в 4096 раз больше, то есть 163840 квадратных сантиметров, или 16,4 квадратных метра. На самом деле площадь исходного листа должна быть значительно больше, поскольку мы складываем не отдельные листы стопкой, а единственный лист, часть которого образует боковые поверхности. Также мы предполагаем, что лист размером 40 х 80 сантиметров толщиной 40 сантиметров ещё можно сложить вдвое. Но если 12 раз сложить вдвое огромный лист тонкой (!) бумаги ещё вполне возможно, то дальше толщина листа измеряется метрами (Рис. 7). 39
Рис. 7 Здесь мы наблюдаем ту же самую картину, что и при выкладывании зёрен на шахматную доску. Толщина листа бумаги растёт так быстро, что даже 15 складываний проделать не удастся. В книге Вальтера Литцмана Великаны и карлики в мире чисел, пятое издание которой вышло в Лейпциге в 1953 году, эксперимент со складыванием бумаги приводится как пример быстрого роста показательной функции. При складывании листа бумаги толщиной 1/10 мм 40 раз высота «стопки» превысит 100 000 километров! Три цифры - раз Яков Перельман в книге Занимательная арифметика (Рис. 1) предлагает решить Задачу №58: Какое самое большое число можно написать тремя цифрами, не употребляя никаких знаков действий? В качестве цифр хочется использовать три девятки – и этот выбор правильный. Второй шаг – правильно составить из этих девятое число. Например, так (Рис. 2). Возможно, вы придумаете и другие способы для записи чисел, но всё равно самое большое число, которое можно записать тремя девятками, - это (Рис. 3). То есть 9 в степени 9 в степени 9. 40
Рис. 1 Рис. 2 Рис. 3 Чтобы легче было соображать, вычислим степень 99 (Рис. 4). 41
Рис. 4 Тогда исходное выражение можно записать так: 9387420489 = ??? Если все предыдущие расчёты легко выполнить на Питоне (что я и сделал), то последнее число даже не пытайтесь вычислять! Оно состоит из 369 693 100 цифр, так что напечатать его вряд ли удастся. А если и удастся, то пользы от него – никакой. Более подробно об этом числе вы можете прочитать в указанной выше книге. Три цифры - два Эта задача нашла продолжение в другой книге Перельмана – Занимательная алгебра (Рис. 1). Но здесь задачи проще. Рис. 1 42
В первой задаче требуется, не употребляя знаков действий, записать тремя двойками наибольшее число. «Подвох» задачи заключается в невольном переносе решения задачи с девятками на задачу с двойками. Но получается (Рис. 2) Рис. 2 совсем небольшое число (Рис. 3). Маловато будет! Рис. 3 Даже «простое» число 222 заметно больше. Следующий претендент на лавры победителя – число 222 (Рис. 4). Рис. 4 Но и оно сравнительно невелико. А правильный ответ такой (Рис. 5). Рис. 5 Во второй задаче место двоек занимают тройки. 43
Здесь двухэтажная степень (Рис. 6) даёт хороший результат (Рис. 7). Рис. 6 Рис. 7 Но не лучший! А побеждает опять одноэтажная степень (Рис. 8). Рис. 8 В этой задаче можно обойтись вообще без вычислений, если заметить, что 3 в кубе – это 27, то есть показатель степени в первом случае меньше, чем во втором (33). Третья задача продолжает этот ряд, и теперь нужно составить наибольшее число из трёх четвёрок. Так как 44 = 256, то двухъярусная степень значительно больше одноярусной с показателем степени 44 (Рис. 9). Рис. 9 44
Читая книгу дальше, вы узнаете, почему для чисел 2 и 3 выгоднее одноярусная степень, а для больших двухъярусная. История с тремя одинаковыми цифрами продолжилась в интересной математической книге для школьников (Рис. 10). Рис. 10 На странице 30 дана для ручных вычислений вот такая задача (Рис. 11). Рис. 11 Здесь почему-то авторы книги нарушили логику и вместо числа 44 употребили число 40. Впрочем, суть задачи от этого не пострадала (Рис. 12). 45
Рис. 12 Гугол Число, которое изображается единицей с сотней нулей, называется гуголом: 10 000 000 000 000 000 000 000 000 000 000 000 000 000 000 000 000 000 000 000 000 000 000 000 000 000 000 000 000 000 000 000 000 000 Мы легко получим его в интерактивной оболочке (Рис. 1). Рис. 1 Но интересно происхождение названия этого числа. Его придумали не математики, а племянник американского математика Эдварда Каснера в 1938 году. Племянника звали Милтон Сиротта, и тогда ему было 9 лет. Эдвард Каснер прогуливался со своими племянниками по парку, и речь у них зашла о числе, у которого 100 нулей. Число такое большое, что им нечего обозначать. Именно поэтому оно не имело собственного названия. Маленький Милтон предложил назвать его гуголом. В 1940 году Эдвард Каснер и Джеймс Ньюмен написали книгу Mathematics and the Imagination (Математика и воображение) (Рис. 2), в которой и рассказали об этом числе. Так это число получило своё имя! 46
Рис. 2 По-английски название гугол пишется как googol. Название известной поисковой машины и компании Google произошло от названия этого числа. А правильное математическое название этого числа: десять дуотригинтиллионов или десять седециллиардов. По той же легенде, племянник придумал название гуголплекс (googolplex) для числа, у которого гугол нулей после единицы. Понятно, что это название – просто курьёз, потому что в природе нет ничего, что можно было бы обозначить таким числом. Задача Рачинского На картине русского художника Николая Петровича Богданова-Бельского Устный счёт. В народной школе С. А.Рачинского (1895) вы можете видеть интересное арифметическое выражение (Рис. 1). 47
Рис. 1. Картина и арифметический пример В журнале Наука и жизнь, № 7 за 2006 год, на страницах 50-51 обсуждаются способы устного решения этой задачи. Задача легко решается, если догадаться, что сумма трёх первых квадратов равна сумме двух последних и равна … вы уже догадались чему? Если не догадались, то давайте вычислим эти суммы на Питоне (Рис. 2). Рис. 2 Теперь задачу легко решить и в уме – получается двойка (не опять двойка, а просто двойка!) В книге Перельмана Занимательная алгебра вы найдёте доказательство, что имеется ещё одна последовательность из 5 чисел, обладающая таким же свойством: сумма квадратов первых трёх чисел в точности равна сумме квадратов последних двух чисел. Но эта последовательность совершенно неинтересная: -2, -1, 0, 1, 2 48
Иногда эту задачу обобщают, то есть не ограничивают длину последовательности. Такие ряды чисел называются последовательностями Рачинского. Они состоят из квадратов идущих подряд чисел, которые можно разбить на 2 группы с равными суммами так, что во второй группе на 1 число меньше, чем в первой: 32 + 42 = 52 = 25 212 + 222 + 232 + 242 = 252 + 262 + 272 = 2030 362 + 372 + 382 + 392 + 402 = 412 + 422 + 432 + 442 = 7230 Проверьте эти равенства на компьютере! Вычисление сложных арифметических выражений Как вы уже знаете, арифметические действия выполняются согласно их приоритетам. Старшинство скобок самое высокое. Затем идёт возведение в степень, а после него умножение, деление и деление по модулю. В последнюю очередь выполняют действия сложения и вычитания. Действия с одинаковым приоритетом выполняются последовательно слева направо (кроме возведения в степень). Понятно, что Питон сумеет вычислить любое арифметическое выражение, которое задают в школе, поэтому придумаем для него задание сложнее: (67 + 7 * 23) + 23**4 – 222 * 12 В программировании, как и в математике, не принято целиком вычислять длинные, сложные выражения, поэтому будем вычислять по частям. Для выражения в скобках (67 + 7 * 23) последовательность действий такая: 1. Двойку возводим в куб. Получаем 8 2. 7 умножаем на 8. Получаем 56 3. Находим сумму чисел 67 и 56. Получаем 123 Всё верно (Рис. 1). Рис. 1 49
Выражение 23**4 самое сложное. Я даже не смог записать его в текстовом редакторе. Здесь нужно выполнить 2 возведения в степень. Как вы уже знаете, возведение в степень выполняется справа налево. Вычисляем выражение 222 * 12: 1. Возводим 22 в квадрат. Получаем 484 2. Умножаем 484 на 12. Получаем 5808 Тоже всё верно (Рис. 2). Рис. 2 Теперь можно вычислить и всё выражение целиком (Рис. 3). Рис. 3 Компьютер – это хороший помощник в математике, но не всегда он бывает под рукой. А в некоторых случаях при расчётах вполне можно обойтись и без него. Если знать некоторые математические трюки и хитрости. Например, произведение некоторых чисел можно более простым способом, чем обычно. Умножение чисел a5 x b5 Посмотрите на таблицу умножения двузначных чисел, оканчивающихся на 5: 15 x 15 = 225 15 x 25 = 375 15 x 35 = 525 15 x 45 = 675 15 x 55 = 825 15 x 65 = 975 15 x 75 = 1125 15 x 85 = 1275 25 x 25 = 625 25 x 35 = 875 25 x 45 = 1125 25 x 55 = 1375 25 x 65 = 1625 25 x 75 = 1875 25 x 85 = 2125 35 x 35 = 1225 35 x 45 = 1575 35 x 55 = 1925 35 x 65 = 2275 35 x 75 = 2625 35 x 85 = 2975 50
45 x 45 = 2025 45 x 55 = 2475 45 x 65 = 2925 45 x 75 = 3375 45 x 85 = 3825 55 x 55 = 3025 55 x 65 = 3575 55 x 75 = 4125 55 x 85 = 4675 75 x 75 = 5625 75 x 85 = 6375 85 x 85 = 7225 65 x 65 = 4225 65 x 75 = 4875 65 x 85 = 5525 Пусть a – число десятков первого числа, а b – число десятков второго числа. Тогда: - если сумма десятков чётная, то произведение закачивается на 25, а число сотен = a x b + (a + b) : 2 - если сумма десятков нечётная, то произведение закачивается на 75, а число сотен = a x b + (a + b - 1) : 2 Сначала вам этот способ умножения может показаться сложным, но небольшая тренировка – и вы сможете легко перемножать такие числа в уме. Умножение чисел, близких к 100 Если оба сомножителя отличаются от 100 не более чем на 10, то их легко перемножить даже в уме. Рассмотрим примеры. 1. Оба числа меньше 100. 93 х 98 Число 93 на 7 меньше 100, а число 98 – на 2. Разности нужно записать со знаком минус так: 93 -07 98 -02 Последние 2 цифры результата равны произведению чисел 2 и 7: 2 х 7 = 14 51
Чтобы найти первые 2 цифры, нужно из числа 93 вычесть 2 или из числа 98 вычесть 7. Разность будет одинаковой - 91, поэтому выбирайте тот способ, который вам больше нравится. При нахождении разности уменьшаемое – это один из сомножителей, а вычитаемое – то число, которое находится по диагонали от него (Рис. 1). Рис. 1 Итак, произведение – это 4-значное число, первые 2 цифры которого – 91, а последние 2 – 14: 93 х 98 = 9114 Новый способ умножения проверяем на компьютере (Рис. 2). Рис. 2 Результат сходится. 2. Оба числа больше 100. 109 х 101 Первый сомножитель на 9 больше 100, а второй – на 1. Записываем эти разности со знаком плюс: 109 + 09 101 + 01 Последние 2 цифры результата равны произведению разностей: 9 х 1 = 09 Первые 3 цифры результата равны 109 + 1 или 101 + 9 = 110: 52
109 х 101 = 11009 Проверка на компьютере показывает, что вычисления верные (Рис. 3). Рис. 3 Этот способ работает и тогда, когда сомножители отличаются от 100 больше, чем на 10. Но тогда придётся перемножать двузначные числа: 81 x 91 → 81 -19 91 -09 Последние 2 цифры произведения - 71: 19 х 9 = 171 Так как при перемножении разностей получилось 3-значное число, то первая цифра (1) – это перенос в разряд сотен. Первые 2 цифры: 81 – 9 = 91 – 19 = 72 Записываем результат, добавляя к сотням 1: 81 x 91 = 7371 Компьютер подтверждает правильность решения (Рис. 4). Рис. 4 3. Одно число меньше 100, а второе - больше. 95 х 104 Первый сомножитель на 5 меньше 100, а второй – на 4 больше. Первую разность записываем с минусом, а вторую – плюсом: 53
95 -05 104 +04 Произведение разностей отрицательное: -5 х 4 = -20 Поэтому мы должны занять 1 из разряда сотен, и тогда последние 2 цифры равны: 100 – 20 = 80 Первые 2 цифры вычисляем, как обычно, но не забываем вычесть 1: 95 + 4 – 1 = 104 – 5 – 1 = 98 Окончательный результат: 95 х 104 = 9880 Проверяем на компьютере (Рис. 5). Рис. 5 И опять всё посчитано без ошибок! Эти способы можно использовать, когда оба сомножителя близки к 1000, 10000 и так далее. Умножение близких чисел Если сумма единиц двух сомножителей равна 10, а остальные цифры одинаковы, то произведение легко найти. Пусть нам нужно найти такое произведение: 48 х 42 54
Последние 2 цифры результата равны произведению последних цифр сомножителей: 8 х 2 = 16 Первые 2 цифры равны произведению первой цифры на число, которое на 1 больше, то есть: 4 х (4 + 1) = 20 Значит, 48 х 42 = 2016 Проверяем на компьютере (Рис. 1). Более сложный пример: Рис. 1 296 х 294 Последние 2 цифры: 6 х 4 = 24 Первые цифры равны: 29 х (29 + 1) = 870 Пример решён: 296 х 294 = 87024 Обязательная проверка на компьютере. Как и в любом другом деле, при таких вычислениях нужна тренировка, но зато потом вы сможете удивлять своим мастерством одноклассников, друзей и родителей! 55
Обобщение 1. С помощью Питона можно легко решить любые вычислительные задачи. Это сделать гораздо проще, чем на бумаге. При этом результат всегда получится верным, потому что Питон не делает ошибок (а вот вы вполне можете наделать ошибок и на компьютере!). 2. Не нужно решать на компьютере все задачи подряд. Нужна тренировка и в «бумажных», и в умственных упражнениях. В таких случаях правильнее использовать компьютер для проверки своих действий. 3. Трудоёмкие вычисления, конечно, лучше сразу выполнять на компьютере. Так вы сможете решить множество интересных математических задач, которые при ручном решении превращаются в мучение и отвращают от математики. Всё-таки математика – это красивая наука для ума, а не тяжкий труд для рук. 56
Глава #3. Делимость Целые числа и их свойства изучает теория чисел, или высшая арифметика. В математических задачах (да и в жизни тоже) нередко нужно быстро определить, делится ли одно число на другое или нет. При этом сам результат деления неважен. У каждого натурального числа имеется, по крайней мере, два делителя - это единица и само число (у единицы они совпадают!). Если других делителей нет, то число называется простым. К ним мы вернёмся немного позже, а сейчас давайте вспомним признаки (то есть правила) делимости. Если делитель – натуральное число, но на него можно разделить любое другое натуральное число – либо нацело, либо с остатком: делимое : делитель = частное + остаток Нас интересует только деление чисел, при котором остаток от деления равен нулю. Признаки делимости чисел Признак делимости на 2 Самый простой признак: число делится на 2 только тогда, когда его последняя цифра равна 0, 2, 4, 6 или 8. Если число делится на 2, то оно называется чётным, если не делится - нечётным. Примеры чётных чисел: 2022, 92, 4, 76, 58. Нечётных: 2023, 91, 5, 77, 61. Иногда этот признак формулируют проще: число делится на 2 тогда и только тогда, когда его последняя цифра чётная. Здесь и далее мы для краткости употребляем слово цифры, но вы должны понимать, что речь идёт об однозначных числах, которые записываются этими цифрами. 57
Признак делимости на 3 Число делится на 3 тогда и только тогда, когда сумма его цифр делится на 3. Например, число 2013 кратно 3, поскольку сумма его цифр равна 6: 2 + 1 + 3 = 6 (при подсчёте нули не учитываем). Если сумма цифр также выражается не однозначным числом, то следует найти сумму его цифр. Если в результате сложения цифр получится одно из чисел 3, 6 или 9, то число делится на 3. В противном случае – не делится. Например, сумма цифр числа 123456789 равняется 45. Число двузначное – опять находим сумму его цифр: 4 + 5 = 9. Получили девятку – значит, исходное число 123456789 делится на три. Признак делимости на 4 Очевидно, что числа, кратные четырём, должны быть чётными. Но этого мало, поэтому мы оставляем от числа только две последние цифры и рассматриваем получившееся двузначное число. Если число сразу двузначное, то ничего отбрасывать не нужно. А если однозначное, то достаточно вспомнить таблицу умножения. Если это двузначное число делится на 4, то и всё число также делится на четыре. Если число сразу двузначное, то ничего отбрасывать не нужно. А если однозначное, то достаточно вспомнить таблицу умножения. Чтобы ещё упростить проверку, сложите число десятков с половиной единиц. Если сумма чётная, то исходное число делится на 4, в противном случае не делится. Проверим число 2022. Число из последних двух цифр равно 22. Оно на 4 не делится, значит, 2022 не кратно четырём. Возьмём другое число 58
4567896. Оставляем две цифры - 96. Складываем 9 с половиной от 6, то есть тройкой и получаем 12. Это число кратно четырём, значит, число 4567896 делится на 4. Признак делимости на 5 Это правило очень похоже на признак делимости на двойку. Число делится на 5, если оно оканчивается на 0 или 5. Признак делимости на 6 Число делится на 6, если одновременно выполняются признаки делимости на 2 и 3. Признак делимости на 7 Хорошего признака делимости чисел на 7 не существует, зато имеется немало достаточно сложных и запутанных. Из них мы выберем один – самый простой и «универсальный». Разбиваем заданное число, начиная с конца, на группы, состоящие из трёх цифр. Например, если мы проверяем число 4567896, то получим три группы цифр: 4 567 896 3 + 2 - 1 + Кстати говоря, в книгах так зачастую и печатают длинные числа, чтобы легче было распознать разряды сотен, тысяч и так далее. В программах на Питоне длинные числа разбиваются на группы знаком подчёркивания: 1_234_567_890 Теперь первое (считаем сзади!) число мы берём со знаком плюс, второе со знаком минус, третье – снова о знаком плюс. То есть знаки плюс и ми59
нус чередуются. Составляем из чисел с их знаками арифметическое выражение и вычисляем его значение: 896 – 567 + 4 = 333 Если результат делится на 7, то и всё число также делится на 7. В противном случае не делится. В нашем примере число 333 на 7 не делится, значит, этот вывод относится и к исходному числу 4567896. Признак делимости на 8 Число делится на 8, если оно чётное, а число, составленное из трёх последних цифр, делится на 8. Так как делить трёхзначное число на 8 тоже нелегко, то можно воспользоваться тем же приёмом, что и в признаке делимости на 4. Тысяча делится на 8 без остатка. Любое число тысяч также разделится на 8, поэтому разряды тысяч и далее в проверяемом числе можно не учитывать. Рассмотрим три последние цифры. К числу, образованному первыми двумя цифрами, добавьте половину единиц, а затем к числу десятков добавьте половину единиц получившегося числа. Если результат - чётное число, то исходное число делится на 8. Проясним этот алгоритм на примере. Начнём с того же числа 2018. Последние три цифры дают двузначное число 18, которое на 8 не делится. Следовательно, не делится и число года. Возьмём другое число 123457928. Оставляем для проверки трёхзначное число 928. Число из первых двух цифр равно 92. Складываем его с половиной единиц - 4 - и получаем 96. Дальше действуем, как в признаке делимости на 4: 9 + 3 = 12. Это число кратно двум, поэтому всё число 123457928 делится на 8. Признак делимости на 9 Этот признак напоминает правило для тройки. Число делится на 9 тогда и только тогда, когда сумма его цифр делится на 9. Раньше мы 60
установили, что число 2013 делится на 3, а сумма его цифр равна шести. Поэтому, согласно этому признаку делимости, на 9 оно не делится. Если сумма цифр выражается не однозначным числом, то следует найти сумму его цифр. То есть действовать так же, как и в признаке делимости на 3. Признак делимости на 10 Ещё проще, чем признак делимости на 5. Число делится на 10 тогда и только тогда, когда оно заканчивается на 0. Например, число 2020 делится на 10, а число 2022 не делится. Признак делимости на 11 Самое любопытное правило; не все его знают, но оно помогает очень просто определить, делится ли, например, номер автобусного билета на 11. Чтобы узнать, делится ли число на 11, нужно подсчитать отдельно сумму цифр, стоящих на нечётных и чётных местах в исходном числе. Если они равны, то число кратно 11. В противном случае нужно из первой суммы вычесть вторую. Если разность делится на 11, то и всё число делится на 11. Если сумма первых трёх цифр равна сумме трёх последних, то такой билет называется счастливым. Например, число 123453 делится на 11, так как 1 + 3 + 5 = 2 + 4 + 3 = 9. А число 123456 не делится (проверьте сами!). Другой признак делимости на 11 полностью совпадает с признаком делимости на 7, но делить сумму чисел нужно на 11. 61
Признак делимости на 12 Число делится на 12 тогда и только тогда, когда одновременно выполняются признаки делимости на 3 и 4. Признак делимости на 13 Признак делимости на 13 тот же самый, что для чисел 7 и 11 (второй способ), но делить сумму чисел нужно на 13. Поэтому я недаром назвал этот признак универсальным. Интересно, что наименьшее число, которое одновременно делится на 7, 11 и 13, равняется 7 х 11 х 13 = 1001, то есть сказочному числу арабских ночей. Признак делимости на 19 Признак делимости чисел на 19 хорошо описан в книге Якова Перельмана Занимательная алгебра. Число делится без остатка на 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 62
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]. Проект Делится - не делится? Исходный код программы находится в файле Делимость.py. Бесконечный цикл while Функции с параметрами Оператор деления по модулю % Оператор деления // Цикл for Условныq операторы if Оператор return Метод для извлечения квадратного корня math.sqrt Списки 63
Метод печати в консольном окне print Метод ввода строки в консольном окне input Преобразование строки в число типа int с помощью метода int Мы вспомнили признаки делимости чисел, без которых человеку обойтись трудно, а вот компьютеру они совсем не нужны, потому что он и без них считает охотно и быстро. C помощью операции деления по модулю можно легко проверить, делится ли одно число на другое нацело или нет. Например, мы хотим узнать, делится ли число 2022 на 3. Пишем: if 2022 % 3 == 0: print(('Делится')) else: print('Не делится') Запускаем программу и тут же узнаём, что делится (Рис. 1). То есть нам только и нужно, что проверить остаток от деления. Если он равняется нулю, то первое число делится на второе. Вот и вся премудрость! Рис. 1. Делится – не делится В функции main мы выбираем число, для которого программа найдёт все его делители: # ГЛАВНАЯ ФУНКЦИЯ def main(): print() print('Делимость чисел') print() # бесконечный цикл ввода данных # пока пользователь не закроет программу # или не введёт 0: while True: print("Введите натуральное число > ", end='') num = int(input()) print(num) if (num == 0): return solve(num) 64
print() main() Затем мы передаём число функции solve, которая в цикле for пытается разделить его на числа из диапазона 1..num и печатает в Консольном окне все делители заданного числа (Рис. 2): import math # НАХОДИМ ВСЕ ДЕЛИТЕЛИ ЗАДАННОГО ЧИСЛА def solve(num): # печатаем результаты в Консольном окне: print('Число', num, 'делится на:') for i in range(1, num + 1): if (num % i == 0): print(i, end=' ') print() Рис. 2. Программа в действии Задавая параметры цикла, мы исходим из того, что любое натуральное число делится на единицу и само на себя. На нуль делить нельзя, отрицательных натуральных чисел нет, а на числа, большие заданного, делить нет смысла. Таким образом, наша программа не только проверяет делимость заданного числа на все числа из диапазона 1..num, но и находит все его делители в порядке возрастания. 65
Для проверки очень больших чисел можно оптимизировать нашу функцию. Достаточно заметить, что заданное число num не может делиться на числа, большие num/2, исключая, естественно, само число. Немного подправим код – и можно находить делители огромных чисел (Рис. 3): # ОПТИМИЗИРОВАННАЯ ФУНКЦИЯ def solveOpt(num): # печатаем результаты в Консольном окне: print('Число', num, 'делится на:') for i in range(1, num // 2 + 1): if (num % i == 0): print(i, end=' ') print(num) print() Рис. 3. Оптимизированная программа Мы ещё во много раз ускорим поиск делителей, если заметим, что произведение симметричных относительно середины ряда делителей равно заданному числу. Например, для числа 64 мы получили такой ряд делителей: 1 2 4 8 16 32 64 66
Проверяем утверждение: 1 х 64 = 2 x 32 = 4 x 16 = 8 x 8 = 64 Всё верно! Единственное «неудобство» причиняют восьмёрки – они дважды входят в произведение, поэтому нам следует подумать, как оставить только одну из них. Теперь легко заметить, что нам достаточно найти делители заданного числа в диапазоне 1..√𝑛𝑢𝑚. Вторую половину делителей мы найдём, поделив заданное число на очередной делитель. Для хранения делителей заведём список res: # ОПТИМИЗИРОВАННАЯ ФУНКЦИЯ СО СПИСКОМ def solveList(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('Число', num, 'делится на:') print(*res, sep=' ', end='') print() И вот почему. Для первого делителя – единицы – мы найдём парный делитель, равный заданному числу num, для второго парный делитель равен (num // второй делитель). То есть делители мы напечатаем не по порядку (Рис. 4). Чтобы выправить ситуацию, мы до печати результатов отсортируем список res с помощью метода sort списочного класса, а затем напечатаем делители строго по ранжиру: # ОПТИМИЗИРОВАННАЯ ФУНКЦИЯ СО СПИСКОМ def solveList(num): # список для записи делителей: res = [] for i in range(1, round(math.sqrt(num)) + 1): 67
if (num % i == 0): res.append(i) # не допускаем повторов делителей: if (i * i != num): res.append(num // i) # печатаем результаты в консольном окне: res.sort() print('Число', num, 'делится на:') print(*res, sep=' ', end='') print() Рис. 4. Неупорядоченные делители Теперь даже для огромных чисел мы мгновенно выписываем все делители (Рис. 5). Рис. 5. Молниеносное нахождение делителей 68
Для сокращения кода в реальных программах вы можете использовать функцию divisors (делители) из библиотеки SymPy, которую нужно установить на компьютер. Мы импортируем только нужную нам функцию: from sympy import divisors В эту функцию достаточно передать число num, а она вернёт список всех делителей в правильном порядке: # ИСПОЛЬЗУЕМ БИБЛИОТЕКУ sympy def solveSympy(num): divs = divisors(num) # печатаем результаты в Консольном окне: print('Число', num, 'делится на:') # print(divs) for i in divs: print(i, end=' ') print() solveSympy(num) Проверка показывает, что функция divisors работает быстро и правильно (Рис. 6). Рис. 6 69
Для последовательного получения делителей какого-либо числа можно воспользоваться простым генератором: def divisorGenerator(num): for i in range(1, num // 2 + 1): if num % i == 0: yield i yield num for i in divisorGenerator(num): print(i, end=' ') Остатки от деления довольно неожиданно проявили себя в компьютерной графике! В следующих проектах мы будем использовать программу Processing. Как её установить и как ею пользоваться, подробно описано в книге Python. Основы компьютерной графики на Питоне (Рис. 7). 70
Проект Таблица Пифагора Исходный код программы находится в папке Pythagoras_table. В номере 8 журнала Наука и жизнь за 2004 год, на страницах 99-100 напечатана любопытная статья Узоры таблицы Пифагора. Таблица Пифагора – это всем известная со школьных времён таблица умножения, которую якобы придумал сам Пифагор (Рис. 1). Рис. 1 Казалось бы, таблица ничем не примечательна, однако пытливые умы нашли в ней немало интересного! С некоторыми свойствами таблицы Пифагора мы познакомимся в этом проекте. В журнале Мир информатики были предложены задачи по этой статье (Рис. 2). 71
Нас интересует такая задача: Рис. 2 7. Разработайте программу, в результате выполнения которой на экран будет выведено следующее изображение (Рис. 3). Рис. 3 Она не имеет непосредственного отношения к таблице Пифагора, но зато мы хорошенько подготовимся к решению более сложных задач. Из файла color.py импортируем цвета: from color import * В программе нам потребуются такие переменные: 72
# квадратик: Q_SIZE = 25 # зазоры между квадратиками: OFFSET = 1 # размер сетки в клетках: COLS = 25 ROWS = 29 # размеры окна: WIDTH = (Q_SIZE + OFFSET) * COLS + OFFSET HEIGHT =(Q_SIZE + OFFSET) * ROWS + OFFSET На Рис. 3 хорошо видно, что все клетки окрашены в 2 цвета – синий и золотой. Мы поместили цвета в список colors: # цвета клеток: colors = [Blue, Gold] В функции settings создаём окно по размерам таблицы: # СОЗДАЁМ ОКНО def settings(): size(WIDTH, HEIGHT) В функции setup просто вызываем функцию drawTable: # ИНИЦИАЛИЗИРУЕМ ПРОГРАММУ def setup(): # рисуем таблицу: drawTable() Функция drawTable очень простая. Перебираем все клетки таблицы и определяем для каждой номер цвета в списке colors – 0 или 1. Этим цветом закрашиваем квадратик в соответствующей клетке таблицы: # РИСУЕМ ТАБЛИЦУ def drawTable(): # чёрный фон: background(0) 73
# квадратики без контура: noStroke() # рисуем сетку из квадратиков: for row in range(0, ROWS): for col in range(0, COLS): # шахматная раскраска: c = (row + col) % 2 clr = colors[c] fill(clr) # устанавливаем квадратик в сетке: x = OFFSET + col * (Q_SIZE + OFFSET) y = OFFSET + row * (Q_SIZE + OFFSET) rect(x, y, Q_SIZE, Q_SIZE)) В данном случае будет напечатана не заданная в журнале таблица, а шахматная доска, которая тоже по-своему красива (Рис. 4). Чтобы решить журнальную задачу, достаточно изменить одну строку в функции drawTable (Рис. 5): # шахматная раскраска: #c = (row + col) % 2 # журнальная раскраска: c = row * col % 2 Чтобы получить шахматную раскраску таблицы, мы находим остатки от деления суммы номеров столбца и колонки для каждой строки: # шахматная раскраска: c = (row + col) % 2 Для верхней строки остатки такие: (0 + 0) % 2 = 0 →1 0 1 0 1 Для следующей строки: (1 + 0) % 2 = 1 → 0 1 0 1 0 Легко заметить, что и в горизонтальных, и в вертикальных рядах повторяются остатки 0, 1, 0, 1…, которые и дают шахматную раскраску. Для журнальной раскраски мы находим остатки от деления на 2 не суммы, а произведения номеров колонки и строки для каждой клетки: c = row * col % 2 74
Рис. 4 Тогда все чётные горизонтали и вертикали будут давать остатки 0, а нечётные – 0,1,0,1…, то есть в шахматном порядке. Журнальная раскраска отличается от нашей, хотя выполнена по той же формуле, но не по-программистски – счёт рядов начинается не с нуля, а с единицы. 75
Рис. 5 Чтобы получить настоящую шахматную раскраску, нужно внести изменения в функцию drawTable: # РИСУЕМ ТАБЛИЦУ def drawTable(): # чёрный фон: background(0) # квадратики без контура: noStroke() # рисуем сетку из квадратиков: for row in range(1, ROWS + 1): for col in range(1, COLS + 1): 76
# шахматная раскраска: #c = (row + col) % 2 c = row * col % 2 clr = colors[c] fill(clr) # устанавливаем квадратик в сетке: x = OFFSET + (col - 1) * (Q_SIZE + OFFSET) y = OFFSET + (row - 1) * (Q_SIZE + OFFSET) rect(x, y, Q_SIZE, Q_SIZE) Вот теперь всё верно: 77
Проект Таблица Пифагора 2 Исходный код программы находится в папке Pythagoras_table2. Следующая задача из журнала Мир информатики – это вторая задача из журнала Наука и жизнь: Чтобы получить представление о том, как в таблице Пифагора расположены числа, дающие одинаковые остатки при делении, например на 5, закрасим числа, дающие остатки 0, 1,2, 3, 4, каждое своим цветом. Как это ни удивительно, но таблица Пифагора оказывается расчлененной на совершенно одинаковые по раскраске квадраты (Рис. 1, слева). В журнале Мир информатики клетки раскрашены по-другому (Рис. 1, справа). Рис. 1 Мы раскрасим квадратики по последнему образцу. Как следует из условия задачи, нам потребуется 5 цветов: # цвета клеток: colors = [Brown, Yellow, Cyan, Green, Red] 78
Всё, что нам нужно сделать в функции drawTable, - написать новую функцию для вычисления цвета квадратиков: # журнальная раскраска: journal = lambda c, r: r % 5 * c % 5 from color import * # квадратик: Q_SIZE = 25 # зазоры между квадратиками: OFFSET = 1 # размер сетки в клетках: COLS = 25 ROWS = 29 # размеры окна: WIDTH = (Q_SIZE + OFFSET) * COLS + OFFSET HEIGHT =(Q_SIZE + OFFSET) * ROWS + OFFSET # цвета клеток: colors = [Brown, Yellow, Cyan, Green, Red] # СОЗДАЁМ ОКНО def settings(): size(WIDTH, HEIGHT) # ИНИЦИАЛИЗИРУЕМ ПРОГРАММУ def setup(): # рисуем таблицу: drawTable() # журнальная раскраска: journal = lambda c, r: r % 5 * c % 5 # РИСУЕМ ТАБЛИЦУ def drawTable(): # чёрный фон: background(0) # квадратики без контура: noStroke() # рисуем сетку из квадратиков: for row in range(ROWS+1): for col in range(COLS+1): # журнальная раскраска: clr = colors[journal(col, row)] fill(clr) 79
# устанавливаем квадратик в сетке: x = OFFSET + (col - 1) * (Q_SIZE + OFFSET) y = OFFSET + (row - 1) * (Q_SIZE + OFFSET) rect(x, y, Q_SIZE, Q_SIZE) Таблица получилась один в один (Рис. 2). Рис. 2 Опять замечаем, что раскраска выполнена не по-программистски. Если внимательно взглянуть на функцию раскраски, то мы увидим, что нулевая 80
строка и нулевой столбец должны быть окрашены в один цвет, а дальше все цвета повторяются циклически: # журнальная раскраска: journal = lambda c, r: r % 5 * c % 5 Правильная программа должна быть такой: from color import * # квадратик: Q_SIZE = 23 # зазоры между квадратиками: OFFSET = 1 # размер сетки в клетках: COLS = 26 ROWS = 31 # размеры окна: WIDTH = (Q_SIZE + OFFSET) * COLS + OFFSET HEIGHT =(Q_SIZE + OFFSET) * ROWS + OFFSET # цвета клеток: colors = [Brown, Yellow, Cyan, Green, Red] # СОЗДАЁМ ОКНО def settings(): size(WIDTH, HEIGHT) # ИНИЦИАЛИЗИРУЕМ ПРОГРАММУ def setup(): # рисуем таблицу: drawTable() # журнальная раскраска: journal = lambda c, r: r % 5 * c % 5 # РИСУЕМ ТАБЛИЦУ def drawTable(): # чёрный фон: background(0) # квадратики без контура: noStroke() # рисуем сетку из квадратиков: for row in range(ROWS): for col in range(COLS): # журнальная раскраска: 81
clr = colors[journal(col, row)] fill(clr) # устанавливаем квадратик в сетке: x = OFFSET + (col - 0) * (Q_SIZE + OFFSET) y = OFFSET + (row - 0) * (Q_SIZE + OFFSET) rect(x, y, Q_SIZE, Q_SIZE) Теперь таблица совершенно правильная (Рис. 3). Рис. 3 82
Проект Таблица Пифагора 3 Исходный код программы находится в папке Pythagoras_table3. Третья задача из журнала Наука и жизнь (Задача 8 из журнала Мир информатики): Благодаря свойству периодичности таблицы Пифагора по остаткам на экране возникают разнообразные мозаики. Очевидно, чем больше k, тем больше будет остатков r, тем больше потребуется цветов. Чтобы узоры не были слишком пёстрыми, ограничимся, например, тремя цветами. Для этого остатки сгруппируем по модулю 3, то есть первым цветом закрасим числа таблицы с остатками 1, 4, 7, 10.., вторым - числа с остатками 2, 5, 8, 11.., а третьим - числа, кратные 3. Работы в этом проекте будет немного. Подправляем значения переменных: # квадратик: Q_SIZE = 18 # зазоры между квадратиками: OFFSET = 1 # размер сетки в клетках: COLS = 40 ROWS = 40 Переставляем цвета в массиве colors: # цвета клеток: colors = [Blue, Brown, Yellow, Cyan, Green, Red] В функции drawTable добавляем ещё 1 параметр – n, - который отвечает за число цветов мозаике, и переписываем функцию раскраски: # журнальная раскраска: journal = lambda c, r, k, n: r * c % k % n # РИСУЕМ ТАБЛИЦУ def drawTable(k, n): # чёрный фон: background(0) 83
# квадратики без контура: noStroke() # рисуем сетку из квадратиков: for row in range(ROWS): for col in range(COLS): # журнальная раскраска: clr = colors[journal(col, row, k, n)] fill(clr) # устанавливаем квадратик в сетке: x = OFFSET + col * (Q_SIZE + OFFSET) y = OFFSET + row * (Q_SIZE + OFFSET) rect(x, y, Q_SIZE, Q_SIZE) И наконец, вызываем эту функцию с соответствующими аргументами: # ИНИЦИАЛИЗИРУЕМ ПРОГРАММУ def setup(): # рисуем таблицу: drawTable(13, 3) Вот такая сложная мозаика получилась в этом проекте (Рис. 1). Рис. 1 84
Проект Таблица Пифагора 4 Исходный код программы находится в папке Pythagoras_table4. Пятая задача из Науки и жизни (Задача 9, Рис. 13 из журнала Мир информатики): Еще один вариант трехцветных мозаик приведен на рис. 5. Здесь для большей симметрии одинаковым цветом закрашены не только числа с одинаковым остатком r, но и числа с остатком, дополняющим r до k. Делаем традиционные изменения в предыдущей программе: from color import * # квадратик: Q_SIZE = 14 # зазоры между квадратиками: OFFSET = 1 # размер сетки в клетках: COLS = 49 ROWS = 49 # размеры окна: 85
WIDTH = (Q_SIZE + OFFSET) * COLS + OFFSET HEIGHT =(Q_SIZE + OFFSET) * ROWS + OFFSET # цвета клеток: colors = [Brown, Yellow, Cyan, Blue, Green, Red] # СОЗДАЁМ ОКНО def settings(): size(WIDTH, HEIGHT) # ИНИЦИАЛИЗИРУЕМ ПРОГРАММУ def setup(): # рисуем таблицу: drawTable(16, 3) # функция раскраски: f = lambda c,r,k: r * c % k # журнальная раскраска: journal = lambda c, r, k, n: f(c,r,k) % n if (f(c,r,k) < k // 2) else (k - f(c,r,k)) % n # РИСУЕМ ТАБЛИЦУ def drawTable(k, n): # чёрный фон: background(0) # квадратики без контура: noStroke() # рисуем сетку из квадратиков: for row in range(ROWS): for col in range(COLS): # журнальная раскраска: clr = colors[journal(col, row, k, n)] fill(clr) # устанавливаем квадратик в сетке: x = OFFSET + col * (Q_SIZE + OFFSET) y = OFFSET + row * (Q_SIZE + OFFSET) rect(x, y, Q_SIZE, Q_SIZE) Мозаика готова (Рис. 1). В журнале Мир информатики она имеет меньше клеток и другие цвета. 86
Рис. 1 87
Проект Таблица Пифагора 5 Исходный код программы находится в папке Pythagoras_table5. Седьмая задача из Науки и жизни (Задача 11, Рис. 15 из журнала Мир информатики): Кружевной монохромный узор (рис.7) возникает, если во всей таблице закрасить одинаковым цветом только числа, дающие остатки, сравнимые с одним и тем же натуральным числом. В данном случае k = 31, а закрашиваются только числа, удовлетворяющие условию: i * j % 31 % 3 == 2: from color import * # квадратик: Q_SIZE = 11 # зазоры между квадратиками: OFFSET = 1 # размер сетки в клетках: COLS = 61 88
ROWS = 61 # размеры окна: WIDTH = (Q_SIZE + OFFSET) * COLS + OFFSET HEIGHT =(Q_SIZE + OFFSET) * ROWS + OFFSET colors = [Yellow, Blue, Brown, Cyan, Green, Red] # СОЗДАЁМ ОКНО def settings(): size(WIDTH, HEIGHT) def setup(): # рисуем таблицу: drawTable(31, 3) # журнальная раскраска: journal = lambda c, r, k, n: 0 if (r * c % k % n == 2) else 1 # РИСУЕМ ТАБЛИЦУ def drawTable(k, n): # чёрный фон: background(0) # квадратики без контура: noStroke() # рисуем сетку из квадратиков: for row in range(ROWS+1): for col in range(COLS+1): # цвет клетки: clr = journal(col, row, k, n) fill(colors[clr]) # устанавливаем квадратик в сетке: x = OFFSET + (col - 1) * (Q_SIZE + OFFSET) y = OFFSET + (row - 1) * (Q_SIZE + OFFSET) rect(x, y, Q_SIZE, Q_SIZE) Мозаика удалась на славу (Рис. 1). 89
Рис. 1 Задачи с сайта Проект Эйлер В Интернете нетрудно найти сайты, на которых предлагаются задачи для самостоятельного решения. Вот очень известный сайт Project Euler: https://projecteuler.net 90
Сейчас на сайте опубликовано около 800 задач. Первые задачи не очень сложные, так что вполне по силам и начинающим любителям программирования. Условия задач даны на английском языке, поэтому не всем могут быть понятны. Но есть и русскоязычная версия проекта (Рис. 1): http://euler.jakumo.org Рис. 1 91
Проект Задача 1 с сайта Проект Эйлер Исходный код программы находится в файле Эйлер001.py. Задача номер 1 (Problem 1) называется Multiples of 3 and 5: If we list all the natural numbers below 10 that are multiples of 3 or 5, we get 3, 5, 6 and 9. The sum of these multiples is 23. Find the sum of all the multiples of 3 or 5 below 1000. Найдите сумму всех натуральных чисел, которые: меньше 1000 делятся без остатка на 3 или на 5 В функции main вызываем функцию solve для непосредственного решения задачи: def main(): print("Решаем задачу 1 из Проекта Эйлер") solve() main() Поскольку нужно проверить всего 999 чисел, то для решения задачи можно воспользоваться методом грубой силы и просто перебрать их в цикле for: # Project Euler. Problem 1 def solve(): sum = 0 for i in range(1, 1000): if i % 3 == 0 or i % 5 == 0: sum += i print("Сумма равна:", sum) 92
Делимость чисел на тройку и пятёрку легко установить с помощью операции деления по модулю: если остаток равен нулю, значит, число i кратно трём (или пяти). Все кратные числа добавляем к переменной sum, которая после завершения цикла и будет равна искомой сумме (Рис. 1). Рис. 1 Если подумать… Если подумать, то задачу можно решить и без перебора, причём для любого диапазона чисел. Все числа, кратные трём, образуют арифметическую последовательность: 3 6 9 12 15 18 . . . (1) Аналогично – для чисел, кратных пяти: 5 10 15 20 25 30 . . . (2) Поскольку все члены этих рядов имеют общий множитель, его можно вынести за скобки: 3 (1 2 3 4 5 6 . . .) 5 (1 2 3 4 5 6 . . .) В общем случае: k (1 2 3 4 5 6 . . .) Сумму последовательности натуральных чисел легко найти по формуле: sum(N) = (N+1)N / 2, где N – число членов в последовательности. Если мы знаем максимальное число, которого не превышают члены последовательности, то число членов можно вычислить так: Nk = [max / k] То есть мы должны найти целую часть дроби. 93
Чтобы не обременять себя расчётами, мы напишем функцию, которая возвращает сумму членов заданной арифметической прогрессии: def getSum(max, k): n = max // k res = k * (n + 1) * n // 2 return res В ней мы используем наши формулы. Теперь можно без всякого перебора, сразу найти сумму заданных чисел, кратных трём или пяти: def solve2(): sum = getSum(999, 3) + getSum(999, 5) print("Сумма равна:", sum) Добавляем вызов новой функции в функцию main: def main(): print("Решаем задачу 1 из Проекта Эйлер") solve() solve2() И запускаем программу. Сумма чисел получилась больше, чем в первом случае (Рис. 2). Рис. 2 Причину недоразумения легко определить, если ещё раз взглянуть на ряды чисел (1) и (2). Мы дважды посчитали числа, которые одновременно делятся на 3 и на 5. Чтобы получить верное решение, необходимо из полученной суммы sum вычесть сумму чисел, кратных 3 и 5 одновременно: def solve2(): sum = getSum(999, 3) + getSum(999, 5) - getSum(999, 3 * 5) print("Сумма равна:", sum) 94
Теперь мы получаем правильный ответ на задачу (Рис. 3). Рис. 3 Задачу из Проекта Эйлера, безусловно, можно решить полным перебором, но для очень длинного ряда чисел без второго метода не обойтись! Ещё 2 способа решения первой задачи: # ФУНКЦИОНАЛЬНОЕ ПРОГРАММИРОВАНИЕ def solve3(): sumn = sum(filter(lambda n: n % 3 * n % 5 == 0, range(1, 999 + 1))) print("Сумма равна:", sumn) # СПИСКОВОЕ ВКЛЮЧЕНИЕ def solve4(): sumn = sum([n for n in range(1, 999 + 1) if n % 3 == 0 or n % 5 == 0]) print("Сумма равна:", sumn) Проект Четыре числа Исходный код программы находится в файле Четыре числа.py. В 1983 году вышла книга The Commodore Puzzle Book: BASIC Brainteasers. Это было в те далёкие времена, когда только начали появляться первые персональные компьютеры. Одним из самых известных был Commodore. Люди в то время были гораздо любознательнее, чем нынче, поэтому книга по решению занимательных математических задач на компьютере была как раз впору и к месту. В этой книге собрано немало задач, которые могут послужить хорошим упражнением и в современном программировании. 95
На странице 4 нас ждёт «дополнительное домашнее задание»: найти максимальное число n, которое при делении на него чисел 1731 5363 7179 9903 даёт одинаковый остаток r. Понятно, что искомое число не превышает наименьшего из этих чисел, то есть 1731. Если n = 1731, то r = 0, но ни одно из остальных чисел не делится нацело на 1731. Значит, нужно проверить число на 1 меньше, чем 1731, то есть 1730: 1731 % 1730 = 1 Но 5363 % 1730 = 173 Опять неудача! Но теперь мы точно знаем, что нужно последовательно вычитать из числа 1731 единицу и находить остатки от деления предложенных чисел на разницу. Когда все остатки от деления совпадут, мы напечатаем ответ (Рис. 1): # -*- coding: cp1251 -*# Задача "Четыре числа" def solve(): # макс.число: for n in range(1731, 0, -1): # остаток: r = 1731 % n # остальные числа должны давать # такие же остатки: if (5363 % n != r): continue if (7179 % n != r): continue if (9903 % n != r): continue # печатаем ответ: print("n = ", n) print("r = ", r) print("(1731 % n = ", (1731 % n)) print("(5363 % n = ", (5363 % n)) print("(7179 % n = ", (7179 % n)) 96
print("(9903 % n = ", (9903 % n)) break def main(): print('Четыре числа') print() # решаем задачу: solve() print() main() Рис. 1 Это код можно без труда переписать в одну строку: n = max([n for n in range(1, 1731+1) if 1731 % n == 5363 % n == 7179 % n == 9903 % n]) print(n) В этом решении немало лишних действий, но зато оно простое. А при небольшом переборе задача решается очень быстро любым способом. Проект Совершенные числа Исходный код программы находится в файле Совершенные числа.py. 97
Бесконечный цикл 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 совершенных числа достаточно маленькие, а затем они стремительно увеличиваются! Из этого наблюдения следует, что первые совершенные числа можно искать методом грубой силы: # ГЛАВНАЯ ФУНКЦИЯ def main(): 98
print() print('Совершенные числа') print() # бесконечный цикл ввода данных # пока пользователь не закроет программу # или не введёт 0: while True: s = 'Введите верхнюю границу > ' print(s, end = '') num = int(input()) # если пользователь ввёл 0, то программу закрываем: if (num == 0): return perfectNumbers(num) print() main() Практически такой же алгоритм поиска совершенных чисел предлагается и в книге Брудно и Каплана (Рис. 1): # -*- coding: Windows-1251 -*# Совершенные числа import math 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('Число', num, 'совершенное') print() Увы, дальше четвёртого числа с функцией рerfectNumbers мы не продвинемся! 99
Рис. 1. Несовершенный алгоритм для поиска совершенных чисел Проект Дружественные числа Исходный код программы находится в файле Дружественные числа.py. Два (различных) натуральных числа называются дружественными, если сумма всех делителей (исключая само число) первого числа равна второму числу, и наоборот. Первая пара дружественных чисел была найдена несколько тысячелетий тому назад. Это числа 220 и 284. Следующая пара отыскалась только в 1866 году – 1184 и 1210 (но раньше уже были известны другие дружественные числа). Сейчас список дружественных чисел включает миллиарды пар. Напишите программу для их поиска [ЗП88, Задача 560]. Учитывайте, что числа в паре либо оба чётные, либо оба нечётные. Для проверки в таблице приведены первые 15 пар дружественных чисел (Рис. 1). Импортируем функцию divisors: # -*- coding: Windows-1251 -*# Дружественные числа from sympy import divisors Она возвращает все делители заданного числа, поэтому из суммы нужно вычесть само число. Тогда в функции friendly легко проверить, являются ли заданные числа a и b дружественными: 100
def friendly(a, b): return a == sum(divisors(b))- b and b == sum(divisors(a)) - a Рис. 1 В главной функции задаём верхнюю границу для поиска дружественных чисел: # ГЛАВНАЯ ФУНКЦИЯ def main(): print() print('Дружественные числа') print() # бесконечный цикл ввода данных # пока пользователь не закроет программу # или не введёт 0: while True: s = 'Введите верхнюю границу > ' print(s, end = '') num = int(input()) # если пользователь ввёл 0, то программу закрываем: if (num == 0): return Во вложенных циклах for перебираем все возможные пары чисел и проверяем их на дружественность. Найденные пары дружественных чисел печатаем в консоли: 101
for a in range(num – 1): for b in range(a + 2, num + 1): if friendly(a, b): print(a, b) print() main() Так можно найти только первые 5 пар дружественных чисел, потому что алгоритм очень медленный (Рис. 2). Рис. 2 Проект Дружественные числа 2 Исходный код программы находится в файле Дружественные числа 2.py. Давайте улучшим наш алгоритм. Начало программы не предвещает ничего нового: # -*- coding: Windows-1251 -*# Дружественные числа 2 from sympy import divisors # ГЛАВНАЯ ФУНКЦИЯ def main(): print() 102
print('Дружественные числа 2') print() # бесконечный цикл ввода данных # пока пользователь не закроет программу # или не введёт 0: while True: s = 'Введите верхнюю границу > ' print(s, end = '') num = int(input()) # если пользователь ввёл 0, то программу закрываем: if (num == 0): return Но затем мы создаём список, в который последовательно добавляем суммы делителей для каждого числа 0..num (исключая само число) lst = [] for i in range(num + 1): lst.append(sum(divisors(i)) - i) Просматриваем весь список. Число а – это индекс числа в списке – 0..num. Само число – это сумма его делителей. Если для этого числа в списке имеется значение, равное числу а, то мы нашли пару дружественных чисел. Чтобы не повторять пары чисел, выписываем их только возрастанию: for a in range(len(lst)): b = lst[a] if a == b: continue if b < len(lst): if a == lst[b] and b == lst[a] and a < b: print(a, b) print() main() Теперь 16 первых пар дружественных чисел мы нашли очень быстро (Рис. 1). Вложенные циклы всегда работают очень медленно, поэтому ищите однопроходные алгоритмы. 103
Рис. 1 Проект Дружественные числа 3 Исходный код программы находится в файле Дружественные числа 3.py. Сейчас известно более миллиарда пар дружественных чисел, но никто не знает, конечно ли множество дружественных чисел или бесконечно. Нам будет вполне достаточно найти дружественные числа, не превышающие миллиона. Эту границу мы и задаём в функции main: # ГЛАВНАЯ ФУНКЦИЯ def main(): print() print('Дружественные числа 3') print() # бесконечный цикл ввода данных - 104
# пока пользователь не закроет программу # или не введёт 0: while True: s = 'Введите верхнюю границу > ' print(s, end='') num = int(input()) # если пользователь ввёл 0, то программу закрываем: if (num == 0): return # получаем дружественные числа: for ap in amicables(num): print(ap) print() main() Функция-генератор amicables возвращает последовательность чисел в заданном диапазоне: def amicables(number): for n1 in range(1, number + 1): n2 = sum_factors(n1) if sum_factors(n2) == n1 and n1 < n2: yield n1, n2 Первое число пары n1 - это число 1..number. Для каждого из них мы находим сумму делителей n2. Но эта сумма должна быть вторым числом пары. И это так и будет, если сумма его делителей равна первому числу. Чтобы пары чисел не повторялись, мы считаем, что первое число в паре меньше второго: if sum_factors(n2) == n1 and n1 < n2: Сумму делителей заданного числа подсчитываем в функции sum_factors (Рис. 1): # Дружественные числа 3 def sum_factors(n): s = 1 for i in range(2, int(n ** 0.5) + 1): if n % i == 0: s += i 105
s += n // i return s Рис. 1 Этот способ не быстрее предыдущего, но не требователен к памяти компьютера. Проект Назойливый остаток Исходный код программы находится в файле Назойливый остаток.py. Вложенные циклы for Бесконечный цикл while Условный оператор if Оператор % Оператор break Оператор continue В книге Бориса Кордемского и Аскера Ахадова Удивительный мир чисел [КА86] вы найдёте немало ин- 106
тересных задач, в том числе и на делимость. На странице 86 авторы предлагают решить задачу Назойливый остаток: Некоторые числа, кратные числу 7, при делении на 2, на 3, на 4, на 5 и на 6 дают остаток 1. Найдите наименьшее из таких чисел. Эта же задача напечатана в книге Фёдора Нагибина и Евгения Канина Математическая шкатулка [Нагибин88], задача 42, страницы 18-19, но в более занимательной форме: Колхозница привезла на рынок для продажи корзину яиц. Продавала она их по одной и той же цене. После продажи яиц колхозница пожелала проверить, верно ли она получала деньги. Но вот беда: она забыла, сколько у неё было яиц. Вспомнила она только, что когда перекладывала яйца по 2, то оставалось одно яйцо; одно яйцо оставалось также при перекладывании яиц по 3, по 4, по 5, по 6. Когда же она перекладывала яйца по 7, то не оставалось ни одного. Помоги колхознице сообразить, сколько у неё было яиц. Бросаемся на помощь бестолковой колхознице и в функции main вызываем функцию solve для решения этой головоломки: # ГЛАВНАЯ ФУНКЦИЯ def main(): print("Назойливый остаток") print() solve() main() Поскольку речь идёт о натуральных числах, то мы можем начать наши поиски с единицы: # Кордемский, с.86, Задача 8 # РЕШАЕМ ЗАДАЧУ def solve(): # мин. число: minnum = 1 107
Если число 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 Проверку очередного числа на остаток, равный единице, также можно проводить в цикле, чтобы не выписывать несколько одинаковых условий. Если число 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 вы видите ответ на эту задачу. Рис. 1. Яиц было немало! 108
Настоящий бесконечный цикл легко получить с помощью конструкции 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() Не знаю, как вам, но мне интересно, а есть ли ещё и другие числа, которые имеют назойливый остаток? Имея компьютер, мы легко утолим своё любопытство. Достаточно написать новую функцию solve2, которая поразительно напоминает первую версию: def solve2(): # мин. число: minnum = 1 # макс. число: maxnum = 10000; for num in range(minnum, maxnum+1): flg = True # число не кратно семи: if num % 7 != 0: continue; for d in range(2, 6+1): if (num % d != 1): flg = False break if (flg): print("Искомое число равно " + str(num)) print() 109
Тут, конечно, следует учесть, что бесконечный цикл уже не годится, потому что он действительно станет бесконечным, если искомых чисел окажется очень много (а это нетрудно предвидеть). Запускаем программу и видим, что среди первой десятки тысяч натуральных чисел довольно много подходящих под условие задачи (Рис. 2). Рис. 2. Неназойливый список назойливых чисел Также нетрудно подметить такую закономерность. Если обозначить через n номер искомого числа, то все числа с назойливыми остатками можно легко найти по формуле: num = 301 + 420(n-1) Любопытно, что, если решать задачу для чисел, кратных не 7, а большим числам, то это непременно должны быть простые числа. Например, решения для чисел 11 и 13 показаны на Рис. 3: def solve11(): print("Решаем задачу для чисел, кратных 11:") # мин. число: minnum = 1 # макс. число: maxnum = 600000; for num in range(minnum, maxnum+1): flg = True # число не кратно 11: if num % 11 != 0: continue; for d in range(2, 11): if (num % d != 1): 110
flg = False break if (flg): print("Искомое число равно " + str(num)) print() def solve13(): print("Решаем задачу для чисел, кратных 13:") # мин. число: minnum = 1 # макс. число: maxnum = 6000000; for num in range(minnum, maxnum+1): flg = True # число не кратно 13: if num % 13 != 0: continue; for d in range(2, 13): if (num % d != 1): flg = False break if (flg): print("Искомое число равно " + str(num)) print() Рис. 3. Исследования продолжаются 111
Чтобы понять, почему так происходит, нужно научиться вычислять НОД, НОК и находить простые числа! В функцию ilcm можно передавать сколько угодно чисел, но только в виде отдельных аргументов. Проект Первый числовой фокус Исходный код программы находится в файле Первый числовой фокус.py. Метод int Оператор return Условный оператор if Функция с параметром Оператор деления по модулю % Оператор деления // Операторы < > or| != А вот и фокус из книги Удивительный мир чисел [КА86], страница 34, задача 2: Скажите другу: «Любое трёхзначное число умножь на 37, потом на 27. К полученному шестизначному числу прибавь удвоенное первоначальное число. Покажи мне результат, и я угадаю задуманное трёхзначное число». Секрет фокуса. Пусть задумано трёхзначное число 100x + 10y + z, где х, y, z - цифры сотен, десятков и единиц соответственно. Выполнив указанные действия, получим: 100 100x + 10 010у + 1001z= 1001 ⦁ (100x + 10у + z). Теперь ясно, что достаточно разделить результат на 1001, чтобы получилось задуманное трёхзначное число. 112
Пример. Пусть задумано число 173. После выполнения указанных действий получилось 173 173. Наблюдаем, что в его записи трёхзначное число 173 повторяется. Вычеркнем 173; это равносильно тому, что 173 173 разделили на 1001; в результате остается задуманное число 173. Замечание. При повторении фокуса легко обнаружится, что если задумано число abc, то результат, показываемый фокуснику, имеет вид abcabc . Чтобы это скрыть, надо добавить еще одно действие в конце фокуса, например потребовать прибавить 1111. Тогда фокуснику скажут не 173 173, а 174 284. Теперь закономерность скрыта, а фокуснику ничего не стоит в уме вычесть 1111, а затем угадать задуманное число. В функции main мы предлагаем всем желающим загадать трёхзначное число и произвести с ним требуемые манипуляции: # ГЛАВНАЯ ФУНКЦИЯ def main(): print('Первый числовой фокус') print() # бесконечный цикл ввода данных # пока пользователь не закроет программу # или не введет 0: while True: print("Любое трёхзначное число умножь на 37, " "\nпотом на 27. " "\nК полученному шестизначному числу прибавь "\nудвоенное первоначальное число. " "\nВведи результат, " "\nи я угадаю задуманное трёхзначное число. num = int(input()) if (num == 0): return solve(num) print() " > ", end='') if __name__ == "__main__": main() Сам фокусник притаился в функции solve, которая получает от загадчика 6-значное число. Но эта кио или акопяно не шибко доверчива, поэтому, прежде всего, проверяет число загадчика и отказывается фокусничать, если ей пытаются подсунуть негодное число: # -*- coding: Windows-1251 -*# Кордемский, страница 34, задача 2 def solve(num): 113
min6 = 100000 max6 = 999999 print() if (num < min6 or num > max6): print("Число должно быть шестизначным!") print() return if (num % 1001 != 0): print("Ты ошибся в вычислениях!") print() return А добросовестному загадчику наш виртуальный фокусник безошибочно сообщает загаданное трёхзначное число: print(f"Ты задумал число {num//1001}!") print() Например, моё число не поставило фокусника в тупик (Рис. 1)! Рис. 1. Компьютерные фокусы Фокус интересный, но очень простой. Постарайтесь учесть замечание и улучшить программу! 114
Проект Второй числовой фокус Исходный код программы находится в файле Второй числовой фокус.py. Цикл 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 = 122877 123123 246000 115
246000 : 2000 = 123 Всё сходится – фокус удался! Вторую фокусную программу мы легко получим из первой. Главное - грамотно написать речь фокусника: # -*- coding: Windows-1251 -*# Кордемский, страница 35, задача 3 def solve(num): if (num % 2000 != 0): print("Ты ошибся в вычислениях!") print() return print() print(f"Ты задумал число {num//2000}!") print() # ГЛАВНАЯ ФУЕУЦИЯ 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() if __name__ == "__main__": main() Кто бы сомневался: конечно, компьютер запросто разделит любое число на 2000 (Рис. 1), а вот загадчику придётся хорошенько потрудиться, умножая и складывая числа! 116
Рис. 1. И тут без промаха! Проект Шестизначное число Исходный код программы находится в файле Шестизначное число.py. Функция без параметров Цикл for Оператор деления по модулю % Оператор continue Оператор break Задача 4 из книги Удивительный мир чисел [КА86], страница 44: Ученику понадобилось написать наибольшее из шестизначных чисел, кратных 11 и чтобы цифра 6 была первой слева. Как надо действовать ученику для быстрого выполнения задания, если признаков делимости на 11 он ещё не знает? Сообщим, что искомое число обладает забавной особенностью: если каждую его цифру повернуть на 180° в плоскости бумаги, оставляя её на прежнем месте, то образовавшееся число окажется дважды кратным 11 (делится на 11 и частное также делится на 11). 117
Выявите ещё одну особенность чисел: найденного и с повёрнутыми цифрами. Как обычно, начинаем с функции main: # -*- coding: Windows-1251 -*# Кордемский, с.44, Задача 4 # ГЛАВНАЯ ФУНКЦИЯ def main(): print() print('Шестизначное число') print() solve() main() Легко догадаться, что нужно искать решение задачи среди шестизначных чисел, начинающихся с шестёрки: # РЕШАЕМ ЗАДАЧУ 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). 118
Рис. 1. Крути-верти По условию задачи, поворачиваем все цифры найденного числа: 699996 → 966669 Проверяем, делится ли перевёрнутое число на 11 * 11 = 121. Это легко сделать в интерактивной консоли. Рис. 2 подтверждает: перевёрнутое число делится на 121. Рис. 2. Поделилось! А ещё одной особенностью этих чисел является их палиндромичность – они одинаково читаются и в прямом, и в обратном направлении. Проект Тройка, семёрка и... только Исходный код программы находится в файле Тройка, семёрка и.py. Функция без параметров Функция с параметром Цикл for Оператор continue Условный оператор if Оператор деления по модулю % Цикл while Комбинированные операторы присваивания Оператор return Списки Задача 8 (13) из книги Удивительный мир чисел [КА86], страница 45: 119
Найдите наименьшее число, обладающее следующими свойствами: • состоит только из цифр 7 и 3, • оно само и сумма его цифр делятся на 7 и на 3. В отличие от Пиковой дамы, туза в этой задаче нет, но тройка и семёрка присутствуют в полном объёме! # ГЛАВНАЯ ФУНКЦИЯ def main(): print('Тройка, семёрка и... только') print() solve() print() if __name__ == "__main__": main() Предположим, что у нас имеется функция get37, которая возвращает числа, составленные из цифр 3 и 7, и мы запоминаем в переменной min37 самое маленькое из них: # -*- coding: Windows-1251 -*# Кордемский, с.45, Задача 8 # РЕШАЕМ ЗАДАЧУ def solve(): min = 1 max = 2400 # минимальное число: 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: 120
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 Таким образом, нам осталось разработать функцию get37, которая умеет генерировать число из троек и семёрок. Поскольку во всех «подозреваемых» числах только 2 цифры, то вполне естественно представить их в двоичной системе. Путь тройку в двоичной записи символизирует нуль, а семёрку – единица, тогда несколько первых 3,7-чисел можно записать в двоичной системе так: 37 73 337 373 377 733 737 773 ... 01 10 001 010 011 100 101 110 ... 121
Так как между двоичными и 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, которые целиком состоят из одинаковых цифр. Для этого можно завести логический список, подобный idigs, а можно просто объявить 2 логические переменные: # число из троек и семёрок: num37 = 0 # флажки для цифр 3 и 7: flg3 = False flg7 = False И последняя тонкость, которую необходимо «утолстить»: при конвертировании входного десятичного числа в двоичную форму мы никогда не получим ведущих нулей, поскольку число обратится в нуль, и деление на двойку закончится: 122
# формируем число из троек и семёрок: 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)! Рис. 1. Немаленькое число! Как и в книге, это число – 3333377733. 123
Кроме этого, минимального 3,7-числа, существует бесконечно много 3,7чисел, которые больше его. Можно формировать 3,7-число сзади, но тогда нужно отбросить последнюю единицу. Проект Любопытное свойство чисел Исходный код программы находится в файле Любопытное свойство чисел.py. Функция без параметров Цикл 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-значных чисел совсем немного, то метод грубой силы здесь 124
вполне уместен. Тем более что алгоритмы, построенные на этом методе почти всегда достаточно простые. Традиционно из функции main мы вызываем функцию solve для непосредственного решения задачи: # ГЛАВНАЯ ФУНКЦИЯ def main(): print("Любопытное свойство чисел") print() solve() if __name__ == "__main__": main() В функции solve обозначаем переменными минимальное и максимальное 6-значные числа, которые нам и надлежит проверить: # -*- coding: Windows-1251 -*# Кордемский, с.102, Задача 15 # РЕШАЕМ ЗАДАЧУ def solve(): # мин. и макс. 6-значные числа: min6 = 100000 max6 = 999999 flg = True Флаг flg призван сигнализировать о (не)соблюдении условия задачи: поначалу он установлен в True, но если по ходу проверки нам встретится число, которое нарушает условие задачи, то мы с чистой совестью сбрасываем флаг и прекращаем дальнейшие проверки – гипотеза опровергнута. Если же после испытания всех 6-значных чисел флаг так и останется установленным в True, значит, гипотеза верна. Поскольку диапазон значений проверяемых чисел точно очерчен, то перебирать числа удобнее всего в цикле for: for num in range(min6, max6 + 1): Все числа, не кратные 7, мы пропускаем: 125
# число не кратно семи: if (num % 7 != 0): continue Всем остальным назначаем проверки. Чтобы перенести последнюю цифру числа в начало, нужно её выделить. Это легко сделать, применив к числу операцию деления по модулю: # последняя цифра числа: end = num % 10 Число из первых 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): 126
# все числа проверены: if (flg): print("Утверждение верное") else: print("Утверждение неверное") print() Рис. 1. Доказали! Так как мы проверили все 6-значные числа, то можем уверенно утверждать, что любое 6-значное число удовлетворяет условиям задачи. Проект Как определил ошибку Чохбилмиш? Исходный код программы находится в файле Как определил ошибку Чохбилмиш.py. Функция без параметров Цикл for Оператор деления по модулю % Оператор деления // Условный оператор if Оператор break Задача 23 (25) из книги Удивительный мир чисел [КА86], страница 57: Чохбилмиш предложил каждому из двух учеников задумать какое-либо шестизначное число и переставить первую цифру в конец записи числа. Одному сказал: «Найди сумму получившихся чисел». Другому сказал: «Найди разность». Ученики выполнили действия и написали: 913 485 и 167 860. Чохбилмиш не знал, какие числа были задуманы учениками, но сразу определил: «Вы оба ошиблись». 127
Как рассуждал Чохбилмиш? В ответе на задачу утверждается, что сумма любого 6-значного числа с другим 6-значным числом, полученным из исходного переносом первой цифры в конец числа, кратна 11 и аналогично полученная разность – кратна 9. Мы можем проверить эти утверждения с помощью полного перебора, тем более что самая трудная часть задачи – перенос цифры – нами уже решена в предыдущем проекте. # -*- coding: Windows-1251 -*# Кордемский, с.57, Задача 23 # РЕШАЕМ ЗАДАЧУ def solve(): # мин. и макс. 6-значные числа: min6 = 100000 max6 = 999999 flg = True 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() # ГЛАВНАЯ ФУНКЦИЯ 128
def main(): print("Как определил ошибку Чохбилмиш?") print() solve() if __name__ == "__main__": main() Проверив все 6-значные числа, мы можем с уверенностью констатировать факт: оба утверждения верные (Рис. 1)! Рис. 1. И здесь всё верно Проект Шестизначный перенос Исходный код программы находится в файле Шестизначный перенос.py. Функция без параметров Цикл for Оператор деления по модулю % Оператор деления // Задача 557 из книги Математическая шкатулка [Нагибин88], страница 94: Первая слева цифра шестизначного числа – 1. Если её перенести с первого места в конец числа, сохранив порядок остальных цифр, то вновь полученное число будет втрое больше первоначального. Восстановите первоначальное число. Эта задача отличается от двух предыдущих только тем, что переносить нужно не последнюю цифру, а первую: 129
# -*- coding: Windows-1251 -*# Нагибин, с.94, Задача 557 # РЕШАЕМ ЗАДАЧУ def solve(): # мин. и макс. 6-значные числа: min6 = 100000 max6 = 299999 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): continue print("Число равно " + str(num)) print() # ГЛАВНАЯ ФУНКЦИЯ def main(): print("Шестизначный перенос") print() solve() if __name__ == "__main__": main() Среди 6-значных чисел, начинающихся с единицы, первоначальное число – единственное. Есть ещё одно подобное число, но оно начинается с двойки (Рис. 1). Рис. 1. Перенос закончен 130
Проект Наименьшее число Исходный код программы находится в файле Наименьшее число.py. Функция без параметров Бесконечный цикл while Цикл for Оператор break Условный оператор if В журнале Наука и жизнь, №2 за 1968 год, на странице 57 напечатана такая задача (Рис. 1). Рис. 1 Задача исключительно на сообразительность. Вот журнальное решение задачи (Рис. 2). Рис. 2 Не каждый этим похвальным даром обладает, поэтому мы пишем простенькую программу: # -*- coding: Windows-1251 -*# РЕШАЕМ ЗАДАЧУ def solve(): 131
n = 0 while True: n += 1 flg = True for i in range(2, 6 + 1): if n % i != i - 1: flg = False break if flg: return n # ГЛАВНАЯ ФУНКЦИЯ def main(): print() print('Наименьшее число') print() n = solve() print('Искомое число =', n) print() main() Когда искомое число заведомо небольшое, то его легко найти простым перебором в бесконечном цикле while. Он заканчивается, когда найдено первое число, удовлетворяющее всем условиям задачи (Рис. 3). Рис. 3. Программирование - удел несообразительных Вопреки ожиданиям наш ответ полностью совпал с «сообразительным» … 132
Проект Трёхзначное число 3 Исходный код программы находится в папке Трёхзначное число 3.py. Функция без параметров Бесконечный цикл while Оператор break Оператор continue Условный оператор if В книге 600 задач на сообразительность, на странице 112 напечатана задача 41: Трёхзначное число Если от трёхзначного числа отнять 7, то оно разделится на 7; если отнять от него 8, то оно разделится на 8; если отнять от него 9, то оно разделится на 9. Какое это число? Опять задача на сообразительность, которой мы как бы не обладаем, а потому сразу садимся за компьютер и решаем задачу: # -*- coding: Windows-1251 -*# РЕШАЕМ ЗАДАЧУ def solve(): n = 99 while True: n += 1 if (n - 7) % 7: continue if (n - 8) % 8: continue if (n - 9) % 9: continue break return n # ГЛАВНАЯ ФУНКЦИЯ def main(): print() print('Трёхзначное число 3') print() n = solve() 133
print('Искомое число =', n) print() main() Ответ такой (Рис. 1). Рис. 1. Опять смекалки не хватило… Он, безусловно, правильный, но, чтобы его получить, вполне можно обойтись и смекалкой. Действительно, если число n – 7 нацело делится на 7, то и число n кратно 7. Аналогично для чисел 8 и 9. Стало быть, искомое число одновременно делится на 7, 8 и 9. Минимальное число, обладающее такими свойствами равно: 7 * 8 * 9 = 504 Следующее такое число 504 * 2 = 1008, но оно не трёхзначное. Задания для самостоятельного решения 1. Из Правил делимости чисел, которые мы рассмотрели в начале главы, легко вывести признаки делимости чисел на 15, 16, 18, 20, 22, 24, 25, 100, 1000. Попробуйте! 2. Докажите, что число, у которого число тысяч равно числу единиц, а число сотен равно числу десятков, делится на 11. 3. Докажите, что если двузначное число в 4 раза больше суммы цифр, то оно делится на 12. 4. Натуральное число называется полусовершенным, если оно равно сумме всех или некоторых своих делителей, исключая само число: 6, 12, 18, 20, 24, 28, 30, 36, 40. Например, 12 = 1 + 2 + 3 + 6 или 12 = 2 + 4 + 6. Из этого определения следует, что всякое совершенное число является и полусовершенным, то есть полусовершенных чисел в заданном диапа134
зоне не меньше, чем совершенных. Напишите программу, которая находила бы несколько полусовершенных чисел. Вы ошиблись в подсчёте Удивительный мир чисел.. Задача 4 (2.2) ], страница 50 Ученик покупает 18 карандашей, 6 тетрадей, 12 ластиков, 9 блокнотов и несколько тетрадей для рисования по 15 к. Девушка-продавец выписала чек на 1 р. 52 к. Взглянув на чек, мальчик сразу же сказал продавцу: «Вы ошиблись в подсчёте». Девушка пересчитала и исправила свою ошибку. Как удалось пареньку так быстро обнаружить просчёт? Ответ: Стоимость каждой покупки, а значит и общая сумма кратна трём, но 1 р. 52 к. на три не делится. Задача #24 Математическая шкатулка Какое целое число делится (без остатка) на любое целое число, отличное от 0?. Ответ: 0 Задача #25 Математическая шкатулка Сумма каких двух натуральных чисел равна их произведению? Ответ: 2 + 2 = 2 x 2- 135
Глава #4. НОД, НОК и компания Начнём мы эту главу с двух родственных программ – для вычисления НОД (наибольшего общего делителя двух чисел) и НОК (наименьшего общего кратного). Проект Наибольший общий делитель Исходный код программы находится в файле НОД.py. Функция с параметром Фуекция без параметров Цикл for Оператор деления по модулю % Оператор деления // Бесконечный цикл while Метод int Оператор return Условный оператор if Условный оператор if-else Цикл while Комбинированные операторы присваивания Для вычисления НОД мы воспользуемся простым и алгоритмами древнегреческого математика Евклида (Рис. 1). быстрым Рис. 1. Евклид (Εὐκλείδης, ок. 325 года до н.э. - до 265 года до н.э.) 136
По-английски НОД называется 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, которая и реализует простой алгоритм Евклида. # ГЛАВНАЯ ФУНКЦИЯ def main(): print() print('НОД двух чисел') print() # бесконечный цикл ввода данных # пока пользователь не закроет программу # или не введёт отрицательные числа: while True: print('Введите первое число > ', end = '') num1 = int(input()) print(num1) print('Введите второе число > num2 = int(input()) if (num1 + num2 < -1): return print(num2) ', end = '') 137
number1, number2 = num1, num2 # если первое число меньше второго, # то меняем их значения: if (number1 < number2): number1, number2 = number2, number1 # находим НОД: if (number2 == 0): nod = number1 else: #nod = euklid(number1, number2) nod = speedEuklid(number1, number2) # печатаем НОД: print(f'НОД({num1},{num2}) = {nod}') print() main() Если меньшее из чисел (или оба) равны нулю, то за НОД следует принять первое число. Если же оба числа положительные, то начинаем действовать по простому алгоритму Евклида: # НОД # ПРОГРАММА ДЛЯ НАХОЖДЕНИЯ НАИБОЛЬШЕГО # ОБЩЕГО ДЕЛИТЕЛЯ ДВУХ НАТУРАЛЬНЫХ ЧИСЕЛ (НОД) # ПРОСТОЙ АЛГОРИТМ ЕВКЛИДА def euklid(n1, n2): while (n2 != n1): if (n1 >= n2): n1 -= n2 else: n2 -= n1; return n1 Тут, конечно, следует учитывать значения чисел: если они очень большие, то лучше пользоваться быстрым, а не простым алоритмом. В цикле while на каждой итерации мы вычитаем из большего числа меньшее – до тех пор, пока значения переменных n1 и n2 не сравняются. Поскольку с каждым разом одно из чисел уменьшается, то рано или поздно это условие будет выполнено. 138
Вычисленное значение НОД мы возвращаем методу main, который и публикует его в консоли. При небольших числах алгоритм работает очень быстро (Рис. 2). Рис. 2. Вычисляем НОД двух чисел Нагнетаем обстановку, задавая всё более «солидные» числа, и простой алгоритм начинает «буксовать» (Рис. 3). Рис. 3. Вычисляем НОД больших чисел А при дальнейшем увеличении чисел он попросту впадает в кому! И тут нам на выручку приходит быстрый алгоритм Евклида: # БЫСТРЫЙ АЛГОРИТМ ЕВКЛИДА def speedEuklid(n1, n2): while (n2 > 0): n1, n2 = n2, n1 % n2 return n1 139
Он отличается от простого алгоритма тем, что на каждой итерации мы находим остаток от деления первого числа на второе, значение второго числа присваиваем первому числу, а значение остатка - второму. Так мы продолжаем до тех пор, пока остаток от деления не обратится в нуль. Как и в первом случае, это условие обязательно будет выполнено. Быстрый алгоритм Евклида основан на свойстве: НОД(m,n) = НОД(n, m%n) при m > n Этот алгоритм не собьёшь с толку никакими числами (Рис. 4)! Вы можете взять любые числа и убедиться, что алгоритм действительно работает практически мгновенно. Рис. 4. Наша программа легко справляется и с очень большими числами! Для лучшего понимания быстрого алгоритма давайте вручную найдём НОД чисел 42 и 14: number1 = 42 number2 = 14 Так как второе число больше нуля, то находим остаток от их деления: n = 42 % 14 = 0 И присваиваем новые значения переменным: number1 = 14 number2 = 0 Второе число теперь равно нулю, значит, НОД найден – он равен второму числу, то есть 14. Рассмотрим другой пример: 140
number1 = 56 number2 = 42 n = 56 % 42 = 14 number1 = 42 number2 = 14 Уже после одного цикла мы пришли к первому примеру. Вы также можете пользоваться готовой функцией igcd из модуля sympy: from sympy import igcd nod = igcd(number1, number2) Она работает отлично (Рис. 5)! Рис. 5 В отличие от наших функций, в функцию igcd можно передавать сколько угодно чисел. Проект Наименьшее общее кратное Исходный код программы находится в файле НОК.py. Бесконечный цикл while Метод int 141
Оператор return Условный оператор if Функция с параметрами Зная наибольший общий делитель двух чисел, мы очень просто вычислим их наименьшее общее кратное по такой формуле: НОК = число1 * число2 / НОД (число1, число2) (1) По-английски НОК называется lcm – least common multiple. Для нахождения НОД мы воспользуемся быстрым алгоритмом Евклида, а НОК вычислим по формуле (1): # ПРОГРАММА ДЛЯ ВЫЧИСЛЕНИЯ НАИМЕНЬШЕГО ОБЩЕГО # КРАТНОГО ДВУХ ЧИСЕЛ (НОК) # БЫСТРЫЙ АЛГОРИТМ ЕВКЛИДА def speedEuklid(n1, n2): while (n2 > 0): n1, n2 = n2, n1 % n2 return n1 # ГЛАВНАЯ ФУНКЦИЯ def main(): print() print('НОК двух чисел') print() # бесконечный цикл ввода данных # пока пользователь не закроет программу # или не введёт отрицательные числа: while True: print('Введите первое число > ', end = '') num1 = int(input()) print('Введите второе число > num2 = int(input()) if (num1 + num2 < -1): return ', end = '') number1, number2 = num1, num2 # если первое число меньше второго, # то меняем их значения: if (number1 < number2): number1, number2 = number2, number1 142
# находим НОK: if (number1 * number2 == 0): nok = 0 else: # вычисляем НОК: nok= number1 * number2 // speedEuklid(number1, number2) # печатаем НОK: print(f'НОК({num1},{num2}) = {nok}') print() main() Поскольку НОД вычисляется очень быстро, то и НОК мы получаем в считанные мгновения (Рис. 1). Рис. 1. Вычисляем НОК Для вычисления НОК в модуле sympy имеется функция ilcm: from sympy import ilcm nok = ilcm(number1, number2) В функцию ilcm можно передавать сколько угодно чисел. 143
Проект НОК нескольких чисел Исходный код программы находится в файле НОК2.py. Функция с параметром Цикл for Оператор return Цикл while Оператор деления по модулю % При решении задачи о назойливом остатке необходимо знать НОК последовательных чисел от 2 до 6 (см. [КА86], решение на странице 133). Найти НОК нескольких чисел можно так. Сначала вычисляем НОК2 первых двух чисел, затем вычисляем НОК3 для НОК2 и третьего числа и так далее. Но давайте решим эту задачу для ряда чисел произвольной длины – от 2 до maxnok. В функции main вызываем функцию NOK2, которой и передаём верхнюю границу ряда: # ГЛАВНАЯ ФУНКЦИЯ def main(): print() print('НОК НЕСКОЛЬКИХ ЧИСЕЛ') print() NOK2(30) main() В функции NOK2 мы задаём начальное значение переменной nok, равное 1, что вполне разумно для наименьшего общего кратного чисел 1 и 1. Далее мы находим НОК для 1 и 2, затем для полученного НОК и тройки – как и было описано выше: # НАХОДИМ НОК РЯДА ЧИСЕЛ def NOK2(maxnok): nok = 1 144
for i in range(2, maxnok + 1): nok = NOK(nok,i) print('НОК чисел 2..%i равно %i' %(i, nok)) print() # НАХОДИМ НОK ДВУХ ЧИСЕЛ def NOK(number1, number2): if (number1 * number2 == 0): nok = 0 else: # вычисляем НОК: nok= number1 * number2 // speedEuklid(number1, number2) return nok Все промежуточные значения НОК печатаем на экране (Рис. 1). Рис. 1. Вычисляем НОК ряда чисел Из полученного списка видно, что НОК ряда чисел 2..6 равно 60. При делении чисел вида 60n на числа 2..6 в остатке мы всегда будем получать 0. А при делении чисел вида (60n + 1) – единицу, что и требуется в условии задачи. Но это число (60n + 1) должно делиться на 7 без остатка, то есть: (60n + 1) = 7k (1) 145
Поскольку мы умеем вычислять НОК любого ряда чисел, то можем обобщить задачу о назойливых остатках для любых рядов, записав формулу так: (nok * n + 1) = maxd * k (2) Решаем задачу с помощью новой функции solve: # ГЛАВНАЯ ФУНКЦИЯ def main(): print() print('НОК НЕСКОЛЬКИХ ЧИСЕЛ') print() #NOK2(30) solve(17) Этой функции мы должны передать то число maxd, на которое делится без остатка искомое число. В задаче это семёрка, но нас, конечно, интересуют более крупные числа! На Рис. 1 хорошо видно, что наименьшее искомое число быстро растёт с увеличением 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 print('Решаем задачу для чисел, кратных %i: ' %maxd) 146
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('Искомое число равно', num) n += 1 num = nok * n + 1; print() Например, взяв вместо числа 7 на десяток больше, мы получим такие решения задачи (Рис. 2). Рис. 2. Обобщили Конечно, без компьютера решать такие задачи было бы крайне затруднительно! 147
А теперь поэкспериментируйте и подумайте, почему для составных чисел maxd задача не решается. Проект Всезнающая статистика Исходный код программы находится в файле Всезнающая статистика.py. Функция без параметров Оператор continue Цикл for Оператор деления по модулю % Задача 14 (16) из книги Удивительный мир чисел [КА86], страницы 86-87: Попал как-то мне в руки обрывок прошлогодней газеты. Моё внимание привлекло чернильное пятно, закрывшее последние три цифры шестизначного числа (Рис. 1). По сохранившемуся кусочку текста я вспомнил: это была заметка, в которой сообщалось, что к концу минувшего года население нашего города возросло до этого числа. В заметке говорилось также о том, что это шестизначное число уникально среди шестизначных: оно делится на 2, 3, 4, 6, 7, 8 и 9. Ого! Не правда ли? Чтобы восстановить все цифры этого числа, нет нужды обращаться в реставрационную лабораторию. Собственная сообразительность подскажет вам математический метод быстрого решения этой задачи. Рис. 1. Обрывочные сведения 148
Опять решение задачи переносим в функцию solve: # ГЛАВНАЯ ФУНКЦИЯ def main(): print("Всезнающая статистика") print() solve() if __name__ == "__main__": main() Эта задача очень простая, поскольку нам предстоит проверить всего одну тысячу чисел. Действительно, на обрывке газеты вы видите, что первые три цифры 6-значного числа – 566. Последние три – от 000 до 999: # -*- coding: Windows-1251 -*# Кордемский, с.86-87, Задача 14 # РЕШАЕМ ЗАДАЧУ def solve(): # мин. и макс. 6-значные числа: min6 = 566000 max6 = 566999 В цикле 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. 149
Рис. 2. Информация восстановлена Проект Восстановите потерянную цифру Исходный код программы находится в файле Восстановите потерянную цифру.py. Функция без параметров Цикл 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: # ГЛАВНАЯ ФУНКЦИЯ def main(): print("Восстановите потерянную цифру") print() solve() 150
if __name__ == "__main__": main() Цифра, обозначенная буквой x, может принимать значения от 0 до 9. Их легко перебрать в цикле for: # -*- coding: Windows-1251 -*# Кордемский, с.85, Задача 7 # РЕШАЕМ ЗАДАЧУ def solve(): for x in range(0, 9+1): Для каждого значения х мы находим арифметическую сумму трёхзначных чисел – как и предписывает нам признак делимости на 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(f"Искомое число равно 170 141 183 460 469 231 {x}31 687 303 715 884 105 727") print() На Рис. 1 вы можете видеть, что x = 7. Рис. 1. Нашли х 151
Проект Снимите маску с одной цифры Исходный код программы находится в файле Снимите маску с одной цифры.py. Функция без параметров Цикл for Оператор целочисленного деления % Условный оператор if Задача 6 из книги Удивительный мир чисел [КА86], страница 84: В записи знаменитого «шахматного» числа М64= 1y446 744 073 709 551 615 на его вторую цифру накинута маска у. Сняв маску, расшифруйте значение у, зная, что достаточно увеличить заданное число на 3 единицы, как оно становится кратным числу 19. Примечание. По легенде, именно такое число пшеничных зёрен следовало выдать в награду изобретателю шахмат, попросившему положить всего одно зерно на первую клетку шахматной доски, а на каждую следующую клетку вдвое большее число зёрен, чем на предыдущую. Эта задача сродни предыдущей, но признак делимости чисел на 19 не очень удобен: # ГЛАВНАЯ ФУНКЦИЯ def main(): print("Снимите маску с одной цифры") print() solve() if __name__ == "__main__": main() Поэтому в функции solve мы просто находим остаток от деления числа num + 3 на 19: 152
# -*- coding: Windows-1251 -*# Кордемский, с.84, Задача 6 # РЕШАЕМ ЗАДАЧУ def solve(): for y in range(0, 9+1): num = (10 + y) * 1000000000000000000 + 446744073709551615 + 3 if (num % 19 != 0): continue # печатаем искомое число: print(f"Искомое число равно 1{y} 446 744 073 709 551 615") print() Других сложностей в задаче нет, и мы быстро находим, что y = 8 (Рис. 1). Рис. 1. Нашли зерно задачи Проект На одно делится, на другое нет Исходный код программы находится в файле На одно делится, на другое нет.py. Функция без параметров Бесконечный цикл for Оператор целочисленного деления % Оператор break Задача 2 из книги Удивительный мир чисел [КА86], страница 84: Найдите наименьшее число, которое делится на 77, а при делении на 74 даёт в остатке 48. Простая переборная задача – как раз для решения на компьютере: # ГЛАВНАЯ ФУНКЦИЯ def main(): print("На одно делится, на другое нет") print() 153
solve() if __name__ == "__main__": main() Поскольку искомое число кратно 77, то его можно представить в виде: num = 77 * n Значение переменной n нужно изменять от 1 до … - пока задача не будет решена. Здесь вполне уместно использовать бесконечный цикл while, прерываемый оператором break, как только искомое число будет найдено: # -*- coding: Windows-1251 -*# Кордемский, с.84, Задача 2 # РЕШАЕМ ЗАДАЧУ def solve(): n = 1 while True: #и скомое число кратно 77: num = 77 * n n += 1 if (num % 74 != 48): continue # печатаем искомое число: print(f"Искомое число равно {num}") break print() Ответ на задачу показан на Рис. 1. Рис. 1. Лёгкая задача 154
Проект Кто где живёт? Исходный код программы находится в файле Кто где живёт.py. Функция без параметров Списки Цикл for Цикл while Оператор return Задача 3 из книги Удивительный мир чисел [КА86], страница 50: Лайне, Майму, Юта, Белла, Елена и Элеонора живут в одном блоке здания. Возвращаясь из школы домой, Лайне проходит внутри здания 84 ступеньки, Майму -126, Юта -147, Белла - 189, Елена -210, а Элеонора 231 ступеньку. До квартиры на первом этаже ступенек нет, а между этажами одинаковые количества ступенек. Кто на каком этаже живёт? # ГЛАВНАЯ ФУНКЦИЯ def main(): print('Кто где живёт?') print() solve() print() if __name__ == "__main__": main() Число ступенек, ведущих на заданный этаж, можно представить в виде: ЧИСЛО СТУПЕНЕК = ПРОЛЁТ*(ЭТАЖ-1) (1) Откуда: ЭТАЖ = (ЧИСЛО СТУПЕНЕК) / ПРОЛЁТ + 1 (2) 155
Единицу мы добавляем потому, что на первый этаж можно подняться без ступенек. Число ступенек для каждого этажа нам известно из условия задачи. Эти данные, равно как и странные имена девочек, можно поместить в список, которым пользоваться удобнее, чем несколькими отдельными переменными: # -*- coding: Windows-1251 -*# Кордемский, с.50, Задача 3 # РЕШАЕМ ЗАДАЧУ 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(f"Этаж {names[i]} = {stupeni[i]//nod + 1}") print() def NOD(n1, n2): while (n2 > 0): n1, n2 = n2, n1 % n2 return n1 156
Ответ на задачу представлен на Рис. 1. Рис. 1. Нашли этажи Так как 21 = 3 х 7, то число ступенек успешно можно разделить и на 3, и на 7. Номера этажей значительно увеличатся, но здравый смысл нам всё-таки подсказывает, что вряд ли между этажами всего 3 или 7 ступенек. Проект Три велосипедиста Исходный код программы находится в файле Три велосипедиста.py. Функция без параметров Функция с параметрами Оператор return Условный оператор if Цикл while Оператор целочисленного деления % Оператор and Задача 16 из книги Удивительный мир чисел [КА86], страница 55: Три велосипедиста начали с общего старта движение по круговой дорожке. Первый делает полный круг за 21 мин, второй - за 35 мин, а третий - за 15 мин. Через сколько минут они ещё раз окажутся вместе в начальном пункте? 157
# ГЛАВНАЯ ФУНКЦИЯ def main(): print('Три велосипедиста') print() solve() print() if __name__ == "__main__": main() Задача решается элементарно, Ватсон, как выразился бы Шерлок Холмс, если догадаться, что велосипедисты окажутся в точке старта через целое число кругов. А это случится, когда пройдёт время t (в минутах), которое нацело делится на 21, 35 и 15. Из этого следует, что искомое время в точности равно НОК этих чисел: # -*- coding: Windows-1251 -*# Кордемский, с.55, Задача 16 # РЕШАЕМ ЗАДАЧУ def solve(): # находим НОК трёх чисел: nok = NOK(NOK(21,35),15) print(f"Велосипедисты встретятся через {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 158
while True: t += 1 if (t % 21 == 0 and t % 35 == 0 and t % 15 == 0): break print(f"Велосипедисты встретятся через {t} мин.") print() Но, как ни решай и ни выражайся, а встреча произойдёт через 105 минут (Рис. 1). Рис. 1. Время встречи изменить нельзя Проект Задача 5 с сайта Проект Эйлер Исходный код программы находится в файле Эйлер005.py. Задача номер 5 (Problem 5) называется Smallest multiple. Она была опубликована 14 декабря 2001 года 2520 is the smallest number that can be divided by each of the numbers from 1 to 10 without any remainder. What is the smallest positive number that is evenly divisible by all of the numbers from 1 to 20? Наименьшее кратное 2520 - самое маленькое число, которое делится без остатка на все числа от 1 до 10. Какое самое маленькое число делится нацело на все числа от 1 до 20? Легко заметить, что число -2520 меньше числа 2520 и тоже делится на все числа от 1 до 10, поэтому искать ответ нужно только среди натуральных чисел. Мы можем предположить, что число будет немаленькое, но такое, что задачу ещё разумно решать методом грубой силы, то есть перебирать все натуральные числа в бесконечном цикле while и проверять их на дели159
мость. Если очередное число не делится нацело на какое-либо число из диапазона 1..20 (хотя на единицу делятся все числа, так что это требование избыточно), то мы переходим к следующему кандидату на минимальное число minn. Как только мы найдём ответ на задачу, сразу печатаем ответ в Консольном окне: # Project Euler. Problem 5 # Smallest multiple # РЕШАЕМ ЗАДАЧУ def solve(): # наименьшее натуральное число, # удовлетворяющее условию задачи: minn = 1 while True: flg = True for i in range(1, 20 + 1): if minn % i != 0: flg = False break if flg: print("Минимальное число равно", minn) print() break minn += 1 def main(): print("Решаем задачу 5 из Проекта Эйлер") solve() main() Увы, на Питоне таким способом задачу не решить — долго! Можно сократить перебор, если исключить некоторые делители, которые заведомо удовлетворяют условию задачи, если искомое число делится на оставшиеся числа из ряда 1..20. Например, достаточно проверять такие делители: def solve2(): # делители: dels = [ 11, 13, 14, 16, 17, 18, 19, 20 ] # наименьшее натуральное число, # удовлетворяющее условию задачи: minn = 1 160
while True: flg = True for i in dels: if minn % i != 0: flg = False break if flg: print("Минимальное число равно", minn) print() break minn += 1 Через некоторое время мы получаем ответ (Рис. 1). Рис. 1 Чтобы убыстрить работу программы, самое время вспомнить, что искомое число — это наименьшее общее кратное чисел 1..20. Исходный код программы находится в файле Эйлер005а.py. Пишем необходимые функции: # Project Euler. Problem 5 # Smallest multiple # БЫСТРЫЙ АЛГОРИТМ ЕВКЛИДА def speedEuklid(n1, n2): while (n2 > 0): n1, n2 = n2, n1 % n2 return n1 # НАХОДИМ НОK ДВУХ ЧИСЕЛ def NOK2(number1, number2): if (number1 * number2 == 0): nok = 0 else: # вычисляем НОК: nok = number1 * number2 // speedEuklid(number1, number2) return nok 161
# НАХОДИМ НОK maxnok ЧИСЕЛ def NOK(maxnok): nok = 1 for i in range(2, maxnok + 1): nok = NOK2(nok,i) return nok И в функции solve вызываем функцию NOK для 20 чисел: # РЕШАЕМ ЗАДАЧУ def solve(): # наименьшее натуральное число, # удовлетворяющее условию задачи: minn = NOK(20) print("Минимальное число равно", minn) print() Этот способ решает задачу мгновенно (Рис. 2). Рис. 2 Не всегда грубая сила хороший помощник! 162
Глава #5. Простые числа Будьте проще – и к вам потянутся люди. Кредо простых чисел Простыми называются натуральные числа, имеющие в точности два разных делителя. Из этого определения следует, что ни нуль, ни единица к простым числам не относятся. Также очень легко установить, что первое простое число - это двойка, потому что она делится на единицу и на саму себя (двойка – единственное чётное простое число!). Дальше вы легко найдёте тройку, пятёрку, семёрку (кто посмелее, доберётся и до туза). Вам не составит труда продолжить этот ряд: 11, 13, 17, 23, 29, 31. Чтобы отбросить многие составные (то есть не простые) числа, достаточно воспользоваться признаками делимости. Но проверять большие числа таким способом «опасно», потому что легко ошибиться при делении (да и вообще делить большие числа затруднительно). На этот случай греческий математик Эратосфен (Рис. 1) придумал надёжный способ поиска простых чисел, который образно называют решетом Эратосфена. Рис. 1. Ἐρατοσθένης ὁ Κυρηναῖος (276 - 194 до н.э.) В Главе 34 Простые числа Мартин Гарднер рассказывает о решете Эратосфена (Рис. 2). 163
Рис. 2 Действует он так [ЗП88, Задача 557]. Поиск простых чисел начинается с засыпки натуральных чисел в «решето», которое удобно представить в виде прямоугольной таблицы. Наименьшее число - это двойка, поскольку единица к простым числам не относится. А наибольшее – любое, по вашему выбору. Мы ограничимся первой сотней чисел (Рис. 3). Находим в таблице двойку – первое простое число – и обводим её кружком. Очевидно, что все последующие числа, кратные двойке, простыми быть не могут, поэтому мы их вычёркиваем. В данном примере мы будем просто закрашивать клетки с составными числами (Рис. 4). Как видите, уже после первого, грубого просеивания в решете осталась только половина чисел. Продвигаемся дальше по таблице и находим первое после двойки невычеркнутое число. Это тройка – второе простое число. Вычёркиваем все ещё не вычеркнутые числа, кратные тройке (Рис. 5). Ну, а затем всё повторяется. Находим следующее простое число – пятёрку - и вычёркиваем числа, кратные пяти. Переходим к семёрке, затем к чис- 164
лам 11 и 13. И так далее, пока не дойдём до конца таблицы. Числа в кружках - простые, все прочие – составные (Рис. 6). Рис. 3. Числа - в решете Рис. 4. Вычеркнули чётные числа 165
Рис. 5. И кратные тройке Рис. 6. Просеивание закончено! 166
Проект Чудеса в решете Эратосфена Исходный код программы находится в файле Решето Эратосфена.py. Бесконечный цикл while Метод int Условный оператор if Оператор return Функция с параметром Списки Цикл for Цикл while Как вы убедились, просеивание чисел – занятие необременительное и даже весёлое, но всё-таки требует некоторого внимания и сноровки, поэтому лучше воспользоваться компьютером, тем более что алгоритм поиска простых чисел незамысловатый, и мы без труда переведём его на язык Питон. Нам достаточно знать только конец диапазона чисел end, ведь поиск всегда начинается с двойки: # ГЛАВНАЯ ФУНКЦИЯ def main(): print() print('Решето Эратосфена') print() # бесконечный цикл ввода данных # пока пользователь не закроет программу # или не введёт 0: while True: s = 'Введите конец диапазона > ' print(s, end = '') end = int(input()) # если конец диапазона равен 0, # то программу закрываем: if (end == 0): return # конец диапазона не меньше двойки: if (end < 2): end = 2 # ищем простые числа: primes(end) 167
main() Здесь мы дополнительно позаботились о том, чтобы пользователь мог закрыть программу, введя в качестве конца диапазона нуль. Это необязательно, но и не помешает. Непосредственно поиск простых чисел мы поместим в функцию рrimes, которой передаём число – конец диапазона end: # Решето Эратосфена import math # ИЩЕМ ПРОСТЫЕ ЧИСЛА def primes(end): print('Простые числа в заданном диапазоне:') Решето вполне естественно представить списком number. А дальше мы действуем по алгоритму Эратосфена: 1. Записываем в список number числа, начиная с двойки и заканчивая концом диапазона: # создаём список натуральных чисел 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. И так далее: 168
while (prime <= math.sqrt(end)): for i in range(prime * prime, end number[i - 2] = 0 +1, prime): Конечно, мы не можем реально зачеркнуть число в списке, поэтому присваиваем соответствующему элементу списка значение нуль, которое означает, что это число составное. Из индекса элемента списка нужно вычесть двойку, поскольку нулевой индекс принадлежит этому числу! 4. Ищем первое «невычеркнутое» число – его значение в списке должно отличаться от нулевого: # ищем следующее простое число: prime += 1 while (prime <= math.sqrt(end) and number[prime-2] == 0): prime += 1 Теперь переменная prime содержит следующее простое число. 5. Если prime не превосходит корня квадратного из максимального числа end, то мы переходим к пункту 3. В противном случае все простые числа в заданном диапазоне уже найдены, их значения в списке - ненулевые. Перебираем весь список, находим по этому признаку простые числа и печатаем их на экране: # печатаем простые числа: print() for i in number: if (i != 0): print(i, ' ', end='') print() print() Поскольку в функции main действует бесконечный цикл while, то пользователь может и дальше «решетить» простые чисел (Рис. 1). Несмотря на «древность» алгоритма, он действует довольно быстро, но очень жаден до памяти компьютера. Этот пример наглядно показывает нам, что для написания эффективной программы нужен хороший алгоритм. А если алгоритм имеется, то перевести его на любой язык программирования совсем несложно. 169
Рис. 1. Решето Эратосфена в действии! 170
Проект Sieve Исходный код программы находится в файле Sieve.py. В модуле sympy имеется класс sieve, который ищет простые числа по методу Эратосфена. В метод extend этого класса нужно передать максимальное число для поиска: import math from sympy import sieve # ГЛАВНАЯ ФУНКЦИЯ def main(): print() print('Sieve') print() sieve._reset() # бесконечный цикл ввода данных # пока пользователь не закроет программу # или не введёт 0: while True: s = 'Введите конец диапазона > ' print(s, end = '') end = int(input()) # если конец диапазона равен 0, # то программу закрываем: if (end == 0): return # конец диапазона не меньше двойки: if (end < 2): end = 2 # ищем простые числа: sieve.extend(end) print(sieve._list) print() print() main() 171
Мы можем только распечатать все найденные простые числа (Рис. 1). Рис. 1 Вместо конца диапазона мы можем передать в метод extend_to_no количество первых простых чисел (Рис. 2): #sieve.extend(end) sieve.extend_to_no(end) print(sieve._list) Рис. 2 Гораздо более полезен метод primerange. Если ему передать конец диапазона, то он генерирует простые числа в диапазоне [2, end), то есть конец диапазона не учитывается. Поэтому при вызове этого метода добавляем 1 к концу диапазона (Рис. 3): primes = sieve.primerange(end + 1) for i in primes: print(i, end = ' ') print() print() Рис. 3 Если в метод primerange передать 2 числа – начало и конец диапазона, то он генерирует простые числа в диапазоне [start, end). 172
Например, мы хотим получить все двузначные простые числа (Рис. 4): primes = sieve.primerange(10, end) Рис. 4 А так мы получим первый десяток простых чисел (Рис. 5): from sympy import sieve, prime primes = sieve.primerange(prime(10) + 1) Рис. 5 Проект Простые числа Исходный код программы находится в файле Простые числа.py. Бесконечный цикл while Метод int Условный оператор if Оператор return Функция с параметрами Вложенные циклы for Оператор целочисленного деления % Всем хорош алгоритм Эратосфена, но не годится для больших чисел - слишком неэкономно он расходует память компьютера. Мы, конечно, и с 173
помощью решета легко узнаем, что следующим простым годом будет 2027-й. Но вот с числами 514229 или 39916801 нам уже придётся повозиться! В таких случаях правильнее один раз написать программу, чем каждый раз считать вручную. Чтобы сделать программу более универсальной, мы добавим ещё одну переменную - begin, чтобы пользователь мог задавать и нижнюю, и верхнюю границу диапазона для поиска простых чисел: # ГЛАВНАЯ ФУНКЦИЯ def main(): print() print('Простые числа') print() # бесконечный цикл ввода данных # пока пользователь не закроет программу # или не введёт 0: while True: s = 'Введите начало диапазона > ' print(s, end = '') begin = int(input()) #print(begin) # если начало диапазона равно 0, то программу закрываем: if (begin == 0): return # начало диапазона не меньше двойки: if (begin < 2): begin = 2 s = 'Введите конец диапазона print(s, end = '') end = int(input()) > ' # если конец диапазона равен 0, # то программу закрываем: if (end == 0): return # конец диапазона не меньше двойки: if (end < 2): end = 2 # ищем простые числа: primes(begin, end) print() main() Функция main действует почти так же, как и «эратосфеновская», но вызывает функцию primes с двумя параметрами: 174
# ПРОГРАММА ДЛЯ ПОИСКА ПРОСТЫХ ЧИСЕЛ # В ЗАДАННОМ ДИАПАЗОНЕ import math # ИЩЕМ ПРОСТЫЕ ЧИСЛА 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(j, ' ', end='') # и записываем в файл: w.write(str(j) + ' ') w.write('\n') w.close() print() print('Всего ' + str(n)) print() Здесь мы в цикле for последовательно перебираем числа от n1=begin до n2=end и проверяем их на простоту. Проверка проходит так: очередное число j мы поочерёдно делим на все числа, начиная с двойки и кончая корнем квадратным из самого числа j. Так как любое натуральное число делится на единицу и само на себя, то два разных делителя у него имеются в любом случае (кроме, единицы, разумеется). Если мы обнаружим хотя бы ещё один делитель, то это будет перебор: число заведомо составное, и проводить испытания дальше нет 175
смысла - мы переходим к проверке следующего числа. Если же число простое, то мы печатаем его на экране (Рис. 1) и записываем в файл. Рис. 1. Простые числа найдены! 176
Проект Простые числа 2 Исходный код программы находится в файле Простые числа 2.py. Бесконечный цикл while Метод int Условный оператор if Оператор return Функция с параметром Списки Оператор break Вложенные циклы for Метод math.sqrt Оператор целочисленного деления % Недостаток нашего алгоритма кроется в делении каждого числа из заданного диапазона begin..end на все числа, начиная с двойки и заканчивая корнем квадратным. Но мы-то знаем, что делить следует только на уже найденные простые числа, которых заведомо меньше. Это значит, что мы должны их где-то хранить. Для этого вполне сгодится и обычный список. В этом случае начало диапазона лучше закрепить за двойкой, чтобы список простых чисел заполнялся правильно. Для разнообразия мы вместо верхней границы диапазона будем требовать от пользователя ввода общего количества простых чисел num, которые он желает найти: # ГЛАВНАЯ ФУНКЦИЯ def main(): print() print('Простые числа') print() # бесконечный цикл ввода данных # пока пользователь не закроет программу # или не введёт 0: while True: s = 'Введите количество простых чисел > ' print(s, end = '') num = int(input()) #print(num) # если пользователь ввёл 0, то программу закрываем: if (num == 0): 177
return # ищем простые числа: primes(num) print() main() В функции primes создаём список prime для хранения найденных простых чисел и сразу помещаем в него двойку: # ПРОГРАММА ДЛЯ ПОИСКА ПРОСТЫХ ЧИСЕЛ import math # ИЩЕМ ПРОСТЫЕ ЧИСЛА def primes(end_num): print('Простые числа:') # список для хранения простых чисел # первое простое число - двойка: prime = [2] print(prime[0],' ', end='') # всего простых чисел: nPrimes = 1 Цикл while мы начинаем со следующего числа, то есть с тройки. Поскольку все остальные простые числа нечётные, то это избавляет нас от необходимости проверять все числа – мы можем ограничиться только нечётными, что мы и делаем, добавляя в цикле while двойку к переменной num: num = 3 while True: # если нашли заданное простое число, # сообщаем об этом пользователю: if (nPrimes == end_num): print() print('Простое число номер', end_num, 'равно', prime[end_num-1]) print() break . . . num += 2 print() 178
Обратите внимание, что цикл while - бесконечный. Дело в том, что мы наперёд не знаем, какое из чисел num окажется по счёту endNumber, поэтому мы прекращаем цикл, когда найдём nPrimes = endNumber простых чисел. Проверку очередного числа num выполняем так. Поочерёдно извлекаем из списка prime простые числа и ограничиваем проверку условием, что очередное простое число i не больше math.sqrt(num). Дальше действуем, как обычно: # проверяем, делится число num # простые числа из списка prime: flg = True for i in prime: # поиск закончен: if (i > math.sqrt(num)): break И если окажется, что число num – простое, то мы увеличиваем счётчик простых чисел на единицу и добавляем num в список: # если число num делится, # значит, число составное: if (num % i == 0): flg = False break # если нашли простое число, if (flg): # подсчитываем: nPrimes += 1 # добавляем новое простое число в список: prime.append(num) # и печатаем его в консольном окне: print(num, ' ', end='') num += 2 print() По ходу поиска простых чисел функция рrimes печатает все простые числа на экране, а в конце «выступления» сообщает, что простое число с заданным номером такое-то (у нас оно всегда последнее из найденных). Например, 101-ое простое число равно 547 (Рис. 1). 179
Рис. 1. Простая задача с простыми числами Проект Prime Исходный код программы находится в файле Prime.py. Вы уже знакомы с функцией prime из модуля sympy. Она возвращает заданное простое число по его номеру в последовательности: from sympy import prime # ГЛАВНАЯ ФУНКЦИЯ def main(): print() print('Sieve') print() # бесконечный цикл ввода данных # пока пользователь не закроет программу # или не введёт 0: while True: s = 'Введите число > ' print(s, end = '') num = int(input()) # если число = 0, # то программу закрываем: if (num == 0): 180
return # число не меньше двойки: if (num < 2): num = 2 prime1 = prime(num) print(prime1) print() print() main() Например, десятое простое число – это 29 (Рис. 1). Рис. 1. Метод primerange действует так же, как одноимённый метод класса sieve: from sympy import prime, primerange Если ему передать единственное число, то он генерирует простые числа в диапазоне [2, end) (Рис. 2): primes = primerange(num + 1) for i in primes: print(i, end=' ') Рис. 2 Если передать 2 числа, то метод генерирует простые числа в диапазоне [start, end) (Рис. 3): primes = primerange(10, num + 1) 181
Рис. 3 Метод primepi возвращает количество простых чисел, которые не больше заданного числа (Рис. 4): from sympy import prime, primerange, primepi prime1 = primepi(num) print(prime1) Рис. 4 Метод isprime возвращает True, если заданное число простое (Рис. 5): from sympy import prime, primerange, primepi, isprime print(isprime(num)) Рис. 5 Метод nextprime возвращает следующее простое число после заданного (Рис. 6): 182
from sympy import prime, primerange, primepi, isprime, nextprime Рис. 6 Второй аргумент в методе nextprime показывает, какое простое число после заданного нужно вернуть. По умолчанию это следующее число, но мы, например, хотим получить второе простое число после заданного (Рис. 7): print(nextprime(num, 2)) Рис. 7 Метод prevprime возвращает предыдущее простое число (Рис. 8): from sympy import prime, primerange, primepi, isprime, nextprime, prevprime print(prevprime(num)) Метод randprime возвращает случайное простое число в заданном диапазоне (Рис. 9): from sympy import prime, primerange, primepi, isprime, nextprime, prevprime, randprime 183
print(randprime(2, num)) Рис. 8 Рис. 9 Проект Простые числа-близнецы Исходный код программы находится в файле Twin primes.py. Все простые числа, кроме двойки, - нечётные, поэтому минимальное расстояние между ними равно двум. Простые нечётные числа, разность между которыми равна 2, называются числамиблизнецами. Таким образом, пара простых чисел (2, 3) – это не близнецы. Первые пары чисел-близнецов легко найти вручную: (3, 5) (5, 7) (11, 13) (17, 19) (29, 31) 184
Если продолжить этот ряд, то можно найти такую закономерность: Все пары чисел-близнецов, кроме (3, 5), имеют вид 6n ± 1, или 6n + 5 и 6n + 7. Этого вполне достаточно, чтобы написать простой генератор простых чисел близнецов: # Twin primes from sympy import isprime from itertools import count # ГЕНЕРАТОР ПРОСТЫХ ЧИСЕЛ-БЛИЗНЕЦОВ def twinPrimesGen(): # первая пара: yield (3, 5) # следующие пары: for i in count(5, 6): if isprime(i) and isprime(i + 2): yield (i, i + 2) О числах-близнецах вы можете прочитать в книге Мартина Гарднера Математические досуги (Рис. 1) на стр. 413. Рис. 1 Функция count из модуля itertools генерирует бесконечную арифметическую прогрессию. Начальный член прогрессии равен start. Разность прогрессии равна step. По умолчанию start=0, step=1: count(start=0, step=1) 185
В функции twinPrimesGen функция count генерирует числа вида 5 + 6n, или 6n + 5. Если число 6n + 5 простое, то мы проверяем число 6n + 7. Если и оно простое, то пара простых чисел-близнецов найдена, и функция twinPrimesGen возвращает очередную пару чисел. В функции main мы должны вовремя остановиться, потому что генератор действует непрерывно (Рис. 2): # ГЛАВНАЯ ФУНКЦИЯ def main(): print() print('Twin primes') print() # бесконечный цикл ввода данных: while True: s = 'Введите число > ' print(s, end = '') num = int(input()) # если число = 0, # то программу закрываем: if (num == 0): return for n in twinPrimesGen(): if n[0] > num: break print(n) print() main() Рис. 2 186
Взаимно простые числа Если наибольший общий делитель двух чисел 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 рассматриваются шестерни с взаимно простыми числами зубьев, что уменьшает их износ. 187
Проект Суперпростые числа Исходный код программы находится в файле Суперпростые числа.py. Под суперпростыми понимают разные числа, но мы исследуем такие числа, которые и целиком – простые, и все их «половинные» части – также простые. Поясню на примере. Возьмём простое число 1373. Разобьём его всеми способами на две части: 1-373 13-73 137-3 Оказывается, что все «частичные» числа – 1, 3, 13, 73, 137, 373 - также простые. А раз это так, то исходное число 1373 мы по полному праву можем именовать суперпростым! Наша задача - отыскать все суперпростые числа заданной длины len. Так как однозначные числа нельзя поделить на части, то они формально суперпростые, но очень неинтересные, поэтому мы разрешим пользователю задавать длину чисел в разумном диапазоне 2..7: # ГЛАВНАЯ ФУНКЦИЯ def main(): print() print('Суперпростые числа') print() # бесконечный цикл ввода данных # пока пользователь не закроет программу # или не введёт 0: while True: s = 'Введите число 2..7 > ' print(s, end = '') len = int(input()) # если число = 0, # то программу закрываем: if (len == 0): return # проверяем диапазон: if (len not in range(2,7+1)): len = 2 188
# ищем суперпростые числа: superprimes(len) print() print() main() По заданной длине чисел мы находим максимальное число max, которое на 1 больше максимального из заданных: # ИЩЕМ ПРОСТЫЕ ЧИСЛА def superprimes(len): print('Суперпростые числа заданной длины:') max = 10 ** len Например, для len = 2 мы получим 1 с двумя нулями – 100. Наибольшее двузначное число на 1 меньше наименьшего трёхзначного, то есть 99. Теперь в цикле for перебираем все числа заданной длины. Например, для двузначных чисел переменная цикла num изменяется от 100 : 10 = 10 до 99: for num in range(max // 10, max): if (not prime(num)): continue Поскольку исходное, длинное число должно быть простым, то и его необходимо проверить, и, если оно не соответствует предъявленным требованием, цикл переходит к проверке следующего числа. Вот тут нам необходима функция для проверки заданного числа на простоту: # ПРОГРАММА ДЛЯ НАХОЖДЕНИЯ # СУПЕРПРОСТЫХ ЧИСЕЛ import math def prime(n): # проверяем, делится ли число n # на числа 2..корень квадратный из числа n: for i in range(2, round(math.sqrt(n)) + 1): 189
# если делится, значит, число составное: if (n % i == 0): return False # простое число: return True Она возвращает True, если число n простое, и False – если составное. Поскольку алгоритм я переписал из наших предыдущих творений, то обсуждать его было бы лишним. Если длинное число num оказалось простым, то нам необходимо проверить и все его составные части. Для этого мы делим его на вспомогательную переменную i. При начальном значении, равном 10, мы разобьём число num на такие части: 1373 // 10 = 137 1373 % 10 = 3 На следующей итерации значение переменной i станет равным 10 x 10 = 100, и мы получим такие части исходного числа: 1373 // 100 = 13 1373 % 100 = 73 Вы легко продолжите этот процесс расщепления до самого конца. Мы же должны убедиться, что каждая из частей также является простым числом. Для этого мы, естественно, опять вызываем функцию prime. Если проверяемое число окажется составным (функция вернёт False), то следующие составные части проверять бесполезно, поэтому с помощью оператора break мы досрочно выходим из цикла while: i = 10 while (i < max): if (not prime(num // i)): break if (not prime(num % i)): break i *= 10 if i == max: # печатаем суперпростое число: print(num, end=' ') 190
На выходе (досрочном или «нормальном») мы сравниваем конечное значение переменной i с максимальным - max. Легко понять, что если они совпадут, то все составные части исходного числа оказались простыми, и нам ничего не остаётся, как только вынести его на нашу доску почёта в виде Консольного окна (Рис. 1). Рис. 1 Семизначных суперпростых чисел не существует! Проект Разложение числа на простые множители Исходный код программы находится в файле Факторизация.py. Бесконечный цикл while Метод int Условный оператор if Оператор return Функция с параметром Вложенные циклы Цикл while Цикл for 191
Оператор целочисленного деления % Основная теорема арифметики утверждает, что любое натуральное число, большее единицы, можно представить в виде произведения простых чисел, причём единственным способом (если не учитывать их порядок). Например: 21 = 3 * 7 23 = 1 * 23 125 = 5 * 5 * 5 Единичный (со)множитель в разложении можно и не указывать, поскольку он имеется у всех чисел. Разложение числа на произведение простых множителей, называется его факторизацией. В этом проекте мы используем самый простой метод для разложения чисел – полный перебор всех чисел от двух до квадратного корня из заданного числа. Он очень простой, но довольно быстро справляется с задачей даже для очень больших чисел (Рис. 1). Однако при встрече с большими простыми числами может и спасовать. 192
Рис. 1. Разлагаем на простые множители большие числа! В функции main задаём число: # ГЛАВНАЯ ФУНКЦИЯ def main(): print('Факторизация числа') print() # бесконечный цикл ввода данных # пока пользователь не закроет программу # или не введет 0: while True: s = "Введите натуральное число (больше единицы) > " print(s, end='') number = int(input()) # если пользователь ввёл 0, то программу закрываем: if (number == 0): return # находим разложение: razl(number) print() main() Испытуемое число передаём в функцию razl, а само разложение числа даётся нам очень просто: # Факторизация # ПРОГРАММА ДЛЯ РАЗЛОЖЕНИЯ НАТУРАЛЬНЫХ ЧИСЕЛ 193
# НА ПРОСТЫЕ МНОЖИТЕЛИ # Находим разложение заданного числа на простые множители def razl(num): print() print("Разложение числа:") print(str(num) + " = ", end='') # проверяем, делится ли num на числа 2..заданное числo: for i in range(2, num + 1): # если делится, выписываем делитель: while (num % i == 0): print(i, end='') num //= i if (num >= i): print(" * ", end='') print() Функция primefactors из модуля sympy возвращает упорядоченный список простых делителей (без повторения!): from sympy import primefactors # находим разложение: #razl(number) factors = primefactors(number) for i in factors: print(i, end=' ') Проверка показывает, что разложение верное, но одна тройка исчезла (Рис. 2). Рис. 2 Функция factoring возвращает словарь: from sympy import primefactors, factorint 194
В словаре ключ – это простой делитель, а значение – его кратность в разложении (Рис. 3): factors = factorint(number) print(factors) Рис. 3 Проект Генератор простых делителей Исходный код программы находится в файле Факторизация 2.py. Функция razl печатает делители, но не возвращает их. Для решения задач более полезна функция-генератор простых делителей: # Факторизация 2 # ПРОГРАММА ДЛЯ РАЗЛОЖЕНИЯ НАТУРАЛЬНЫХ ЧИСЕЛ # НА ПРОСТЫЕ МНОЖИТЕЛИ from itertools import chain, count # ГЕНЕРАТОР ПРОСТЫХ ЖЕЛИТЕЛЕЙ # ЗАДАННОГО ЧИСЛА def razl(n): for i in chain([2], count(3, 2)): if n <= 1: break while n % i == 0: n //= i yield i # ГЛАВНАЯ ФУНКЦИЯ def main(): print('Факторизация числа 2') print() 195
# бесконечный цикл ввода данных # пока пользователь не закроет программу # или не введет 0: while True: s = "Введите натуральное число (больше единицы) > " print(s, end='') number = int(input()) # если пользователь ввёл 0, то программу закрываем: if (number == 0): return # находим разложение: print(number, "= ", end="") s = "" for i in razl(number): s += str(i) + " * " print(s[:-3]) print() main() В функции main нам нужно самим позаботиться о печати делителей (Рис. 1). Рис. 1 Задача 3 с сайта Проект Эйлер Исходный код программы находится в файле Эйлер003.py. Задача номер 3 (Problem 3) называется Largest prime factor: The prime factors of 13195 are 5, 7, 13 and 29. What is the largest prime factor of the number 600851475143 ? Наибольший простой делитель Простые делители числа 13195 - это 5, 7, 13 и 29. 196
Каков самый большой делитель числа 600851475143, являющийся простым числом? Число большое, поэтому для решения можно воспользоваться генератором простых делителей. Но мы для тренировки напишем новую программу. Функция first_prime_factor возвращает первый (то есть наименьший) простой делитель заданного числа: import math def first_prime_factor(num): for i in range(2, int(math.sqrt(num)) + 1): if num % i == 0: return i return num В функции main мы получаем это число и делим на него заданное. Так мы получаем новое число, которое меньше исходного. Для него опять ищем первый делитель. И так продолжаем до тех пор, пока текущее число number меньше очередного делителя: # ГЛАВНАЯ ФУНКЦИЯ def main(): print('Задача 3 с сайта Проект Эйлер') print() number = 600851475143 while True: pf = first_prime_factor(number) if pf < number: number //= pf else: break print("Наибольший простой делитель числа 600851475143 =", number) main() Так как для маленьких чисел найти делители легче, чем для большого исходного, то в целом программа работает очень быстро. А ответ на задачу такой (Рис. 1). 197
Рис. 1 Проект Спираль Улама Исходный код программы находится в файле Спираль Улама.py и в папке Ulam. Спираль из простых чисел называют спиралью (или скатертью) Улама (Ulam Spiral). Согласно легенде, американский математик Станислав Улам вычертил вот такую числовую спираль на каком-то скучном докладе (Рис. 1). Говорят, что это было в 1963 году. Об этом случае рассказал Мартин Гарднер в книге Математические досуги, на стр. 413-417. Рис. 1. Квадрат Улама из книги Математические досуги Мартина Гарднера Если подобным образом закрутить простые числа в интервале 1..10000, то получится узор, который и называют скатертью Улама (Рис. 2). 198
Рис. 2 С точки зрения программирования, задача формулируется так: Начиная с 1 и двигаясь против часовой стрелки по спирали, заполнить квадратную матрицу последовательными числами. В главной функции задаём размерность таблицы: # ГЛАВНАЯ ФУНКЦИЯ def main(): print('Спираль Улама') print() # бесконечный цикл ввода данных # пока пользователь не закроет программу # или не введет 0: while True: s = "Введите натуральное число (больше единицы) > " print(s, end='') n = int(input()) n = n if n > 2 else 2 matrix = createMatrix(n) 199
Создаём матрицу из спирально закрученных чисел: # СОСТАВЛЯЕМ МАТРИЦУ def createMatrix(n): # массив индексов: index = [(0,0) for col in range(n * n)] # результирующий массив: res = [[0 for row in range(n)] for col in range(n)] # текущее число: curNum = n * n # длина текущей стороны: size = n x, y = 0, 0 # двигаемся к центру: while (size > 0): # вправо: for i in range(x, x + size): res[y][i] = curNum curNum -= 1 index[curNum] = (y, i) # вниз: for j in range(y + 1, y + size): res[j][x + size - 1] = curNum curNum -= 1 index[curNum] = (j, x + size - 1) # влево: for i in range(x + size - 2+0, x, -1): res[y + size - 1][i] = curNum curNum -= 1 index[curNum] = (y + size - 1, i) # вверх: for j in range(y + size - 2+1, y + 0, -1): res[j][x] = curNum curNum -= 1 index[curNum] = (j, x) # новый виток: x += 1 y += 1 size -= 2 return res 200
Если мы хотим, чтобы спираль закручивалась по часовой стрелке, то зеркально отражаем её (Рис. 3): # закрутка по часовой стрелке: mirror = list(matrix[::-1]) mirror.extend(matrix[1:]) # печатаем матрицу: for j in range(n): s = '' for i in range(n): print(f'{mirror[j][i] : 4}', end=' ') print() print() main() Рис. 3 В книге Мартина Гарднера спираль закручена в противоположном направлении (Рис. 4). Рис. 4 201
Такую спираль закрутить и того проще: matrix = createMatrix(n) # закрутка по часовой стрелке: #mirror = list(matrix[::-1]) #mirror.extend(matrix[1:]) # печатаем матрицу: for j in range(n): s = '' for i in range(n): #print(f'{mirror[j][i] : 4}', end=' ') print(f'{matrix[j][i] : 4}', end=' ') print() print() На первый взгляд, спираль Улама ничем не примечательна, но он высмотрел в ней интересные закономерности, связанные с простыми числами (поэтому её называют также Prime Number Spiral). У него это вышло случайно, когда он обводил кружками простые числа. Поскольку нам уже поздно полагаться на случай, то мы мы намеренно выкрасим в жёлтый цвет клетки с простыми числами. Но скатерть Улама в Консольном окне не нарисуешь, поэтому уходим в Процессинг и рисуем скатерть так, как мы рисовали Таблицы Пифагора. Задаём размеры таблицы, как на Рис. 3: from color import * # квадратик: Q_SIZE = 70 # зазоры между квадратиками: OFFSET = 1 # размер сетки в клетках: COLS = 10 ROWS = 10 # размеры окна: WIDTH = (Q_SIZE + OFFSET) * COLS + OFFSET HEIGHT =(Q_SIZE + OFFSET) * ROWS + OFFSET # цвета клеток: colors = [Blue, Gold] # СОЗДАЁМ ОКНО def settings(): 202
size(WIDTH, HEIGHT) В функции setup создаём матрицу, получаем список простых чисел в заданном диапазоне и рисуем таблицу: # ИНИЦИАЛИЗИРУЕМ ПРОГРАММУ def setup(): n = COLS # закрутка по часовой стрелке: global matrix matrix = createMatrix(n) getPrimes(n) # рисуем таблицу: drawTable() Функция createMatrix у нас уже готова: # СОСТАВЛЯЕМ МАТРИЦУ def createMatrix(n): # массив индексов: index = [(0,0) for col in range(n * n)] # результирующий массив: res = [[0 for row in range(n)] for col in range(n)] # текущее число: curNum = n * n # длина текущей стороны: size = n x, y = 0, 0 # двигаемся к центру: while (size > 0): # вправо: for i in range(x, x + size): res[y][i] = curNum curNum -= 1 index[curNum] = (y, i) # вниз: for j in range(y + 1, y + size): res[j][x + size - 1] = curNum curNum -= 1 index[curNum] = (j, x + size - 1) # влево: for i in range(x + size - 2+0, x, -1): res[y + size - 1][i] = curNum curNum -= 1 203
index[curNum] = (y + size - 1, i) # вверх: for j in range(y + size - 2+1, y + 0, -1): res[j][x] = curNum curNum -= 1 index[curNum] = (j, x) # новый виток: x += 1 y += 1 size -= 2 return res Функция getPrimes возвращает список простых чисел в заданном диапазоне: # ИЩЕМ ПРОСТЫЕ ЧИСЛА def getPrimes(n): # список для хранения простых чисел # первое простое число - двойка: global prime prime = [2] # второе простое число: num = 3 for num in range(3, n * n + 1): # проверяем, делится число num # простые числа из списка prime: flg = True for i in prime: # если число num делится, # значит, число составное: if (num % i == 0): flg = False break # если нашли простое число, if (flg): # добавляем новое простое число в список: prime.append(num) num += 2 print(prime) Теперь мы можем начертить таблицу. Во вложенных циклах for последовательно извлекаем закрученные в спираль числа из массива matrix. Проверяем: если число простое, то оно содержится в списке prime. Такие числа обозначаем золотым квадратиком, а составные числа - синим: 204
# РИСУЕМ ТАБЛИЦУ def drawTable(): # чёрный фон: background(0) # квадратики без контура: noStroke() global matrix # рисуем сетку из квадратиков: for row in range(ROWS): for col in range(COLS): # число: num = matrix[row][col] clr = 1 if num in prime else 0 clr = colors[clr] fill(clr) # устанавливаем квадратик в сетке: x = OFFSET + col * (Q_SIZE + OFFSET) y = OFFSET + row * (Q_SIZE + OFFSET) rect(x, y, Q_SIZE, Q_SIZE) Запускаем программу – маленькая скатерть Улама построена (Рис. 5). Рис. 5 205
Небольшие изменения в программе: # квадратик: Q_SIZE = 7# 70 # зазоры между квадратиками: OFFSET = 1 # размер сетки в клетках: COLS = 100#10 ROWS = 100 #10 И большая скатерть Улама готова. На ней хорошо видно, что простые числа охотно выстраиваются вдоль диагоналей (Рис. 6). Рис. 6 206
Проект Задача 7 с сайта Проект Эйлер Исходный код программы находится в файле Эйлер007.py. Задача номер 7 (Problem 7) называется 10001st prime. Она была опубликована 28 декабря 2001 года. By listing the first six prime numbers: 2, 3, 5, 7, 11, and 13, we can see that the 6th prime is 13. What is the 10 001st prime number? 10001-ое простое число Выписав первые шесть простых чисел, мы получим 2, 3, 5, 7, 11 и 13. Очевидно, что 6-ое простое число - 13. Какое число является 10001-ым простым числом? В одном из проектов мы уже разработали генератор простых чисел. Сейчас нам нужно получить от него единственное число, поэтому немного подправляем код — и задача решена: # Project Euler. Problem 7 # 10001st prime import math # ГЕНЕРАТОР ПРОСТЫХ ЧИСЕЛ def primes(n = -1): # список для хранения простых чисел # первое простое число - двойка: prime = [2] # всего простых чисел: nPrimes = 1 # текущее число для проверки: num = 3 while True: # проверяем, делится число num # простые числа из списка prime: flg = True for i in prime: # поиск закончен: if i > math.sqrt(num): break 207
# если число num делится, # значит, число составное: if (num % i == 0): flg = False break # если нашли простое число, if flg: # подсчитываем: nPrimes += 1 if nPrimes == n: return num # добавляем новое простое число в список: prime.append(num) num += 2 print() # РЕШАЕМ ЗАДАЧУ def solve(): p = primes(10001) # печатаем ответ: print("10001-е простое число =", p) print() def main(): print("Решаем задачу 7 из Проекта Эйлер") solve() main() А 10001-е простое число оказалось совсем небольшим (Рис. 1)! Рис. 1 208
Проект Задача 10 с сайта Проект Эйлер Исходный код программы находится в файле Эйлер010.py. Задача номер 10 (Problem 10) называется Summation of primes. The sum of the primes below 10 is 2 + 3 + 5 + 7 = 17. Find the sum of all the primes below two million. Сложение простых чисел Сумма простых чисел меньше 10 равна 2 + 3 + 5 + 7 = 17. Найдите сумму всех простых чисел, которые меньше 2 000 000. Наш генератор простых чисел подправляем для решения новой задачи: # Project Euler. Problem 10 # Summation of primes import math # ГЕНЕРАТОР ПРОСТЫХ ЧИСЕЛ def primes(): # список для хранения простых чисел # первое простое число - двойка: prime = [2] yield 2 # текущее число для проверки: num = 3 while True: # проверяем, делится число num # простые числа из списка prime: flg = True for i in prime: # поиск закончен: if i * i > num: break # если число num делится, # значит, число составное: if (num % i == 0): flg = False break # если нашли простое число, 209
if flg: # добавляем новое простое число в список: prime.append(num) yield num num += 2 print() def main(): print("Решаем задачу 10 из Проекта Эйлер") solve() main() В функции solve получаем простые числа до 2000000 и добавляем их к текущему значению переменной sump, а затем печатаем ответ на экране: # РЕШАЕМ ЗАДАЧУ def solve(): # сумма простых чисел: sump = 0 for p in primes(): if p > 2_000_000: break sump += p # печатаем ответ: print("Сумма =", sump) print() А ответ на задачу такой (Рис. 1). Рис. 1 Как видите, задача очень простая – для тех, кто умеет находить простые числа… 210
Проект Гипотеза Гольдбаха Исходный код программы находится в файле Гипотеза Гольдбаха.py. Начните проект с познавательного ролика на Ютубе: Goldbach Conjecture - Numberphile https://www.youtube.com/watch?v=MxiTG96QOxw Теперь вы знаете суть гипотезы Гольдбаха: каждое чётное целое число, большее 2, можно представить как сумму двух простых чисел. В главной функции мы задаём начало и конец диапазона для проверки гипотезы Гольдбаха: # ГЛАВНАЯ ФУНКЦИЯ def main(): # бесконечный цикл ввода данных # пока пользователь не закроет программу: while True: print() print("Гипотеза Гольдбаха") print() print("Начало диапазона - чётное число, больше двух > ", end='') beg = int(input()) if (beg < 4 or beg % 2 != 0): return print("Конец диапазона > ", end='') end = int(input()) solve(beg, end) print() main() В функции solve мы проверяем гипотезу для заданных чисел. Сначала мы создаём список простых чисел в диапазоне от 3 до конечного числа end: # -*- coding: Windows-1251 -*# Гипотеза Гольдбаха from sympy import sieve def solve(beg, end): 211
primes = list(sieve.primerange(3, end)) В цикле for последовательно проверяем ряд заданных чётных чисел: for num in range(beg, end + 1, 2): Извлекаем из списка простых чисел первое простое число для суммы: for n in primes: Чтобы не повторяться, считаем, что первое простое число в сумме не больше второго: if n > num - n: break Второе число в сумме равно num – n. Если оно также простое, то гипотеза для очередного числа num верна, и мы печатаем само число и сумму двух простых чисел: if (num - n) in primes: print(f'{num} = {n} + {num - n}') Если вы внимательно посмотрели ютубик, то знаете, что большие чётные числа можно представить как сумму двух простых чисел множеством способов. Например, для числа 2022 мы получили 59 вариантов (Рис. 1). Если не учитывать первое простое число двойку, то сумма двух простых чисел всегда чётное число. С двойкой мы сможем представить некоторые нечётные числа как сумму двух простых чисел (Рис. 2): def solve3(beg, end): primes = list(sieve.primerange(2, end)) print(primes) for num in range(beg, end + 1, 2): for n in primes: if n > num - n: break if (num - n) in primes: print(f'{num} = {n} + {num - n}') 212
Рис. 1 Рис. 2 213
Если их нечётного числа вычесть нечётное простое число, то мы получим чётное число. Любое чётное число представимо в виде суммы двух простых чисел. Из этого следует, что любое нечётное число, большее семи, можно представить как сумму трёх простых чисел. Если мы подобным образом будем проверять простые числа, то найдём пары простых чисел-близнецов: большее число стоит в правой части равенства, меньшее в левой (Рис. 3): def solve2(beg, end): primes = list(sieve.primerange(2, end)) for num in range(beg, end + 1, 2): if not num in primes: continue for n in primes: if n > num - n: break if (num - n) in primes: print(f'{num} = {n} + {num - n}') Рис. 3 214
Простые нечётные числа, разность между которыми равна 4, называются числами-кузенами (то есть двоюродными братьями) - Cousin primes. Найти их очень просто (Рис. 4): def solve4(beg, end): primes = list(sieve.primerange(2, end)) for num in range(beg, end + 1, 2): if not num in primes: continue if (num + 4) in primes: print(f'{num} = {4} + {num + 4}') Рис. 4 Простые нечётные числа, разность между которыми равна 6, называются сексуально простыми числами - Sexy primes. Ничего сексуального в этих числах нет. Название чисто каламбуристическое – six → sex. Игра слов - и ничего больше. 215
Найти их также очень просто (Рис. 5): def solve6(beg, end): primes = list(sieve.primerange(2, end)) for num in range(beg, end + 1, 2): if not num in primes: continue if (num + 6) in primes: print(f'{num} = {6} + {num + 6}') Рис. 5 Задания для самостоятельного решения 1. Совершенно очевидно, что суперпростые числа могут состоять только из цифр 1, 3, 5 и 7. Попробуйте сократить проверки, используя это наблюдение. 216
2. Мы убедились, что семизначных суперпростых чисел нет. Напишите программу, которая могла бы проверять на сверхпростоту одно заданное число, но с большим число цифр. Простые числа 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. Из этого определения следует, что всякое совершенное число является и полусовершенным, то есть полусовершенных чисел в заданном диапазоне не меньше, чем совершенных. Напишите программу, которая находила бы несколько полусовершенных чисел. 217
Глава #6. Числовые ребусы Первый числовой ребус составил Генри Дьюдени: SEND + MORE = MONEY В числовых ребусах словами зашифрованы числа, причём одинаковые буквы обозначают одинаковые цифры, и наоборот, одинаковые цифры обозначены одинаковыми буквами. Задача состоит в том, чтобы заменить буквы соответствующими им цифрами так, чтобы получилось верное равенство. Обычно числовые ребусы решаются с помощью логических рассуждений, но практически никогда не удаётся обойтись без предположений, то есть без перебора вариантов. А с такой работой даже самый захудалый компьютер справится быстрее любого из нас. А от вас требуется только одно – написать простую программу. Проект Каковы жуки? Исходный код программы находится в файле Каковы жуки.py. Функция без параметров Вложенные циклы for Условный оператор if Оператор continue Оператор return Оператор or Задача 9 из книги Удивительный мир чисел [КА86], страница 100: Решите уравнение в целых неотрицательных числах: 520⦁(Ж⦁У⦁К⦁И + Ж⦁У + Ж⦁И + К⦁И + 1) = 577⦁(У⦁К⦁И + У + И). 218
Числовые ребусы вполне успешно можно решать методом полного перебора. В них каждая цифра заменена буквой, но, поскольку цифр только десять, то каждая буква может принимать всего 10 разных значений - от 0 до 9. В любом ребусе не более десятка разных букв, то есть в худшем случае нам придётся проверить 10 000 000 000 вариантов. Современным компьютерам это вполне по силам. Впрочем, так бездумно компьютер не используют, поэтому даже при полном переборе следует разумно ограничивать число вариантов. Обычно сделать это очень просто, поскольку некоторые способы решения криптарифмов лежат на поверхности и не требуют глубоких размышлений. Например, для уменьшения числа вариантов достаточно учесть тот очевидный факт, что все буквы должны иметь разное значение. Это следует из условия самой головоломки: разным буквам соответствуют разные цифры. Вот теперь можно смело браться за любой числовой ребус. Функция main просто вызывает функцию solve, а затем печатает число найденных решений: # ГЛАВНАЯ ФУНКЦИЯ def main(): print("Каковы жуки?") print() nVar = solve() print("Найдены все варианты решения - " + str(nVar)) print() if __name__ == "__main__": main() Функция solve, как это обычно и бывает при полном переборе, очень простая. Записываем столько вложенных циклов for, сколько разных букв в ребусе. В нашем примере всего 4 разные буквы, поэтому и циклов тоже будет 4. Начиная со второй буквы, делаем проверку: её цифровое представление не должно совпадать с предыдущими буквами. Найдя значение каждой буквы, проверяем условие: 520*(Ж*У*К*И + Ж*У + Ж*И + К*И + 1) == 577*(У*К*И + У + И) Если оно выполняется (то есть левая часть выражения равна правой), то мы выписываем найденное решение в консольном окне, а затем ищем другие решения: 219
# -*- coding: Windows-1251 -*# Кордемский, с.100, Задача 09 # РЕШАЕМ ЗАДАЧУ 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 В итоге мы найдём единственное решение этого ребуса (Рис. 1). Рис. 1. Задача решена! 220
Точно так же вы можете решить любой числобус, НО – каждый раз вам придётся писать новую функцию solve! Такова плата за простоту программы… Проект Четыре "пари" Исходный код программы находится в файле Четыре пари.py. Функция без параметров Вложенные циклы for Условный оператор if Оператор continue Оператор return Оператор or Задача 8 из книги Удивительный мир чисел [КА86], страница 100: Как велико ПАРИ, если: В этой системе числовых ребусов также необходимо найти цифровые значения четырёх букв, поэтому большую часть кода мы можем скопировать из предыдущего проекта: # ГЛАВНАЯ ФУНКЦИЯ def main(): print('Четыре "пари"') print() nVar = solve() print("Найдены все варианты решения - " + str(nVar)) print() 221
if __name__ == "__main__": main() Однако теперь нам предстоит проверить выполнение не одного, а сразу четырёх условий! Лучше их проверять последовательно, и если какоелибо условие окажется невыполненным, то с помощью оператора continue сразу переходить к проверке следующих значений переменных цикла П, А, Р, И: # -*- coding: Windows-1251 -*# Кордемский, с.100, Задача 08 #Р ЕШАЕМ ЗАДАЧУ 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 222
Когда все равенства станут верными, мы печатаем решение задачи на экране (Рис. 1). Рис. 1. Мы выиграли ПАРИ! Проект Тайна трёх слагаемых Исходный код программы находится в файле Тайна трёх слагаемых.py. Функция без параметров Вложенные циклы for Условный оператор if Оператор continue Оператор return Оператор or Задача 11 из книги Удивительный мир чисел [КА96], страница 79: Переведите на язык арифметики чисел: Первое слагаемое (БОРЯ) должно быть возможно максимальным. 223
Этот ребус с БОРЕЙ – вполне классический: все цифры заменены буквами, и нужно узнать сумму нескольких чисел. Вы найдёте в книгах огромное множество таких ребусов, и все они решаются одинаково – с помощью вложенных циклов for. Решая классические ребусы, вы должны учитывать, что первая цифра числа не может быть нулём. Это значит, что буквы Б, И, Д могут принимать значения от 1 до 9, но не нуль. Это требование легко учесть в циклах for для соответствующих переменных, что мы и сделали в функции solve: # -*- coding: Windows-1251 -*# Кордемский, с.79, Задача 11 # РЕШАЕМ ЗАДАЧУ 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(Б) + 224
str(Р) print(s) print() return result # ГЛАВНАЯ ФУНКЦИЯ def main(): print('Тайна трёх слагаемых') print() nVar = solve() print("Найдены все варианты решения - " + str(nVar)) print() if __name__ == "__main__": main() Кроме большого числа циклов и длинных проверок, ничего сложного в подобного рода задачах нет, поэтому вы решите любую из них без проблем! Рис. 1 показывает 72 ответа. Рис. 1. 72 БОРИ Обычно хороший числобус имеет единственное решение, что в этой задаче достигается дополнительным требованием, чтобы число БОРЯ было максимальным. Конечно, можно просто вручную найти в списке решений то, в котором это условие выполняется, но всякий перебор лучше перепоручать компьютеру. Введём переменную max, которая поначалу имеет нулевое значение (или любое отрицательное): 225
max = 0 Перед строчкой result += 1 вставляем код для проверки и запоминания максимального значения для БОРИ: if (max > Б*1000 + О*100 + Р*10 + Я): continue # запоминаем новое максимальное значение: max = Б*1000 + О*100 + Р*10 + Я Теперь функция solve будет печатать только «рекордные» результаты для числа БОРЯ. Самое большое значение будет последним в списке. На Рис. 2 вы видите любопытную картину – число БОРЯ увеличивается с каждой итерацией и достигает своего максимума только в самом конце списка решений. Рис. 2. Самый большой БОРЯ Мы можем предположить, что автор задачи решал её так же, как и мы, то есть на компьютере. 226
Проект Меняем четыре буквы на четыре цифры Исходный код программы находится в файле Меняем четыре буквы на четыре цифры.py. Функция без параметров Вложенные циклы for Условный оператор if Оператор continue Оператор return Оператор or Задача 10 из книги Удивительный мир чисел [КА86], страница 73: В равенстве 7 * (a2 + b)3 = 23с2k (1) и независимо от него в равенстве 37037 * ab =19019 * ck (2) замените буквы а, b, с, k на подходящие цифры так, чтобы оба равенства подтвердились. Очень простая задача – всего на 4 вложенных цикла! # ГЛАВНАЯ ФУНКЦИЯ def main(): print("Меняем четыре буквы на четыре цифры") print() nVar = solve() print("Найдены все варианты решения - " + str(nVar)) print() if __name__ == "__main__": main() В функции solve, пожалуй, стоит обратить внимание только на выделенные строчки, в которых проверяется условие первой задачи: # -*- coding: Windows-1251 -*# Кордемский, с.73, Задача 10 227
# РЕШАЕМ ЗАДАЧУ def solve(): result = 0 for a in range(0, 9+1): for b in range(0, 9+1): 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 #ab= a*10 + b #ck= c*10 + k; #if (37037*ab != 19019*ck): # 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 Первая часть задачи решена (Рис. 1)! Рис. 1. Первая половина задачи Чтобы решить вторую часть задачи, следует изменить условие: #a2b = a*10 + 2 + b #if (7*a2b*a2b*a2b != 2*10000 + 3*1000 + c*100 + 20 + k): # continue 228
ab= a*10 + b ck= c*10 + k; if (37037*ab != 19019*ck): continue Вторая часть задачи имеет 2 решения (Рис. 2)! Рис. 2. И вторая половина Проект Коварная задача папы Исходный код программы находится в файле Коварная задача папы.py. Функция без параметров Вложенные циклы for Условный оператор if Оператор continue Оператор return Оператор or 229
Задача 11 из книги Удивительный мир чисел [КА86], страницы 22-23: Мой папа - шофёр - часто ездит по трассе, вдоль которой расположены пять небольших поселков: А, В, С, D, Е. Папа знал точно, сколько домов в каждом из этих посёлков, и составил для меня такую задачу: «Сколько всего домов в этих пяти посёлках, если • • • • • в А и В вместе 13 домов, в В и С вместе 31 дом, в С и Е - 17 домов, в Е и D - 26 домов, в А и D - 23 дома?» Я подметил, что число домов в каждом посёлке подсчитывалось папой дважды (по тексту задачи), поэтому моё решение было молниеносным: (13 + 31 + 17 +26+ 23) / 2 = 55 (домов в пяти поселках вместе). Но... вот уж действительно: поспешишь — людей насмешишь! Оказалось, что задачу папа сознательно составил так, что она не может быть решена. И я должен был это доказать. Помогите! В каждом посёлке не меньше 0 домов и не больше 31 дома – эти числа легко получить из условия задачи. Достаточно перебрать все варианты числа домов в этом диапазоне для посёлков А, В, С, D, Е, чтобы найти все решения задачи. # ГЛАВНАЯ ФУНКЦИЯ def main(): print('Коварная задача папы') print() nVar = solve() print("Найдены все варианты решения - " + str(nVar)) print() if __name__ == "__main__": main() Выписываем 5 вложенных циклов for и по ходу итераций проверяем папины фантазии: # -*- coding: Windows-1251 -*# Кордемский, с.22-23, Задача 11 230
# РЕШАЕМ ЗАДАЧУ 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 Как и отметил торопливый сынуля, папа его дезинформировал (Рис. 1). Рис. 1. С полным перебором не поспоришь! Правда, если в условии задачи нежно подправить условие: C + E = 17 → C + E = 27 231
то папина задача (а я подозреваю, что это был либо папа Карло, либо Валдис Пельш) успешно разрешится единственным способом (Рис. 2). Рис. 2. Поправили папу Из лучших педагогических побуждений будем считать, что папа просто ошибся. Проект Универсальный решатель Исходный код программы находится в файле Универсальный решатель.py. Функция c параметрами Функция eval Функция permutations Списки Множества Словари Поскольку лень двигатель прогресса, то мы должны подумать о написании универсального решателя числовых ребусов. Питон имеет очень полезную для этого случая функцию eval, которая умеет вычислять переданные ей выражения. Это может быть любая строка программного кода, заключённая в кавычки. Например, она может вычислить «шахматное число»: print (eval('2**64')) 232
Числовые ребусы называют также криптарифмами, поскольку цифры в них зашифрованы – заменены буквами. Естественно, Питон не может вычислить такое выражение, и наша задача заключается в том, чтобы заменять буквы всеми возможными цифрами и уже числовое выражение отправлять в функцию eval для проверки. Так как числобус это равенство, то и функция eval должна проверять левую и правую части выражения на равенство. Поэтому обычный знак равенства нужно заменить двойным ==, так как мы сравниваем значения правой и левой частей выражения. Если функция eval вернёт True, значит, равенство верное, и мы можем напечатать решение на экране. Вполне может оказаться, что задача имеет не единственное решение, поэтому мы продолжаем проверки до полного исчерпания всех возможных комбинаций букв и цифр. Буквы в числобусе можно разделить на 2 группы – начальные и все остальные. Начальные буквы не могут заменять нуль, а все прочие могут. Следовательно, нам нужно иметь 2 списка букв. Также мы должны учесть, что разных букв не должно быть больше 10, иначе на них не хватит цифр. В начале функции solve мы выделяем из переданной строки отдельные слова. Затем первые буквы помещаем в множество first_chars, а все остальные буквы – в множество other_chars. Также нам потребуется список всех букв all_chars: # -*- coding: Windows-1251 -*# ПРОГРАММА ДЛЯ РЕШЕНИЯ ЧИСЛОВЫХ РЕБУСОВ from itertools import permutations # РЕШАЕМ ЗАДАЧУ def solve(puzzle): # число вариантов: n_var = 0 # составляем список слов: words = [w for w in puzzle.split() if and w.isupper()] w.isalpha() # print(words) # множество первых букв, # которые не могут равняться нулю: first_chars = {w[0] for w in words} # print(first_chars) # множество остальных букв, # которые могут равняться нулю: other_chars = {a for a in ''.join(words) if a not in first_chars} # print(other_chars) 233
# список всех букв: all_chars = [ord(c) for c in first_chars] + [ord(c) for c in other_chars] # print(all_chars) # число разных букв не должно превыщать 10: if (len(all_chars) > 10): print('Слишком много цифр!') return 0 Если вы хотите проследить, как работает наша функция, то раскомментируйте строчки с печатью информации. Мы получаем от функции permutations все перестановки из 10 цифр, из которых берём только первые len(all_chars), то есть ровно столько, сколько разных букв в числобусе. Если в первых len(first_chars) очередной перестановки имеется нуль, то такая перестановка не годится, так как первые first_chars в списке all_chars – это первые буквы слов, которые не могут быть нулём: # генерируем все возможные перестанови из len(chars) for perm in permutations('0123456789', len(all_chars)): # первой цифрой не может быть нуль: if '0' not in perm[:len(first_chars)]: Если всё нормально, то мы заменяем все буквы числобуса соответствующими цифрами в перестановке и получаем строку, в которой все буквы заменены цифрами. Знаки арифметических действий при этом остаются без изменений: # составляем числовое выражение, # заменяя буквы цифрами: equation = puzzle.translate(dict(zip(all_chars, perm))) # print (equation) Например, строки equation для числобуса 'IMPRESARIO + PARATROOPS + TETRAMETER == ASPIRATION' будут такими (Рис. 1). 234
Рис. 1. Ищем! Функция eval проверяет их, и если какая-либо строка окажется верной, то на экране появляется очередное решение: # если оно верное, if eval(equation): # то печатаем очередное решение: n_var += 1 print('Вариант #', n_var) print('\n'.join((puzzle, equation))) Общее число найденных решений функция solve возвращает в главную функцию: return n_var А в функции main мы вызываем функцию solve со строкой-заданием: # ГЛАВНАЯ ФУНКЦИЯ def main(): print() print('Универсальный решатель') print() puzzle = 'IMPRESARIO + PARATROOPS + TETRAMETER == ASPIRATION' nVar = solve(puzzle) print('Найдены все варианты решения -', nVar) print() main() 235
Как и всякое универсальное решение, наша программа не самая быстрая, но зато позволяет решать любые классические числовые ребусы (Рис. 2), не переписывая каждый раз исходный код программы. Рис. 2. Неспешно, но универсально… Более того, функция eval справляется с любыми выражениями, поэтому в числобусе могут быть не только знаки плюс, но также знаки вычитания, умножения, деления, возведение в степень и другие. Так что программа у нас получилась даже более универсальной, чем мы планировали. Чтобы все слова были правильно извлечены из входной строки, ставьте между пробелы ними и арифметическими знаками! Давайте решим с помощью нашей программы вот такой ребус: puzzle = 'ABCD * E == FGHIJ' То есть мы хотим найти решения для примера, в котором участвуют все 10 разных цифр. Недолго думая, наша программа выдаёт 13 решений поставленной перед ней задачи: Решаем числовой ребус Вариант # 1 ABCD * E == FGHIJ 5694 * 3 == 17082 Вариант # 2 ABCD * E == FGHIJ 6819 * 3 == 20457 Вариант # 3 Вариант # 7 ABCD * E == FGHIJ 7039 * 4 == 28156 Вариант # 8 ABCD * E == FGHIJ 9127 * 4 == 36508 Вариант # 9 ABCD * E == FGHIJ 5817 * 6 == 34902 236
ABCD * E == FGHIJ 6918 * 3 == 20754 Вариант # 4 ABCD * E == FGHIJ 8169 * 3 == 24507 Вариант # 5 ABCD * E == FGHIJ 9168 * 3 == 27504 Вариант # 6 ABCD * E == FGHIJ 3907 * 4 == 15628 Вариант # 10 ABCD * E == FGHIJ 3094 * 7 == 21658 Вариант # 11 ABCD * E == FGHIJ 4093 * 7 == 28651 Вариант # 12 ABCD * E == FGHIJ 9304 * 7 == 65128 Вариант # 13 ABCD * E == FGHIJ 9403 * 7 == 65821 Найдены все варианты решения 13 В выражениях можно использовать и скобки, не забывая ставить между ними и буквами пробелы: puzzle = '( ABCD + E ) * E == FGHIJ' Этот учебный ребус имеет 59 решений (Рис. 3). Рис. 3. Многовато будет 237
И даже с математическими функциями наш решатель успешно справляется (Рис. 4)! puzzle = 'math.sqrt( ABCD ) == EF' Рис. 4. Функционально! Более интересный пример (Рис. 5): puzzle = 'math.sqrt( ABCDE ) == FGH' Рис. 5. Почти готовая задача А вот так просто можно решать комбинаторные задачи (Рис. 6): puzzle = ' A + B + C + D == 12' 238
Рис. 6. Универсальная комбинаторика Изменим всего одну строчку кода: words = [w for w in puzzle.split() if w.isupper()] И мы сможем решать «системы уравнений» (Рис. 7): puzzle = 'A + B + C + D == 12 and A * B == 10 and A - B == 3' Рис. 7. Системно! А вот так можно легко и просто решить задачу про ПАРИ (Рис. 8): 239
puzzle = 'П * А * Р * И == 5 * ( П + А + Р ) and П * А * Р * И == 3 * ( И + Р + А ) and 3 * П * А * Р * И == 10 * ( П + И + Р )' Рис. 8. Здорово!!! Как вы видите, чтобы решить задачу, четвёртое выражение не нужно. Задания для самостоятельного решения Поиграем в прятки Удивительный мир чисел. Задача 8 (9) ], страница 73 Все 10 цифр спрятались под буквами в каждом из пяти равенств: 1) 2) 3) 4) 5) Д ⦁ Г У В А = Б Ж Я И Е Е ⦁ Д А И Г = Б ⦁ У В Ж Я Е Ж ⦁ Г В А = Б У Я Д И ЖИ ⦁ ДАГ = ЕУВБЯ УА ⦁ ВБГ = ИЯ ⦁ ЖДА Задача несложная, но трудоёмкая: чтобы решить её, придётся выписывать 10 вложенных циклов! ЛОБ ТРИ САМ Удивительный мир чисел. Задача 2, страница 70 Это трёхзначные числа, такие, что ЛОБ +ТРИ = САМ Расшифруйте сложение, обходясь без цифры нуль. 240
В любом возможном решении должна обнаружиться определенная закономерность в числе «САМ». Какая? Семейство, спрятавшееся в «БАКУ» Удивительный мир чисел. .Задача 5, страница 77 а) Расшифруйте произведение чисел БАКУ и §§§§, зная, что в каждом сомножителе цифры образуют (слева направо) строго убывающую последовательность. Какое семейство цифр спряталось за буквами Б, А, К, У? б) Расшифруйте в этом ребусе ещё и второй множитель (§§§§), если строго убывающую последовательность образуют цифры только первого сомножителя (БАКУ) и если в произведении вторая цифра слева нуль. 241
Глава #7. Степени и корни Зри в корень! Козьма Прутков Из множества занимательных и поучительных задач со степенями и корнями мы решим всего 42-√𝟗, но зато очень интересных! Проект Кубическое число Исходный код программы находится в файле Кубическое число.py. Функция с параметром Функция 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 # РЕШАЕМ ЗАДАЧУ 242
def solve(num): print("Кубическое число = " + str(round(math.pow(num, 1 / 3.0)))) print() # ГЛАВНАЯ ФУНКЦИЯ def main(): print('Кубическое число') print() solve(996703628669) solve(1011443374872) print() if __name__ == "__main__": main() На Рис. 1 показано решение задачи. Рис. 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() И получаем точные значения искомых корней (Рис. 2). 243
Рис. 2. И перебор не в тягость! При этом нам не понадобился метод pow класса math! Проект И «хвост», и «грива» Исходный код программы находится в файле И хвост, и грива.py. Функция без параметров Функция с параметром Оператор целочисленного деления % Оператор return Цикл while Вложенные циклы for Оператор or Задача 8 из книги Удивительный мир чисел [КА86], страница 72: Если есть четырёхзначные числа, первые две и последние две цифры каждого из которых совпадают соответственно с первыми двумя и последними двумя цифрами квадрата и куба искомого числа, то какое из них наименьшее и какое - наибольшее? Числа, оканчивающиеся единицей и двумя нулями, исключаем. В зашифрованном виде: xyzt2= xy...zt и xyzt3= xy...zt. В правой и левой частях этих равенств буквой х зашифрована одна и та же цифра, буквой у - также, буквой z - также и буквой t - также, но эти цифры могут быть и одинаковыми. 244
# ГЛАВНАЯ ФУНКЦИЯ def main(): print('Кубическое число') print() solve(996703628669) solve(1011443374872) # solve2(996703628669) # solve2(1011443374872) print() if __name__ == "__main__": main() Чтобы получить из квадратов и кубов две первые и две последние цифры (точнее, двузначные числа, составленные из этих цифр), мы напишем 2 вспомогательных функции. Две последние цифры легко получить как остаток от деления исходного числа на сотню: # -*- coding: Windows-1251 -*# Кордемский, с.72, Задача 8 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 достаточно проста: 245
# РЕШАЕМ ЗАДАЧУ 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 result += 1 print("Вариант # " + str(result)) print("xyzt = " + str(xyzt)) print("xyzt2 = " + str(xyzt2)) print("xyzt3 = " + str(xyzt3)) print() return result Запустив программу, мы тут же получаем ответ (Рис. 1). Рис. 2. Решили задачу игриво: и в хвост и в гриву Так как функции first2 и last2 позволяют извлекать нужные цифры из любых чисел, то задачу можно решить, проверяя в одном цикле for все четырёхзначные числа. Попробуйте! 246
Проект Возведение в квадрат без операции умножения Исходный код программы находится в файле Возведение в квадрат без операции умножения.py. Бесконечный цикл 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. В главной функции мы организуем традиционный бесконечный цикл ввода данных от пользователя, которому предлагаем ввести число, квадрат которого он хотел бы получить: # ГЛАВНАЯ ФУНКЦИЯ def main(): print('Квадраты чисел') print() # бесконечный цикл ввода данных # пока пользователь не закроет программу # или не введет 0: while True: s = "Введите число > " print(s, end='') num = int(input()) 247
# если пользователь ввёл 0, то программу закрываем: if (num == 0): return # вычисляем квадрат заданного числа: q = quadrat(num) print(f"Квадрат числа {num} = {q}") print() if __name__ == "__main__": main() Функция quadrat вычисляет квадрат заданного числа num по алгоритму, описанному выше: # -*- coding: Windows-1251 -*# Дагене, #6, с.27 # ВЫЧИСЛЯЕМ КВАДРАТ def quadrat(num): # нечётное число: nech = 1 # квадрат: q = 0 # вычисляем квадрат как сумму нечётных чисел: for i in range(1, num + 1): q += nech nech += 2 return q Несмотря на множество операций сложения, квадраты даже очень больших чисел функция quadrat находит очень быстро (Рис. 1). Рис. 1. Решение квадратной задачи 248
Проект Возведение в куб без операции умножения Исходный код программы находится в файле Возведение в куб без операции умножения.py. Бесконечный цикл 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 Затем найдём разность между соседними числами (средняя строка) и повторим эту операцию ещё раз. В нижней строке все числа одинаковые, это значит, что номер нечётного числа можно вычислить по формуле: 249
a⦁num2 + b⦁num + c Для нахождения коэффициентов этого выражения, следует воспользоваться методом исчисления конечных разностей. Он описан, например, в моей книге Как решать комбинаторные задачи на компьютере. В итоге мы получим вот такую замечательную формулу: n = num (num-1) / 2 + 1 (1) По номеру n мы легко найдём и само нечётное число. Оно равняется: (2) 2n – 1 Эти две формулы можно объединить в одну: Первое нечётное число = 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 требует только небольшой правки: # ГЛАВНАЯ ФУНКЦИЯ def main(): print('Кубы чисел') print() # бесконечный цикл ввода данных # пока пользователь не закроет программу # или не введет 0: while True: s = "Введите число > " print(s, end='') num = int(input()) # если пользователь ввёл 0, то программу закрываем: if (num == 0): 250
return # вычисляем куб заданного числа: q = cube(num) print(f"Куб числа {num} = {q}") print() if __name__ == "__main__": main() В функции cube мы сначала вычисляем номер n первого нечётного числа суммы, а затем по формуле (2) и само нечётное число. Остальная часть метода ничем не отличается функции quadrat: # -*- coding: Windows-1251 -*# Дагене, #6, с.27-28 # ВЫЧИСЛЯЕМ КУБ def cube(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 Начинаем осторожно проверять работу нового метода на небольших числах, кубы которых нам точно известны (Рис. 1, слева). Но, разохотившись, вы можете заняться вычислением БОЛЬШИХ кубов (Рис. 1, справа). Кубическая задача успешно решена! Пользуясь функцией cube, можно составить таблицу кубов, о которой идёт речь в задаче, но лучше написать отдельную функцию, тем более что она не отнимет у нас много сил: def printTable(num): print() nech = 1 # для всех чисел 1..num: for i in range(1, num + 1): q = 0 for j in range(1, i + 1): 251
q += nech nech += 2 print(f"Куб числа {i} = {q}") Рис. 1. Это вам не в кубики играть! Алгоритм, заложенный в эту функцию, буквально повторяет условие задачи. Мы последовательно находим сумму нечётных чисел, продолжая их ряд с каждым новым числом, для которого находим куб. На Рис. 2 вы можете видеть отчёт о проделанной работе. Рис. 2. Кубическая таблица 252
Проект Зашифрованные жуки Исходный код программы находится в файле Зашифрованные жуки.py. Функция без параметров Вложенные циклы for Оператор continue Условный оператор if Оператор or Задача 4 из книги Удивительный мир чисел [КА86], страница 71: Эту задачу вполне можно считать числовым ребусом и потому решать точно так же – с помощью вложенных циклов. Все буквы, входящие в равенства а) и б), должны быть различными, а буква Ж не может равняться нулю. Значение степени И заведомо не меньше трёх и не больше шести. # ГЛАВНАЯ ФУНКЦИЯ def main(): print("Зашифрованные жуки") print() nVar = solve() print("Найдены все варианты решения - " + str(nVar)) print() if __name__ == "__main__": main() 253
С задачей а) мы справляемся без особых хлопот и затей: # -*- coding: Windows-1251 -*# Кордемский, с.71, Задача 4 # РЕШАЕМ ЗАДАЧУ 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 Она имеет единственное решение, показанное на Рис. 1. Рис. 1. Первый ЖУК пойман Для решения задачи б) нужно только изменить проверку: # if (num != 244140000 + Ж*100 + У*10 + К): # continue 254
if (num != 19987173000 + Ж * 100 + У * 10 + К): continue Она также имеет единственное решение (Рис. 2). Рис. 2. И второй ЖУК тоже Проект Ж-Ж-Ж! Исходный код программы находится в файле Ж-Ж-Ж.py. Функция без параметров Цикл for Оператор return Условный оператор if Оператор continue Задача 3 из книги Удивительный мир чисел [КА86], страница 71: Убрав из жучиной программы всё лишнее, мы легко берём очередную задачу: 255
# -*- coding: Windows-1251 -*# Кордемский, с.71, Задача 3 # РЕШАЕМ ЗАДАЧУ 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)) print() print("Ж = " + str(Ж)) print() return result # ГЛАВНАЯ ФУНКЦИЯ def main(): print("Ж-Ж-Ж!") print() nVar = solve() print("Найдены все варианты решения - " + str(nVar)) print() if __name__ == "__main__": main() На Рис. 1 вы видите, что Ж = 7, то есть полное решение такое: (7-1)5 = 7776 Рис. 1. И с жуж-ж-желицей справились 256
Проект Девять в квадрате Исходный код программы находится в файле Девять в квадрате.py. Функция без параметров Цикл for Оператор целочисленного деления % Оператор return Задача 1 из книги Удивительный мир чисел [КА86], страница 70: Найдите шестизначное число, зашифрованное в ребусе: ДЕВЯТЬ2=§§§§§§ДЕВЯТЬ Мы легко найдём, что наименьшее 6-значное число, состоящее из разных цифр, равно 102345, а наибольшее – 987654. Таким образом, нужно перебрать все числа в этом диапазоне, возвести их в квадрат и сравнить последние 6 цифр квадрата с исходным числом. По условию задачи, они должны совпадать. # ГЛАВНАЯ ФУНКЦИЯ def main(): print("Девять в квадрате") print() nVar = solve() print("Найдены все варианты решения - " + str(nVar)) print() if __name__ == "__main__": main() В функции solve мы и выполняем означенные действия: # -*- coding: Windows-1251 -*# Кордемский, с.70, Задача 1 # РЕШАЕМ ЗАДАЧУ 257
def solve(): minnum = 102345 maxnum = 987654 result = 0 for num in range(minnum, maxnum + 1): num2 = num * num if (num2 % 1000000 != num): continue result += 1 print("Вариант # " + str(result)) s = "num = " + str(num) s += " num2 = " + str(num * num) print(s) print() return result Задача имеет 2 решения (Рис. 1). Рис. 1. Расквадратили Проект Пара чисел: 3149 и 3151 Исходный код программы находится в файле Пара чисел 3149 и 3151.py. Функция без параметров Цикл for Условный оператор if Оператор continue 258
Задача 3.2 из книги Удивительный мир чисел [КА86], страница 43: Десятичная запись куба каждого из чисел 3149 и 3151 начинается двумя его первыми цифрами, а оканчивается двумя его последними цифрами: 31493 = 31 226 116 949 31513 = 31 285 651 951 Есть ещё два последовательных четырёхзначных числа, обладающих таким же свойством. Какие это числа? Задача решается полным перебором всех четырёхзначных чисел: # ГЛАВНАЯ ФУНКЦИЯ def main(): print("Пара чисел: 3149 и 3151") print() nVar = solve() print("Найдены все варианты решения - " + str(nVar)) print() if __name__ == "__main__": main() # -*- coding: Windows-1251 -*# Кордемский, с.43, Задача 3-2 def last2(num): return num % 100 def first2(num): while (num >= 100): num //= 10 return num # РЕШАЕМ ЗАДАЧУ def solve(): minnum = 1000 maxnum = 9999 result = 0 for num in range(minnum, maxnum + 1): 259
Для каждого четырёхзначного числа находим его куб. Затем сравниваем в этих числах 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 На Рис. 1 приведён полный список чисел, выполняющих условие задачи. В книге указана пара чисел 3200 и 3201, но вы видите, что есть и другие пары чисел: 1000 и 1001 1024 и 1025 9975 и 9976 Рис. 1. Уточнили ответ 260
Проект Число 698 896 Исходный код программы находится в файле Число 698 896.py. Функция без параметров Цикл for Функция с параметром Цикл while Оператор return Функция max Оператор целочисленного деления % Задача 3.3 из книги Удивительный мир чисел [КА86], страница 43: Число 698 896 квадратное (8362), палиндромическое. Предполагают, что оно - наименьшее квадратное палиндромическое число с чётным количеством цифр. Подтвердить это или опровергнуть можно, только покопавшись в таблице квадратов чисел, меньших 836. И правда, давайте покопаемся! # -*- coding: Windows-1251 -*# Кордемский, с.43, Задача 3-3 # ГЛАВНАЯ ФУНКЦИЯ def main(): print() print('Число 698 896') print() nVar = solve() print('Найдены все варианты решения -', nVar) print() main() Например, можно докопаться до чисел, гораздо больших, чем 836: # РЕШАЕМ ЗАДАЧУ def solve(): 261
minnum = 4 maxnum = 1000000 result = 0 for num in range(minnum, maxnum + 1): num2 = num * num Чтобы распознать квадратный палиндром, мы задействуем функцию 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) Как и полагается по условию задачи, мы рассматриваем только числа с чётным набором цифр: 262
# число цифр: if (numDigit(num2) % 2 != 0): continue result += 1 print('Вариант #', result) s = 'num = ' + str(num) s += ' num2 = ' + str(num*num) print(s) print() return result За несколько секунд мы, покопавшись и порывшись, установили, что первое квадратное палиндромическое число именно 698 896, а второе – гораздо больше (Рис. 1). Рис. 1. Квадратные палиндромы Если искать квадратные палиндромы с любым числом цифр, то можно откопать и накопать весьма любопытные экземпляры (Рис. 2)! Рис. 2. Тоже квадратно 263
Проект Числа 11 826, 12 363, 14 676 Исходный код программы находится в файле Числа 11 826, 12 363, 14 676.py. Функция без параметров Цикл 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 разных пятизначных чисел (убедитесь!), но, пользуясь калькулятором, вы без большого труда выделите из них требуемое число. Компьютер именно без труда большого труда переберёт все пятизначные числа: # ГЛАВНАЯ ФУНКЦИЯ def main(): print('Числа 11 826, 12 363, 14 676') print() nVar = solve() print("Найдены все варианты решения " + str(nVar)) print() 264
if __name__ == "__main__": main() # -*- coding: Windows-1251 -*# Кордемский, с.43, Задача 3-4 # РЕШАЕМ ЗАДАЧУ def solve(): min = 10000 max = 99999 result = 0 for num in range(min, max + 1): num2 = num * num Единственная проблема – выяснить, что цифры в числе не повторяются и что среди них нет нуля. Нам ничего не остаётся делать, как только написать новую функцию для этого: # если цифры повторяются: if (not isNoRepDigit(num2)): 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 = [] 265
Поведение функции 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 для всех чисел с неповторяющимися цифрами (Рис. 1). На этом подготовительные работы заканчиваются, и мы приступаем к решению задачи. 266
На Рис. 2 вы можете видеть часть списка пятизначных чисел, квадраты которых состоят из разных цифр и не содержат нуля. Всего таких чисел ровно 30. Рис. 1. Разноциферные числа Рис. 2. Весь пятизначный список Что касается числа, состоящего из цифр 1,2,3,4,5, то оно в списке занимает третье место. Это число 12543. Если вы пожелаете найти разноциферные числа с нулём, то перенесите комментарий на строчку выше: # если цифры повторяются: #if (not isNoRepDigit(num2)): if (not isNoRepDigit(num2, False)): continue Как и следовало ожидать, чисел с неповторяющимися цифрами стало заметно больше, но в некоторых из этих чисел отсутствует, например, цифра 8, так что ряд цифр получается разорванным (Рис. 3). Тут, пожалуй, лучше изменить условие проверки чисел так, чтобы были напечатаны только 10-значные числа: # если цифры повторяются: #if (not isNoRepDigit(num2)): #if (not isNoRepDigit(num2, False)): if (not isNoRepDigit(num2, False) or num2 < 1000000000): continue 267
На удивление их оказалось довольно много (Рис. 4). Рис. 3. Не все цифры присутствуют Рис. 4. Самые длинные квадраты с неповторяющимися цифрами 268
Проект Числа 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. Предполагают, что первое - наименьшее, а второе - наибольшее из пятизначных чисел с таким свойством. Подтвердить или опровергнуть это утверждение под силу только ЭВМ или энтузиасту - любителю счёта. Как верно отмечено в условии задачи, это нам под силу! Более того, мы ненароком уже решили эту проблему в предыдущем проекте, и на Рис. 4⬆отчётливо видно, что указанные числа действительно наименьшее и наибольшее из всех пятизначных чисел. Проект Число 117 649 Исходный код программы находится в файле Число 117 649.py. Функция без параметров Цикл for Условный оператор if Оператор continue Функция round Функция с параметром Задача 3.6 из книги Удивительный мир чисел [КА86], страница 44: Число 117 649 существует одновременно в трёх качествах. Оно: • квадратное • кубическое • кратное семи: 269
117 649 = 3432 = 493 = 7k, k ∈ N. Более того, на отрезке от единицы до миллиона оно единственное с таким свойством. Докажите! Чтобы доказать это утверждение, нужно проверить все числа в заданном диапазоне (но мы расширим диапазон, чтобы найти и другие числа). # ГЛАВНАЯ ФУНКЦИЯ def main(): print('Число 117 649') print() solve() print() if __name__ == "__main__": main() # РЕШАЕМ ЗАДАЧУ def solve(): minnum = 7 maxnum = 10000000 result = 0 Чтобы не проверять делимость чисел на 7, можно начать поиск с семёрки и в каждой следующей итерации добавлять к переменной цикла также семёрку, тогда все проверяемые числа будут сразу кратны семи, и одна проверка отпадает сама собой: for num in range(minnum, maxnum+1, 7): # чиcло должно быть полным квадратом и кубом: Проверку числа на квадратность и кубичность мы доверим двум новым функциям: if (not isQuadrat(num)): continue if (not isCube(num)): continue 270
Если очередное число 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: # -*- coding: Windows-1251 -*# Кордемский, с.43, Задача 3-6 import math # ОПРЕДЕЛЯЕМ ЯВЛЯЕТСЯ ЛИ ЗАДАННОЕ ЧИСЛО # ПОЛНЫМ КВАДРАТОМ def isQuadrat(num): r = round(math.sqrt(num)) return r * r == num # ОПРЕДЕЛЯЕМ ЯВЛЯЕТСЯ ЛИ ЗАДАННОЕ ЧИСЛО # ПОЛНЫМ КУБОМ def isCube(num): r = round(math.pow(num, 1.0 / 3.0)) return r * r * r == num Рис. 1 показывает, что первое число с указанными в задаче свойствами, 117 649, а второе – 7 529 536 – значительно больше 1 миллиона. А всего среди первого миллиарда чисел только 4 кратны 7 и являются полными квадратами и кубами. Если слегка подправить функцию solve, то можно найти ещё несколько любопытных чисел (Рис. 2): #for num in range(minnum, maxnum+1, 7): for num in range(1, 500_000_000): 271
Рис. 1. Семикратные корни Рис. 2. Квадратно-кубические числа Или в более наглядной форме: • 33752 = 2253 = 11390625 • 49132 = 2893 = 24137560 И так далее. 272
Проект Красивые цепочки равенств Исходный код программы находится в файле Красивые цепочки равенств.py. Функция без параметров Бесконечный цикл 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) = хх Найдите подходящее значение х. # ГЛАВНАЯ ФУНКЦИЯ def main(): print('Красивые цепочки равенств') print() solve() print() if __name__ == "__main__": main() Так как в условии задачи участвуют очень большие степени, то можно предположить, что число х должно быть, наоборот, очень маленьким. 273
В бесконечном цикле while мы перебираем значения х, начиная с единицы, и при этом проверяем выполнение условий задачи: # РЕШАЕМ ЗАДАЧУ 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: # -*- coding: Windows-1251 -*# Кордемский, с.45, Задача 9 274
# НАХОДИМ СУММУ ЦИФР ЗАДАННОГО ЧИСЛА def summa(num): sum = 0 while (num > 0): sum += num % 10 num //= 10 return sum Как мы и предполагали, значение х совсем небольшое (Рис. 1). Рис. 1. Нашли икс Задания для самостоятельного решения Задача-ребус Удивительный мир чисел, страницы 69-70 Представим задачу в форме такого ребуса: Эта задача практически ничем не отличается от той, что мы решили в проекте Девять в квадрате. Задача #33 Математическая шкатулка Какую последнюю цифру может иметь квадрат натурального числа? Куб его? Четвёртая степень? 275
Задача #34 Математическая шкатулка Могут ли числа 458, 523, 652 быть квадратами или кубами целого числа? 276
Глава #8. Последовательности Без вычисления факториалов и чисел Фибоначчи не обходится, пожалуй, ни одна книга по программированию. А всё потому, что вычислять их легко и весело! С них и начнём. Проект Факториал Исходный код программы находится в файле Факториал.py. Бесконечный цикл while Метод int Функция с параметром Условный оператор if Оператор return Цикл for Произведение натуральных чисел от единицы до заданного (пусть это будет n) называется факториалом. Обозначается факториал восклицательным знаком после числа: 1! 2! 3! n! (читается 1-факториал, 2-факториал, 3-факториал, энфакториал). Чтобы его вычислить, нужно, как и следует из определения, просто перемножить все числа от единицы до этого числа, включительно: n! = 1 х 2 х . . . х (n-1) х n (1) По определению, 0! = 1. Формула очень простая, но нетрудно догадаться, что для больших значений n факториал будет выражаться огромным числом. Для примера распишем факториал числа 6: 6! = 1*2*3*4*5*6 277
Обычно для вычисления факториала используют два алгоритма - рекурсивный и итерационный. Нас интересует только итерационный - он работает быстрее. В функции main пользователь вводит число, после чего вызывается функция factorial, которая возвращает вычисленное значение факториала: # ГЛАВНАЯ ФУНКЦИЯ 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() if __name__ == "__main__": main() Функция factorial действует строго по формуле (1): # -*- coding: Windows-1251 -*# ПРОГРАММА ДЛЯ ВЫЧИСЛЕНИЯ ФАКТОРИАЛОВ # ВЫЧИСЛЯЕМ ФАКТОРИАЛ ЗАДАННОГО ЧИСЛА def factorial(num): if (num == 0): return 1 fact = num for i in range(2, num): fact *= i return fact 278
Умножать на единицу, конечно, смысла нет. Здесь мы учитываем, что факториал нуля равен единице, это особый случай и его нужно учесть отдельно (Рис. 1)! Рис. 1. Маленькие и большие факториалы Проект Факториальные нули Исходный код программы находится в файле Факториальные нули.py. Функция без параметров Оператор break Бесконечный цикл while Задача 840 из книги Математическая шкатулка [Нагибин88], страница 128: Сколько нулей в конце записи числа, выражающего произведение 1 ⦁ 2 ⦁ 3 ⦁ 4 ⦁ 5 ⦁ 6 ⦁ . . . ⦁14 ⦁ 15? Произведение чисел 1..15 равно факториалу числа 15. В данном случае можно просто напечатать значение факториала на экране и посмотреть, сколько нулей в конце строки. Но вполне вероятно, что вам 279
встретится и другая задача на подсчёт хвостовых нулей, поэтому мы добросовестно пересчитаем их в функции solve: def solve(): # получаем 15! f15 = list(facts(16))[15] # список цифр: f15digits = get_digits(f15) # считаем нули: n = 0 # последний инлекс в списке: id = len(f15digits) - 1 while True: # очередная цифра сзади: dig = f15digits[id] if dig != 0: break n += 1 id -= 1 print("15! =", list(facts(16))[15]) print("Число нулей в заданном числе =", n) Функция get_digits возвращает список цифр: # ВОЗВРАЩАЕМ СПИСОК ЦИФР # ЗАДАННОГО ЧИСЛА def get_digits(n): return list(map(int, str(n))) Последние цифры легко получить по их индексам. Как только, идя сзади, мы встретим цифру, отличную от нуля, дальнейший счёт прекращаем. На Рис. 1 очень хорошо видно, что факториал числа 15 заканчивается тремя нулями. Рис. 1. Нулевая считалка Программа целиком: def facts(n = -1): num = 1 280
a = 1 yield a while True: num += 1 yield a a *= num if num == n: break # ВОЗВРАЩАЕМ СПИСОК ЦИФР # ЗАДАННОГО ЧИСЛА def get_digits(n): return list(map(int, str(n))) def solve(): # получаем 15! f15 = list(facts(16))[15] # список цифр: f15digits = get_digits(f15) # считаем нули: n = 0 # последний инлекс в списке: id = len(f15digits) - 1 while True: # очередная цифра сзади: dig = f15digits[id] if dig != 0: break n += 1 id -= 1 print("15! =", list(facts(16))[15]) print("Число нулей в заданном числе =", n) def main(): solve() main() Давайте обойдёмся только числами, без строк! from math import factorial 281
В функции getDigits мы отправляем все цифры заданного числа n в список res. Так как мы идём от конца числа к началу, то цифры в списке хранятся в обратном порядке, поэтому мы переворачиваем список и только тогда возвращаем его: # ВОЗВРАЩАЕМ СПИСОК ЦИФР # ЗАДАННОГО ЧИСЛА def getDigits(n): res = [] while n > 0: res.append(n % 10) n = n // 10 return res[::-1] Так мы получим список всех цифр заданного числа: # получаем список всех цифр заданного числа: lst = getDigits(factorial(15)) print(lst) Как подсчитать конечные нули, вы уже знаете. Во многих задачах нужно найти сумму цифр заданного числа. Сделать это несложно: # ВОЗВРАЩАЕМ СУММУ ЦИФР ЗАДАННОГО ЧИСЛА def getSumDigits(n): res = 0 while n > 0: res += n % 10 n = n // 10 return res Проект Факториалы Исходный код программы находится в файле Факториалы.py. 282
В модуле math есть функция factorial, которая возвращает факториал заданного числа (Рис. 1): from math import factorial def main(): for i in range(10): print(f"Факториал числа {i} =", factorial(i)) main() Рис. 1 Поскольку алгоритм вычисления факториалов очень простой, то мы уже написали собственную функцию getFactorial для вычисления факториалов: def getFactorial(n): fact = 1 for i in range(1, n + 1): fact *= i return fact def main(): for i in range(10): print(f"Факториал числа {i} =", factorial(i)) print() for i in range(10): print(f"Факториал числа {i} =", getFactorial(i)) main() 283
В книгах по программированию часто приводят рекурсивный метод вычисления факториалов: def getFactorialRec(n): return 1 if (n < 1) else n * getFactorialRec(n - 1) Рекурсивные функции обычно короткие, но малопонятные и медленные: print() for i in range(10): print(f"Факториал числа {i} =", getFactorialRec(i)) Да, говорят, что медленные, но наша рекурсивная функция даже огромные факториалы вычисляет очень быстро: print() for i in range(100): print(f"Факториал числа {i} =", getFactorialRec(i)) Проект Последовательность факториалов Исходный код программы находится в файле Последовательность факториалов.py. Факториалы наши лучшие друзья, так что генератор мы напишем без труда. И не забываем учесть, что факториал нуля равен нулю (Рис. 1): # ПОСЛЕДОВАТЕЛЬНОСТЬ ФАКТОРИАЛОВ def facts(n = -1): num = 1 a = 1 yield a while True: num += 1 yield a a *= num if num == n: break 284
def main(): n = 0 for f in facts(10): print(n, f) n += 1 print("Сумма пяти факториалов =", sum(facts(5))) main() Рис. 1 Проект Задача 20 с сайта Проект Эйлер Исходный код программы находится в файле Эйлер020.py. Задача номер 20 (Problem 20) называется Factorial digit sum. n! means n × (n − 1) × ... × 3 × 2 × 1 For example, 10! = 10 × 9 × ... × 3 × 2 × 1 = 3628800, and the sum of the digits in the number 10! is 3 + 6 + 2 + 8 + 8 + 0 + 0 = 27. Find the sum of the digits in the number 100! Найдите сумму цифр числа 100! 285
Генератор факториалов у нас подготовлен и разогрет. Нам нужна ещё функция, которая возвращает список цифр. Самый простой вариант такой: # ВОЗВРАЩАЕМ СПИСОК ЦИФР # ЗАДАННОГО ЧИСЛА def get_digits(n): return map(int, str(n)) Генератор выдаёт нам последовательность из 100 факториалов, из которой мы выбираем последний, то есть 100!: def main(): # получаем 100! f100 = list(facts(100))[99] Новая функция get_digits возвращает список цифр этого числа, а мы находим их сумму: # печатаем ответ: print("Сумма цифр числа 100! =", sum(get_digits(f100))) main() А правильный ответ такой (Рис. 1). Рис. 1 Если не верите, пересчитайте цифры вручную. Проект Последовательность двойных факториалов Исходный код программы находится в файле Последовательность двойных факториалов.py. Немногие знают, что кроме факториалов обычных есть ещё и двойные. Двойной факториал обозначается двумя восклицательными знаками по286
сле натурального числа. Если факториал – это произведение всех чисел от 1 до n (включая само число n), то двойной факториал – это такое же произведение, но только чисел той же чётности. Если n – чётное, то n!! – это произведение всех чётных чисел до n включительно. Если n – нечётное, то n!! – это произведение всех нечётных чисел до n включительно. По определению, 0!! = 1. Так как двойные факториалы для чётных и нечётных чисел вычисляются по-разному, то в функции facts2 мы заведём для них 2 переменные – f21 для нечётных и f22 – для чётных. А дальше поочерёдно умножаем их на текущее число n: # ДВОЙНЫЕ ФАКТОРИАЛЫ def facts2(n = -1): num = 1 f21 = 1 f22 = 1 yield 1 while True: # нечётное: if num % 2: f21 *= num yield f21 # чётное: else: f22 *= num yield f22 num += 1 if num > n: break Делаем проверку (Рис. 1): def main(): n = 0 for f in facts2(10): print(n, f) n += 1 print("Сумма шести двойных факториалов =", sum(facts2(5))) main() 287
Рис. 1 Проект Последовательность простых чисел Исходный код программы находится в файле Последовательность простых чисел py. Простые числа нужны для решения многих задач, поэтому мы напишем программу с функцией-генератором последовательности простых чисел: import math # ГЕНЕРАТОР ПРОСТЫХ ЧИСЕЛ def primes(n = -1): # список для хранения простых чисел # первое простое число - двойка: prime = [2] yield 2 # всего простых чисел: nPrimes = 1 # текущее число для проверки: num = 3 while True: if nPrimes == n: return # проверяем, делится число num # простые числа из списка prime: flg = True 288
for i in prime: # поиск закончен: if i > math.sqrt(num): break # если число num делится, # значит, число составное: if (num % i == 0): flg = False break # если нашли простое число, if flg: # подсчитываем: nPrimes += 1 # добавляем новое простое число в список: prime.append(num) yield num num += 2 print() # ГЛАВНАЯ ФУНКЦИЯ def main(): print('Простые числа') print() # бесконечный цикл ввода данных # пока пользователь не закроет программу # или не введет 0: while True: s = "Введите длину последовательности > " print(s, end = '') number = int(input()) # если пользователь ввёл 0, то программу закрываем: if (number < 1): return for i in primes(number): print(i) print() main() Испытываем генератор простых чисел в деле (Рис. 1). 289
Рис. 1. Дело сделано Проект Вьетнамская задача Исходный код программы находится в файле Вьетнамская задача.py. Последовательности могут быть любые! Вот, например, задача из вьетнамской книги MathSpace (Рис. 1). Рис. 1 Смысла её я не понял, но мы предположим, что требуется найти сумму всех чисел, записанных единицами, от 1 до числа из 2015 единиц. В первую очередь, нам нужен генератор чисел последовательности. Первое число равно 1: # Вьетнамская задача def ones(n = -1): 290
num = 1 res = 1 Следующее число получается из предыдущего добавлением единицы. Для этого умножаем текущее значение переменной res на 10 и добавляем единицу: def ones(n = -1): num = 1 res = 1 while True: yield res res = res * 10 + 1 if num == n: break num += 1 Так мы получаем нужную нам последовательность чисел (Рис. 2): def main(): for o in ones(20): print(o) main() Рис. 2 Распечатывать все 2015 чисел мы не будем, иначе у нас получится числовая пирамида Хеопса. Сразу печатаем сумму чисел последовательности: print(sum(ones(2015))) Сумма большая, число длинное, но очень интересное: print() print(sum(ones(2015))) 123456790123456790123456790123456790123456790123456790123456790123456790 123456790123456790123456790123456790123456790123456790123456790123456790 123456790123456790123456790123456790123456790123456790123456790123456790 291
123456790123456790123456790123456790123456790123456790123456790123456790 123456790123456790123456790123456790123456790123456790123456790123456790 123456790123456790123456790123456790123456790123456790123456790123456790 123456790123456790123456790123456790123456790123456790123456790123456790 123456790123456790123456790123456790123456790123456790123456790123456790 123456790123456790123456790123456790123456790123456790123456790123456790 123456790123456790123456790123456790123456790123456790123456790123456790 123456790123456790123456790123456790123456790123456790123456790123456790 123456790123456790123456790123456790123456790123456790123456790123456790 123456790123456790123456790123456790123456790123456790123456790123456790 123456790123456790123456790123456790123456790123456790123456790123456790 123456790123456790123456790123456790123456790123456790123456790123456790 123456790123456790123456790123456790123456790123456790123456790123456790 123456790123456790123456790123456790123456790123456790123456790123456790 123456790123456790123456790123456790123456790123456790123456790123456790 123456790123456790123456790123456790123456790123456790123456790123456790 123456790123456790123456790123456790123456790123456790123456790123456790 123456790123456790123456790123456790123456790123456790123456790123456790 123456790123456790123456790123456790123456790123456790123456790123456790 123456790123456790123456790123456790123456790123456790123456790123456790 123456790123456790123456790123456790123456790123456790123456790123456790 123456790123456790123456790123456790123456790123456790123456790123456790 123456790123456790123456790123456790123456790123456790123456790123456790 123456790123456790123456790123456790123456790123456790123456790123456790 12345679012345679012345679012345679012345679012345679012345679012345455 Проект Задача 14 с сайта Проект Эйлер Исходный код программы находится в файле Эйлер014.py. Задача номер 14 (Problem 14) называется Longest Collatz sequence. Она была опубликована 5 апреля 2002 года. The following iterative sequence is defined for the set of positive integers: n → n/2 (n is even) n → 3n + 1 (n is odd) Using the rule above and starting with 13, we generate the following sequence: 13 → 40 → 20 → 10 → 5 → 16 → 8 → 4 → 2 → 1 292
It can be seen that this sequence (starting at 13 and finishing at 1) contains 10 terms. Although it has not been proved yet (Collatz Problem), it is thought that all starting numbers finish at 1. Which starting number, under one million, produces the longest chain? NOTE: Once the chain starts the terms are allowed to go above one million. Самая длинная последовательность Коллатца Следующая последовательность определена для натуральных чисел: n → n/2 (n - чётное) n → 3n + 1 (n - нечётное) Используя описанное выше правило и начиная с 13, вы можете сгенерировать такую последовательность: 13 → 40 → 20 → 10 → 5 → 16 → 8 → 4 → 2 → 1 Получившаяся последовательность 13 . . . 1 содержит 10 элементов. Хотя это до сих пор и не доказано (проблема Коллатца (Collatz)), предполагается, что все сгенерированные таким образом последовательности заканчиваются 1. Какой начальный элемент меньше миллиона генерирует самую длинную последовательность? Примечание: члены последовательности могут иметь значения больше миллиона. Решаем задачу методом грубой силы: # Project Euler. Problem 14 # Longest Collatz sequence # РЕШАЕМ ЗАДАЧУ def solve(): # число, начинающее последовательность: startNum = 0 # макс. длина последовательности: maxLen = 0 # макс. число в последовательности: maxNum = 0 for num in range(1, 1000000): n = num length = 1 while (n != 1): if (n % 2 == 0): 293
n //= 2 else: n = 3 * n + 1 if (n > maxNum): maxNum = n length += 1 # запоминаем if (length > startNum maxLen = самую длинную последовательность: maxLen): = num length print("Самая длинная последовательность начинается с", startNum) print("Её длина =", maxLen) print() print("Самое большое число в последовательностях =", maxNum) def main(): print("Решаем задачу 14 из Проекта Эйлер") solve() main() Полный и развёрнутый ответ на задачу показан на Рис. 1. Рис. 1 Поскольку Питон ползает не очень быстро, то придётся подождать несколько секунд. Исходный код программы находится в файле Эйлер014а.py. При решении может пригодиться функция, генерирующая последовательность Коллатца: # ГЕНЕРАТОР ПОСЛЕДОВАТЕЛЬНОСТИ КОЛЛАТЦА def collatz(n): 294
yield n res = n while res > 1: if res % 2 == 1: res = 3 * res + 1 else: res //= 2 yield res Для проверки распечатываем последовательность из условия задачи (Рис. 2): for n in collatz(13): print(n, " ", end ='') Рис. 2 Всё верно! Используем генератор в функции solve: # РЕШАЕМ ЗАДАЧУ def solve(): # число, начинающее последовательность: startNum = 0 # макс. длина последовательности: maxLen = 0 # макс. число в последовательности: maxNum = 0 for num in range(1, 1000000): length = len(list(collatz(num))) #print(length) # запоминаем самую длинную последовательность: if (length > maxLen): startNum = num maxLen = length print("Самая длинная последовательность начинается с", startNum) print("Её длина =", maxLen) print() Функция работает верно, но в этой задаче нам нужна только длина последовательности, поэтому мы можем сразу считать длину: def collatz_len(n): len = 1 while n > 1: 295
if n % 2 == 1: n = 3 * n + 1 else: n //= 2 len += 1 return len # РЕШАЕМ ЗАДАЧУ def solve2(): # число, начинающее последовательность: startNum = 0 # макс. длина последовательности: maxLen = 0 # макс. число в последовательности: maxNum = 0 for num in range(1, 1000000): length = collatz_len(num) # запоминаем самую длинную последовательность: if (length > maxLen): startNum = num maxLen = length print("Самая длинная последовательность начинается с", startNum) print("Её длина =", maxLen) print() Для любителей роликов - ютубик (Рис. 3): AMAZING MATH MYSTERY ! Build "Collatz Sequence" program with Python 3.7.6 https://www.youtube.com/watch?v=V8ktulLbuz0 Рис. 3 296
Более интересный рассказ о последовательности Коллатца, но без программирования: UNCRACKABLE? The Collatz Conjecture - Numberphile https://www.youtube.com/watch?v=5mFpVDpKX70 Числа Фибоначчи Предмет математики настолько серьёзен, что полезно не упускать случаев делать его немного занимательным. Блез Паскаль Числа Фибоначчи названы по имени средневекового математика Леонардо Пизанского, известного под прозвищем Фибоначчи (Рис. 1). Он написал о них в Книге абака в 1202 году. Рис. 1. Леонардо Пизанский (Leonardo Pisano, 1170 - 1250), по прозвищу Фибоначчи (Fibonacci) Впрочем, дотошные историки утверждают, что этот ряд чисел был известен в Индии, где он использовался при стихосложении, задолго до Фибоначчи. 297
В этой книге, среди прочих, есть и задача о размножении кроликов. Фибоначчи взял пару взрослых кроликов (точнее, кролика и крольчиху) и предположил, что они могут производить на свет потомство каждый месяц. Причем у них всегда рождается пара крольчат разного пола, у которых через два месяца также рождаются крольчата. Фибоначчи решил подсчитать, сколько будет кроликов через год, если за это время ни один кролик не умрёт. «Эксперимент» он начал в январе, когда у него была только одна пара кроликов. В феврале семья пополнилась парой крольчат, итого кроликов стало две пары. В марте родительская пара принесла ещё пару крольчат, а первым крольчатам исполнился месяц. Итак, в марте семья кроликов состояла из трёх пар кроликов. В апреле родительская пара снова произвела на свет пару крольчат, а их первое потомство достигло половозрелого возраста и внесло свой вклад в дело размножения семьи, которая теперь насчитывала 5 пар. Просматривая этот сериал про семейство кроликов и дальше, мы сможем составить таблицу. Её часть вы можете видеть на Рис. Полная таблица имеет невероятные размеры, ведь мы знаем, что в июле кроликов будет 21 пара, в августе – 34 пары, в сентябре – 55 пар, в октябре – 89 пар, в ноябре – 144 пары, в декабре – 233 пары. Поскольку цыплят считают по осени, а кроликов парами, то через год в вольере счастливое семейство кроликов будет насчитывать 233 пары кроликов. У первой пары кроликов появятся 11 пар внуков, 9 пар правнуков, и так далее. 298
Первое число Фибоначчи равно 1 - это кролик и крольчиха. Затем у них родилась 1 пара крольчат, и кроликов стало 1 + 1 = 2 пары. Затем к ним добавилась ещё пара крольчат, и всего стало 3 пары. Если отвлечься от кроликов, что и сделали математики, то получится ряд чисел: 1, 1, 2, 3, 5, 8, …. Это и есть ряд чисел Фибоначчи. Но математики настолько отвлеклись от кроликов, что начали этот ряд с нуля, то есть с того момента, когда кроликов вообще не было. Математикам, как и программистам, свойственно начинать счёт с нуля, а не с единицы, так что удивляться не приходится. Но люди, далёкие от математики, 299
часто начинают ряд с единицы, а не с нуля, и вы найдёте в разных источниках 2 ряда Фибоначчи, которые отличаются первым элементом (у математиков это нулевой элемент): есть нуль или его нет. В одном из номеров Уральских пельменей обыгрывается стремительное размножение кроликов (Рис. 2). Рис. 2. Кролики. Кому кроликов? 300
Проект Числа Фибоначчи Исходный код программы находится в файле Числа Фибоначчи.py. Бесконечный цикл while Метод int Списки Условный оператор if Цикл for Оператор or Оператор return В книгах по программированию принято находить числа Фибоначчи с помощью красивого рекурсивного алгоритма, основанного на рекуррентном определении самих чисел: Первое число Фибоначчи = 0 Второе число = 1 Все последующие равны сумме двух предыдущих, то есть: Третье число = 0 + 1 = 1 Четвёртое = 1 + 1= 2 Пятое = 1 + 2 = 3 и так далее, до бесконечности. Рекурсивные алгоритмы обычно довольно короткие, но не всегда быстрые. Поэтому для ускорения вычислений мы воспользуемся методом динамического программирования, то есть просто запомним уже найденные числа Фибоначчи в списке. Этим мы убьём сразу двух зайцев (или кроликов) – и заданное число получим, и все предыдущие числа сохраним в массиве! В функции main пользователь вводит любое неотрицательное число, после чего функция fibo находит числа Фибоначчи от нулевого до заданного: # ГЛАВНАЯ ФУНКЦИЯ def main(): 301
print('Числа Фибоначчи') print() # бесконечный цикл ввода данных # пока пользователь не закроет программу # или не введет отрицательное число: while True: s = "Введите число > " print(s, end='') num = int(input()) # если пользователь ввёл отрицательное число, # то программу закрываем: if (num < 0): return # находим все числа Фибоначчи от 0 до num: f = fibo(num) # и печатаем их: for i in range(0, num + 1): print(f"Число Фибоначчи {i} = {f[i]}") print() if __name__ == "__main__": main() Чтобы сохранить все промежуточные числа, мы создадим список чисел f и сразу же поместим в него три первых числа Фибоначчи, чтобы можно было найти следующие. Далее мы передаём функции fibo номер числа Фибоначчи, которое желает узнать пользователь. Сам алгоритм следует определению чисел Фибоначчи: # -*- coding: Windows-1251 -*# ПРОГРАММА ДЛЯ ВЫЧИСЛЕНИЯ ЧИСЕЛ ФИБОНАЧЧИ # ВЫЧИСЛЯЕМ ЗАДАННОЕ ЧИСЛО ФИБОНАЧЧИ 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. Последнее из найденных чисел 302
соответствует заданному пользователем номеру числа Фибоначчи. Из этого следует, что если пользователю нужно только оно, то можно возвращать пользователю только последнее из найденных чисел. Наша уловка со списком позволяет практически мгновенно находить совершенно невероятные числа Фибоначчи (Рис. 1)! Рис. 2. Вычисляем мгновенно! 303
Проект Числа Фибоначчи 2 Исходный код программы находится в файле Числа Фибоначчи 2.py. Бесконечный цикл while Метод int Функция с параметром Цикл for Оператор return Легко заметить, что из всего списка чисел Фибоначчи для вычисления следующего числа нам нужны только два последних элемента. И если все числа Фибоначчи от первого до заданного сохранять не нужно, то можно обойтись и вообще без списка. И при этом мы не потеряем скорость вычислений! Вот как это делается. «Клонируйте» предыдущий проект и исправьте функцию main: # ГЛАВНАЯ ФУНКЦИЯ 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() if __name__ == "__main__": main() 304
Как видите, кода стало меньше, но теперь мы получим из функции fibo2 только одно число. Впрочем, вы можете вызывать эту функцию в цикле и получить все числа Фибоначчи от нулевого до заданного. Конечно, придётся выполнить лишнюю работу, но скорость программы такова, что вы ничего не заметите. Функция fibo2 для нахождения чисел Фибоначчи тоже упростилась по стравнению с первой версией: # -*- coding: Windows-1251 -*# ПРОГРАММА ДЛЯ ВЫЧИСЛЕНИЯ ЧИСЕЛ ФИБОНАЧЧИ # НАХОДИМ ЧИСЛО ФИБОНАЧЧИ БЕЗ СПИСКА 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 Проверка подтверждает наши ожидания – новый алгоритм считает быстро и аккуратно (Рис. 1). Рис. 1. Тоже неплохо! 305
Проект Последовательность чисел Фибоначчи Исходный код программы находится в файле Последовательность чисел Фибоначчи.py. Теперь числа Фибоначчи нам хорошо известны, поэтому генератор fibs мы напишем без труда: # Последовательность чисел Фибоначчи def fibs(n=-1): num = 0 a, b = 0, 1 while True: yield a a, b = b, a + b if num == n: break num += 1 n = 0 for f in fibs(10): print(n, f) n += 1 print("Сумма пяти чисел Фибоначчи =", sum(fibs(5))) Запускаем программу и получаем от неё 10/11 первых чисел Фибоначчи, а также сумму первых 5/6 чисел (Рис. 1). Рис. 1 306
Проект Задача 2 с сайта Проект Эйлер Исходный код программы находится в файле Эйлер002.py. Задача номер 2 (Problem 2) называется Even Fibonacci numbers. Each new term in the Fibonacci sequence is generated by adding the previous two terms. By starting with 1 and 2, the first 10 terms will be: 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, ... By considering the terms in the Fibonacci sequence whose values do not exceed four million, find the sum of the even-valued terms. Найдите сумму всех чётных чисел Фибоначчи, которые не превышают 4 000 000. В функции main вызываем функцию solve для непосредственного решения задачи: def main(): print("Решаем задачу 2 из Проекта Эйлер") solve() main() Генератор мы уже написали. В цикле for получаем от него числа Фибоначчи, пока не встретится число, которое больше четырёх миллионов. По ходу мы добавляем к значению переменной sum очередное число Фибоначчи, если оно чётное: # Project Euler. Problem 2 # Последовательность чисел Фибоначчи def fibs(): a, b = 0, 1 while True: yield a a, b = b, a + b 307
# РЕШАЕМ ЗАДАЧУ def solve(): sum = 0 for f in fibs(): if f > 4000000: break if f % 2 == 0: sum += f print("Сумма равна:", sum) Или так: sum = 0 for f in fibs(): if f > 4000000: break sum += f if not f % 2 else 0 print("Сумма равна:", sum) В итоге быстро получаем правильный ответ (Рис. 1). Рис. 1 Задачу можно решить в функциональном стиле: from itertools import takewhile res = filter(lambda x: not x % 2, takewhile(lambda x: x < 4000000, fibs())) print("Сумма чисел Фибоначчи =", sum(res)) Ответ, естественно, будет тот же самый (Рис. 2). Рис. 2 Исправьте название локальной переменной sum! 308
Проект «Избранные» числа Исходный код программы находится в файле «Избранные» числа».py. Функция без параметров Цикл for Условный оператор if Оператор continue Оператор целочисленного деления % Функция с параметром Цикл while Задача 12 из книги Удивительный мир чисел [КА86], страница 65: Есть 80 четырёхзначных и 800 пятизначных чисел, не оканчивающихся нулем, и таких, что если от любого из них вычтем 999 в случае четырёхзначного числа и 9999 в случае пятизначного числа, то всякий раз получим обращённое число, т. е. записанное теми же цифрами, но в обратном порядке. Какие это числа? Найдите способ быстрого вычисления суммы этих чисел (без привлечения компьютера). Мы проигнорируем замечание в скобках и решим сначала задачу для 4значных чисел: # ГЛАВНАЯ ФУНКЦИЯ def main(): print('Избранные числа') print() nVar = solve() print("Найдены все варианты решения - " + str(nVar)) print() if __name__ == "__main__": main() Так как условие задачи запрещает числам оканчиваться нулём, то минимальное 4-значное число равно 1001, а максимальное – 9999: 309
# РЕШАЕМ ЗАДАЧУ def solve(): 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 sum += num print("Вариант # " + str(result)) s = "num = " + str(num) s += " > " + str(num2) print(s) print() return result Для обращения чисел мы используем функцию reverseNum: # -*- coding: Windows-1251 -*# Кордемский, с.65, Задача 12 def reverseNum(num): rev = 0 310
while (num > 0): rev = rev * 10 + num % 10 num //= 10 return rev Как и указано в книге Удивительный мир чисел, всего существует 80 таких чисел (Рис. 1). Рис. 1. Четырёхзначные числа-перевёртыши Для поиска 5-значных чисел необходимо изменить значения локальных переменных в функции solve: #min = 1001 #max = 9999 #minus = 999 min = 10001 max = 99999 minus = 9999 5-значных чисел ровно в 10 раз больше, чем 4-значных, что и сообщает нам Рис. 2. 311
Рис. 2. Пятизначные числа-перевёртыши Поскольку мы решаем задачу на компьютере, то было бы странно, если бы мы принялись вычислять сумму найденных чисел вручную! Поэтому объявляем переменную для подсчёта суммы: # сумма чисел: sum = 0 И каждое найденное число добавляем к общей сумме: result += 1 sum += num В конце метода solve печатаем эту сумму: print("Сумма всех чисел = " + str(sum)) print() return result Вот решение задачи для 4- и 5-значных чисел (Рис. 3). 312
Рис. 3. Числовой сумматор Проект Безошибочный прогноз Исходный код программы находится в файле Безошибочный прогноз.py. Класс random Функция с параметром Условный оператор if Оператор return Операторы or и and Списки Цикл for Функция с параметром-массивом Цикл while Функция abs Задача 16 (17) из книги Удивительный мир чисел [КА86], страница 67: Расположите по кругу 4 произвольных натуральных числа а1, а2, а3, а4. Замените их абсолютными значениями разностей: |а1 — а2|, |а2 — а3|, |а3 — а4|, |а4 — а1|. Иллюстрация к задаче 313
С получившимися разностями поступите так же, как с исходными числами. Повторите процедуру вычисления разностей несколько раз, и на некотором шаге все разности одновременно станут нулями. Можете начать вычисления не с 4, а с 8 или с 16, вообще с 2k (k = 2, 3, ...) чисел - финал будет таким же. Докажите хотя бы для 4 исходных чисел, что прогноз безошибочен. Дополнительную информацию об этой задаче смотрите на странице 6. Задача очень интересная, и мы за неё возьмёмся! Поскольку нам потребуются «произвольные», то есть случайные числа, то без генератора таких чисел нам не обойтись: # -*- coding: Windows-1251 -*# Кордемский, с.67, Задача 16 import random В функции main мы передаём функции solve количество чисел, которые желаем испытать: # ГЛАВНАЯ ФУНКЦИЯ def main(): print('Безошибочный прогноз') print() solve(4) print() if __name__ == "__main__": main() По условию задачи, это число не меньше 4 и должно быть степенью двойки, поэтому, прежде всего, следует проверить значение параметра n: # РЕШАЕМ ЗАДАЧУ def solve(n): # число n должно быть степень двойки: if (n < 4 or n & (n - 1) != 0): return 314
Если условие задачи нарушено, то следующую часть кода выполнять нельзя, и мы возвращаемся в главную функцию программы. Если все числа изначально равны нулю, то условие задачи будет выполнено, но, скорее всего, нам потребуется сделать для этого несколько шагов, которые мы, ради интереса, подсчитываем в переменной step: # номер шага: step = 0 В задаче могут участвовать не только 4, но и 8, и 16 чисел, поэтому для их хранения потребуется список, а не n отдельных переменных: # список чисел: a = [0] Как вы помните, нумерация элементов в списке начинается с нуля, а в задаче индексы чисел начинаются с 1. Мы сразу добавляем в список нуль, и тогда в списке а окажутся элементы с индексами 0..n, причём нулевой элемент в задаче не используется, но мы знаем, что его значение равно 0. Теперь мы заполняем список случайными числами, что соответствует присвоению значений числам a1..a4 в задаче: # заполняем список случайными числами: max = 20 for i in range(1, n + 1): a.append(random.randint(0, max)) Тут же можно полюбопытствовать, что у нас получилось: print(f"Шаг {step}") # печатаем список: print(*a[1:], sep=" ", end="") print() В задаче утверждается, что через некоторое число шагов все элементы списка одновременно станут нулями. На самом деле это не так: некоторые элементы массива могут обратиться в нули раньше других, поэтому мы будем контролировать процесс по сумме всех элементов в массиве. Как только сумма обратится в нуль, цикл while будет закончен: 315
# сумма элементов списка: 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) step += 1 print(f"Шаг {step}") print(*a[1:], sep=" ", end="") print() # находим сумму элементов списка: sum = 0 for i in a: sum += i Важно учесть, что последнюю разность a4 – a1 нельзя вычислить в цикле for. Действительно, значение первого элемента списка заменится абсолютным значением разности a1 = a1 – a2 и последняя разность будет вычислена неверно! Поэтому мы перед началом цикла изменения значений элементов списка запоминаем во вспомогательной переменной a1 текущее значение первого элемента. Его мы и используем для замены значения последнего элемента списка. Результаты проверки «гипотезы» для четырёх чисел показаны на Рис. 1. Как вы видите, для обнуления элементов списка может потребоваться разное число шагов – в зависимости от начальных значений элементов массива. С числами 8, 16 и далее поэкспериментируйте самостоятельно! 316
Рис. 1. Прошагали Проект Ошибочный прогноз Исходный код программы находится в файле Ошибочный прогноз.py. Класс random Функция без параметров Списки Цикл for Цикл while Функция abs Операторы and и or Пример из книги Удивительный мир чисел [КА86], страница 6: 317
Иной результат наблюдается для серии разностей в случае комплекта из трёх произвольных натуральных чисел: в финале всегда получаются две единицы и нуль в том или ином чередовании. Пример. Пусть исходная тройка чисел 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) Поскольку один пример ничего не доказывает, то мы напишем программу и подвергнем серьёзному сомнению и проверке это утверждение. # ГЛАВНАЯ ФУНКЦИЯ def main(): print('Ошибочный прогноз') print() solve() print() if __name__ == "__main__": main() Небольшие изменения в функции solve – и можно приниматься за научноисследовательские работы! # -*- coding: Windows-1251 -*# Кордемский, с.6, Пример import random # РЕШАЕМ ЗАДАЧУ def solve(): # тройка чисел: n = 3 # номер шага: step = 0 # список чисел: a = [0] # заполняем список случайными числами: 318
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(f"Шаг {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(f"Шаг {step}: ", end=" ") 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 Раскомментируйте строчки с условиями примера, закомментируйте заполнение списка случайными значениями и запустите программу. Рис. 1 подтверждает, что на 7 шаге одно число превратится в нуль, а остальные два – в единицу. Рис. 1. Пока всё верно Теперь закомментируйте раскомментированное и пуститесь в свободное плавание по числовому океану! 319
Сначала было тихо и штильно (Рис. 2, слева), но вскоре заштормило (Рис. 2, в середине). И не на шутку (Рис. 2, справа и ещё правее)! Рис. 2. А дальше скверно Как вы видите, элементы списка а всегда превращаются в элегантные числа: 1 нуль и 2 «ненуля». Причём ненули это не всегда пара единиц! Проект Нумерация страниц Исходный код программы находится в файле Нумерация страниц.py. Функция без параметров Цикл for Условный оператор if-elif-else Задача 6 из книги Удивительный мир чисел [КА86], страница 64: Я спросил наборщика типографии: 320
- Сколько отдельных литер с цифрами потребуется для нумерации 1000 страниц книги? - Легко вычислить,- ответил наборщик. - Вот моё решение: 3000 - 18 – 90 +1= 2893. Я решил иначе, но получил такой же результат. Придумайте свой план решения и найдите объяснение способу, предложенному наборщиком. Начинаем решать задачу: # Нумерация страниц # Кордемский, с.64, Задача 6 # ГЛАВНАЯ ФУНКЦИЯ def main(): print('Нумерация страниц') print() solve() print() main() Число литер легко найти так: • • • • для записи чисел 1..9 нужно по 1 литере для записи чисел 10..99 нужно по 2 литеры для записи чисел 100..999 нужно по 3 литеры для записи чисел 1000..9999 нужно по 4 литеры В функции solve мы «пролистываем» книгу от 1 до 1000 страницы и считаем литеры, как описано выше: # РЕШАЕМ ЗАДАЧУ def solve(): # число литер: sum = 0 for i in range(1, 1000+1): if (i < 10): sum += 1 321
elif (i sum elif (i sum else: sum < 100): += 2 < 1000): += 3 += 4 print("Потребуется литер: ", sum) print() Все правы: потребуется 2893 литеры (Рис. 1). Рис. 1. Литерная задача Подключаем генератор натуральных чисел: # ГЕНЕРАТОР НАТУРАЛЬНЫХ ЧИСЕЛ: def nats(n = 0): a = 1 while True: yield a if a == n: break a += 1 И получаем второе решение задачи: # ГЛАВНАЯ ФУНКЦИЯ def main(): print('Нумерация страниц') print() #solve() #print() sum = 0 for n in nats(1000): sum += len(str(n)) print("Потребуется литер: ", sum) 322
Проект Сколько страниц в книге? Исходный код программы находится в файле Сколько страниц в книге.py. Функция без параметров Бесконечный цикл for Вложенные условные операторы if-else Оператор break Задача 5 из книги Удивительный мир чисел [КА86], страница 64: Для нумерации страниц книги наборщик типографии использовал 2529 литер с цифрами. На каждой литере одна цифра. Сколько страниц в этой книге? Задача очень похожа на предыдущую, поэтому нам нужно только подправить её код: # Сколько страниц в книге # Кордемский, с.64, Задача 5 # ГЛАВНАЯ ФУНКЦИЯ def main(): print('Сколько страниц в книге?') print() solve() print() # РЕШАЕМ ЗАДАЧУ def solve(): # число использованных литер: SUMMA = 2529 # число литер: sum = 0 i = 1 Мы знаем, что первая страница обозначена числом 1, вторая – числом 2, и так далее. Если мы будем последовательно находить сумму литер, затра323
ченных на нумерацию страниц, то рано или поздно она сравняется с заданной – SUMMA (а если задача составлена неверно, то это событие может и не произойти!). На этом бесконечный цикл while закончится, и мы напечатаем ответ на задачу: # перелистываем страницы и считаем литеры: 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 страниц (Рис. 1), а задачу автор составил верно! Рис. 1. Посчитали все страницы 324
Проект И такие есть числа Исходный код программы находится в файле И такие есть числа.py. Функция без параметров Цикл for Условный оператор if Оператор continue Оператор return Задача 4-1 из книги Удивительный мир чисел [КА86], страница 63: Какое двузначное число в 19 раз больше числа его единиц? # ГЛАВНАЯ ФУНКЦИЯ def main(): print("И такие есть числа") print() nVar = solve() print("Найдены все варианты решения -", nVar) print() if __name__ == "__main__": main() Чтобы получить ответ, достаточно перебрать все двузначные числа и проверить условие задачи: # -*- coding: Windows-1251 -*# Кордемский, с.63, Задача 4-1 # РЕШАЕМ ЗАДАЧУ def solve(): result = 0 for n in range(10, 99 + 1): if (n != n % 10 * 19): continue 325
result += 1 print("Вариант # " + str(result)) s = "Число = " + str(n) print(s) print() return result Как вы видите на Рис. 1, задача имеет единственное решение: искомое число равно 95. Рис. 1. Число найдено Проект Трёхзначное число Исходный код программы находится в файле Трёхзначное число.py. Функция без параметров Цикл for Условный оператор if Оператор continue Оператор целочисленного деления % Оператор деления // Задача 3 из книги Удивительный мир чисел [КА86], страница 63: Найдите трёхзначное число, обладающее следующими свойствами: • число десятков на 4 меньше числа единиц, но на 4 больше числа сотен; • если цифры этого числа разместить в обратном порядке, то новое полученное число будет на 792 больше искомого. 326
# ГЛАВНАЯ ФУНКЦИЯ def main(): print('Трёхзначное число') print() solve() print() if __name__ == "__main__": main() Чтобы найти трёхзначное число с требуемыми свойствами, необходимо перебрать все трёхзначные числа и выбрать те из них, которые удовлетворяют условиям задачи. # -*- coding: Windows-1251 -*# Кордемский, с.63, Задача 3 # РЕШАЕМ ЗАДАЧУ def solve(): min3 = 100 max3 = 999 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 Выполнить проверки и того проще. И вот мы уже получаем ответ на эту задачу (Рис. 1): s = "Число = " + str(n) print(s) print() 327
Рис. 1. Нашли трёхзначное число Поскольку мы перебрали все трёхзначные числа и нашли только одно, которое удовлетворяет условиям задачи, то последняя проверка на разность с перевёрнутым числом оказалась бы лишней! Проект Таких чисел только два Исходный код программы находится в файле Таких чисел только два.py. Функция без параметров Цикл for Оператор целочисленного деления % Оператор деления / / Условный оператор if Оператор continue Задача 1 из книги Удивительный мир чисел [КА86], страница 63: Есть только два двузначных числа, каждое из которых равно неполному квадрату разности своих цифр. Найдите эти числа. Чтобы облегчить решение, подскажем, что одно число на 11 больше другого. Так как двузначных чисел очень мало, то мы и без подсказки найдём оба искомых числа! # ГЛАВНАЯ ФУНКЦИЯ def main(): print('Таких чисел только два') print() solve() 328
print() if __name__ == "__main__": main() Выделяем из каждого числа его единицы и десятки и сравниваем само число с неполным квадратом разности его цифр: # -*- coding: Windows-1251 -*# Кордемский, с.63, Задача 1 # РЕШАЕМ ЗАДАЧУ 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): continue s = "Число = " + str(n) print(s) print() Задача решена (Рис. 1)! Рис. 1. Искомая парочка 329
Проект Ещё два числа Исходный код программы находится в файле Ещё два числа.py. Функция без параметров Условный оператор if Цикл for Оператор continue Оператор деления // Оператор целочисленного деления % Задача 2 из книги Удивительный мир чисел [КА86], страница 63: Сходным свойством обладают ещё два двузначных числа: каждое равно неполному квадрату суммы своих цифр. Найдите эти числа, зная, что одно число на 50 больше другого. Эта задача решается точно так же, как предыдущая: # ГЛАВНАЯ ФУНКЦИЯ def main(): print('Ещё два числа') print() solve() print() if __name__ == "__main__": main() Только в функции solve нужно исправить одну строчку: # -*- coding: Windows-1251 -*# Кордемский, с.63, Задача 2 # РЕШАЕМ ЗАДАЧУ def solve(): min2 = 10 max2 = 99 330
for n in range(min2, max2 + 1): # число единиц: e = n % 10 # число десятков: d = n // 10 % 10 if (e * e + d * d + e * d != n): continue s = "Число = " + str(n) print(s) print() Правда, к нашему удивлению, чисел оказалось не два, а три (Рис. 1)! Рис. 1. А таких чисел три! Да, с полным перебором состязаться невозможно! Проект Отгадать число, ничего не спрашивая Исходный код программы находится в файле Отгадать число, ничего не спрашивая.py. Функция без параметров Вложенные циклы for Условный оператор if Оператор continue Оператор деления // Оператор целочисленного деления % Ради счастья, ради нашего, Если хочешь ты его, Ни о чем меня не спрашивай, Не расспрашивай, не выспрашивай, Не выведывай ничего. Песня из комедии Свадьба в Малиновке Задача 4 из книги Удивительный мир чисел [КА86], страница 36: Пусть кто-нибудь задумает двузначное число в виде 10x + y, где х — цифра десятков, у - цифра единиц, х – у ≥ 2, у ≠ 0. Потребуйте теперь, чтобы он переставил цифры в обратном порядке и вычел меньшее число из большего. Полученную разность пусть сложит с нею же, но написан331
ной в обратном порядке следования цифр. Ничего не спрашивая у загадавшего, вы сообщаете, что у него получилось 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. Если вышеуказанные действия будут применены к любому трёхзначному числу 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. # ГЛАВНАЯ ФУНКЦИЯ def main(): 332
print('Отгадать число, ничего не спрашивая') print() solve() print() if __name__ == "__main__": main() # -*- coding: Windows-1251 -*# Кордемский, с.36, Задача 4 # РЕШАЕМ ЗАДАЧУ 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 # разность чисел: r = abs(num - numr) # число единиц в разности: e = r % 10 # число десятков в разности: d = r // 10 # сумма: sum = r + e * 10 + d Для контроля печатаем каждое двузначное число, удовлетворяющее условиям задачи, а также результат наших действий над ним: print("Исходное число равно: " + str(num)) print("Полученное число равно: " + str(sum)) print() print() 333
Часть списка представлена на Рис. 1. Из него видно, что все двузначные числа обращаются в 99. Рис. 1. Удачный фокус Трёх- и четырёхзначные числа проверьте самостоятельно! Проект Три лягушки Исходный код программы находится в файле Три лягушки.py. Функция без параметров Списки Бесконечный цикл for Вложенные циклы for Условный оператор if Оператор break Оператор continue Комбинированные операторы присваивания Задача 6 из книги Удивительный мир чисел [КА86], страница 51: 334
Три лягушки находятся на дне колодца глубиной 60 м. За день они поднимаются на 18 м каждая, а потом спускаются первая на 12 м, вторая на 16 м, третья на 17 м и остаются на своих местах до следующего дня. На следующий день каждая лягушка проделывает снова такой же маршрут и т. д. Через сколько дней лягушки выйдут из колодца? # ГЛАВНАЯ ФУНКЦИЯ def main(): print('Три лягушки') print() solve() print() if __name__ == "__main__": main() Для начала объявляем необходимые переменные и списки: # -*- coding: Windows-1251 -*# Кордемский, с.51, Задача 6 # РЕШАЕМ ЗАДАЧУ def solve(): # глубина колодца: GLUBINA = 60 # высота подъёма: UP = 18 # глубина опускания: DOWN = [12, 16, 17] # текущее расстояние до поверхности: frogs = [GLUBINA, GLUBINA, GLUBINA] # освободившиеся лягушки: free = [False, False, False] Приключения начинаются! По значению элементов списка free мы определяем, все ли лягушки-квакушки выбрались из колодца. Если это радостное событие состоялось, значит, все элементы в списке обратились в True: n = 1 while True: # все лягушки свободны: if not False in free: print() print("Все лягушки свободны!") 335
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(f"Лягушка {i + 1} свободна! День: {n}") Если лягушкам выбраться не удалось, то они опускаются вниз – каждая на свой «размер»: # лягушки опускаются: for i in range(0, len(frogs)): # эта лягушка уже свободна: if (free[i]): continue frogs[i] += DOWN[i] n += 1 print() Запускаем программу и освобождаем лягушек (Рис. 1)! Рис. 1. Свободу французским лягушкам! 336
Ура-ква-ква! Квак преквасен этот мир! Проект Гаусс Исходный код программы находится в файле Гаусс.py. Функция с параметрами Цикл for Задача 5 из книги Математическая шкатулка [Нагибин88], страница 15: Рассказывают, что в начальной школе, где учился мальчик Карл Гаусс, ставший потом знаменитым математиком, учитель, чтобы занять класс на продолжительное время самостоятельной работой, дал детям такое задание - вычислить сумму всех натуральных чисел от 1 до 100. Но маленький Гаусс это задание моментально выполнил. Попробуй и ты быстро выполнить это задание. Нетрудно в этом ряде чисел увидеть арифметическую прогрессию, начинающуюся с единицы и насчитывающую 100 членов. Разность арифметической прогрессии равна 1. Зная все параметры этого ряда, мы быстро найдём его сумму: Сумма = (1 + 100)*100/2 = 5050 # ГЛАВНАЯ ФУНКЦИЯ 337
def main(): print('Гаусс') print() solve(1, 100, 1) solve(1, 999 // 2 + 1, 2) solve(5, 100 // 5, 5) print() if __name__ == "__main__": main() Учитель же, конечно, предполагал, что школяры будут вычислять сумму ряда последовательным прибавлением очередного члена ряда к текущей сумме. Давайте в функции solve потакнём недалёкому учителю, тем более что компьютер – не Гаусс и быстро, а главное – с удовольствием найдёт сумму именно так. Пусть сумма ряда вначале равна нулю: # -*- coding: Windows-1251 -*# Нагибин, с.15, Задача 5 # РЕШАЕМ ЗАДАЧУ def solve(start, num, r): # сумма ряда: sum = 0 Текущий член ряда – это сначала первый член прогрессии, который мы назовём start: # текущий член ряда: n = start Если в последовательности num членов, то мы их друг за другом добавляем к сумме: for i in range(0, num): sum += n При этом не забываем вычислять следующий член последовательности, прибавляя к текущему разность арифметической прогрессии: 338
n += r print("Сумма ряда равна %i" % sum) print() В нашей задаче: • • • start = 1 num = 100 r = 1 Наша глуповатая, но прилежная функция правильно подсчитала сумму заданного ряда (Рис. 1). Рис. 1. Методический сумматор Зато наша функция получилась универсальной! Подставляя нужные значения в функцию solve, мы быстро найдём сумму любой арифметической прогрессии. Например, давайте решим такую задачу. Задача 7.1 из книги Математическая шкатулка [Нагибин88], страница 15: Как быстро вычислить сумму 1 + 3+ 5+7 + 9 + ... + 997 +999? Добавьте в функцию main выделенную строку: solve(1, 100, 1) solve(1, 999 // 2 + 1, 2) И получите ответ на задачу (Рис. 2). Здесь главное – не ошибиться со значениями параметров в функции solve! • • start = 1 num = 999//2+1 339
• r = 2 Рис. 2. Гауссиана продолжается! При вычислении num следует добавить 1, так как при целочисленном делении результат будет округлён отбрасыванием целой части, поэтому число членов окажется на 1 меньше действительного. Задача 838 из книги Математическая шкатулка [Нагибин88], страница 128: Вычислите: 5 + 10+ 15+20 + 25 + ... + 100. И опять одна строка программы решает задачу (Рис. 3): solve(1, 100, 1) solve(1, 999 // 2 + 1, 2) solve(5, 100 // 5, 5) Рис. 3. Арифметике нас учить не надо! В этой задаче: • • • start = 5 num = 100/5 r = 5 340
Проект Ряд натуральных чисел Исходный код программы находится в файле Ряд натуральных чисел.py. Самая известная последовательность – это ряд натуральных чисел: 1, 2, 3,… Поскольку ряд бесконечный, то мы можем получить только некоторое число элементов. Это можно сделать с помощью функции range, которая возвращает список целых чисел в заданном диапазоне. Вот так мы сможем получить 20 натуральных чисел (Рис. 1): def main(): nat = range(1, 21) print(*nat) Рис. 1 Получив в своё распоряжение ряд натуральных чисел, мы легко найдём их сумму и решим известную задачу Гаусса. Ряд натуральных чисел мы получать умеем, поэтому нужно найти только их сумму (Рис. 2): # решаем задачу Гаусса: print() sum100 = sum(range(1, 101)) print("Сумма 100 первых натуральных чисел =", sum100) Рис. 2 В некоторых случаях удобнее пользоваться не списком, а генератором натуральных чисел. Особенно, если мы хотим получить бесконечный ряд. Генератор очень простой: 341
# ГЕНЕРАТОР НАТУРАЛЬНЫХ ЧИСЕЛ: def nats(n = 0): a = 1 while True: yield a if a == n: break a += 1 По умолчанию, параметр n равен 0, то есть при вызове функции nats без аргументов она выдаст бесконечную последовательность. Тогда необходимо дополнительное условие, чтобы закончить её. Вот так наш генератор выдаст ровно 20 первых натуральных чисел (Рис. 3): print() for n in nats(20): print(n, end = ' ') Рис. 3 С генератором задача Гаусса решается ещё проще, чем со списком (Рис. 4): print() print("Сумма 100 первых натуральных чисел =", sum(nats(100))) Рис. 4 Проект Ряд целых чисел Исходный код программы находится в файле Ряд целых чисел.py. Целые числа – это не только натуральные, но ещё нуль и отрицательные числа. Это значит, что кроме длины последовательности (числа членов) len, мы должны знать и её начало – число begin. 342
Последовательность натуральных чисел умеет генерировать функция range: nat = range(begin, begin + len) print(*nat) В функции ints мы начинаем возвращать числа, начиная с а = begin, а затем добавляем по 1 к значению переменной а, чтобы получить следующее целое число. При этом мы дополнительно считаем уже выданные числа в переменной num (это необязательно, но так проще:) # ГЕНЕРАТОР ЦЕЛЫХ ЧИСЕЛ: def ints(begin = 1, len = 0): a = begin num = 1 while True: yield a if num == len: break a += 1 num += 1 Если вызвать функцию ints, то она вернёт ряд натуральных чисел, как наша предыдущая функция nats. В функции main нужно задать значения параметрам функции ints (Рис. 1): def main(): begin = int(input("Введите первый элемент ряда > ")) print("Первый элемент ряда =", begin) len = int(input("Введите длину ряда > ")) print("Длина ряда =",len) print("Последовательность целых чисел:") for i in ints(begin, len): print(i, end = " ") main() Рис. 1 343
Проект Ряд квадратов целых чисел Исходный код программы находится в файле Ряд квадратов целых чисел.py. Для решения некоторых задач требуется ряд квадратов целых чисел. Его легко получить тем же способом, что и целые числа, только нужно возводить их в квадрат (Рис. 1): # ГЕНЕРАТОР КВАДРАТОВ ЦЕЛЫХ ЧИСЕЛ: def qints(begin = 1, len = 0): a = begin num = 1 while True: yield a * a if num == len: break a += 1 num += 1 def main(): begin = int(input("Введите первый элемент ряда > ")) print("Первый элемент ряда =", begin) len = int(input("Введите длину ряда > ")) print("Длина ряда =",len) print("Последовательность квадратов целых чисел:") for i in qints(begin, len): print(i, end = " ") main() Рис. 1 344
Ряд квадратов целых чисел – это арифметическая прогрессия второго порядка. В арифметической прогрессии второго порядка последовательность разностей соседних членов образует простую арифметическую прогрессию. Рассмотрим последовательность квадратов натуральных чисел: 0, 1, 4, 9, 16, 25, 36… Разности образуют простую арифметическую прогрессию с шагом 2: 1, 3, 5, 7, 9, 11, … Последовательности кубов образуют арифметическую прогрессию третьего порядка, и так далее. Проект Ряд квадратов целых чисел. Задача 1 Исходный код программы находится в файле Ряд квадратов целых чисел. Задача1.py и Ряд квадратов целых чисел. Задача2. Задача с сайта: https://easy-physic.ru/nemnogo-celyx-chisel-zadachi/ Задача 4. Вычислите все возможные значения выражения величина y является решением уравнения ? , если У нас есть генераторы для последовательностей в числителе и в знаменателе, поэтому мы легко найдём их суммы, а, значит, и значение дроби (Рис. 1): # ГЕНЕРАТОР ЦЕЛЫХ ЧИСЕЛ: def ints(begin = 1, len = 0): a = begin num = 1 while True: yield a if num == len: break a += 1 num += 1 345
# ГЕНЕРАТОР КВАДРАТОВ ЦЕЛЫХ ЧИСЕЛ: def qints(begin = 1, len = 0): a = begin num = 1 while True: yield a * a if num == len: break a += 1 num += 1 def solve(): sum1 = sum(ints(1, 64)) sum2 = sum(qints(1, 64)) res = sum1 / sum2 print(res) def main(): solve() main() Рис. 1 Казалось бы, задача решена, но на сайте мы видим красивый ответ (Рис. 2). Рис. 2 Поскольку мы знаем точный ответ, то давайте решим задачу в натуральных дробях. Для этого нам понадобится класс Fraction из модуля fractions. Импортируем его в программу: from fractions import Fraction В функции solve находим не десятичную дробь, а натуральную: def solve(): sum1 = sum(ints(1, 64)) sum2 = sum(qints(1, 64)) 346
res = Fraction(sum1, sum2) print(res) Вот теперь наш ответ не только правильный, но и красивый (Рис. 3). Рис. 3 Проект Арифметическая прогрессия Исходный код программы находится в файле Арифметическая прогрессия.py. Арифметическая прогрессия – самая известная числовая последовательность после ряда натуральных чисел, потому что её изучают в школе. Она отличается от ряда целых чисел только шагом, который может быть любым. Он обозначается буквой d. Если d = 0,то последовательность стационарная, то все члены равны а, если а – это первый член прогрессии. Если d = 1, то мы получаем последовательность целых чисел. Если d > 0, то последовательность монотонно возрастающая. Если d < 0, то последовательность монотонно убывающая. Если члены ряда – целые числа, то арифметическую последовательность можно получить с помощью функции range (Рис. 1): def main(): begin = int(input("Введите первый элемент прогрессии > ")) print("Первый элемент ряда =", begin) step = int(input("Введите разность прогрессии > ")) print("Разность прогрессии =", step) len = int(input("Введите длину прогрессии > ")) print("Длина ряда =",len) nat = range(begin, begin + len * step, step) print(*nat) main() 347
Рис. 1 С генератором мы получим такую же последовательность: # Арифметическая прогрессия # ГЕНЕРАТОР: def arithms(begin = 1, step = 1, len = 0): a = begin num = 1 while True: yield a if num == len: break a += step num += 1 def main(): begin = int(input("Введите первый элемент прогрессии > ")) print("Первый элемент ряда =", begin) step = int(input("Введите разность прогрессии > ")) print("Разность прогрессии =", step) len = int(input("Введите длину прогрессии > ")) print("Длина ряда =",len) #nat = range(begin, begin + len * step, step) #print(*nat) print("Арифметическая прогрессия:") for i in arithms(begin, step, len): print(i, end = " ") 348
Проект Арифметическая прогрессия 2 Исходный код программы находится в файле Арифметическая прогрессия 2.py. Мы легко превратим наш генератор в универсальный, чтобы он мог выдавать и вещественные числа: # Арифметическая прогрессия 2 # ГЕНЕРАТОР: def arithms(begin = 1.0, step = 1.0, len = 0): a = begin num = 1 while True: yield a if num == len: break a += step num += 1 def main(): begin = float(input("Введите первый элемент прогрессии > ")) print("Первый элемент ряда =", begin) step = float(input("Введите разность прогрессии > ")) print("Разность прогрессии =", step) len = int(input("Введите длину прогрессии > ")) print("Длина ряда =",len) print("Арифметическая прогрессия:") for i in arithms(begin, step, len): print(i, end = " ") main() Но из-за неточности в вычислениях некоторые элементы выглядят странно (Рис. 1). 349
Рис. 1 Проект Нагибин88-15 Исходный код программы находится в файле Нагибин88-15.py. В проекте Гаусс мы уже решили Задачу 7.1 из книги Математическая шкатулка [Нагибин88], страница 15: Как быстро вычислить сумму 1 + 3 + 5 + 7 + 9 + ... + 997 + 999? Но давайте применим наш генератор на деле, то есть для решения этой задачи. Единственная «сложность» в программе – правильно высчитать длину последовательности (Рис. 1): # ГЕНЕРАТОР: def arithms(begin = 1.0, step = 1.0, len = 0): a = begin num = 1 while True: yield a if num == len: break a += step num += 1 350
def main(): begin = 1 step = 2 len = 999 // 2 + 1 print("Сумма =", sum(arithms(begin, step, len))) main() Рис. 1 Проект Нагибин88-15 2 Исходный код программы находится в файле Нагибин88-15 2.py. Чтобы обойтись без длины последовательности, мы можем извлекать элементы в бесконечном цикле for, но тогда нужно самостоятельно считать сумму элементов и контролировать условие прекращения цикла. В этой задаче цикл заканчивается, когда очередной элемент превысит максимальное значение 999: # ГЕНЕРАТОР: def arithms(begin = 1.0, step = 1.0, len = 0): a = begin num = 1 while True: yield a if num == len: break a += step num += 1 def main(): begin = 1 step = 2 sum = 0 for i in arithms(begin, step): if i > 999: break 351
sum += i print("Сумма =", sum) main() Проект Нагибин88-128 Исходный код программы находится в файле Нагибин88-128.py. Задачу 838 из книги Математическая шкатулка [Нагибин88], страница 128: Вычислите: 5 + 10 + 15 + 20 + 25 + ... + 100. Мы также решали в проекте Гаусс. Эта задача даже проще предыдущей (Рис. 1): # ГЕНЕРАТОР: def arithms(begin = 1.0, step = 1.0, len = 0): a = begin num = 1 while True: yield a if num == len: break a += step num += 1 def main(): begin = 5 step = 5 len = 100 // 5 print("Сумма =", sum(arithms(begin, step, len))) main() Рис. 1 352
Проект Функция count Исходный код программы находится в файле Функция count.py. В модуле itertools имеется функция-итератор count, которая генерирует бесконечную арифметическую прогрессию: count(start=0, step=1) start – первый член прогрессии step – разность прогрессии Это значит, что мы должны предусмотреть условие завершения цикла. Давайте решим последнюю задачу без нашего генератора, который заменим функцией count: from itertools import count def main(): begin = 5 step = 5 sum = 0 for i in count(begin, step): if i > 100: break sum += i print("Сумма =", sum) main() Проект Функция cycle Исходный код программы находится в файле Функция cycle.py. 353
Функция-итератор cycle циклически повторяет заданную последовательность элементов iterable: cycle(iterable) Итерируемым объектом может быть, например, список (Рис. 1): from itertools import cycle def main(): num = 0 for i in cycle(range(1, 11)): num += 1 if num > 25: break print(i, end = " ") main() Рис. 1 Или строка (Рис. 2): def main(): num = 0 #for i in cycle(range(1, 11)): for i in cycle("12345"): num += 1 if num > 25: break print(i, end = " ") Рис. 2 354
Проект Функция repeat Исходный код программы находится в файле Функция repeat.py. Функция-итератор repeat выдаёт (бес)конечную последовательность объектов: repeat(object[, times]) Если второй аргумент при вызове функции не указан, то последовательность бесконечная. Если указан, то объект возвращается times раз. Эта программа 20 раз печатает число 11 (Рис. 1): from itertools import repeat def main(): for i in repeat(11, 20): print(i, end = " ") main() Рис. 1 Проект Геометрическая прогрессия Исходный код программы находится в файлах Геометрическая прогрессия.py и Геометрическая прогрессия 2.py. Геометрическая прогрессия – тоже известная школьная числовая последовательность. Отличается от арифметической прогрессии тем, что каждый следующий элемент, начиная со второго, получается умножением 355
предыдущего на одно и то же число, которое называется знаменателем прогрессии. Обозначим первый элемент прогрессии буквой а, а знаменатель – буквой q. Тогда: • если a > 0 и q > 1, то последовательность возрастающая • если 0 < q < 1, то последовательность убывающая • если q < 0, то последовательность знакочередующаяся В функцию geoms мы передаём значение первого элемента прогрессии, знаменатель и число элементов: # Геометрическая прогрессия def geoms(first = 1, znam = 2, n = -1): num = 0 a, b = first, znam while True: yield a a *= b num += 1 if num == n: break Если третий аргумент при вызове функции отсутствует, то она выдаёт бесконечную последовательность. Иначе – заданное число элементов (Рис. 1): def main(): for g in geoms(1, 2, 20): print(g) main() Зёрна в знаменитой шахматной задаче образуют геометрическую прогрессию. Первый элемент равен 1 (на первую клетку положили 1 зерно). Знаменатель равен 2 (на следующие клетки клали в 2 раза больше, чем на предыдущие). Нам нужно найти сумму 64 элементов этой геометрической прогрессии (Рис. 2): sumg = sum(geoms(1, 2, 64)) print("Сумма =", sumg) print() 356
Рис. 1 Рис. 2 Если вам нужна геометрическая прогрессия вещественных чисел, то передавайте функции geoms вещественные аргументы (Рис. 3): for g in geoms(1.0, 2.0, 12): print(g) Рис. 3 357
Проект Прогрессивные квадраты Исходный код программы находится в папке Squares. Нарисуем большой квадрат (Рис. 1). Рис. 1 Пусть длина стороны квадрата равна а, тогда его площадь равна а2. Соединив середины сторон квадрата, мы получим новый квадрат (Рис. 2). 358
Рис. 2 Обозначим через а2 половину длины большого квадрата: а2 = a / 2. Тогда длина стороны меньшего квадрата равна: корень квадратный из 2 * a22. А площадь равна 2 * a22 → 2 * a2 / 4 = a2 / 2, то есть ровно половине площади большого квадрата. Если мы соединим середины сторон меньшего квадрата, то получим квадрат, площадь которого ещё в 2 раза меньше. И так мы можем продолжать до бесконечности. При этом площади квадратов образуют геометрическую прогрессию со знаменателем 1 / 2. 359
Понятно, что на экране нельзя начертить очень маленькие квадраты, поэтому мы увидим только небольшую часть этой замечательной последовательности (Рис. 3). Рис. 3 Так как стандартные графические средства Питона не слишком хороши, мы напишем программу в среде Процессинг: def setup(): size(800, 800) # белый фон: background(255) 360
# чёрные линии: stroke(0) noFill() rectMode(CORNERS) pushMatrix() draw_squares() popMatrix() # ЧЕРТИМ КВАДРАТЫ def draw_squares(): # толщина линий: strokeWeight(4) # угол поворота квадратов: angle = 45 # устанавливаем начало координат # в центре окна: translate(width/2, height/2) # цвет заливки: clr = 240 fill(clr) # длина стороны квадрата: sz = width # коэффициент масштабирования координат: k = 2 # коэффициент масштабирования длины сторон: s = 2 # чертим # сторон while sz x1 = y1 = x2 = y2 = квадраты, пока длина больше 6 пикселей: > 6: -sz/k -sz/k sz/k sz/k # чертим квадрат: rect(x1, y1, x2, y2) # длина стороны следующего квадрата: sz = sz * sqrt(s) / 2 # толщина линий: strokeWeight(1) # цвет заливки более тёмный: clr /= 1.1 fill(clr) # поворачиваем системы координат: rotate(radians(angle)) 361
А работу этой программы вы уже видели. Если рисовать квадраты в обратную сторону, то есть от меньших к большим, то знаменатель геометрической прогрессии будет равен двум – как в шахматной задаче. Начертим маленький квадрат с длиной стороны 1 мм. Его площадь составит 1 мм2. Построим вокруг него ещё 63 квадрата, как мы это делали в последнем проекте. Площадь последнего. 64-го квадрата будет равна шахматному числу 18446744073709551615. Это и есть его площадь в квадратных миллиметрах. Переведём в квадратные метры и получим 18446744073709,551615 м2. Тоже очень много, поэтому посмотрим, сколько это будет в квадратных километрах. Это будет 18 446 744 км2, то есть больше территории России! Проект Плюс-минус Исходный код программы находится в файле Плюс-минус.py. Функция без параметров Цикл for Комбинированные операторы присваивания Задача 7-2 из книги Математическая шкатулка [Нагибин88], страница 15: Как быстро вычислить: 99 - 97 + 95 - 93 + 91 - 89 + ... + 7 - 5 + 3 – 1? Конечно, быстро вычислить можно на компьютере! # ГЛАВНАЯ ФУНКЦИЯ def main(): print('Плюс-минус') print() solve() print() if __name__ == "__main__": main() 362
По сравнению с обычной арифметической прогрессией (здесь она убывающая) знак членов ряда чередуется: плюс-минус-плюс-минус… Достаточно ввести переменную sign, чтобы решить и эту задачу (Рис. 1): # -*- coding: Windows-1251 -*# Нагибин, с.15, Задача 7-2 # РЕШАЕМ ЗАДАЧУ def solve(): # сумма ряда: sum = 0 start = 99 # текущий член ряда: n = start r = -2; num = 99 // 2 + 1 # знак алгебраической суммы: sign = 1 for i in range(0, num): sum += n * sign n += r # меняем знак: sign *= -1 print("Сумма ряда равна %i" % sum) print() Рис. 1. Плюсы и минусы – это наши плюсы! К сожалению, в издании 1988 года отсутствует и ответ, и решение этой задачи, а вот в более раннем издании 1958 года приведено решение – и весьма остроумное! 363
Проект Минус-плюс Исходный код программы находится в файле Минус-плюс.py. Функция без параметров Цикл for Задача 482-8 из книги Математическая шкатулка [Нагибин88], страница 88: Найдите простой приём вычисления: 1002 – 992 + 982 – 972 + 962 – 952 + ... + 42 – 32 + 22 – 12 Здесь мы видим практически тот же ряд, что и в предыдущей задаче, только ещё проще! # ГЛАВНАЯ ФУНКЦИЯ def main(): print('Минус-плюс') print() solve() print() Задача решается без труда, но довольно интересно, что ответ на неё полностью совпадает с тем ответом, который мы получили, решая задачу Гаусса (Рис. 1). # -*- coding: Windows-1251 -*# Нагибин, с.88, Задача 482-8 # РЕШАЕМ ЗАДАЧУ def solve(): # сумма ряда: sum = 0 # знак алгебраической суммы: sign = 1 for i in range(100, 1 - 1, -1): sum = sum + i * i * sign # меняем знак: 364
sign *= -1 print("Сумма ряда равна %i" % sum) print() Рис. 1. Гаусс вечен! Проект Дробный ряд Исходный код программы находится в файле Дробный ряд.py. Функция без параметров Тип данных float Цикл for Комбинированные операторы присваивания Оператор деления / Задача 9-1 из книги Математическая шкатулка [Нагибин88], страница 16: Найдите простой приём вычислений и воспользуйтесь им для вычисления суммы: # ГЛАВНАЯ ФУНКЦИЯ def main(): print('Дробный ряд') print() solve() print() 365
if __name__ == "__main__": main() Этот ряд – почти арифметическая прогрессия: в знаменателе первый сомножитель изменяется от 1 до 9 с приращением 1. Второй сомножитель равен первому, плюс 1. Произведение этих сомножителей, а значит и дробей, мы легко найдём. Осталось найти сумму дробей: -*- coding: Windows-1251 -*# Нагибин, с.16, Задача 9-1 # РЕШАЕМ ЗАДАЧУ def solve(): # сумма ряда: sum = 0.0 # текущий член ряда: n = 1 r = 1 num = 9 for i in range(0, num): sum += 1.0 / (n * (n + 1)) n += r print("Сумма ряда равна %f" % sum) print() Рис. 1 показывает, что сумма равна 0,9, и подсказывает нам: у этой задачи имеется красивое некомпьютерное решение. Найдите его! Рис. 1. Дробовая задача! Проект Ещё один дробный ряд Исходный код программы находится в файле Ещё один дробный ряд.py. Функция без параметров 366
Тип данных float Цикл for Комбинированные операторы присваивания Оператор деления / Задача 9-2 из книги Математическая шкатулка [Нагибин88], страница 16: Найдите простой приём вычислений и воспользуйтесь им для вычисления суммы: # ГЛАВНАЯ ФУНКЦИЯ def main(): print('Ещё один дробный ряд') print() solve() print() if __name__ == "__main__": main() Задача очень похожа на предыдущую, поэтому решается исправлением двух строк в коде (Рис. 1): # -*- coding: Windows-1251 -*# Нагибин, с.16, Задача 9-2 # РЕШАЕМ ЗАДАЧУ def solve(): # сумма ряда: sum = 0.0 # текущий член ряда: n = 10 r = 1 num = 90 for i in range(0, num): sum += 1 / (n * (n + 1)) n += r 367
print("Сумма ряда равна", sum) print() Рис. 1. Дробим всё! Проект Трёхзначное число 2 Исходный код программы находится в файле Трёхзначное число 2.py. Функция с параметрами Цикл for Вложенные операторы if Оператор деления // Оператор целочисленного деления % Оператор break Задача 597 из книги Математическая шкатулка [Нагибин88], страницы 97-98: Сколько слагаемых суммы 1 + 2 + 3 + 4 + 5 + … надо взять, чтобы получить трёхзначное число, состоящее из одинаковых цифр? # ГЛАВНАЯ ФУНКЦИЯ def main(): print('Трёхзначное число') print() solve(1, 100, 1) print() if __name__ == "__main__": main() 368
Поскольку числовой ряд представляет собой арифметическую прогрессию, сумму которой мы умеем находить, то нам нужно только проследить, когда эта сумма превратится в трёхзначное число с одинаковыми цифрами. Искать заданное число можно в бесконечном цикле while, который следует прервать по исчерпании всех трёхзначных сумм: # -*- coding: Windows-1251 -*# Нагибин, с.97-98, Задача 597 # РЕШАЕМ ЗАДАЧУ def solve(start, num, r): # сумма ряда: sum = 0 # текущий член ряда: n = start i = 1 while True: sum += n; if (sum >= 111): # число единиц: e = sum % 10 # число десятков: d = sum // 10 % 10 # число сотен: s = sum // 100 if (e == d and e == s): print("Число слагаемых равно", i) print("Трёхзначное число равно", sum) # трёхзначные числа закончились: if (sum > 999): break n += r i += 1 print() Правда, по условиям задачи, цикл можно закончить сразу, как только будет найдено первое трёхзначное число с одинаковыми цифрами, но мы проверим все трёхзначные суммы – в надежде, что среди них найдутся и другие подходящие. Ан нет: Рис. 1 разочаровывает нас – задача имеет единственное решение! Рис. 1. Поиски оказались бесплодными 369
Проект Вычисляем пи и е Исходный код программы находится в файле Вычисляем пи и е.py. Функция без параметров Тип данных 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 году Вильям Шенкс 370
после 20 лет расчётов нашёл 707 знаков числа пи, но в 1944 году Д.Фергюсон с помощью механического калькулятора выяснил, что верны только первые 527 знаков числа Шенкса. Для своих расчётов Шенкс использовал формулу Дж. Мачина: А арктангенс вычислял по формуле: Сам Мачин (Рис. 1) ещё в 1706 году по этой формуле вычислил 100 знаков числа пи. Рис. 1. 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 371
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… Конечно, все эти знаки не упомнить (да и не надо!), а вот несколько первых знать совсем не помешает. Помню в школьной стенной газете был такой стишок для запоминания первых цифр числа пи: Чтобы нам не ошибаться, Надо правильно прочесть: Три, четырнадцать, пятнадцать, Девяносто два и шесть. Я его запомнил и надеюсь, что и вам это удастся! Формула Валлиса Английский математик Джон Валлис (Рис. 2) вывел эту красивую формулу в 1655 году, когда вычислял площадь круга. Она представляет собой бесконечное произведение дробей. Рис. 2. John Wallis by Sir Godfrey Kneller (1616 - 1703) К сожалению, чтобы вычислить даже несколько правильных знаков числа пи по этой формуле, нужно затратить немало времени и сил. Однако, имея компьютер, мы можем облегчить себе задачу. Итак, пишем программу: 372
# -*- coding: Windows-1251 -*# Вычисляем пи и е # ВЫЧИСЛЯЕМ ПИ ПО ФОРМУЛЕ ВАЛЛИСА 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 =', i, 'pi =', (2*pi)) print() # ГЛАВНАЯ ФУНКЦИЯ def main(): print() print('Вычисляем пи и е') print() Wallis() print() main() После десяти миллионов итераций получаем ответ (Рис. 3). Рис. 3. Да! Уже седьмой знак после запятой неверный Ряд Лейбница Попробуем зайти с другого конца и воспользуемся знакочередующимся рядом, который предложил немецкий математик Лейбниц (Рис. 4). Рис. 4. Gottfried Wilhelm von Leibniz (1646 – 1716) А ряд вот такой: 373
Он хорош тем, что его легко приспособить под наши нужды: # ВЫЧИСЛЯЕМ ПИ - Ряд Лейбница # pi/4 = 1 - 1/3 + 1/5 - 1/7 + ... def Leibniz(): pi = 0.0 neg = 1 for i in range(0, 1000000+1): pi += neg / (i * 2 + 1) neg = -neg; print('i =', i, 'pi =', (4*pi)) print() После миллиона итераций число пи найдено (Рис. 5). Рис. 5. К сожалению, результат не лучше первого! Вообще говоря, точность вычислений по этим формулам и не может быть высокой из-за того что тип 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 4709369995 2746639193 3232862794 8914993488 3710753907 2656029760 9283681902 6996794686 7635148220 8879332036 374
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… 7646480429 9618188159 9208680582 7862320900 5209618369 1038375051 Его можно вычислить как сумму бесконечного ряда: e = 1 + 1/1! + 1/2! + 1/3! + ... Очень удобная формула для вычисления на компьютере: знаменатель каждой следующей дроби равен знаменателю предыдущей дроби, умноженному на 1, 2, 3, … Из этого следует, что достаточно предыдущую дробь последовательно делить на эти числа и добавлять к общей сумме: # ВЫЧИСЛЯЕМ e # e = 1 + 1/1! + 1/2! + 1/3! + ... def calc_e(): e = 1.0 f = 1.0 i = 1 for i in range(1, 17+1): f /= i e += f # печатаем промежуточные результаты: print('i =', i, 'e =', e) print() # печатаем окончательный результат: print('i =', i, 'e =', e) print() Уже через 27 итераций мы получим 15 правильных десятичных знаков числа е после запятой (Рис. 6). В той же школьной стенной газете было и такое прозаическое правило для запоминания первых цифр числа е: 2 – 7 – 1828 – 1828 (год рождения Льва Толстого) – 45 - 90 – 45 (углы прямоугольного равнобедренного треугольника). 375
Вполне достаточно, чтобы очаровать учительницу математики (на учителей математики этот приём, увы, не действует)! Эти же формулы используются в книге [100], страницы 24-26 для нахождения чисел пи и е. Рис. 6. Неплохоеее е! В книге [100] предлагается вычислить пи и по такой формуле: Новый метод легко получить из метода Лейбница: # ВЫЧИСЛЯЕМ ПИ def calc_pi_2(): pi = 0.0 for i in range(0, 10000000+1): n = i * 2 + 1 pi += 1 / (n*n) 376
print('i =', i, 'pi =', math.sqrt(8*pi)) print() Как и следовало ожидать, точность вычислений невысокая (Рис. 7). # ВЫЧИСЛЯЕМ ПИ def calc_pi_2(): pi = 0.0 for i in range(0, 10000000+1): n = i * 2 + 1 pi += 1 / (n*n) print('i =', i, 'pi =', math.sqrt(8*pi)) print() Рис. 7. Тоже пи Другая формула для вычисления пи из этой книги: Здесь мы опять наталкиваемся на ограничение при выборе типа данных (Рис. 8): # ВЫЧИСЛЯЕМ ПИ def calc_pi_3(): 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 =', i, 'pi =', math.pow(32*pi, 1.0/3)) print() 377
Рис. 8. Точности не хватает! В приведённой выше формуле из книги допущена опечатка – степень пи не 2, а 3, поэтому мы должны извлечь кубический корень из суммы ряда. Следующий ряд состоит как бы из двух рядов, сумму которых можно вычислить отдельно, а затем найти пи: В целом ряд напоминает ряд Лейбница, поэтому новую функцию написать не очень сложно (Рис. 9): # ВЫЧИСЛЯЕМ ПИ def calc_pi_4(): sum1 = 0 sum2 = 0 neg = 1 n5 = 5 n239 = 239 for i in range(0, sum1 += neg * sum2 += neg / neg = -neg n5 *= 25 n239 = n239 * 100000+1): 4 / n5 / (i*2+1) n239 / (i*2+1) 239 * 239 print('i =', i, 'pi =', 4*(sum1 - sum2)) print() Рис. 9. Повышаем точность Здесь все знаки числа пи (кроме последнего) верные! 378
И наконец, вычислите самостоятельно пи по такой формуле: Поскольку здесь участвуют квадратные корни, то точность вычислений также будет невысокой. Учительница математики спрашивает у Вовочки: - Сколько будет пи в квадрате? Вовочка не растерялся: - Пи-пи! Для вычислений с произвольной точностью в языке Питон имеется специальный модуль decimal, который необходимо импортировать в проект: from decimal import * Давайте с его помощью перепишем функцию calc_pi_4: # ВЫЧИСЛЯЕМ ПИ def calc_pi_4_dec(): getcontext().prec = 1000+2 sum1 = Decimal(0) sum2 = Decimal(0) neg = 1 n5 = Decimal(5) n239 = Decimal(239) for i in range(0, sum1 += neg * sum2 += neg / neg = -neg n5 *= 25 n239 = n239 * 1000+1): 4 /n5 / (i*2+1) n239 / (i*2+1) 239 * 239 print('i =', i, 'pi =', 4*(sum1 - sum2)) print() Как вы видите, прежде всего, нужно задать желаемую точность вычислений: 379
getcontext().prec = 1000+2 Затем нужно создать все переменные, которые должны иметь высокую точность, как объекты класса Decimal. Сами же вычисления проводятся точно так же, как и раньше. Зато уже после 1000 итераций мы получаем 1000 правильных знаков числа пи (Рис. 10). Рис. 10. Хорошая точность Аналогично вы можете вычислить и около 2000 десятичных знаков числа е (Рис. 11). # ВЫЧИСЛЯЕМ e def calc_e_2(): # число цифр: NUM_DIG = 2000+2 getcontext().prec = NUM_DIG e = Decimal(0) i = 0 MAX_VALUE = 10**NUM_DIG while True: fact = math.factorial(i) e += Decimal(1)/fact i += 1 if fact > MAX_VALUE: 380
break print() print('i = ', i) print('e =', e) print() Рис. 11. Отличная точность Вычисления с высокой точностью выполняются значительно медленнее обычных, и это следует учитывать при разработке программ! По формуле Чудновского можно очень быстро вычислить многое цифр числа пи: 381
Формула очень сложная, поэтому мы не будем обсуждать все тонкости её перевода на компьютерный язык. За подробностями обращайтесь на сайт: http://www.craig-wood.com/nick/articles/pi-chudnovsky/ А вот код для вычисления числа пи: # ВЫЧИСЛЯЕМ КВАДРАТНЫЙ КОРЕНЬ С ЗАДАННОЙ ТОЧНОСТЬЮ def sqrt(n, prec): # точность вычислений: precision = 10 ** 16 n_float = float((n * precision) // prec) / precision x = (int(precision * math.sqrt(n_float)) * prec) // precision n_prec = n * prec while True: x_old = x x = (x + n_prec // x) // 2 if x == x_old: break return x # ВЫЧИСЛЯЕМ пи ПО ФОРМУЛЕ ЧУДНОВСКОГО def Chudnovsky(digits): """ Вычисляем только цифры числа пи, а не само число """ C = 640320 C3_OVER_24 = C ** 3 // 24 def bs(a, b): if b - a == 1: if a == 0: Pab = Qab = 1 else: Pab = (6 * a - 5) Qab = a * a * a * Tab = Pab * (13591409 if a & 1: Tab = -Tab else: m = (a + b) // 2 Pam, Qam, Tam = bs(a, Pmb, Qmb, Tmb = bs(m, Pab = Pam * Pmb Qab = Qam * Qmb * (2 * a - 1) * (6 * a - 1) C3_OVER_24 + 545140134 * a) m) b) 382
Tab = Qmb * Tam + Pam * Tmb return Pab, Qab, Tab DIGITS_PER_TERM = math.log10(C3_OVER_24 / 6 / 2 / 6) N = int(digits / DIGITS_PER_TERM + 1) P, Q, T = bs(0, N) prec = 10 ** digits sqrtC = sqrt(10005 * prec, prec) return (Q * 426880 * sqrtC) // T Тысячу знаков функция Chudnovsky вычисляет практически мгновенно! И даже 10000 значков числа пи выдаёт быстрее, чем за 1 секунду. Можете проверить! 31415926535897932384626433832795028841971693993751058209749445923078164062862089986280 34825342117067982148086513282306647093844609550582231725359408128481117450284102701938 52110555964462294895493038196442881097566593344612847564823378678316527120190914564856 69234603486104543266482133936072602491412737245870066063155881748815209209628292540917 15364367892590360011330530548820466521384146951941511609433057270365759591953092186117 38193261179310511854807446237996274956735188575272489122793818301194912983367336244065 66430860213949463952247371907021798609437027705392171762931767523846748184676694051320 00568127145263560827785771342757789609173637178721468440901224953430146549585371050792 27968925892354201995611212902196086403441815981362977477130996051870721134999999837297 80499510597317328160963185950244594553469083026425223082533446850352619311881710100031 37838752886587533208381420617177669147303598253490428755468731159562863882353787593751 95778185778053217122680661300192787661119590921642019893809525720106548586327886593615 33818279682303019520353018529689957736225994138912497217752834791315155748572424541506 95950829533116861727855889075098381754637464939319255060400927701671139009848824012858 36160356370766010471018194295559619894676783744944825537977472684710404753464620804668 42590694912933136770289891521047521620569660240580381501935112533824300355876402474964 73263914199272604269922796782354781636009341721641219924586315030286182974555706749838 50549458858692699569092721079750930295532116534498720275596023648066549911988183479775 35663698074265425278625518184175746728909777727938000816470600161452491921732172147723 50141441973568548161361157352552133475741849468438523323907394143334547762416862518983 56948556209921922218427255025425688767179049460165346680498862723279178608578438382796 79766814541009538837863609506800642251252051173929848960841284886269456042419652850222 10661186306744278622039194945047123713786960956364371917287467764657573962413890865832 64599581339047802759009946576407895126946839835259570982582262052248940772671947826848 26014769909026401363944374553050682034962524517493996514314298091906592509372216964615 15709858387410597885959772975498930161753928468138268683868942774155991855925245953959 43104997252468084598727364469584865383673622262609912460805124388439045124413654976278 07977156914359977001296160894416948685558484063534220722258284886481584560285060168427 39452267467678895252138522549954666727823986456596116354886230577456498035593634568174 32411251507606947945109659609402522887971089314566913686722874894056010150330861792868 09208747609178249385890097149096759852613655497818931297848216829989487226588048575640 14270477555132379641451523746234364542858444795265867821051141354735739523113427166102 13596953623144295248493718711014576540359027993440374200731057853906219838744780847848 96833214457138687519435064302184531910484810053706146806749192781911979399520614196634 28754440643745123718192179998391015919561814675142691239748940907186494231961567945208 09514655022523160388193014209376213785595663893778708303906979207734672218256259966150 14215030680384477345492026054146659252014974428507325186660021324340881907104863317346 49651453905796268561005508106658796998163574736384052571459102897064140110971206280439 383
03975951567715770042033786993600723055876317635942187312514712053292819182618612586732 15791984148488291644706095752706957220917567116722910981690915280173506712748583222871 83520935396572512108357915136988209144421006751033467110314126711136990865851639831501 97016515116851714376576183515565088490998985998238734552833163550764791853589322618548 96321329330898570642046752590709154814165498594616371802709819943099244889575712828905 92323326097299712084433573265489382391193259746366730583604142813883032038249037589852 43744170291327656180937734440307074692112019130203303801976211011004492932151608424448 59637669838952286847831235526582131449576857262433441893039686426243410773226978028073 18915441101044682325271620105265227211166039666557309254711055785376346682065310989652 69186205647693125705863566201855810072936065987648611791045334885034611365768675324944 16680396265797877185560845529654126654085306143444318586769751456614068007002378776591 34401712749470420562230538994561314071127000407854733269939081454664645880797270826683 06343285878569830523580893306575740679545716377525420211495576158140025012622859413021 64715509792592309907965473761255176567513575178296664547791745011299614890304639947132 96210734043751895735961458901938971311179042978285647503203198691514028708085990480109 41214722131794764777262241425485454033215718530614228813758504306332175182979866223717 21591607716692547487389866549494501146540628433663937900397692656721463853067360965712 09180763832716641627488880078692560290228472104031721186082041900042296617119637792133 75751149595015660496318629472654736425230817703675159067350235072835405670403867435136 22224771589150495309844489333096340878076932599397805419341447377441842631298608099888 68741326047215695162396586457302163159819319516735381297416772947867242292465436680098 06769282382806899640048243540370141631496589794092432378969070697794223625082216889573 83798623001593776471651228935786015881617557829735233446042815126272037343146531977774 16031990665541876397929334419521541341899485444734567383162499341913181480927777103863 87734317720754565453220777092120190516609628049092636019759882816133231666365286193266 86336062735676303544776280350450777235547105859548702790814356240145171806246436267945 61275318134078330336254232783944975382437205835311477119926063813346776879695970309833 91307710987040859133746414428227726346594704745878477872019277152807317679077071572134 44730605700733492436931138350493163128404251219256517980694113528013147013047816437885 18529092854520116583934196562134914341595625865865570552690496520985803385072242648293 97285847831630577775606888764462482468579260395352773480304802900587607582510474709164 39613626760449256274204208320856611906254543372131535958450687724602901618766795240616 34252257719542916299193064553779914037340432875262888963995879475729174642635745525407 90914513571113694109119393251910760208252026187985318877058429725916778131496990090192 11697173727847684726860849003377024242916513005005168323364350389517029893922334517220 13812806965011784408745196012122859937162313017114448464090389064495444006198690754851 60263275052983491874078668088183385102283345085048608250393021332197155184306354550076 68282949304137765527939751754613953984683393638304746119966538581538420568533862186725 23340283087112328278921250771262946322956398989893582116745627010218356462201349671518 81909730381198004973407239610368540664319395097901906996395524530054505806855019567302 29219139339185680344903982059551002263535361920419947455385938102343955449597783779023 74216172711172364343543947822181852862408514006660443325888569867054315470696574745855 03323233421073015459405165537906866273337995851156257843229882737231989875714159578111 96358330059408730681216028764962867446047746491599505497374256269010490377819868359381 46574126804925648798556145372347867330390468838343634655379498641927056387293174872332 08376011230299113679386270894387993620162951541337142489283072201269014754668476535761 64773794675200490757155527819653621323926406160136358155907422020203187277605277219005 56148425551879253034351398442532234157623361064250639049750086562710953591946589751413 10348227693062474353632569160781547818115284366795706110861533150445212747392454494542 36828860613408414863776700961207151249140430272538607648236341433462351897576645216413 76796903149501910857598442391986291642193994907236234646844117394032659184044378051333 89452574239950829659122850855582157250310712570126683024029295252201187267675622041542 05161841634847565169998116141010029960783869092916030288400269104140792886215078424516 70908700069928212066041837180653556725253256753286129104248776182582976515795984703562 384
22629348600341587229805349896502262917487882027342092222453398562647669149055628425039 12757710284027998066365825488926488025456610172967026640765590429099456815065265305371 82941270336931378517860904070866711496558343434769338578171138645587367812301458768712 66034891390956200993936103102916161528813843790990423174733639480457593149314052976347 57481193567091101377517210080315590248530906692037671922033229094334676851422144773793 93751703443661991040337511173547191855046449026365512816228824462575916333039107225383 74218214088350865739177150968288747826569959957449066175834413752239709683408005355984 91754173818839994469748676265516582765848358845314277568790029095170283529716344562129 64043523117600665101241200659755851276178583829204197484423608007193045761893234922927 96501987518721272675079812554709589045563579212210333466974992356302549478024901141952 12382815309114079073860251522742995818072471625916685451333123948049470791191532673430 28244186041426363954800044800267049624820179289647669758318327131425170296923488962766 84403232609275249603579964692565049368183609003238092934595889706953653494060340216654 43755890045632882250545255640564482465151875471196218443965825337543885690941130315095 26179378002974120766514793942590298969594699556576121865619673378623625612521632086286 92221032748892186543648022967807057656151446320469279068212073883778142335628236089632 08068222468012248261177185896381409183903673672220888321513755600372798394004152970028 78307667094447456013455641725437090697939612257142989467154357846878861444581231459357 19849225284716050492212424701412147805734551050080190869960330276347870810817545011930 71412233908663938339529425786905076431006383519834389341596131854347546495569781038293 09716465143840700707360411237359984345225161050702705623526601276484830840761183013052 79320542746286540360367453286510570658748822569815793678976697422057505968344086973502 01410206723585020072452256326513410559240190274216248439140359989535394590944070469120 91409387001264560016237428802109276457931065792295524988727584610126483699989225695968 8159205600101655256375678 За несколько секунд вы получите 100 000 знаков числа пи, но я не нашёл в книге столько места, чтобы напечатать их… Проект Вычисляем пи по методу Архимеда Исходный код программы находится в файле Вычисляем пи по методу Архимеда.py. Функция с параметрами Тип данных decimal Бесконечный цикл while Оператор деления // Как вы знаете, Архимед нашёл приближённое значение числа пи, вписывая в окружность и описывая вокруг неё правильные многоугольники (Рис. 1 из Википедии). 385
Рис. 1. Чем больше сторон у многоугольника, тем точнее значение числа пи. При бесконечном числе сторон мы получили бы точное значение числа пи, что практически сделать невозможно. В этом проекте мы имитируем построение правильных многоугольников с очень большим числом сторон, вписанных в окружность. Мы начинаем с правильного четырёхугольника (квадрата), у которого 4 стороны. Если радиус окружности равен r = 1, то из прямоугольного треугольника ОАВ мы найдём длину стороны квадрата √2 и длину окружности 4√2 = 2π, откуда π = 2√2 (Рис. 2). Рис. 2. Теперь на каждой стороне квадрата построим равнобедренный треугольник. В итоге из квадрата мы получим 8-угольник. Чтобы найти длину стороны 8-угольника, рассмотрим треугольник АВС (Рис. 3). Из теоремы Пифагора следует, что AC2 = AD2 + CD2. 386
Рис. 3 Обозначим длину стороны квадрата буквой d, тогда AC2 = (d/2)2 + CD2 (1) CD = 1 – OD OD легко найти из прямоугольного треугольника ODB. Гипотенуза OB = 1, а сторона DB = d/2. По теореме Пифагора: CD2 = (1 - √1 − (𝑑/2)2 )2 Подставим это выражение в уравнение (1) и после преобразований получим формулу для вычисления стороны 8-угольника: √ 2 − 2√1 − 𝑑 2 (2) 4 Если мы её добросовестно вычислим, то для пи получим такое значение (Рис. 4). Рис. 4 Если мы теперь на каждой стороне 8-угольника построим треугольник, то получим 16-угольник. Длину его стороны можно вычислить по той же 387
формуле (2), только теперь d – это длина стороны 8-угольника. Для 16угольника длина окружности вполне приемлемая (Рис. 5). Рис. 5 Таким образом, по формуле (2) мы можем вычислить длину стороны любого правильного 2n- угольника. Для вычислений с большой точностью мы импортируем модуль decimal: # -*- coding: Windows-1251 -*from decimal import Decimal, getcontext В функции pi_archimedes мы действуем по методу Архимеда. Длину сторон правильного многоугольника подсчитываем по формуле (2): dn+1 = √2 − 2√1 − 𝑑2 4 Начинаем вычисления с квадрата, у которого 4 стороны. Их длину также можно вычислить по формуле (2), но для этого нужно знать длину сторон предшествующего многоугольника, у которого сторон в 2 раза меньше. Однако многоугольников с двумя сторонами не существует, поэтому возникает вопрос: если dn+1 – это длина сторон квадрата, то чему равно d? – Посмотрите на Рис. 6. Ясно видно, что треугольник АВС построен на диаметре окружности, то есть символическим предшественником квадрата можно считать диаметр окружности. Благодаря этому допущению мы можем стороны всех многоугольников вычислять по одной и той же формуле (2). Других «хитростей» в функции pi_archimedes нет. # ВЫЧИСЛЯЕМ ЧИСЛО ПИ ПО МЕТОДУ АРХИМЕДА def pi_archimedes(n): length = Decimal(2) sides = 4 for i in range(n): length = 2 - 2 * (1 - length / 4).sqrt() 388
sides *= 2 return sides * length.sqrt() / 2 Рис. 6 В главной функции мы задаём нужную нам точность вычислений и номер начальной итерации, которая определяется по эмпирической формуле. При желании вы можете посмотреть все итерации, но это займёт немало времени. def main(): print() print('Вычисляем число пи по методу Архимеда') print() # число значащих цифр: NUM_DIG = 1000 + 1 pred = None # начальная итерация: n = 166 * NUM_DIG // 100 - 1 while True: # вычисляем с двойной точностью: getcontext().prec = NUM_DIG * 2 res = pi_archimedes(n) # print(n, res) break # печатаем с заданной точностью, # округляя текущий результат: getcontext().prec = NUM_DIG res = +res # печатаем промежуточные результаты: # print(n, res) # достигли заданной точности: if res == pred: break n += 1 389
# печатаем результат с заданной точностью: print(n, res) print() main() С каждой итерацией число сторон многоугольника удваивается, так что он всё больше приближается к окружности. И таким образом мы сможем вычислить значение числа пи очень точно. Например, после 1662 удвоений мы получим 1000 правильных десятичных знаков числа пи после запятой (Рис. 7). Рис. 7 390
Если бы у Архимеда был компьютер, он нашёл бы очень точное значение числа пи ещё две с половиной тысячи лет тому назад! Проект Тригонометрические функции Исходный код программы находится в файле Тригонометрические функции.py. Функция с параметром Оператор return Тип данных float Оператор деления / Константа math.pi Бесконечный цикл while Метод math.sin Метод math.cos Метод math.atan Цикл for Оператор return Условный оператор if Комбинированные операторы присваивания Задача 5⨀⨀ из книги 100 задач по программированию [100], страница 26: Составьте функции для вычисления с указанной точностью значений тригонометрических функций путём сложения членов следующих рядов: В последней формуле |x| < 1. 391
Ряд для вычисления синуса должен напомнить вам ряд для вычисления основания натуральных логарифмов е: 1 + 1/1! + 1/2! + 1/3! + ... А поскольку это так, то вам остаётся только учесть, что ряд для синуса знакопеременный, а в числителе 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: # ГЛАВНАЯ ФУНКЦИЯ def main(): print('Тригонометрические функции') print() # бесконечный цикл ввода данных # пока пользователь не закроет программу: while True: print("Градусы: ", end='') # угол в градусах: 392
grad = float(input()) # переводим в радианы: rad = grad2Rad(grad) print("Синус " + str(grad) + " = ", end='') sin = Sin(rad) print(str(sin)) print("math.sin = " + str(math.sin(rad))) print() if __name__ == "__main__": main() Приведённая в книге формула для вычисления синуса – это ряд Тейлора. Так как числитель дробей постоянно и резко уменьшается, а знаменатель очень быстро растёт, то точности встроенного типа float хватает всего на пару десятков итераций: # -*- coding: Windows-1251 -*# Тригонометрические функции import math # ВЫЧИСЛЯЕМ СИНУС ЗАДАННОГО УГЛА 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 Запускаем программу и задаём хорошо известные углы в градусах. Рис. 1 убеждает нас, что наша небольшая функция Sin почти не уступает в точности встроенному методу класса math! 393
Рис. 1. Отсинусились В модуле math имеется функция radians, которая переводит градусы в радианы: sin = Sin(math.radians(grad)) Переходим к вычислению косинусов: print("Косинус " + str(grad) + " = ", end='') cos = Cos(rad) print(str(cos)) print("math.cos = " + str(math.cos(rad))) print() Чтобы получить функцию Cos, нужно в функции для вычисления синуса исправить всего несколько строчек: # ВЫЧИСЛЯЕМ КОСИНУС ЗАДАННОГО УГЛА 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 394
xn = xn * x * x return cos Здесь мы иногда наблюдаем расхождение с методом cos класса math в последнем знаке (Рис. 2), но в целом и этот метод можно признать успешным! Рис. 2. И откосинусились Ряд для вычисления арктангенса заданного угла мало отличается от ряда для синуса. Он даже проще: print("Арктангенс " + str(grad) + " = ", end='') atan = Atan(rad) print(str(atan)) print("math.atan = " + str(math.atan(rad))) print() Мы быстро справляемся с новой функцией: # ВЫЧИСЛЯЕМ АРКТАНГЕНС ЗАДАННОГО УГЛА 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 395
В данном ряду знаменатели увеличиваются довольно медленно, поэтому для достижения максимально возможной точности потребуется значительно больше итераций. Также следует учитывать, что ряд правильно работает только для углов, которые меньше 1 радиана (Рис. 3)! Рис. 3. Вполне точные арктангенсы Проект Сотая цифра Исходный код программы находится в файле Сотая цифра.py. Функция без параметров Цикл while Вложенные циклы while Условный оператор if Оператор break Списки Комбинированные операторы присваивания 396
Задача 300 из книги Математическая шкатулка [Нагибин88], страница 45: Все натуральные числа, начиная с 1, записаны в порядке их возрастания: 1 2 3 4 5 6 7 8 9 10 11… Какая цифра в этой записи стоит на сотом месте? Проще всего решить задачу «строковым» способом: # ГЛАВНАЯ ФУНКЦИЯ def main(): print('Сотая цифра') print() solve() print() if __name__ == "__main__": main() # -*- coding: Windows-1251 -*# Нагибин, с.45, Задача 300 # РЕШАЕМ ЗАДАЧУ def solve(): s = '' i = 1 while(len(s) < 100): s += str(i) i += 1 print("s =", s) print("На сотом месте стоит цифра:", s[99]) print() Пока в строке меньше 100 знаков, мы добавляем к ней следующее число. Поскольку при этом длина строки может оказаться больше 100 символов, то мы просто интересуемся, какая цифра стоит на сотом месте (её индекс 99). На Рис. 1 мы видим, что сотое место в числовой записи занимает цифра 5. 397
Рис. 1. Даёшь сотню! Но строковый способ решения задачи не совсем честный, поэтому давайте обойдёмся без строк - только числами! Функция 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.append(n % 10) anum.insert(len(anum) - 1, n % 10) n //= 10 nd += 1 num += 1 if (nd >= 100): break print(anum) print("На сотом месте стоит цифра:", anum[99]) print() Здесь число num увеличивается от 1 и до тех пор, пока число цифр в списке anum не достигнет сотни. Теперь нам нужно самостоятельно выделять из каждого числа его цифры и заносить в список anum. Здесь важно учесть, что цифры мы выделяем с конца числа, а заносить их в список нужно в обратном порядке. Ответ мы получим, естественно, тот же самый, что и раньше (Рис. 2). 398
Рис. 2. Даёшь сотню! Проект Случайные ферзи Исходный код программы находится в файле Случайные ферзи.py. Функция с параметрами Цикл while Цикл for Условный оператор if Класс Random Расстановка ферзей на шахматной доске – излюбленная программистская задача. Обычно стремятся найти все возможные решения. Для небольших досок это нетрудно, но с увеличением их размеров число расстановок быстро растёт. Предположим, что нам нужна всего одна, случайная расстановка ферзей. Если мы найдём все расстановки и сохраним их, то сможем выбирать случайные. Но тогда нам нужно затратить время на поиск всех решений, а также память для хранения этих расстановок. Можно поступить иначе: расставить ферзей на доске в случайном порядке. Если при этом хотя бы одна пара ферзей окажется под боем, то снова расставить ферзей. И так до победного конца. Конечно, если расставлять ферзей бездумно, то удачной расстановки ждать придётся очень долго. Однако достаточно заметить, что в каждой горизонтали и вертикали может находиться единственный ферзь, как задача существенно облегчится! 399
Пронумеруем горизонтали и вертикали числами от 0 до 7, как это принято в программировании (Рис. 1). Рис. 1. Программистская оцифровка Пройдёмся по горизонталям сверху вниз и отметим вертикали, в которых стоят ферзи: 25317460 Понятно, что номера вертикалей – это также числа 0..7. Если бы числа повторялись, то в одной вертикали стояло бы несколько ферзей, что недопустимо. Таким образом, всякая расстановка ферзей описывается перестановкой чисел 0..7. Если бы мы расставляли ладей, то любая перестановка этих чисел давала бы решение задачи. Всего таких перестановок 8! = 40320. Однако мы точно знаем, что ферзей можно расставить всего 92 способами, поэтому вероятность удачной расстановки равна 92 / 40320, или, что понятнее, нам потребуется в среднем 40320 / 92, то есть чуть больше 438 попыток для получения удачной позиции (Рис. 2). Рис. 2. Попытка - не пытка Для стандартной доски это совсем немного. Для доски 9 х 9 клеток число попыток составляет больше тысячи: 400
1030.909090909091 А для доски 10 х 10 клеток уже больше 5 тысяч: 5012.154696132597 Для досок больших размеров рассматриваемый способ непригоден. В главной функции программы создаём генератор псевдослучайных чисел rand и список pos для хранений позиций ферзей на доске, а также задаём число ферзей nQueens: # ГЛАВНАЯ ФУНКЦИЯ def main(): # генератор псевдослучайных чисел: rand = random.Random() # число ферзей: nQueens = 8 # начальная расстановка ферзей: pos = list(range(nQueens)) # число найденных решений: found_solutions = 0 # число попыток: tries = 0 Если мы хотим последовательно получить несколько решений, то указываем их число в переменной nSolutions. При этом вы должны учитывать, что решения могут и совпасть, поскольку каждое решение мы находим совершенно случайно, то есть независимо от уже найденных решений. # число решений: nSolutions = 10 Пока найдены не все решения, мы расставляем ферзей на доске случайным образом и проверяем, удовлетворяет ли очередная расстановка условиям задачи: while found_solutions < nSolutions: # переставляем ферзей случайным образом: rand.shuffle(pos) tries += 1 if resolution(pos): 401
found_solutions += 1 print(f"Решение # {found_solutions}: {pos}. Число попыток = {tries}") tries = 0 print() main() Для этого мы вызываем функцию resolution и передаём ей список номеров вертикалей, в которые мы поставили ферзей: # ПРОВЕРЯЕМ ЗАДАННУЮ ПОЗИЦИЮ def resolution(pos): # по всем горизонталям, кроме первой (0): for row in range(1, len(pos)): # плохая расстановка ферзей: if not row_resolution(pos, row): return False # хорошая расстановка: return True Так как ферзя в верхней горизонтали другие ферзи бить не могут (мы считаем, что они ещё не выставлены на доску), то начинаем проверку со второй горизонтали. Правильно ли стоит ферзь в очередной горизонтали – это мы узнаём с помощью функции row_resolution, которая получает позицию на поле и номер горизонтали. Так как мы идём сверху вниз, то ферзя в очередной горизонтали нужно проверять на «битьё» только с предыдущими, уже выставленными ферзями в горизонталях 0..row-1: # ПРОВЕРЯЕМ ДИАГОНАЛИ def row_resolution(pos, row): # по всем горизонталям 0..row: for r in range(row): # проверяем диагонали: if not test_diagonal(r, pos[r], row, pos[row]): return False # нет угроз по диагонали: return True Если два ферзя стоят на одной горизонтали, то абсолютные величины разности их координат равны, что хорошо видно на следующем рисунке (Рис. 3). 402
Рис. 3. Диагональные угрозы Если это не так, значит, ферзи не бьют друг друга: # -*- coding: Windows-1251 -*""" Программа для поиска заданного числа случайных расстановок ферзей на доске. Не годится для числа ферзей > 12 """ import random # ПРОВЕРЯЕМ ЗАДАННУЮ ДИАГОНАЛЬ def test_diagonal(с, r, сol, row): dr = abs(row - r) dc = abs(сol - с) # в разных диагоналях: return dr != dc На этом решение задачи закончено (Рис. 4). Рис. 4. Случайные расстановки ферзей 403
Задания для самостоятельного решения Задача #297 Математическая шкатулка Чтобы пронумеровать страницы некоторой книги, понадобилось 1164 цифры. Сколько в этой книге страниц? Ответ: 424 Задача #298 Математическая шкатулка Сколько цифр нужно употребить для нумерации книги, в которой 634 страницы? Ответ: 1794 Формула Бине [ЗП88]. Задача 556 n-ное число Фибоначчи можно вычислить по формуле Бине: В ней буквой фи обозначено золотое сечение: Из неё следует, что Fn равно ближайшему к . целому числу. 404
Так как абсолютная величина выражения меньше единицы, то при больших n числа Фибоначчи можно вычислять по приближённой формуле: Напишите метод, вычисляющий заданное число Фибоначчи по формуле Бине. 405
Глава #9. Диофантовы уравнения и линейное программирование Под диофантовыми понимаются неопределённые уравнения, которые решаются в целых числах (часто – в натуральных). Они названы в честь древнегреческого математика Диофанта, жившего в третьем веке нашей эры (Рис. 1). В честь Диофанта назван и кратер на Луне (на рисунке – в правом нижнем углу). Рис. 1. Диофант Александриийский Διόφαντος ὁ Ἀλεξανδρεύς, Diophantus В тринадцатитомном труде Арифметика (до нас дошли только 6 первых книг) он показывает, как решать подобные уравнения. 406
В 1974 году была издана книга Арифметика и книга о многоугольных числах, содержащая перевод на русский язык всех трудов Диофанта (Рис. 2, слева). В 2007 году книга была переиздана (Рис. 2, справа). Рис. 2. Диофантовы книги Более подробно о Диофанте вы можете прочитать в книге Якова Перельмана Занимательная математика (Рис. 3, слева) и Изабеллы Башмаковой (Рис. 3, справа). Рис. 3. И книги о Диофанте Впрочем, диофантовы уравнения появились задолго до самого Диофанта. Первое из таких уравнений было известно ещё в Древнем Вавилоне: x2 + y2 = z2 Оно было решено пифагорейцами. 407
Второе уравнение: x2 – ay2 = 1 решил в целых числах Евклид. О способах решения диофантовых уравнений можно узнать в книге Башмаковой, а также в Справочном пособии к решению задач: Диофантовы уравнения Дмитрия Базылева (Рис. 4). Рис. 4. Справочник по диофантовым уравнениям Самая известная занимательная задача, связанная с диофантовыми уравнениями, это, безусловно, про кроликов и фазанов. Проект На ферме Исходный код программы находится в файле На ферме.py. Функции без параметров Вложенные циклы for Условный оператор if Оператор continue Задача 12 (13) из книги Удивительный мир чисел [КА86], страница 53: 408
На ферме выращивают кроликов и фазанов. В настоящее время их столько, что у всех вместе 740 голов и 1980 ног. Сколько же в настоящее время находится на ферме кроликов и фазанов? Поскольку и число голов, и число ног у кроликов и фазанов выражается целыми числами, то достаточно перебрать все варианты распределения 740 голов по кроликам и фазанам: # ГЛАВНАЯ ФУНКЦИЯ def main(): print() print('На ферме') print() solve() print() main() # -*- coding: Windows-1251 -*# Кордемский, с.53, Задача 12 # РЕШАЕМ ЗАДАЧУ 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: if (kroliki*4 + fazany*2 != NOGI): continue print('Кроликов = %i \nФазанов = %i' % (kroliki, fazany)) print() 409
И вмиг кролики и фазаны пересчитаны и занесены в Консольное окно нашей программы (Рис. 1). Рис. 1. Пересчитали поголовье и поножье Проект Кролики и фазаны Исходный код программы находится в файле Кролики и фазаны.py. Эту задачу мы уже много раз решали в других книгах, но простым перебором значении в циклах. Дело это нехитрое, и вы наверняка его уже хорошо освоили. В этом проекте мы решим эту задачу с помощью математическои библиотеки для символьных вычислении SymPy. Если вычисления небольшие и не требуют написания программы, то можно посчитать прямо в браузере: https://live.sympy.org/ Но мы будем писать программы, поэтому установите библиотеку на свои компьютер: from sympy import * Для решения нашеи задачи нам нужна функция linsolve из модуля sympy.solvers.solveset: from sympy.solvers.solveset import linsolve Она находит решения системы линейных уравнений. Давайте решим другую задачу про кроликов и фазанов: У фазанов и кроликов 62 ноги и 19 голов. Сколько фазанов и кроликов? 410
Импортируем функцию linsolve: from sympy import * from sympy.solvers.solveset import linsolve В функции main вызываем функцию solve для непосредственного решения задачи: # ГЛАВНАЯ ФУЕКЦИЯ def main(): print("Решаем задачу 'Кролики и фазаны'") solve() main() Обозначаем константами число ног и голов: def solve(): # общее число голов: HEADS = 19 # общее число ног: LEGS = 62 Это необязательно, но с ними программа понятнее. Переменные в библиотеке SymPy вводятся с помощью функции symbols: rabbits, pheasants = symbols('rabbits, pheasants') В данном случае переменные – это неизвестные, которые мы должны наити. В математике неизвестные принято обозначать последними буквами латинского алфавита: x, y, z = symbols('x, y, z') В программировании идентификаторы более информативные, поэтому я использовал более говорящие названия: rabbits — кролики и pheasants — фазаны. 411
Кстати говоря, можно поити дальше и все константы-переменные назвать по-русски: # общее число голов: ГОЛОВ = 19 # общее число ног: НОГ = 62 кролики, фазаны = symbols('кролики, фазаны') res = linsolve([кролики + фазаны - ГОЛОВ, 4 * кролики + 2 * фазаны - НОГ], (кролики, фазаны)) кролики, фазаны = next(iter(res)) print("Кроликов:", кролики) print("Фазанов: ", фазаны) Решение задачи от этого не пострадает. Вот теперь можно решать задачу! Для этого функции linsolve передаем список уравнении и переменных: res = linsolve([rabbits + pheasants - HEADS, 4 * rabbits + 2 * pheasants - LEGS], (rabbits, pheasants)) print(res) С переменными все понятно, а вот уравнения еще нужно составить, внимательно прочитав условие задачи, и вспомнить урок биологии, на котором вы узнали, что у кроликов 4 ноги (или в общем случае конечности), а у фазанов 2. Согласно математическим правилам, мы составляем систему уравнении: rabbits + pheasants = HEADS 4 * rabbits + 2 * pheasants = LEGS Но в функцию linsolve нужно передавать не уравнения, а только их левую часть. При этом правая часть должна быть равна нулю. То есть уравнения нужно привести к такому виду, что совсем несложно: rabbits + pheasants – HEADS = 0 4 * rabbits + 2 * pheasants – LEGS = 0 Поскольку все члены уравнения находятся в левои части, а правая часть всегда равна нулю, то ее не указывают. 412
Функция linsolve решает уравнение и возвращает в переменную res конечное множество корнеи (Рис. 1): Рис. 1 Ответ верныи, но слишком математическии. Даваите напечатаем ответ в более понятном виде: rabbits, pheasants = next(iter(res)) print("Кроликов:", rabbits) print("Фазанов: ", pheasants) Тогда ответ на экране будет ясен и понятен каждому любителю живои природы и диофантовых уравнении (Рис. 2). Рис. 2 Чтобы решить задачу из предыдущего проекта, достаточно изменить значения констант (Рис. 3): # общее число голов: HEADS = 740#19 # общее число ног: LEGS = 1980#62 Рис. 1 На сайте http://festival.1september.ru/articles/569698/ вы найдёте такую разновидность этой китайской задачи с подробным разбором её решения: 413
Сегодня на уроке мы вновь встретимся с вами с хорошо известной вам задачей про фазанов и кроликов (задача выводится на доску “В клетке находятся фазаны и кролики. Известно, что у них 35 голов и 94 ноги. Узнайте число фазанов и число кроликов”), но если раньше мы её решали арифметическим способом, то сегодня будем её решать с помощью уравнений и даже системы уравнений. Решите её самостоятельно! Проект Решите систему уравнений Исходный код программы находится в файле Решите систему уравнений.py. Функция без параметров Вложенные циклы for Условный оператор if Оператор continue Оператор return Задача 7 из книги Удивительный мир чисел [КА86], страница 100: Решите систему уравнений: Далеко неочевидно, но значения переменных x и y должны быть целыми, иначе верхнее равенство вряд ли выполнится. Его лучше записать в другом виде: x + y = 2x 414
Теперь можно сделать замену во втором уравнении: 2х * 6x = 248832 → 12х = 248832 После преобразований задача решается в одном коротком цикле: # -*- coding: Windows-1251 -*# Кордемский, с.100, Задача 7 # РЕШАЕМ ЗАДАЧУ def solve(): for x in range(10): if 12 ** x == 248832: print(f'x = {x} y = {2 ** x - x}') break # ГЛАВНАЯ ФУНКЦИЯ def main(): print() print('Решите систему уравнений') print() solve() main() На Рис. 1 вы видите ответ на задачу. Значения переменных оказались совсем небольшими! Рис. 1. Системная задача А вот SymPy не умеет решать такие задачи… Проект Сооружение для лаборатории Исходный код программы находится в файле Сооружение для лаборатории.py. 415
Функция без параметров Вложенные циклы for Условный оператор if Оператор continue Оператор return Задача 7 из книги Удивительный мир чисел [КА86], страница 64: Для одной лаборатории изготовили конструкцию из двух спаянных пустотелых параллелепипедов (см. рисунок справа). Длины всех рёбер (в дециметрах) - целые числа. Основания параллелепипедов — квадраты. Высота первого параллелепипеда равна стороне основания второго, а высота второго равна стороне основания первого параллелепипеда. Поместится ли эта конструкция, объём которой 3900 дм3, в лаборатории, если расстояние от пола лаборатории до потолка равно 2 м 75 см? # ГЛАВНАЯ ФУНКЦИЯ def main(): print("Сооружение для лаборатории") print() nVar = solve() print("Найдены все варианты решения -", nVar) print() if __name__ == "__main__": main() Поскольку x и y – целые числа, то мы можем просто перебрать все их возможные комбинации. Например, можно сразу заключить, что оба эти числа больше нуля. 416
Из уравнения x2*y + x*y2 = 3900 (1) следует, что ни одно из этих чисел не больше корня квадратного из 3900. Точное значение корня нам не нужно, пусть это будет 70. Осталось в двух вложенных циклах for составить все пары чисел x и y и для каждой пары проверить выполнение условия (1): # -*- coding: Windows-1251 -*# Кордемский, с.64, Задача 7 # РЕШАЕМ ЗАДАЧУ 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 Как показывает Рис. 1, либо x = 12, y = 13, либо y = 12, x = 13, но в обоих случаях высота конструкции составляет 12 + 13 = 25 дециметров = 2,5 417
метра, что меньше высоты лаборатории. Значит, конструкция в ней поместится. Рис. 1. Конструкторская задача Проект И такие есть числа 3 Исходный код программы находится в файле И такие есть числа 3.py. Функция без параметров Вложенные циклы for Условный оператор if Оператор continue Функция с параметром Оператор return Оператор целочисленного деления % Оператор and Задача 4-3 из книги Удивительный мир чисел [КА86], страница 63: Два простых числа обладают свойством: если от каждого из них вычесть половину другого, то одна разность будет в 5 раз больше другой. Найдите эти числа. 418
# ГЛАВНАЯ ФУНКЦИЯ def main(): print('И такие есть числа 3') print() solve() print() if __name__ == "__main__": main() Пусть первое число n1 меньше второго числа n2. Тогда n2 как минимум на 1 больше n1, но не более, чем в 2 раза. Первое число мы ищем в бесконечном цикле while, начиная с двойки первого простого числа. Второе число мы ищем в цикле for, начиная со следующего числа и заканчивая числом, вдвое большим, чем первое: # -*- coding: Windows-1251 -*# Кордемский, с.63, Задача 4-3 import math # РЕШАЕМ ЗАДАЧУ def solve(): n1 = 2 - 1 while True: n1 += 1 if (not isPrime(n1)): continue for n2 in range(n1 + 2, 2 * n1 + 1): Для каждой пары чисел проверяем условие задачи: for n2 in range(n1 + 2, 2 * n1 + 1): if (2 * n2 - n1 != 5 * (2 * n1 - n2)): continue Чтобы избежать дробных чисел, мы оба числа умножили на двойку. Как только оно выполнится, мы печатаем решение на экране и заканчиваем поиски: 419
s = "Первое число = " + str(n1) print(s) s = "Второе число = " + str(n2) print(s) return Обратите внимание, что для решения этой задачи нам даже не потребовалось проверять числа n1 и n2 на простоту (Рис. 1). Рис. 1. Парочисловая задача решена Но если вы закомментируете строку: #return то найдёте ещё множество пар чисел, которые удовлетворяют условиям задачи: если от каждого из них вычесть половину другого, то одна разность будет в 5 раз больше другой. Но при этом не являются простыми (Рис. 2). Корректируем функцию 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) 420
s = "Второе число = " + str(n2) print(s) return Рис. 2. Непростое решение простой задачи Теперь каждое число 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 теперь «правильная». 421
Проект И такие есть числа 4 Исходный код программы находится в файле И такие есть числа 4.py. Функция без параметров Вложенные циклы for Условный оператор if Оператор continue Оператор or Задача 4-4 из книги Удивительный мир чисел [КА86], страница 63: Сумма первого числа и квадрата второго равна 57, а сумма второго числа и квадрата первого равна 71. Найдите эти числа. Пусть первое число – это n1, а второе - n2. Тогда мы быстро получаем систему нелинейных уравнений: n1 + n2 * n2 – 57 n1 * n1 + n2 - 71 Импортируем функцию nonlinsolve, которая решает систему нелинейных уравнений: # -*- coding: Windows-1251 -*# Кордемский, с.63, Задача 4-3 from sympy import * from sympy.solvers.solveset import nonlinsolve Передаём в функцию nonlinsolve 2 уравнения и неизвестные: # РЕШАЕМ ЗАДАЧУ def solve(): n1, n2 = symbols('n1, n2') res = nonlinsolve([n1 + n2 * n2 - 57, n1 * n1 + n2 - 71], n1, n2) n1, n2 = next(iter(res)) 422
print("n1:", n1) print("n2:", n2) # ГЛАВНАЯ ФУНКЦИЯ def main(): print() print('И такие есть числа 4') print() solve() print() main() Запускаем программу и получаем ответ (Рис. 1). Рис. 1. И такие числа мы нашли Проект Ящики Исходный код программы находится в файле Ящики.py. Функция без параметров Вложенные циклы for Условный оператор if Оператор continue Форматированный вывод Задача 424 из книги Математическая шкатулка [Нагибин88], страница 81: Завод должен переслать заказчику 1100 деталей, которые для пересылки упаковываются в ящики трёх типов. Один ящик первого типа вмещает 70 деталей, второго типа – 40 деталей, третьего типа – 25 423
деталей. Стоимость пересылки одного ящика первого, второго и третьего типов равна соответственно 20, 10 и 7 р. Какие ящики должен использовать завод, чтобы стоимость пересылки бала наименьшей? Недогрузка ящиков не допускается. Эта типичная задача линейного (точнее - целочисленного) программирования может быть успешно решена простым перебором вариантов. Примеры задач целочисленного программирования: • • • • задача о назначениях задача коммивояжера задача почтальона задача о максимальном паросочетании # ГЛАВНАЯ ФУНКЦИЯ def main(): print('Ящики') print() solve() print() if __name__ == "__main__": main() Обозначим через k1, k2 и k3 число ящиков каждого типа. Минимальное число этих ящиков равно 0. Верхнюю границу легко определить, поделив общее число деталей на вместимость каждого ящика: # -*- coding: Windows-1251 -*# Нагибин, с.81, Задача 424 # РЕШАЕМ ЗАДАЧУ def solve(): DETALI = 1100 K1 = DETALI // 70 K2 = DETALI // 40 K3 = DETALI // 25 424
Для хранения текущего значения минимальной стоимости пересылки мы заведём переменную 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(f"k1 = {k1} k2 = {k2} k3 = {k3}") print("Минимальная стоимость = %i" % minCost) print() Если разные расфасовки дадут одинаковую стоимость, мы напечатаем все, чтобы узнать общее число решений задачи. Поскольку результаты нашей упаковочной деятельности не ухудшаются по ходу работы программы, то минимальная стоимость окажется в самом низу списка, который вы видите на Рис. 1. Итак, решение задачи единственное: Детали нужно упаковать в 25 ящиков второго типа и в 4 ящика третьего типа. При этом минимальная стоимость пересылки составит 278 рублей. 425
Рис. 1. Экономная упаковка Проект Путёвки Исходный код программы находится в файле Путёвки.py. Функция без параметров Вложенные циклы for Условный оператор if Оператор continue Форматированный вывод Задача 425 из книги Математическая шкатулка [Нагибин88], страница 81: Предполагается использовать 2000 р. на путёвки в дома отдыха. Путёвки имеются на 15, 27 и 45 дней, стоимость их соответственно 21, 40 и 60 р. Сколько и каких путёвок нужно купить, чтобы общее число дней отдыха было наибольшим? Эту задачу можно решить за пару минут, просто слегка изменив предыдущий проект: # -*- coding: Windows-1251 -*# Нагибин, с.81, Задача 425 426
# РЕШАЕМ ЗАДАЧУ def solve(): SUMMA = 2000 K1 = SUMMA // K2 = SUMMA // K3 = SUMMA // # макс. число maxDays = 0 21 40 60 дней: for k1 in range(0, K1 + 1): for k2 in range(0, K2 + 1): for k3 in range(0, K3 + 1): if (k1 * 21 + k2 * 40 + k3 * 60 != SUMMA): continue # число дней отдыха: days = k1 * 15 + k2 * 27 + k3 * 45 if (maxDays <= days): #if (maxDays <= days and k1 * k2 * k3 != 0): maxDays = days print("k1 = %i k2 = %i k3 = %i" % (k1, k2, k3)) print("Максимальное число дней =", maxDays) print() # ГЛАВНАЯ ФУНКЦИЯ def main(): print('Путёвки') print() solve() print() if __name__ == "__main__": main() Замечательно, что, решив одну задачу, потом можно использовать её код как заготовку при решении множества других подобных задач! Наша программа советует не покупать путёвки на 15 дней, а сэкономленные деньги потратить на приобретение двух 27- и тридцати двух 45дневных путёвок. Они позволят трудящимся заслуженно отдыхать 1494 дня (Рис. 1). Рис. 1. Отдыхай с умом! 427
В книге приведён другой ответ. Вероятно, автор не учёл, что можно вообще не покупать путёвки какого-либо вида. Чтобы проверить эту догадку, закомментированную строку замените другой: # if (maxDays <= days): if (maxDays <= days and k1 * k2 * k3 != 0): Теперь нулевые значения числа путёвок будут проигнорированы, и программа выдаст «книжные» результаты (Рис. 2). Рис. 2. Наши люди отдыхают дольше! У автора задачи получилось на 15 дней меньше, чем у нас. Да, без компьютера толком не отдохнёшь! Проект На базаре Исходный код программы находится в файле На базаре.py. Функция без параметров Вложенные циклы for Условный оператор if Оператор continue Форматированный вывод Управляющие последовательности Задача 7 (2.3) из книги Удивительный мир чисел [КА86], страница 51: На базаре за 9 кг орехов и 2 кг фейхоа заплатили столько же денег, сколько за 6 кг гранатов. А за 6 кг орехов, 5 кг фейхоа и 4 кг гранатов заплатили 43 р. 428
Сколько стоит 1 кг орехов, фейхоа и гранатов отдельно, если известно, что стоимость каждого продукта выражается целым числом рублей? # ГЛАВНАЯ ФУНКЦИЯ def main(): print('На базаре') print() solve() print() if __name__ == "__main__": main() Эту базарную задачу легко решить по аналогии с упаковочной - достаточно учесть её фруктовый контекст: # -*- coding: Windows-1251 -*# Кордемский, с.51, Задача 7 # РЕШАЕМ ЗАДАЧУ 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): if (orehi * 6 + feihoa * 5 + granat * 4 != COST): continue if (orehi * 9 + feihoa * 2 != granat * 6): continue print(f"Орехи = {orehi} \nФейхоа = {feihoa} \nГранаты = {granat}") print() Теперь вы можете смело отправляться на рынок 1986 года – вас никто не обманет (Рис. 1)! Рис. 1. Деньги любят счёт в банке 429
Проект Дедушка и внучка Исходный код программы находится в файле Дедушка и внучка.py. Функция без параметров Цикл while Форматированный вывод Оператор деления // Задача 10 (10.1) из книги Удивительный мир чисел [КА86], страница 52: Сколько дедушке лет, столько месяцев внучке. Дедушке с внучкой вместе 91 год. Сколько лет дедушке и сколько внучке? Самое сложное в этой задаче – составить простое уравнение: x + 12 * x = 91 * 12 Функция solveset легко справляется с такими уравнениями. Ей нужно передать обе части уравнения и имя переменной: # -*- coding: Windows-1251 -*# Кордемский, с.52, Задача 10 from sympy import * # РЕШАЕМ ЗАДАЧУ def solve(): x = symbols('x') res = solveset(Eq(x + 12 * x, 91 * 12), x) В данном случае неизвестное – это первое число в множестве res. Извлекаем его и получаем возраст дедушки в чистом виде. Возраст внучки вычисляем по условию задачи (Рис. 1): x = next(iter(res)) 430
print('Возраст дедушки в годах =', x) print('Возраст внучки в годах =', 91 - x) print() # ГЛАВНАЯ ФУНКЦИЯ def main(): print() print('Дедушка и внучка') print() solve() print() main() Рис. 1. Внучка совсем ещё зелёная Проект Хитрое уравнение Исходный код программы находится в файле Хитрое уравнение.py. В этом проекте мы решим ютубовское уравнение: Дайте этот пример своему учителю! Самое хитрое уравнение на ютубе (Рис. 1) https://www.youtube.com/watch?v=W_1VUCc8b2M Рис. 1 431
Задача очень интересная и связана с числами Фибоначчи. Вот финал решения этой задачи (Рис. 2). Рис. 2 Как видите, хитрое уравнение имеет 2 иррациональных корня. Мы решим хитрое уравнение по-хитрому! В функцию solveset передаём уравнение без правой части, которая должна быть нулевой, а также множество, на котором нужно искать решения. В данном случае это рациональные числа: # -*- coding: Windows-1251 -*from sympy import * # РЕШАЕМ ЗАДАЧУ def solve(): x = symbols('x') res = solveset(89 * x ** 12 - 144 * x ** 11 - 1, x, domain=S.Reals) print(res) print() # ГЛАВНАЯ ФУНКЦИЯ def main(): print() print('Хитрое уравнение') print() solve() print() 432
main() Запускаем программу и получаем те же самые 2 корня хитрого уравнения (Рис. 3). Рис. 3 Проект Сколько у мамы дочерей и сыновей? Исходный код программы находится в файле Сколько у мамы дочерей и сыновей.py. Функция без параметров Вложенные циклы for Условный оператор if Оператор continue Оператор деления // Форматированный вывод Управляющие последовательности Это и есть фейхоа! Задача 15 (4) из книги Удивительный мир чисел [КА86], страница 55: В семье 12 детей. Мама принесла для них 70 штук фейхоа. Половину всех фейхоа она раздала дочерям поровну, остальные отдала сыновьям, которые разделили их между собой также поровну. Каждый мальчик получил на 2 фейхоа больше, чем каждая девочка. Сколько было у этой мамы дочерей и сыновей? Оne day англичанин и шотландец нашли клад и решили его поделить. - Давай поделим его по-честному, - предложил англичанин. - Нет, давай лучше поровну! – возразил шотландец. 433
С точки зрения математики, мальчики и девочки ничем не отличаются от кроликов и фазанов, поэтому эту задачу мы решаем так же, как и про животных на ферме: # ГЛАВНАЯ ФУНКЦИЯ def main(): print('Сколько у мамы дочерей и сыновей?') print() solve() print() if __name__ == "__main__": main() # -*- coding: Windows-1251 -*# Кордемский, с.55, Задача 15 # РЕШАЕМ ЗАДАЧУ def solve(): # число фейхоа: 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 штук. Всё остальное – дело техники, то есть компьютера. На Рис. 1 представлены плоды раздела плодов и полов в семье плодовитой мамы. 434
Рис. 1. Честный делёж Проект Из жизни Дефурнеля Исходный код программы находится в файле Из жизни Дефурнеля.py. Функция без параметров Цикл while Комбинированные операторы присваивания Оператор and Задача 24 (26) из книги Удивительный мир чисел [КА86], страница 57: Некогда в отрывном календаре были приведены любопытные факты из биографии француза Пьера Дефурнеля. Он был отцом трёх сыновей, родившихся в разных веках. Он сам и старший сын родились в XVII веке. Женился он на девятнадцатом году жизни. Через год стал отцом первого сына. Спустя 37 лет женился второй раз. Через год стал отцом второго сына. А ещё через 43 года родилась его будущая третья жена. Через 19 лет после этого они поженились. Через год родился третий сын. Спустя 8 лет Пьер Дефурнель умер. Установите годы рождения Пьера Дефурнеля, сыновей, третьей жены и годы, когда женился Дефурнель и когда он умер? # ГЛАВНАЯ ФУНКЦИЯ def main(): print('Из жизни Дефурнеля') print() solve() print() 435
if __name__ == "__main__": main() Поскольку XVII век начинается с 1601 года, то мы можем в цикле while начать перебор с предыдущего года и добавлять по 1 к текущему числу year – году рождения Дефурнеля: # -*- coding: Windows-1251 -*# Кордемский, с.57, Задача 24 # РЕШАЕМ ЗАДАЧУ def solve(): # год рождения Дефурнеля: year = 1600 # годы рождения сыновей: 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("Год print("Год print("Год print("Год print("Год рождения рождения рождения рождения рождения Дефурнеля:", year) первого сына:", son1) второго сына:", son2) третьего сына:", son3) третьей жены:", (son2 + 43)) 436
print("Год первой женитьбы:", (son1 - 1)) print("Год второй женитьбы:", (son2 - 1)) print("Год третьей женитьбы:", (son3 - 1)) print("Год смерти Дефурнеля:", (son3 + 8)) print("Дефурнель прожил:", (son3 + 8 - year)) print() Она представлена в полном объёме на Рис. 1. Рис. 1. Компьютерный ЗАГС в действии Проект Счётные палочки Исходный код программы находится в файле Счётные палочки.py. Функция без параметров Вложенные циклы for Условный оператор if Оператор continue Форматированный вывод Управляющие последовательности Задача 594 из книги Математическая шкатулка [Нагибин88], страница 97: 437
Из 36 счётных палочек построили треугольники, квадраты и домики (Рис. 1) – всего 10 фигур. Рис. 1. Палочные фигуры Найдите число фигур каждого вида. # ГЛАВНАЯ ФУЕКЦИЯ def main(): print('Счётные палочки') print() solve() print() if __name__ == "__main__": main() Ясно, что из 36 палочек можно построить от 0 до 36//3 треугольников, от 0 до 36//4 квадратов и от 0 до 36//6 домиков. Все варианты строительства мы, как обычно, перебираем во вложенных циклах for: # -*- coding: Windows-1251 -*# Нагибин, с.97, Задача 594 # РЕШАЕМ ЗАДАЧУ 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 438
if (t * 3 + k * 4 + d * 6 != PALOCHKI): continue print(f"Треугольники = {t} \nКвадраты = {k} \nДомики = {d}") print() Рис. 2 уверенно демонстрирует нам 3 решения задачи, хотя в книге указано только второе. Рис. 2. Строительство закончено Мы опять можем предположить, что следовало построить все фигуры хотя бы по одному разу, хотя в книге это требование отсутствует. Задания для самостоятельного решения Задача 11, страница 52 Удивительный мир чисел Овчарка погналась за лисой, когда между ними было расстояние 99 м. Скачок лисы 1,1 м, скачок овчарки 2,2 м. Когда овчарка делает 19 скачков, лиса делает 29 скачков. Сколько метров проскачут они, пока овчарка догонит лису? 439
Глава #10. Интересные числа Вы уже знаете, что числа бывают разные: • • • • • • • • • • • • • • • • • натуральные, то есть целые положительные однозначные и многозначные целые отрицательные нуль – особенное число простые дроби вещественные, десятичные дроби иррациональные числа трансцендентные числа комплексные числа чётные и нечётные числа простые и составные числа суперпростые числа числа Фибоначчи факториалы двойные факториалы совершенные числа дружественные числа И это ещё не всё! В этой главе вы познакомитесь и с другими интересными числами. Проект Римские числа Исходный код программы находится в файле Римские числа.py. В математике римские числа давно не используются, потому что вычисления с ними очень неудобны и сложны. Но и сейчас их можно найти в книгах для обозначения глав. Римскими числами считают столетия, царей, съезды и других исторических событий (Рис. 1). Как вы видите, одна римская буква может заменить многозначное число. Кратность одинаковых букв-цифр не превышает трёх, поэтому из них можно составить только числа, не превышающие 3999. Для больших чисел использовали другие символы, которых нет на клавиатуре. 440
Рис. 1 Для запоминания последовательности римских цифр (в порядке убывания) придумали такое предложение (Рис. 2). Рис. 2 Некоторые числа обозначались двумя буквами, причём так, что первая буква-цифра вычиталась из второй: 4 9 40 90 400 900 → → → → → → IV IX XL XC CD CM Все эти числа – в римском и арабском варианте – мы поместим в списки: # ПРОГРАММА ДЛЯ ПЕРЕВОДА АРАБСКИХ # ЧИСЕЛ В РИМСКИЕ ROME = ['M', 'CM', 'D', 'CD', 'C', 'XC', 'L', 'XL', 'X', 'IX', 'V', 'IV', 'I'] ARABIC = [1000, 900, 500, 400, 100, 90, 50, 40, 10, 9, 5, 4, 1] 441
В бесконечном цикле while вводим арабское число и сохраняем его в переменной number: def main(): while True: while True: number = int(input('Введите арабское число 1..3999: ')) Затем проверяем арабское число, потому что мы сможем перевести в римское только числа в заданном диапазоне: # eсли задан нуль, # то работу с программой заканчиваем: if number == 0: return print("Aрабское число =", number) if number >= 1 and number <= 3999: break else: print("Повторите ввод!") continue Арабское число мы должны перевести в римское. Но римские числа записываются буквами, поэтому объявим строковую переменную для хранения римского числа. Пока в этой строке нет ни одного символа: def rome(number): # Формируем строку с римским числом, # равным заданному числу number: n = 0 sNumber = '' При объявлении переменной нужно присвоить начальное значение. В данном случае пустую строку. Если же вы не хотите инициализировать переменную, то поставьте после знака равенства многоточие: sNumber = ... Так вы только объявляете переменную, но она не имеет ни типа, ни значения. Дальше вы должны присвоить ей какое-нибудь значение. Из чисел в римском списке мы соберём любое допустимое арабское число. Вот как это делается. 442
Вся премудрость конвертирования чисел таится в двух циклах while. Мы последовательно сравниваем заданное число number с теми числами, которые хранятся в списке ARABIC, начиная с тысяч. Внутренний цикл while как раз и нужен для того, чтобы определять, сколько в number имеется тысяч, сотен, десятков и единиц (все остальные римские числа - 900, 500, 400, 90, 50, 9, 5, 4 - могут быть только в единственном числе). Если текущее значение number не меньше этих чисел, то из него число вычитаем, а в строку добавляем буквы, соответствующие этому числу в римской записи. Как только от числа ничего не останется (это контролирует внешний цикл while), преобразование числа заканчивается, и мы смело можем печатать на экране перевод римского числа на современный европейский математический язык: def rome(number): # Формируем строку с римским числом, # равным заданному числу number: n = 0 sNumber = '' # sNumber = ... #print(sNumber) while number > 0: while ARABIC[n] <= number: sNumber += ROME[n] number -= ARABIC[n] n += 1 return sNumber def main(): while True: while True: number = int(input('Введите арабское число 1..3999: ')) # eсли задан нуль, # то работу с программой заканчиваем: if number == 0: return print("Aрабское число =", number) if number >= 1 and number <= 3999: break else: print("Повторите ввод!") continue print('Римское число = ', rome(number)) print() main() 443
Запускаем программу и вводим числа. Наш числовой переводчик работает без акцента (Рис. 3). Рис. 3 Сделаем дополнительную проверку и зададим числа 9999 и -1. Программа просто игнорирует неверный ввод (Рис. 4). Рис. 4 Наконец вводим нуль – и работа с программой заканчивается. Проект Примориалы Исходный код программы находится в файле Примориалы.py. Примориал числа n обозначается n# и равен произведению всех простых чисел, не превышающих n. Генератор простых чисел мы уже написали: 444
# ИЩЕМ ПРОСТЫЕ ЧИСЛА def primes(): # список для хранения простых чисел # первое простое число - двойка: prime = [2] yield 2 # всего простых чисел: nPrimes = 1 num = 3 while True: # проверяем, делится число num # простые числа из списка prime: flg = True for i in prime: # поиск закончен: if (i > math.sqrt(num)): break # если число num делится, # значит, число составное: if (num % i == 0): flg = False break # если нашли простое число, if (flg): # подсчитываем: nPrimes += 1 # добавляем новое простое число в список: prime.append(num) yield num num += 2 print() Функция primorial получает число n, а возвращает его примориал pf: def primorial(n): num = 0 pf = 1 for i in primes(): if i > n: break pf *= i return pf В функции main вводим число, примориал, которого хотим узнать: 445
# ГЛАВНАЯ ФУНКЦИЯ def main(): print('Примориалы') print() # бесконечный цикл ввода данных # пока пользователь не закроет программу # или не введет 0: while True: s = "Введите натуральное число) > " print(s, end = '') number = int(input()) # если пользователь отрицательное число, # то программу закрываем: if (number < 0): return p = primorial(number) print(f"Примориал числа {number} =", p) print() main() А вот и примориалы (Рис. 1)! Рис. 1 446
Проект Суперфакториалы Исходный код программы находится в файле Суперфакториалы.py. Суперфакториал числа n равен произведению первых n факториалов: sf(n)=1! * 2! * 3! * 4! * … * n! Последовательность обычных факториалов выдаёт функция facts: # ПОСЛЕДОВАТЕЛЬНОСТЬ ФАКТОРИАЛОВ def facts(n = -1): num = 0 a = 1 yield a while True: num += 1 if num > n: break a *= num yield a В функции superfactorial мы вычисляем и возвращаем произведение n первых факториалов, то есть суперфакториал заданного числа n: # СУПЕРФАКТОРИАЛ def superfactorial(n): sf = 1 for f in facts(n): sf *= f return sf В функции main вводим число, суперфакториал которого хотим получить: # ГЛАВНАЯ ФУНКЦИЯ def main(): print('Суперфакториалы') print() # бесконечный цикл ввода данных # пока пользователь не закроет программу # или не введет 0: while True: 447
s = "Введите натуральное число > " print(s, end = '') number = int(input()) # если пользователь отрицательное число, # то программу закрываем: if (number < 0): return p = superfactorial(number) print(f"Суперфакториал числа {number} =", p) print() main() Суперфакториалы растут с огромной скоростью (Рис. 1)! Рис. 1 Проект Среднее арифметическое Исходный код программы находится в файле Среднее арифметическое.py. Мы уже давно научились вычислять НОД и НОК, пришла пора заняться ещё парой родственных чисел. Среднее арифметическое - это число, равное сумме всех чисел набора, делённой на их количество. 448
Из определения ясно следует, что нам нужен набор чисел. Их можно ввести вручную, но это долго, поэтому мы просто заполним список случайными числами, количество которых можно выбрать по своему желанию: from random import randint # ГЛАВНАЯ ФУНКЦИЯ def main(): print() print('Среднее арифметическое') print() # бесконечный цикл ввода данных # пока пользователь не закроет программу # или не введёт 0: while True: s = 'Введите количество чисел > ' print(s, end = '') num = int(input()) # если пользователь ввёл 0, то программу закрываем: if (num == 0): return # список чисел: nabor = [] for i in range(num): # случайное число: n = randint(0, 20) - 10 nabor.append(n) print("Список чисел:", nabor) Сумму элементов списка мы получим от функции sum и разделим её на число элементов (Рис. 1): # сумма чисел: sumn = sum(nabor) print("Сумма чисел =", sumn) # вычисляем среднее арифметическое: sa = sumn / num print("Среднее арифметическое =", sa) main() Среднее арифметическое часто используется в статистике. Вы можете импортировать функцию mean из модуля statistics: 449
from statistics import mean Рис. 1 И без вычислений получить среднее арифметическое набора чисел (Рис. 2): print("Среднее арифметическое =", mean(nabor)) print() Рис. 2 Проект Среднее геометрическое Исходный код программы находится в файле Среднее геометрическое.py. Среднее геометрическое набора из n положительных чисел - это корень n-ной степени из произведения чисел этого набора. Среднее геометрическое двух чисел называется средним пропорциональным. 450
Извлечь корень — не проблема. Сложнее найти произведение чисел. Это можно сделать в цикле, но лучше применить функцию reduce из модуля functool: # произведение чисел: mul = reduce(lambda x, y: x*y, nabor) А вот и среднее геометрическое (Рис. 1)! Рис. 1 Среднее геометрическое требуется нечасто, но и функция в запасе тоже не повредит (Рис. 2): import operator # СРЕДНЕЕ ГЕОМЕТРИЧЕСКОЕ def geomean(nabor): power = 1 / len(nabor) return reduce(operator.mul, nabor) ** power print("Среднее геометрическое =", geomean(nabor)) Рис. 2 451
Проект Совершенные числа 2 Исходный код программы находится в файле Совершенные числа 2.py. Продолжаем знакомство с совершенными числами, начатое в Главе Признаки делимости. Евклид заметил, что: 6 28 496 8128 3 7 31 127 * * * * 2 2 * 2 2 * 2 * 2 * 2 2 * 2 * 2 * 2 * 2 * 2 ( 4 ( 8 ( 32 (128 - 1) 1) 1) 1) * 4 * 8 * 32 * 128 / / / / 2 2 2 2 Первые сомножители – это простые числа 3, 7, 31, 127, которые на 1 меньше степеней двойки 22, 23, 25 и 27. В книге IX Начал, Евклид доказал, что число (2p - 1) * 2p – 1 – совершенное, если 2p – 1 – простое число. Гораздо позже Эйлер подтвердил, что все совершенные числа можно представить в виде такого произведения. Поиск совершенных чисел по формуле Евклида гораздо эффективнее метода грубой силы и поможет нам найти 8 совершенных чисел (Рис. 1). Рис. 1 А дальше идут такие страшные числа, что даже Питон их боится. В функции main задаём верхнюю границу для поиска совершенных чисел, исходя из таблицы: 452
# ГЛАВНАЯ ФУНКЦИЯ def main(): print() print('Совершенные числа 2') print() # бесконечный цикл ввода данных # пока пользователь не закроет программу # или не введёт 0: while True: s = 'Введите верхнюю границу > ' print(s, end = '') num = int(input()) # если пользователь ввёл 0, то программу закрываем: if (num == 0): return Теперь мы получаем простые числа р от функции-генератора primes и находим степень 2р: # получаем простые числа: for p in primes(): p2 = 2 ** p По правилу Евклида, число 2p – 1 должно быть простым, что мы и проверяем: # число для проверки на совершенство: perfect = (p2 - 1) * (p2 // 2) # если число 2 ** p - 1 простое, # то число perfect совершенное: if is_prime(p2 - 1): print('Число', perfect, 'совершенное') Если это так, то мы печатаем совершенное число в Консольном окне. Рано или поздно мы переберём все числа в заданном диапазоне, и на этом наши поиски закончатся: # перешли верхнюю границу # поиски заканчиваем: else: if perfect > num: 453
break print() Функция is_prime также получает простые числа от генератора primes и делит на них заданное число n. Если удастся разделить, то число n составное, в противном случае — простое: # ПРОВЕРЯЕМ ЗАДАННОЕ ЧИСЛО НА # ПРОСТОТУ def is_prime(n): for p in primes(): # составное число: if not n % p: return False # простое число: elif p * p > n: return True Функция primes не самая простая, но зато очень быстрая: # Совершенные числа 2 # ГЕНЕРАТОР ПРОСТЫХ ЧИСЕЛ def primes(): # первые простые числа: first_primes = [2, 3, 5, 7, 11, 13, 17, 19, 23, 29] yield from first_primes base = 30 * (first_primes[-1] // 30 + 1) new_primes = [] while True: for offs in (1, 7, 11, 13, 17, 19, 23, 29): # следующее число для проверки на простоту: k = base + offs for p in first_primes: # составное число: if not k % p: break # новое простое число: elif p * p > k: new_primes.append(k) break # нашли новые простые числа: if new_primes: yield from new_primes # добавляем их в список: first_primes.extend(new_primes) 454
new_primes = [] base += 30 О совершенных числах вы можете почитать в книге От абака до кубита (Рис. 2) на с. 76. Рис. 2 Проект Последовательность простых чисел 2 Исходный код программы находится в файле Последовательность простых чисел 2.py. Давайте используем новую функцию primes для генерирования последовательности простых чисел: # -*- coding: Windows-1251 -*# ГЕНЕРАТОР ПРОСТЫХ ЧИСЕЛ def primes(n = 0): # первые простые числа: first_primes = [2, 3, 5, 7, 11, 13, 17, 19, 23, 29] yield from first_primes # всего простых чисел: nPrimes = len(first_primes) if n > 0 and nPrimes > n: return 455
base = 30 * (first_primes[-1] // 30 + 1) new_primes = [] while True: for offs in (1, 7, 11, 13, 17, 19, 23, 29): # следующее число для проверки на простоту: k = base + offs for p in first_primes: # составное число: if not k % p: break # новое простое число: elif p * p > k: new_primes.append(k) break # нашли новые простые числа: if new_primes: yield from new_primes # добавляем их в список: first_primes.extend(new_primes) nPrimes += len(new_primes) if n > 0 and nPrimes > n: return new_primes = [] base += 30 # ГЛАВНАЯ ФУНКЦИЯ def main(): print('Простые числа') print() # бесконечный цикл ввода данных # пока пользователь не закроет программу # или не введет 0: while True: s = "Введите длину последовательности > " print(s, end = '') number = int(input()) # если пользователь ввёл 0, то программу закрываем: if (number < 1): return n = 0 for i in primes(number): n += 1 print(n, i) print() main() 456
Функция работает быстро, но обычно выдаёт больше простых чисел, чем требуется (Рис. 1). Рис. 1 Но мы легко создадим список из заданного числа первых простых чисел: lst lst n = for = list(primes(number)) = lst[:number] 0 i in lst: n += 1 print(n, i) print() Рис. 2 457
История о том, как Леонард Эйлер открыл числа Каталана Здесь был Эйлер! Надпись на граните науки Однажды Леонард Эйлер занимался триангуляцией правильных многоугольников, то есть попросту разбивал их на треугольные части, проводя непересекающиеся диагонали. Вопрос, на который ему предстояло ответить, был непрост: сколько всего имеется разных способов триангуляции выпуклых n-угольников? Ясно, что здесь n не меньше трёх, так как при n=1 мы имеем точку, а при n=2 – прямую. Эти геометрические фигуры на треугольники, конечно, не поделишь. Треугольник (n = 3) уже «разбит» на единственный треугольник, так что никаких диагоналей в нём проводить не нужно, да это и невозможно (Рис. 1). Рис. 1 Следующий правильный многоугольник – квадрат с четырьмя сторонами. Мы легко найдём 2 способа поделить его поровну (Рис. 2). Рис. 2 Далее по старшинству идёт пятиугольник. Его можно разделить на треугольники пятью способами (Рис. 3). 458
Рис. 3 А шестиугольник – четырнадцатью (Рис. 4). Рис. 4 По ходу анатомирования фигур Эйлер установил, что число непересекающихся диагоналей, которые можно провести в n-угольнике, всегда равно n-3, а число получающихся при этом треугольников – n-2. А вот общая формула для подсчёта вариантов разбиений далась Эйлеру нелегко. Поскольку Эйлер резал многоугольники, для которых n (число сторон) три и больше, то формула не учитывает меньшие значения n. В итоге своих экспериментов Эйлер получил вот такой ряд чисел: 1, 2, 5, 14, 42, 132, … Эйлер получил его, когда решал геометрическую комбинаторную задачу. А своим названием эта числовая последовательность обязана бельгийскому математику Эжену Шарлю Каталану (Рис. 5), который в 1838 году решал задачу о расстановке скобок в строках из n букв, где n >=2. Вы найдёте эту последовательность на сайте Н. Слоуна под номером 577. 459
Рис. 5. Eugène-Charles Catalan (1814-1894) Иногда последовательность Каталана начинают с двух единиц: 1, 1, 2, 5, 14, 42, 132, 429,… У этого варианта последовательности имеется одно любопытное свойство. Все нечётные числа, большие единицы, занимают в ней места, номера которых совпадают со степенью двойки. Так, пятёрка, первое после единицы нечётное число Каталана, расположилась на четвёртом месте (22), 429 – на восьмом (23). И так далее. Если строка состоит из n букв, то в ней можно расставить n-1 пару скобок. При этом каждая пара скобок должна заключать ровно 2 терма, под которыми подразумеваются: две буквы; буква и следующая за ней группа символов в скобках; две соседние группы символов в скобках. Число способов расстановки скобок в последовательности букв в точности совпадает с числом вариантов расстановки n-1 пары скобок по алгоритму Кнута. Действительно, две буквы можно заключить в скобки единственным способом: (ab) 460
Три буквы – двумя: ((ab)c) (a(bc)) Четыре буквы – пятью: ((ab)(cd)) (((ab)c)d) (a(b(cd))) (a((bc)d)) ((a(bc))d) Эта «тенденция» сохранится и дальше. Проект Числа Каталана Исходный код программы находится в файле Числа Каталана.py. Числа Каталана очень интересные, так почему бы нам не написать программу, которая выдаст последовательность таких чисел заданной длины? Вопрос риторический - приступаем! В главной функции традиционно задаём длину последовательности: # ГЛАВНАЯ ФУНКЦИЯ def main(): print() print('Числа Каталана') print() # бесконечный цикл ввода данных # пока пользователь не закроет программу # или не введёт 0: while True: s = 'Введите длину последовательности > ' print(s, end = '') len = int(input()) # получаем число Каталана: for i in range(len): ca = catalans(i) print(i, ca) print() 461
main() Тут важно не горячиться, потому что функция для вычисления чисел Каталана рекурсивная, а потому небыстрая: # -*- coding: Windows-1251 -*# Числа Каталана # РЕКУРСИВНАЯ ФУНКЦИЯ def catalans(n): b = 0 if n == 0: return 1 else: for i in range (n): b += catalans(i) * catalans(n-1-i) return b Я слегка погорячился и получил за приемлемое время только 18 (или 19, если считать математически) чисел (Рис. 1). Рис. 1 Для повседневных нужд и этого хватит, но генератор из рекурсивной функции не выходит. Это первое. Второе: чтобы получить единственное число, рекурсивная функция вполне уместна, а для последовательности не годится, потому что мы каждое число находим с самого начала, а не пользуемся уже готовыми числами. 462
Проект Числа Каталана 2 Исходный код программы находится в файле Числа Каталана 2.py. Для n-го числа Каталана существует формула с факториалами: Вычисления по ней выполняются гораздо быстрее, хотя тоже для каждого числа отдельно. В функции main задаём длину последовательности, но числа Каталана получаем от генератора: # ГЛАВНАЯ ФУНКЦИЯ def main(): print() print('Числа Каталана 2') print() # бесконечный цикл ввода данных # пока пользователь не закроет программу # или не введёт 0: while True: s = 'Введите длину последовательности > ' print(s, end = '') len = int(input()) # получаем число Каталана: n = 0 for ca in catalans(len): print(n, ca) n += 1 print() main() Факториалы мы умеем вычислять и сами, но в модуле math для этого имеется готовая функция, которой мы и воспользуемся, чтобы не загромождать код. Вычисления проводим по приведённой выше формуле: 463
from math import factorial def catalans(limit): c = 1 n = 0 while n < limit: yield int(c) n += 1 c = (factorial(2 * n)) / (factorial(n + 1) * factorial(n)) С новой функцией мы быстро найдём и 30 чисел Каталана (Рис. 1). Рис. 1 Проект Квадратные числа Исходный код программы находится в папке QuadratNumbers. 464
С квадратными числами мы уже встречались в проекте Ряд квадратов целых чисел. Их бесконечно много, поэтому мы ограничимся только первым десятком квадратных чисел – с номерами от 0 до 9. Поскольку мы хотим нарисовать квадратные числа, то программировать будем в среде Процессинг. Для выбора нужного числа пользователь должен нажать соответствующую цифровую клавишу. Ему это удобно, а нам легко обрабатывать событие нажатия на такую клавишу. # КВАДРАТНЫЕ ЧИСЛА # диаметр точки: d = 40 def setup(): size(400, 400) background(0) strokeWeight(d) def draw(): pass Цифровые клавиши имеют коды от 48 (нуль) до 57 (девять). Когда пользователь нажимает клавишу, вызывается метод keyPressed. Код нажатой клавиши хранится в системной переменной keyCode Если из значения этой переменной вычесть 48, то как раз и получится номер нажатой цифровой клавиши 0..9: def keyPressed(): draw_qnum(keyCode - 48) Этот номер мы передаём функции draw_qnum, которая рисует квадрат из цветных кружков размерами num х num: def draw_qnum(num): # слишком большое число: if num < 0 or num > 9: return 465
# очищаем окно: background(0) # цвет кружков: clr = random_color() stroke(clr) # рисуем квадрат из кружков: for col in range(num): for row in range(num): # координаты: x = d * col + d y = d * row + d point(x,y) В функции random_color мы увеличили нижнюю границу значений для цветовых составляющих, чтобы кружки всегда были яркими и хорошо выделялись на чёрном фоне: # ВОЗВРАЩАЕМ СЛУЧАЙНЫЙ ЦВЕТ def random_color(): clr = color(random(32,255), random(32, 255), random(32, 255)) return clr Так как нулевое квадратное число нарисовать невозможно, то экран останется чёрным. Во всех остальных случаях вы увидите красивую картинку – квадрат, сложенный из цветных кружков (Рис. 1). Рис. 1. Квадратное число номер 9 466
Проект Задача 6 с сайта Проект Эйлер Исходный код программы находится в файле Эйлер006.py. Задача номер 6 (Problem 6) Sum square difference. Она была опубликована 14 декабря 2001 года. The sum of the squares of the first ten natural numbers is, 12 + 22 + ... + 102 = 385 The square of the sum of the first ten natural numbers is, (1 + 2 + ... + 10)2 = 552 = 3025 Hence the difference between the sum of the squares of the first ten natural numbers and the square of the sum is 3025 − 385 = 2640. Find the difference between the sum of the squares of the first one hundred natural numbers and the square of the sum. Разность между суммой квадратов и квадратом суммы Сумма квадратов первых десяти натуральных чисел равна: 12 + 22 + ... + 102 = 385 Квадрат суммы первых десяти натуральных чисел равен: (1 + 2 + ... + 10)2 = 552 = 3025 Разность между суммой квадратов и квадратом суммы первых десяти натуральных чисел составляет: 3025 − 385 = 2640. Найдите разность между суммой квадратов и квадратом суммы первых ста натуральных чисел. Так как чисел всего сотня, то задача легко решается методом грубой силы: # Project Euler. Problem 6 # Sum square difference # РЕШАЕМ ЗАДАЧУ def solve(): # сумма чисел: sumNum = 0 467
# сумма квадратов чисел: sumSqr = 0 for i in range(1, 100 + 1): # добавляем очередное число к сумме чисел: sumNum += i # добавляем квадрат очередного числа к # сумме квадратов чисел: sumSqr += i * i # печатаем ответ: print("Разность равна", sumNum * sumNum - sumSqr) print() def main(): print("Решаем задачу 6 из Проекта Эйлер") solve() main() Через пару миллисекунд получаем полноценный ответ (Рис. 1). Рис. 1 Если бы чисел было в миллионы раз больше, то их сумму можно было бы вычислить по формуле для суммы членов арифметической прогрессии: n(n + 1)/2 И для суммы квадратов натуральных чисел также существует простая формула: n(2n + 1)(n + 1)/6 Таким образом, при решении этой задачи можно обойтись вообще без цикла, а сразу найти разность по формуле: n*n(n + 1)(n + 1)/4 - n(2n + 1)(n + 1)/6 = n(n + 1)/2 * (n(n + 1)/2 - (2n + 1)/3) Записываем формулу в функции solve2: def solve2(n): return n * (n + 1) // 2 * (n * (n + 1) // 2 - (2 * n + 1) // 3) 468
И вызываем её из главной функции: def main(): print("Решаем задачу 6 из Проекта Эйлер") solve() # печатаем ответ: print("Разность равна", solve2(100)) На Рис. 2 вы видите, что оба метода дают одинаковый результат. Рис. 2 Проект Теорема Лагранжа Исходный код программы находится в файле Теорема Лагранжа.py. В 1770 году французский математик Жозеф Луи Лагранж доказал теорему о сумме четырёх квадратов: Любое натуральное число можно представить в виде суммы четырёх квадратов целых чисел. Впервые эта теорема упоминается в Арифметике Диофанта, но строго доказал её только Лагранж. В теореме недаром говорится о сумме квадратов целых, а не натуральных чисел. Иначе некоторые числа не удастся представить как сумму четырёх квадратов. Например, числа 1, 2, 3 равны сумме, соответственно, одного, двух и трёх квадратов: 1 = 12 2 = 1 2 + 12 469
3 = 1 2 + 12 + 12 Найдите самостоятельно и другие примеры! В этих случаях сумму дополняют квадратом нуля, который не изменяет самой суммы, но зато увеличивает число слагаемых: 1 = 1 2 + 02 + 02 + 02 2 = 12 + 12 + 02 + 02 3 = 12 + 12 + 12 + 02 Приём, конечно, искусственный, но зато теорема выполняется для всех натуральных чисел (и нуля). Поскольку единица – это квадрат, то любое натуральное число n можно представить как сумму n единиц в квадрате 12. Понятно, что 4 единицы можно заменить четвёркой, то есть двойкой в квадрате. И так далее. Теорема Лагранжа утверждает, что число слагаемых для любого натурального числа не превышает четырёх. Этот факт уже не столь очевиден. Немецкий математик Гаусс доказал, что только для чисел, которые можно записать в виде: 4n(8m + 7), необходимо ровно 4 слагаемых. Остальные числа могут быть представлены меньшим числом слагаемых. Теорема Лагранжа не даёт нам способа отыскания самих слагаемых. Вот этим мы сейчас и займёмся. Сначала мы напишем универсальную программу, которая отыскивает все разбиения заданного числа на минимальное количество слагаемыхквадратов, а потом – несколько «специализированных» приложений. Пусть задано натуральное число n, которое нужно представить в виде суммы четырёх квадратов: n = a2 + b2 + c2 + d2 Для неповторяющихся решений должно выполняться условие: 470
a <= b <= c <= d Своего максимального значения переменная а достигает при равенстве всех переменных: a = b = c = d Тогда: a2 = n/4 → максимальное значение a = √𝒏/𝟒 Рассуждая аналогично, мы получим максимальные значения и для других переменных: max b = √𝒏/𝟑 max c = √𝒏/𝟐 max d = √𝒏 В функции main пользователь задаёт начало и конец диапазона для представления чисел в виде суммы квадратов. Если нужно найти разбиение единственного числа, то числа beg и end должны совпадать: # ГЛАВНАЯ ФУНКЦИЯ def main(): # бесконечный цикл ввода данных # пока пользователь не закроет программу: while True: print() print("Теорема Лагранжа") print() print("Начало диапазона > ", end='') beg = int(input()) if (beg < 0): return print("Конец диапазона > ", end='') end = int(input()) solve(beg, end) print() main() Затем мы передаём эти числа функции solve для решения задачи. 471
Здесь мы во вложенных циклах for ищем разбиения всех чисел из заданного диапазона и подсчитываем в переменной nVar число найденных решений: def solve(beg, end): nVar = 0 for n in range(beg, end + 1): Чтобы не искать более длинные представления, если уже найдены короткие, мы определяем две переменные: nq = 0 minnq = 4 Первая показывает текущее число слагаемых, а вторая – минимальное. Дальше мы организуем 4 цикла, в которых переменные a, b, c, d изменяются от минимального до максимального значения. Для переменной а – это 0 и max a. Этот вопрос мы уже обсудили. for a in range(0, int(sqrt(n / 4)) + 1): a2 = a * a for b in range(a, int(sqrt(n / 3)) + 1): b2 = b * b if (a2 + b2 > n): break for c in range(b, int(sqrt(n / 2)) + 1): c2 = c * c if (a2 + b2 + c2 > n): break Если при каком-либо значении переменной d сумма квадратов окажется равной числу n, то мы формируем строку с очередным разбиением и печатаем её на экране: for d in range (c, int(sqrt(n)) + 1): dif = n - (a2 + b2 + c2 + d * d) if (dif == 0): nq = 1 s = str(n) + " = " if (a > 0): 472
s += str(a2) + " + " nq += 1 if (b > 0): s += str(b2) + " + " nq += 1 if (c > 0): s += str(c2) + " + " nq += 1 if (nq > minnq): break else: minnq = nq s += str((d * d)) # print(s) print(s, is4(n)) nVar += 1 break print("Всего:", nVar) При этом мы опускаем нулевые слагаемые, которые нам не нужны, а также корректируем значение переменной minnq, чтобы не искать длинные представления. Пользуясь формулой Гаусса, мы определяем, являются ли четыре слагаемых минимально необходимыми (True) или число можно представить и меньшим числом квадратов (False): # -*- coding: Windows-1251 -*# Теорема Лагранжа from math import * def is4(n): while (n % 4 == 0): n /= 4 return (n - 7) % 8 == 0 Запускаем программу и находим все разбиения чисел от 1 до 2022 на квадраты (Рис. 1). Глядя на Рис. 1, мы можем сделать такие выводы: • функция is4 здесь не нужна, поскольку программа всегда находит минимальное число слагаемых; • для 2022 чисел мы получили список из 15573 представлений, то есть многие числа можно представить несколькими вариантами даже при минимальном числе слагаемых; 473
• число 2022 можно представить только суммой трёх слагаемых; • хотелось бы иметь на экране не только квадраты чисел, но и сами эти числа. Рис. 1. Разбиения чисел на квадраты Проведём эксперимент и закомментируем строки: #if (nq > minnq): # break #else: # minnq = nq Наш список увеличится до 65968 представлений (Рис. 2). Например, число 2009 можно разбить на 2, 3 и 4 слагаемых, что и отразилось на общей сумме вариантов (в первом случае мы не находили разбиений на 3 и 4 слагаемых для этого числа). Решим более простую задачу – будем искать разбиения чисел ровно на два квадрата в новом проекте. 474
Рис. 2. Все разбиения чисел Исходный код программы находится в файле Теорема Лагранжа 2.py. Функция main почти не изменится, поэтому мы сразу обратимся к методу solve. В циклах мы учли, что переменная a теперь изменяется от 1 до √(n/2), а переменная b – от a до √𝑛: def solve2(beg, end): nVar = 0 for n in range(beg, end + 1): for a in range(1, int(sqrt(n / 2)) + 1): a2 = a * a for b in range (int(sqrt(n)) + 1): if (a2 + b * b == n): s = str(n) + " = " s += str(a2) + " + " s += str(b * b) s += " = " + str(a) + "^2" + " + " + str(b) + "^2" print(s) nVar += 1 print("Всего:", nVar) 475
Для того же диапазона чисел 1..2022 наше приложение отыскало только 787 разбиений (Рис. 3). Рис. 3. Разбиения чисел на 2 квадрата Причину уменьшения числа представлений мы уже знаем: не все числа можно разбить на 2 квадрата. С другой стороны, некоторые числа даже так можно представить не единственным способом. Метод solve2 получился очень коротким, но его можно сократить ещё на один цикл for, если учесть, что для текущего значения переменной a может быть единственное значение переменной b, такое что: a2 + b2 = n Из этого равенства находим: b 2 = n - a2 Если число b – точный квадрат, то очередное разбиение найдено, и можно переходить к следующему значению переменной a, не тратя времени на перебор в цикле других значений переменной b, как это мы делали в первом случае. Изменения в методе solve2 ограничиваются тремя выделенными строками: 476
def solve2_2(beg, end): nVar = 0 for n in range(beg, end + 1): for a in range(1, int(sqrt(n / 2)) + 1): a2 = a * a b2= n - a2 b = sqrt(b2) if (b == trunc(b)): b = trunc(b) s = str(n) + " = " s += str(a2) + " + " s += str(b * b) s += " = " + str(a) + "^2" + " + " + str(b) + "^2" print(s) nVar += 1 print("Всего:", nVar) Если число b – точный квадрат, то логическое условие : b == trunc(b) выполняется, и мы печатаем разбиение. Здесь мы исходим из того, что точный квадрат – это целое число, у которого нет дробной части. Поэтому после отбрасывания дробной части у переменной b в методе trunc число не изменится. Теперь даже огромные числа мы разбиваем на квадраты очень быстро (Рис. 4). Сравните скорость работы с первым способом! Голландский математик Альбер Жирар (1595–1632) нашёл критерий, по которому можно определить, представимо ли натуральное число в виде суммы квадратов двух целых чисел: это возможно тогда и только тогда, когда в его разложение на простые множители любой множитель, дающий остаток 3 при делении на 4, входит в чётной степени. Добавьте к проекту функцию, которая возвращает True, если заданное число можно разложить на 2 квадрата, и используйте его в функции solve2. 477
Рис. 4. Разбиваем на квадраты большие числа С помощью этой программы можно искать и пифагоровы тройки, если вместо числа n разлагать на квадраты число n2 (Рис. 5): def solve2Pyth(beg, end): nVar = 0 for n in range(beg, end + 1): n2 = n * n for a in range(1, int(sqrt(n2/ 2)) + 1): a2 = a * a for b in range(a, int(sqrt(n2)) + 1): if (a2 + b * b == n2): s = str(n) + "^2 = " s += str(a2) + " + " s += str(b * b) s += " = " + str(a) + "^2" + " + " + str(b) + "^2" print(s) nVar += 1 print("Всего:", nVar) При этом будут найдены все тройки, а не только простейшие. 478
Рис. 5. Неожиданное применение программы Программы для разложения чисел на три и четыре квадрата разберите самостоятельно! Следующая пара проектов только для контроля! Исходный код программы находится в файле Теорема Лагранжа 3.py. def solve3(beg, end): nVar = 0 for n in range(beg, end + 1): for b in range(1, int(sqrt(n / 3)) + 1): b2 = b * b for c in range (b, int(sqrt(n / 2)) + 1): c2 = c * c if (b2 + c2 > n): break for d in range(c, int(sqrt(n)) + 1): if (b2 + c2 + d * d == n): s = str(n) + " = " s += str(b2) + " + " 479
s += str(c2) + " + " s += str(d * d) print(s) nVar += 1 print("Всего:", nVar) Исходный код программы находится в файле Теорема Лагранжа 4.py. # -*- coding: Windows-1251 -*# Теорема Лагранжа 4 from math import * def is4(n): while (n % 4 == 0): n /= 4 return (n - 7) % 8 == 0 def solve4(beg, end): nVar = 0 for n in range(beg, end + 1): for a in range(1, int(sqrt(n / 4)) + 1): 480
a2 = a * a for b in range(a, int(sqrt(n / 3)) + 1): b2 = b * b if (a2 + b2 > n): break for c in range (b, int(sqrt(n / 2)) + 1): c2 = c * c if (a2 + b2 + c2 > n): break for d in range(c, int(sqrt(n)) + 1): if (a2 + b2 + c2 + d * d == n): s = str(n) + " = " s += str(a2) + " + " s += str(b2) + " + " s += str(c2) + " + " s += str(d * d) print(s + '. ' + str(is4(n))) nVar += 1 print("Всего:", nVar) # ГЛАВНАЯ ФУНКЦИЯ def main(): # бесконечный цикл ввода данных # пока пользователь не закроет программу: while True: print() print("Теорема Лагранжа 4:") print("Сумма четырёх квадратов") print() print("Начало диапазона > ", end='') beg = int(input()) if (beg < 0): return print("Конец диапазона > ", end='') end = int(input()) solve4(beg, end) print() С помощью этих проектов я нашёл любопытные примеры представления чисел, состоящих только из единиц: 1 = 12 11 = 12 + 12 + 32 111 = 12 + 52 + 62 + 72 1111 = 12 + 72 + 102 + 312 481
11111 = 32 + 312 + 542 +852 111111 = 1552 + 1612 + 1632 + 1862 1111111 = 5142 + 5152 + 5212 + 5572 11111111 = 52 + 1292 + 12032 + 31062 111111111 = 12 + 6992 + 32502 + 100032 1111111111 = 12 + 112 + 186052 + 276582 Как видите, большие «единичные» числа можно представить только суммой четырёх квадратов. Проект Задача 142 с сайта Проект Эйлер Исходный код программы находится в файле Эйлер142.py. Задача номер 142 (Problem 142) называется Perfect Square Collection. Find the smallest x + y + z with integers (x > y > z > 0) such that x + y, x – y, x + z, x – z, y + z, y – z are all perfect squares. Коллекция идеальных квадратов Найдите тройку натуральных чисел x, y и z таких, что выражения x + y, x - y, x + z, x - z, y + z, y – z представляют собой квадраты натуральных чисел. При этом сумма чисел x + y + z должна быть минимальной. Соблазнительно решить задачу методом грубой силы, просто перебирая числа в трёх вложенных циклах for. Имея много времени, можно дождаться решения задачи, но через пару минут становится очевидно, что на сайте Проект Эйлера не предложили бы задачу на банальный перебор в циклах. Значит, проблему нужно решать иначе. Поскольку все выражения – это квадраты натуральных чисел, то мы можем обозначить их так: x x x x – + – + y y z z = = = = a2 b2 c2 d2 (1) (2) (3) (4) 482
y – z = e2 y + z = f2 (5) (6) Находим сумму двух первых равенств: x – y + x + y = a2 + b2 Откуда: x = (a2 + b2)/2 (7) Вычитая из второго равенства первое получаем: y = (b2 - a2)/2 (8) Из равенств (7) и (8) вытекает, что числа a и b либо оба чётные, либо оба нечётные, иначе числа x и y будут дробными. Так как y > 1, то (b2 - a2)/2 > 1 и b > a Вычитая из выражения (4) выражение (3), получаем: x + z – (x - z) = d2 + c2 Или: z = (d2 - c2)/2 (9) Складывая выражения (3) и (4), находим: x + z + (x - z) = d2 + c2 x = (d2 + c2)/2 Из выражения (7) следует, что: x = (d2 + c2)/2 = (a2 + b2)/2 То есть: d2 = a2 + b2 - c2 483
Подставляя в выражение (9), получаем формулу для вычисления z: z = (a2 + b2 - c2 - c2)/2 z = (a2 + b2)/2 - c2 z = x - c2 Нам опять потребуются 3 вложенных цикла для чисел a, b и c, но теперь диапазон их изменения гораздо меньше! Поскольку b > a > 1, то b не может равняться 1 и 2, в противном случае а будет равно 0. Итак, переменная цикла b изменяется от 3 до – пока мы не найдём решения задачи. Переменная цикла а изменяется от 1 или от 2 – в зависимости от того, нечётное или чётное число b. Причём а изменяется с приращением 2, иначе его чётность будет отличаться от чётности числа b. Для числа с имеем: z < y x - c2 < y a2 < c2 a < c x - y < c2 (a2 + b2)/2 - (b2 - a2)/2 < c2 С другой стороны, z = x – c2 > 0 x > c2 c < √𝒙 Так как x = (a2 + b2)/2 и a < b, то с < b. Таким образом, переменная цикла c изменяется от a + 1 до b - 1. После теоретических изысканий, мы без труда напишем функцию для решения задачи: # Project Euler. Problem 142 # Perfect Square Collection from math import sqrt def isSquare(n): i = int(n ** 0.5) return n == i*i def solve(): b = 3 while True: b2 = b * b; ## if (b % 2 == 1): ## a = 1 484
## ## else: a = 2 for a in range(1 + (0 & b), b, 2): a2 = a * a x = (a2 + b2) >> 1 # 2 for c in range(a + 1, b): #for (c = a + 1; c < Math.Sqrt(x); ++c) y = (b2 - a2) >> 1 #2 z = x - c * c if (isSquare(y - z) and isSquare(y + z) and isSquare(x - z) and isSquare(x + z)): return x, y, z b += 1 def main(): print("Решаем задачу 142 из Проекта Эйлер") x, y, z = solve() print("Наименьшая сумма x+y+z равна", x+y+z) print() print("x-y =", (x-y), sqrt(x-y), "*", sqrt(x-y)) print("x+y =", (x+y), sqrt(x+y), "*", sqrt(x+y)) print("x-z =", (x-z), sqrt(x-z), "*", sqrt(x-z)) print("x+z =", (x+z), sqrt(x+z), "*", sqrt(x+z)) print("y-z =", (y-z), sqrt(y-z), "*", sqrt(y-z)) print("y+z =", (y+z), sqrt(y+z), "*", sqrt(y+z)) main() Здесь мы применили некоторую оптимизацию кода, чтобы ускорить процесс. Условный оператор: if (b % 2 == 1): a = 1 else: a = 2 заменили побитовым: 1 + (0 & b) Деление на двойку – оператором сдвига вправо: x = (a2 + b2) >> 1 # 2 y = (b2 - a2) >> 1 # 2 485
Повторное возведение в квадрат – переменными a2, b2. Использовали условие: с < b вместо: c < √𝒙, чтобы избежать медленной операции извлечения корня. Запускаем программу и получаем ответ (Рис. 1). Рис. 1 Исходный код программы находится в файле Эйлер142а.py. В целом можно считать решение задачи вполне удовлетворительным, однако львиная доля времени уходит на проверки «квадратности» чисел в функции isSquare. Предположим, что мы не знаем ответа на задачу, но догадываемся, что в решении окажутся не очень большие числа. Поэтому мы создаём логический список isSquare: 486
def solve(): isSquare = [False] * 100000000 И с большим запасом заполняем его квадратами чисел от 1 до 10000: for n in range(10000): isSquare[n ** 2] = True При решении задачи мы просто проверяем, имеется ли заданное число в списке isSquare: def solve(): isSquare = [False] * 100000000 for n in range(10000): isSquare[n ** 2] = True b = 3 while True: b2 = b * b; for a in range(1 + (0 & b), b, a2 = a * a; x = (a2 + b2) >> 1 for c in range(a + 1, b): y = (b2 - a2) >> 1 z = x - c * c if isSquare[y - z] and and and return x, y, z b += 1 2): isSquare[y + z] isSquare[x - z] isSquare[x + z]: Рис. 2 показывает, что задача решена правильно. Рис. 2 487
И тем не менее, возможности для оптимизации кода далеко не исчерпаны, но тут уже потребуется более эффективный алгоритм решения задачи. Подумайте над ним! Проект Ряд треугольных чисел Исходный код программы находится в файле Ряд треугольных чисел.py. Треугольные числа – это частный вид фигурных чисел. Треугольные числа – близкие родственники квадратных. Если из определённого числа кружков можно построить правильный треугольник, то такое число и называют треугольным. Самое первое треугольное число – это единица. Правда, один кружок не очень-то похож на треугольник, но у математиков свой взгляд на вещи (Рис. 1). Рис. 1 Следует добавить, что нулевое треугольное число представляет собой треугольник, которого вообще не видно. Он существует только в мозгу математиков. Второе треугольное число – тройка. Из трёх кружочков уже можно построить вполне реалистичный треугольник (Рис. 2). Рис. 2 Третье треугольное число – шестёрка (Рис. 3). 488
Рис. 3 Четвёртое треугольное число – шестёрка (Рис. 4). Рис. 4 Дальше можно не продолжать – закономерность понятна: Первое число равно 1. Второе – 1 + 2. Третье - 1 + 2 + 3. Четвёртое - 1 + 2 + 3 + 4. Пятое - 1 + 2 + 3 + 4 + 5. n-ное треугольное число равно сумме первых n натуральных чисел: 0, 1, 3, 6, 10, 15, 21, 28, 36, 45 Функция-генератор треугольных чисел очень простая. Первое треугольное число а = 0, а все последующие получаются добавлением номера числа к предыдущему треугольному числу (Рис. 5): # ГЕНЕРАТОР ТРЕУГОЛЬНЫХ ЧИСЕЛ: def tris(len = 0): a = 0 num = 1 while True: yield a if num == len: break a += num 489
num += 1 def main(): len = int(input("Введите длину ряда > ")) print("Длина ряда =",len) print("Последовательность треугольных чисел:") num = 0 for i in tris(len): print(num, i) num += 1 main() Рис. 5 Проект Треугольные числа Исходный код программы находится в папке TriangleNumbers. Опять рисуем кружочки в среде Процессинг. 490
Так как треугольные числа похожи на квадратные, то мы просто переделаем программу для рисования квадратных чисел. Но теперь мы будем рисовать не большие точки, а кружки такого же размера: # ТРЕУГОЛЬНЫЕ ЧИСЛА # диаметр кружка: d = 40 def setup(): size(400, 400) background(0) ellipseMode(CENTER) noStroke() def draw(): pass def keyPressed(): draw_tnum(keyCode - 48) def draw_tnum(num): # слишком большое число: if num < 0 or num > 9: return # очищаем окно: background(0) # цвет кружков: clr = random_color() fill(clr) # рисуем треугольник из кружков: for row in range(num): for col in range(row+1): # координаты: x = d * col + d y = d * row + d ellipse(x,y, d, d) # ВОЗВРАЩАЕМ СЛУЧАЙНЫЙ ЦВЕТ def random_color(): clr = color(random(32,255), random(32, 255), random(32, 255)) return clr 491
Мы учитываем, что в каждом горизонтальном ряду число кружков в точности равно номеру ряда. В итоге мы получаем не правильный треугольник, а прямоугольный равнобедренный (Рис. 1). Рис. 1 Чтобы получить равносторонний треугольник, нужно верхний ряд сдвигать относительно нижнего на половину диаметра: # рисуем треугольник из кружков: for row in range(num): for col in range(row+1): # координаты: x = d * col + d//2 * (num - row + 1) y = d * row + d ellipse(x,y, d, d) Теперь треугольник почти правильный, только между горизонтальными рядами образовался зазор (Рис. 2). 492
Рис. 2 Верхние кружки не лежат на нижних, а парят над ними (Рис. 3). Значит, нужно уменьшить расстояние между горизонтальными рядами кружков. Начертим кружки в правильном положении (Рис. 4). Из треугольника АВС мы легко найдём длину высоты h: 𝒉= 𝒅√𝟑 𝟐 Также мы должны опустить весь треугольник на полтора диаметра, потому что общая высота треугольника уменьшилась. Окончательный код такой: # рисуем треугольник из кружков: for row in range(num): for col in range(row+1): # координаты: x = d * col + d//2 * (num - row + 1) y = d*3/2 + d / 2. * sqrt(3) * row ellipse(x,y, d, d) 493
Рис. 3 Рис. 4 494
Теперь все кружки нашли своё законное место (Рис. 5). Рис. 5 Проект Задача 12 с сайта Проект Эйлер Исходный код программы находится в файле Эйлер012.py. Задача номер 12 (Problem 12) называется Highly divisible triangular number. The sequence of triangle numbers is generated by adding the natural numbers. So the 7th triangle number would be 1 + 2 + 3 + 4 + 5 + 6 + 7 = 28. The first ten terms would be: 1, 3, 6, 10, 15, 21, 28, 36, 45, 55, ... Let us list the factors of the first seven triangle numbers: 1: 1 3: 1,3 495
6: 1,2,3,6 10: 1,2,5,10 15: 1,3,5,15 21: 1,3,7,21 28: 1,2,4,7,14,28 We can see that 28 is the first triangle number to have over five divisors. What is the value of the first triangle number to have over five hundred divisors? Найдите треугольное число, у которого более 500 делителей. Треугольные числа мы получим от нашей функции tris: # ГЕНЕРАТОР ТРЕУГОЛЬНЫХ ЧИСЕЛ: def tris(len = 0): a = 0 num = 1 while True: yield a if num == len: break a += num num += 1 В функции main мы считаем делители у каждого треугольного числа, пока не найдём такое, у которого в общей сложности более 500 делителей: def main(): # номер треугольного числа: num = 0 for i in tris(): if (num_divisors(i) > 500): print(num, i, num_divisors(i)) break num += 1 main() В функции num_divisors мы учитываем особый случай – у числа 1 всего 1 делитель, а у всех других чисел не менее двух (2 делителя только у простых чисел): # СЧИТАЕМ ДЕЛИТЕЛИ ЗАДАННОГО ЧИСЛА def num_divisors(num): 496
if (num == 1): return 1 # текущее число: n = num # очередной делитель: i = 2 # число делителей: p = 1 while (i * i <= n): # кратность делителя: c = 1 while (n % i == 0): n //= i c += 1 i += 1 p *= c if (n == num or n > 1): p *= 2 return p А ответ такой (Рис. 1). Рис. 1 Треугольное число равно 76 576 500, по счёту оно 12375-е, и у него 576 делителей. Проект Числа Армстронга Исходный код программы находится в файле Числа Армстронга.py. n-значное число называется числом Армстронга, если оно равно сумме n-ых степеней своих цифр. Первое число Армстронга – 153. Проверим это утверждение: 13 + 53 + 33 = 1 + 125 + 27 = 153 Всё верно! 497
Первые девять натуральных чисел 1..9 формально также являются числами Армстронга, но они слишком тривиальны, чтобы быть интересными. Эти числа названы в честь математика, который первым их исследовал. По-английски их называют Armstrong numbers или Pluperfect Digital Invariants – PPDIs. Другое название этих чисел – нарциссические (Narcissic numbers). Оно восходит к самовлюблённому Нарциссу и обыгрывает тот факт, что числа Армстронга так любят себя, что сумма степеней их цифр равняется самим числам. Всего существует 88 чисел Армстронга (для десятичной системы счисления). Первое из них, как вы уже знаете, 153. Мы легко найдём и несколько следующих: 370, 371, 407. Но самое большое состоит из 39 цифр. Числа Армстронга против грубой силы Мы напишем 2 генератора чисел Армстронга и устроим между ними соревнование на скорость. В качестве секундомера используем функцию time из одноимённого модуля: # Числа Армстронга import time В функции main мы поочерёдно вызываем оба генератора и замеряем время их работы: def main(): print("Числа Армстронга") print() start_time = time.time() for a in arms1(10000000): print(a) print("Время =", time.time() - start_time) 498
start_time = time.time() for a in arms2(10000000): print(a) print("Время =", time.time() - start_time) print() print("ВСЁ!") print() main() Генераторы действуют одинаково. Для каждого числа из заданного диапазона они находят количество цифр, преобразуя для этого число в строку. Количество цифр определяет степень, в которую следует возвести цифры числа. Цифры извлекаем из строки и превращаем их в однозначные числа. Если сумма степеней цифр текущего числа равна самому числу, то возвращаем очередное число Армстронга: # ГЕНЕРАТОР ЧИСЕЛ АРМСТРОНГА 1 def arms1(limit): for n in range(10, limit + 1): # преобразуем число в строку: sn = str(n) # число цифр в числе: power = len(sn) # находим цифры числа: d = map(int, sn) if sum(dig ** power for dig in d) == n: yield n # ГЕНЕРАТОР ЧИСЕЛ АРМСТРОНГА 2 def arms2(limit): for n in range(10, limit + 1): sn = str(n) power = len(sn) if sum(map(lambda d: d ** power, map(int, sn))) == n: yield n Конечно, абсолютное время работы функций зависит от компьютера, но сравнить скорости мы вполне можем (Рис. 1). Функции финишировали ноздря в ноздрю! 499
Рис. 1 Проект Числа Армстронга 2 Исходный код программы находится в файле Числа Армстронга 2.py. Чаще нужно найти числа Армстронга заданной длины n, то есть такие, которые состоят из n цифр. Мы последовательно перебираем все числа от 10n-1 до 10n-1 и для каждого находим сумму n-ной степени его цифр. Если она совпадёт с исходным числом, значит, число Армстронга найдено! В этом лобовом алгоритме нас особенно должно возмутить постоянное возведение в степень n одних и тех же чисел 0..9, из которых состоят все подозреваемые в нарциссизме числа. Поэтому мы несколько ускорим перебор, если сразу же создадим таблицу заданной степени n этих чисел, а при нахождении суммы степеней будем просто извлекать нужную степень из списка. Больше ничего хорошего от метода грубой силы ждать не приходится: 500
# Числа Армстронга 2 import time # ГЕНЕРАТОР ЧИСЕЛ АРМСТРОНГА ЗАДАННОЙ ДЛИНЫ def arms3(lenn): power = lenn table = [d ** power for d in range(10)] for n in range(10 ** (power - 1), 10 ** power - 1): sn = str(n) if sum(map(lambda d: table[d], map(int, sn))) == n: yield n def main(): print("Числа Армстронга 2") print() while True: lenn = int(input("Введите длину чисел > ")) print("Длина чисел =", lenn) start_time = time.time() #for lenn in range(2, 8): for a in arms3(lenn): print(a) print("Время =", time.time() - start_time) print() print("ВСЁ!") print() main() Новый алгоритм заметно быстрее, но для 3-7-значных чисел необходимо перебрать 10 миллионов чисел, и на это понадобится более 20 секунд (Рис. 1). 501
Рис. 1 Дальше счёт пойдёт на минуты. Проект Числа Армстронга 3 Исходный код программы находится в файле Числа Армстронга 3.py. Если у вас много свободного времени, то вы сможете продвинуться ещё на пару степеней, но грубый алгоритм работает слишком медленно. А чтобы ускорить его, надо придать ему уму, как говорил один из героев Савелия Крамарова. Мозговой штурм Всё выше, и выше, и выше! Марш советских авиаторов Медлительность нашего алгоритма заключается в том, что он по нескольку раз находит сумму степеней одних и тех же чисел. Действительно, многие числа состоят из одинаковых цифр. Например, 135 153 315 351 513 531 И чем больше в числах цифр, тем больше среди них «анаграмматических». Но для всей группы таких чисел сумма степеней цифр одинакова, поскольку одинаковы и сами цифры, а мы вычисляем эту сумму для каждого числа отдельно! Для чисел, состоящих из цифр 1,3,5, целых 6 раз! Для разработки нового алгоритма мы воспользуемся программой одного из лучших популяризаторов занимательного программирования (правда, 502
на языке Delphi) Gary Darby, который для быстрого нахождения чисел Армстронга предложил генерировать все подмножества с повторениями (он их называет мультимножествами) множества цифр {0,1,2,3,4,5,6,7,8,9}. Для нашего примера достаточно проверить подмножество цифр {1,3,5}. Ясно, что из всех чисел, которые можно составить из этих цифр, только одно может быть числом Армстронга (а может и не быть!). Умный алгоритм такой: Последовательно генерируем все подмножества с повторениями множества всех цифр. Число элементов в подмножестве равняется длине исходного числа (а значит, и показателю степени). Для каждого подмножества вычисляем сумму степеней его цифр. Если она состоит из всех цифр подмножества, значит, это число Армстронга. Возвращаемся к подмножеству {1, 3, 5} и вычисляем сумму степеней его элементов. Как мы уже знаем, она равна 153. Легко проверить, что все цифры этой суммы принадлежат подмножеству {1, 3, 5}. Следовательно, 153 – это число Армстронга. Таким образом, мы проверяем уже не все числа заданной длины n, а только все подмножества, состоящие из n элементов, которых, понятное дело, гораздо меньше, особенно при больших n. Для начала мы разработаем алгоритм генерирования всех подмножеств заданной длины, тем более что они могут пригодиться нам и во многих других задачах. В функции main мы задаём число элементов и отправляемся в функцию generate: def main(): while True: n = int(input("Введите число элементов в подмножестве > ")) print("Число элементов =", n) generate(n) main() По этому числу в функции generate мы заполняем список num одним нулём и (n-1)-ой девяткой, после чего передаём его в функцию 503
getNextMultiSet, которая и генерирует очередное подмножество. Нам остаётся только напечатать его на экране: # ГЕНЕРИРУЕМ ВСЕ МНОЖЕСТВА С ПОВТОРЕНИЯМИ def generate(n): nVar = 0 # число цифр = n num = [9] * n num[0] = 0 print(num) while getNextMultiSet(num): nVar += 1 s = "" for i in range(len(num)): s += str(num[i]) + " " print(s) print("Найдены все подмножества:", nVar) print() И вот что получается (Рис. 1). Рис. 1 Наше подмножество {1, 3, 5} представлено в списке элементами списка 5,3,1. Поскольку порядок элементов нас не интересует, то будем считать, что первый этап разработки алгоритма успешно завершён. 504
Всего в списке окажется 219 подмножеств - от {1, 0, 0} до {9, 9, 9}. Всего же трёхзначных чисел 900 штук. Для четырёхзначных чисел статистика следующая: 714 подмножеств и 9000 чисел. Статистика приятная - уже для четырёхзначных чисел выигрыш в переборе более чем десятикратный! А вот и функция getNextMultiSet, которую мы положили в основу нашего алгоритма: def getNextMultiSet(a): lim = 0 j = len(a) - 1 done = False while(done == False): if j > 0: lim = a[j-1] else: lim = 9 if (a[j] < lim): a[j] += 1 for i in range(j + 1, len(a)): a[i] = 0 done = True else: j -= 1 if j < 0: done = True return j >= 0 Теперь мы можем перейти к поиску чисел Армстронга с нашим генератором подмножеств. В функции main мы задаём длину чисел и передаём её генератору чисел Армстронга armstrong: def main(): print("Числа Армстронга 3") print() while True: lenn = int(input("Введите длину чисел > ")) print("Длина чисел =", lenn) start_time = time.time() for a in armstrong(lenn): print(a) 505
print("Время =", time.time() - start_time) print() print("ВСЁ!") print() Начало функции armstrong берём из предыдущего проекта, но теперь в цикле while мы уже не удовлетворяемся новым подмножеством цифр, а действуем в соответствии с умным алгоритмом: # Числа Армстронга 3 from copy import deepcopy import time BASE = 10 def isSelected(a, m): result = False for i in range(len(a)): if a[i] == m: result = True a[i] = -1 break return result # ИЩЕМ ЧИСЛА АРМСТРОНГА def armstrong(n): nVar = 0 num = [0] * n copynum = [0] * n powTableL = [0] * BASE start = 1 sum = 0 found = True val = 0 for i in range(BASE): powTableL[i] = i for i in range(1, n): start *= BASE for k in range(BASE): powTableL[k] *= k num[i] = 9 num[0] = 0 506
while getNextMultiSet(num): # находим сумму степеней цифр: sum = 0 for i in range(len(num)): sum += powTableL[num[i]] И здесь нам понадобится дополнительный список copynum с теми же цифрами, что и в списке num. Как нам предписывает третий пункт нашего алгоритма, мы должны проверить, все ли цифры суммы имеются в подмножестве. Если бы цифры не повторялись, то было бы достаточно и списка num, но поскольку это не так, то нам придётся «вычёркивать» уже совпавшие цифры, что неизбежно испортит основной список: found = False if sum >= start: # копируем список: copynum = deepcopy(num) val = sum found = True for i in range(1, n + 1): m = val % 10 if not isSelected(copynum, m): found = False break val //= 10 if found: #print(sum) yield sum nVar += 1 print("Найдены все числа Армстронга длиной", print() n, " -->", nVar) Запускаем программу (Рис. 2)! 507
508
Рис. 2 Даже 19-значные числа мы берём достаточно быстро (Рис. 3). Рис. 3 Gary Darby дошёл до конца и отыскал все 88 чисел Армстронга. На это у него ушло более полутора суток. Вполне вероятно, что ваш компьютер справится с заданием быстрее, но всё равно ждать часами – это не для слабонервных. С другой стороны, такое время исчерпывающего поиска отнюдь не редкость для подобных задач. Для нас же важен сам факт того, что наш алгоритм позволяет найти все числа Армстронга за вполне удовлетворительное время. А чтобы не томить вас долгими ожиданиями и вычислениями, вот вам готовый «списочек» всех чисел Армстронга: № n Число Армстронга 1 2 3 4 5 6 3 3 3 3 4 4 153 370 371 407 1634 8208 509
7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 4 5 5 5 6 7 7 7 7 8 8 8 9 9 9 9 10 11 11 11 11 11 11 11 11 14 16 16 17 17 17 19 19 19 19 20 21 21 23 23 23 23 23 24 24 24 25 25 25 25 9474 54748 92727 93084 548834 1741725 4210818 9800817 9926315 24678050 24678051 88593477 146511208 472335975 534494836 912985153 4679307774 32164049650 32164049651 40028394225 42678290603 44708635679 49388550606 82693916578 94204591914 28116440335967 4338281769391370 4338281769391371 21897142587612075 35641594208964132 35875699062250035 1517841543307505039 3289582984443187032 4498128791164624869 4929273885928088826 63105425988599693916 128468643043731391252 449177399146038697307 21887696841122916288858 27879694893054074471405 27907865009977052567814 28361281321319229463398 35452590104031691935943 174088005938065293023722 188451485447897896036875 239313664430041569350093 1550475334214501539088894 1553242162893771850669378 3706907995955475988644380 3706907995955475988644381 510
57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 25 27 27 27 27 27 29 29 29 29 31 31 31 32 33 33 34 35 35 37 38 39 39 4422095118095899619457938 121204998563613372405438066 121270696006801314328439376 128851796696487777842012787 174650464499531377631639254 177265453171792792366489765 14607640612971980372614873089 19008174136254279995012734740 19008174136254279995012734741 23866716435523975980390369295 1145037275765491025924292050346 1927890457142960697580636236639 2309092682616190307509695338915 17333509997782249308725103962772 186709961001538790100634132976990 186709961001538790100634132976991 1122763285329372541592822900204593 12639369517103790328947807201478392 12679937780272278566303885594196922 1219167219625434121569735803609966019 12815792078366059955099770545296129367 115132219018763992565095597973971522400 115132219018763992565095597973971522401 В таблицу не включены первые девять чисел Армстронга 1..9. Для некоторых степеней числа Армстронга отсутствуют. Проект Паразитические числа Исходный код программы находится в файле Паразитические числа.py. В журнале Наука и жизнь, №6 за 1964 год, на странице 69 напечатана интересная задача (Рис. 1). Рис. 1 На первый взгляд, задача не кажется сложной, поэтому можно попробовать решить её методом грубой силы, то есть без лишних размышлений: 511
import math # РЕШАЕМ ЗАДАЧУ МЕТОДОМ ГРУБОЙ СИЛЫ def solveBF(): # число: n = 2 while True: # добавляем 10, # чтобы получить очередное число, # оканчивающееся на 2: n += 10 # число без последней двойки: n_bez_2 = n // 10 # умножаем последнюю двойку: n_10 = int(math.log10(n)) n_2 = 2 * 10 ** n_10 # добавляем число без двойки: n_new = n_2 + n_bez_2 if n_new == n * 2: print('', n, n_new) break # ГЛАВНАЯ ФУНКЦИЯ def main(): solveBF() main() Запускаем программу и после нескольких минут напрасного ожидания начинаем подозревать что-то неладное. Из любопытства добавляем к функции solveBF ещё пару строк: print('', n, n_bez_2, n_10, n_2, n_new) if n > 1000000: break Они быстро убеждают нас, что программа работает нормально, однако перебор слишком велик, чтобы за разумное время получить ответ на жгучий вопрос, а чему же равно число. К счастью, в том же номере журнала приводится и ответ на эту задачу (Рис. 2). 512
Рис. 2 Совершенно неожиданно для нас число оказалось огромным, а потому метод грубой силы здесь не годится. Нужно думать! Легко проверить, что равенство 105263157894736842 * 2 = 210526315789473684 верное. Но для лучшего думания его следует записать иначе: 105263157894736842 2 210526315789473684 Теперь хорошо видно, что в исходном числе и в произведении совпадают все цифры, кроме первой и последней. Более того, предпоследняя цифра исходного числа становится последней цифрой произведения. Третья цифра сзади в исходном числе становится предпоследней цифрой в произведении. И так далее до самой первой цифры исходного числа, которая занимает в произведении второе место. И наконец, последняя цифра исходного числа перебирается на первое место в произведении. Впрочем, все эти забавные факты ещё не позволяют нам решить задачу, так что давайте подумаем, как можно получить последнюю цифру произведения. А очень просто! По условию задачи, последняя цифра в исходном числе – двойка. Также нам известно, что произведение в 2 раза больше исходного числа, то есть мы должны умножить его на 2. Тогда последняя 2 в исходном числе превратится в 4 в произведении. Но последняя цифра произведения – это предпоследняя цифра исходного числа. Рассуждая аналогично, мы придём к выводу, что следующая цифра слева – это 4 х 2 = 8, что мы и видим. Двигаемся далее: 8 x 2 = 16. Значит, перед восьмёркой должна стоять шестёрка. Но обратите внимание, что в результате умножения мы получили число 16, и поэтому 1 должна перейти в следующий разряд. Тогда 6 x 2 + 1 = 13. Очередная цифра – 3, и мы имеем ещё 1 для следующего разряда. 513
Процесс прост и понятен, и его можно продолжать бесконечно. Однако мы не кощеи бессмертные, поэтому обязаны вовремя остановиться. И остановиться мы должны тогда, когда в числе появится двойка без переноса в следующий разряд. И тогда мы получим ещё не искомое число, а вот такое: 2105263157894736842 Чтобы избавиться от первой двойки, на бумаге достаточно просто зачеркнуть её, а в программе – вернуться к предыдущему числу. В функции solve мы должны учесть, что число при переносе цифры не может увеличиться более чем в lastnum раз, где lastnum – последняя цифра исходного числа: # РЕШАЕМ ЗАДАЧУ def solve(krat, lastnum): # число не может увеличиться # более чем в lastnum раз: if krat > lastnum: print ('Не выполняется условие krat <= lastnum!') return Например, мы переносим двойку, и она занимает в произведении первое место. Эту двойку мы можем получить только умножением единицы на двойку. Если бы число увеличилось в 3 раза, тогда при умножении единицы на 3 первой цифрой произведения была бы не двойка, а тройка, что не согласуется с условиями задачи. Также мы должны помнить, что задача имеет бесконечно много решений, и мы поступим верно, если найдём только первые два из них. Чтобы сделать функцию solve более универсальной, мы используем параметры: krat – кратность увеличения нового числа при переносе последней цифры lastnum. Скоро этот простой приём позволит нам обобщить задачу: # число решений: n_solution = 0 Первоначально искомое число и его последняя (она же - первая) цифра равны lastnum, то есть двойке. Перенос отсутствует: 514
# число: n = lastnum # первая цифра слева: n_first = lastnum # перенос в следующий разряд: perenos = 0 Теперь в бесконечном цикле while по разработанному нами алгоритму находим следующую первую цифру произведения n_first, умножая текущую первую цифру на число krat и добавляя перенос: while True: # очередная первая цифра: n_first = n_first * krat + perenos Вычисляем перенос в следующий разряд: perenos = n_first // 10 И вот здесь нас подстерегает технологическая тонкость: если очередная первая цифра окажется нулём, то мы не сможем поместить его в начало числа, поскольку Питон ведущий нуль не учитывает. В этом случае мы должны сразу же найти и следующую первую цифру, которая должна стоять перед нулём: # если очередная первая цифра # равна нулю, то # сразу вычисляем следующую # (перед ней): n_first %= 10 if n_first == 0: n_first = perenos * 10 perenos = 0 То есть мы одновременно находим 2 начальные цифры произведения. Перенос в следующий разряд, естественно, следует обнулить. Такая неприятная ситуация может возникнуть, например, если krat = 5, а очередная цифра искомого числа больше 1. Тогда мы получим: 2 (или 4, 6, 8) х 5 = 10. Обе эти цифры и следует поставить в начало искомого числа. 515
Теперь мы можем вычислить текущее значение искомого числа. Пока оно равно n, но мы должны поставить перед ним одну или 2 найденные цифры n_first. Для этого мы приписываем к ним нужное число нулей, а затем прибавляем текущее значение искомого числа: # число десятичных разрядов в текущем числе: n_10 = int(math.log10(n)) + 1 # переносим первую цифру в начало числа: n_shift = n_first * 10 ** n_10 # добавляем к текущему числу: n += n_shift Для дальнейших действий нам нужна единственная первая цифра (если их две, то последняя из них:) if n_first >= 10: n_first //= 10 Проверяем, не найдено ли решение. И в случае успеха печатаем его на экране: # если перенос отсутствует и # первая цифра равна заданной, # то число найдено: if not perenos and n_first == lastnum: n_solution += 1 #print('Решение',n_solution,':', n // 10 // krat, '*', krat, '=', n // 10) print('Решение',n_solution,':', n - n_shift, '*', krat, '=', (n - n_shift) * krat) # достаточно двух решений: if n_solution == 2: break Запускаем программу и тут же получаем 2 решения задачи (Рис. 3). Рис. 3 Первое решение нам уже известно из журнала, а второе получается из первого простым повторением искомого числа. Точно так же вы можете «утроить» или «учетверить» короткое искомое число и получить новые решения. 516
Не всегда алгоритмы, пригодные для ручного решения задачи, оказываются лучшими при машинном решении. В данной программе следует заменить «ручную» проверку более естественной, но очень неудобной при решении вручную. Действительно, из условия задачи следует, что при переносе последней двойки исходное число должно увеличиться ровно в 2 раза. Текущее значение исходного числа нам известно. Оно равно n. Чтобы убрать из исходного числа последнюю цифру, достаточно разделить его на 10: ''' # если перенос отсутствует и # первая цифра равна заданной, # то число найдено: if not perenos and n_first == lastnum: n_solution += 1 #print('Решение',n_solution,':', n // 10 // krat, '*', krat, '=', n // 10) print('Решение',n_solution,':', n - n_shift, '*', krat, '=', (n - n_shift) * krat) # достаточно двух решений: if n_solution == 2: break ''' # число без последней цифры: n_bez_last = n // 10 # умножаем последнюю цифру: n_10 = int(math.log10(n)) #n_krat = krat * 10 ** n_10 n_krat = lastnum * 10 ** n_10 # добавляем число без последней цифры: n_new = n_krat + n_bez_last Так мы получаем новое число, которое должно быть произведением исходного числа на последнюю цифру (вернее - кратность). Если это условие выполняется, то решение найдено: if n_new == n * krat: n_solution += 1 print('Решение',n_solution,':', n, '*', krat, '=', n_new) # достаточно двух решений: if n_solution == 2: break В противном случае мы продолжаем увеличивать длину исходного числа. 517
Этот алгоритм требует умножения длинных чисел, но для компьютера это не проблема. Зато алгоритм проверки чисел получился более простым и понятным. Так как при переносе двойки число может увеличиться только в 2 раза, то никаких других решений для книжной задачи не существует. Если последняя цифра числа тройка, то число может увеличиться в 2 или 3 раза: 157894736842105263 * 2 = 315789473684210526 1034482758620689655172413793 * 3 = 3103448275862068965517241379 Для четвёрки мы получим 3 решения: 210526315789473684 * 2 = 421052631578947368 1379310344827586206896551724 * 3 = 4137931034482758620689655172 102564 * 4 = 410256 Для пятёрки - 4 решения: 263157894736842105 * 2 = 526315789473684210 1724137931034482758620689655 * 3 = 5172413793103448275862068965 128205 * 4 = 512820 102040816326530612244897959183673469387755 * 5 = 510204081632653061224489795918367346938775 Для шестёрки - 5 решений: 315789473684210526 * 2 = 631578947368421052 518
2068965517241379310344827586 * 3 = 6206896551724137931034482758 153846 * 4 = 615384 122448979591836734693877551020408163265306 * 5 = 612244897959183673469387755102040816326530 1016949152542372881355932203389830508474576271186440677966 * 6 = 6101694915254237288135593220338983050847457627118644067796 Для семёрки - 6 решений: 368421052631578947 * 2 = 736842105263157894 2413793103448275862068965517 * 3 = 7241379310344827586206896551 179487 * 4 = 717948 142857 * 5 = 714285 1186440677966101694915254237288135593220338983050847457627 * 6 = 7118644067796610169491525423728813559322033898305084745762 1014492753623188405797 * 7 = 7101449275362318840579 Для восьмёрки - 7 решений: 421052631578947368 * 2 = 842105263157894736 2758620689655172413793103448 * 3 = 8275862068965517241379310344 205128 * 4 = 820512 163265306122448979591836734693877551020408 * 5 = 816326530612244897959183673469387755102040 519
1355932203389830508474576271186440677966101694915254237288 * 6 = 8135593220338983050847457627118644067796610169491525423728 1159420289855072463768 * 7 = 8115942028985507246376 1012658227848 * 8 = 8101265822784 Для девятки - 8 решений: 473684210526315789 * 2 = 947368421052631578 3103448275862068965517241379 * 3 = 9310344827586206896551724137 230769 * 4 = 923076 183673469387755102040816326530612244897959 * 5 = 918367346938775510204081632653061224489795 1525423728813559322033898305084745762711864406779661016949 * 6 = 9152542372881355932203389830508474576271186440677966101694 1304347826086956521739 * 7 = 9130434782608695652173 1139240506329 * 8 = 9113924050632 10112359550561797752808988764044943820224719 * 9 = 91011235955056179775280898876404494382022471 Любопытно, что некоторые искомые числа можно отыскать среди дробей: 2/19 3/19 4/19 5/19 6/19 7/19 8/19 9/19 = = = = = = = = 0.105263157894736842… 0.157894736842105263… 0.210526315789473684… 0.263157894736842105… 0.315789473684210526… 0.368421052631578947… 0.421052631578947368… 0.473684210526315789… 520
Так как обычной точности для вычисления этих дробей не хватает, то нужно воспользоваться модулем decimal: from decimal import Decimal for i in range(2, 10): n1 = Decimal(i) n2 = Decimal(19) n3 = n1 / n2 print(n3) Легко заметить, что период всех дробей равен 18, поэтому, чтобы получить целые числа, дроби следует умножить на 1018: 105263157894736842 157894736842105263 210526315789473684 263157894736842105 315789473684210526 368421052631578947 421052631578947368 473684210526315789 При переносе последней цифры в начало числа оно увеличится в 2 раза. Аналогично можно найти числа, которые увеличиваются в 3 раза при переносе последней цифры: from decimal import * getcontext().prec = 28 print() for i in range(3,10): n1 = Decimal(i) n2 = Decimal(29) n3 = n1 / n2 print(n3*10**28) 1034482758620689655172413793 1379310344827586206896551724 1724137931034482758620689655 521
2068965517241379310344827586 2413793103448275862068965517 2758620689655172413793103448 3103448275862068965517241379 В 4 раза: getcontext().prec = 6 print() for i in range(4,10): n1 = Decimal(i) n2 = Decimal(39) n3 = n1 / n2 print(n3*10**6) 102564 128205 153846 179487 205128 230769 В 5 раз: 102041 122449 142857 163265 183673 В 6 раз: getcontext().prec = 58 print() for i in range(6,10): n1 = Decimal(i) n2 = Decimal(59) n3 = n1 / n2 print(n3*10**58) 1016949152542372881355932203389830508474576271186440677966 1186440677966101694915254237288135593220338983050847457627 1355932203389830508474576271186440677966101694915254237288 1525423728813559322033898305084745762711864406779661016949 522
В 7 раз: getcontext().prec = 22 print() for i in range(7,10): n1 = Decimal(i) n2 = Decimal(69) n3 = n1 / n2 print(n3*10**22) 1014492753623188405797 1159420289855072463768 1304347826086956521739 В 8 раз: getcontext().prec = 13 print() for i in range(8,10): n1 = Decimal(i) n2 = Decimal(79) n3 = n1 / n2 print(n3*10**13) 1012658227848 1139240506329 В 9 раз: getcontext().prec = 44 print() for i in range(9,10): n1 = Decimal(i) n2 = Decimal(89) n3 = n1 / n2 print(n3*10**44) 10112359550561797752808988764044943820224719 Так как при переносе последней цифры n число увеличивается не более чем в n раз, то общее количество чисел каждый раз уменьшается на 1. Числитель дроби равен последней цифре числа, а знаменатель – 19, 29, 39,…, 89, то есть 10 * n – 1. А вот длину периода можно найти только опытным путём. 523
В англоязычной литературе рассмотренные нами числа называются паразитическими (parasitic numbers, parasite numbers). Вы можете прочитать о них в книге Клиффорда Пиковера Wonders of Numbers Adventures in Mathematics, Mind, and Meaning, в Главе 80 (Рис. 3). Рис. 3 Например, числа, увеличивающиеся в 2 раза при переносе последней цифры, называются 2-parasite. В 3 раза – 3-parasite. И так далее. Ассоциации цифр и чисел с биологическими паразитами слишком натянуты, чтобы принимать их всерьёз. Настоящими паразитическими числами считаются такие, которые увеличиваются (или уменьшаются) в k раз, если последняя цифра n = k. Если k отличается от n, то числа называются псевдопаразитическими (pseudoparasite numbers, pseudoparasites). Интересный материал о паразитных числах вы найдёте также на сайте Wikipedia. Пиковер предлагает вычислить дробь 137174210/1111111111 и найти в ней что-то особенное. Программа очень простая: 524
getcontext().prec = 30 print() n1 = Decimal(137174210) n2 = Decimal(1111111111) n3 = n1 / n2 print(n3) А дробь, действительно, очень любопытная! В ней бесконечно повторяются все цифры по порядку: 0.123456789012345678901234567890 Клиффорд Пиковер предлагает найти и ультрапаразитов, под которыми он подразумевает числа, которые увеличиваются в несколько раз при перемещении и первой и последней цифр. В книге нет примеров таких чисел. Существуют ли они – неизвестно. Если первую и последнюю цифры просто поменять местами, то они должны быть равны, но при этом исходное число не изменится. В журнале Наука и жизнь №11 за 1971 год, на странице 118 напечатана заметка (Рис. 4). Рис. 4 Этот пример – почти ответ на задачу Пиковера, но в нём первая и последняя цифры не меняются местами, а добавляются новые цифры в начало и конец числа. В журнале Наука и жизнь, №2 за 1972 год, на странице 142 напечатана заметка о паразитических числах (Рис. 5). 525
Рис. 5 Легко заметить, что в ней присутствуют числа, начинающиеся с нуля, которые мы не учитывали. Там же сообщается (Рис. 6). Рис. 6 526
Числа-палиндромы Слова-палиндромы всем хорошо известны с детства: ДЕД ОКО ДОХОД ЗАКАЗ КАБАК РОТОР ТОПОТ ШАЛАШ РОТАТОР – самый длинный палиндром в русском языке Таких слов в любом языке всего ничего, поскольку словарный запас весьма ограничен. А вот чисел бесконечно много, поэтому и палиндромов среди них не счесть! Но числа-палиндромы всё равно очень интересные и часто встречаются на олимпиадах и в занимательных задачах. Проект Задача 4 с сайта Проект Эйлер Исходный код программы находится в файле Эйлер004.py. Задача номер 4 (Problem 4) называется Largest palindrome product. Она была опубликована 16 ноября 2001 года. A palindromic number reads the same both ways. The largest palindrome made from the product of two 2-digit numbers is 9009 = 91 * 99. Find the largest palindrome made from the product of two 3-digit numbers. Наибольшее произведение-палиндром Число-палиндром с обеих сторон (справа налево и слева направо) читается одинаково. Самое большое число-палиндром, полученное умножением двух двузначных чисел – 9009 = 91 × 99. Найдите самый большой палиндром, полученный умножением двух трехзначных чисел. Задача любопытная и не очень сложная. 527
Обозначим первое трёхзначное число буквой i, а второе - буквой j. Чтобы не находить каждый палиндром дважды, будем считать, что j >= i. Тогда значениями переменной i могут быть числа от 100 (наименьшее трёхзначное число) до 999 (наибольшее трёхзначное число). Переменная j может иметь значения от i до 999. Во вложенных циклах мы находим произведения всех трёхзначных чисел, выделяем из них палиндромы и запоминаем самый большой из них. Функция main вызывает функцию solve, которая и решает задачу: # ГЛАВНАЯ ФУНКЦИЯ def main(): print("Решаем задачу 4 из Проекта Эйлер") solve() main() Самое сложное действие в функции solve – это определение «палиндромности» очередного произведения: # РЕШАЕМ ЗАДАЧУ def solve(): nVar = 0 max = 0 maxi = 0 maxj = 0 for i in range(100, 999 + 1): for j in range(i, 999 + 1): mul = i * j smul = str(mul) if isPalindrome(smul): print(i, "*", j, "=", smul) if mul > max: max = mul maxi = i maxj = j nVar += 1 print("Всего палиндромов:", nVar) print() print("Наибольший палиндром =", maxi, "*", maxj, "=", max) print() 528
Наиболее простой способ для этого – преобразовать число в строку, а затем проверить её: # Project Euler. Problem 4 def isPalindrome(word): flg = True lens = len(word) # слово-палиндром? for i in range(lens // 2): ch1 = word[-i - 1] ch2 = word[i] if ch1 != ch2: # не палиндром: flg = False break return flg Вот и вся задача! Запускаем программу и заглядываем в конец длинного списка чисел (Рис. 1). Рис. 1 529
Цифровой вариант решения Исходный код программы находится в файле Эйлер004а.py. Обычно на всякого рода олимпиадах и конкурсах в программах следует использовать только такие возможности, которые имеются практически во всех языках программирования. За функцию str мы поручиться не можем, поэтому должны либо написать такую функцию для нашего проекта, либо решить задачу вообще без строк, что, безусловно, правильнее, коль мы имеем дело с числами. Самая важная функция в нашей программе - это isPalindrome, которая сравнивает симметричные относительно середины слова word символы. При этом символы извлекаются из строки, как из списка. Из целого числа так просто извлечь нужную цифру нельзя. Это значит, что нам нужно превратить заданное число в список цифр digits. Тогда функцию isPalindrome легко переделать: def isPalindrome(digits): flg = True lend = len(digits) # число-палиндром? for i in range(lend // 2): d1 = digits[-i - 1] d2 = digits[i] if d1 != d2: # не палиндром: flg = False break return flg В функции getDigits мы последовательно выделяем из числа его цифры, начиная с разряда единиц, то есть двигаемся справа налево. Каждую цифру помещаем в список цифр digits: def getDigits(num): digits = [] while num > 0: digits.append(num % 10) num //= 10 530
digits.reverse() return digits Заполнение списка происходит так. Если число равно нулю, то список будет содержать единственный элемент со значением 0. Цикл while не выполнится ни разу. Во всех остальных случаях (мы не рассматриваем отрицательные числа, поскольку у нас их быть не может) выполняется тело цикла. Последнюю цифру числа легко выделить, если найти остаток от деления этого числа на 10. Например, для однозначных чисел это будет само число. Для многозначных – его последняя цифра: 102 999 → 2 → 9 Поместив цифру в список digits, мы должны избавиться от последней цифры в числе, которую мы уже определили в список. Для этого мы просто делим число на 10. Так как число num имеет целый тип, то останется только целая часть частного: 102 999 → 10 → 99 Теперь цикл while добавит в список последнюю цифру частного: 10 99 → 0 → 9 И укоротит число ещё на одну – последнюю – цифру: 10 99 → 1 → 9 В следующей итерации в список попадут оставшиеся цифры: 1 9 → 1 → 9 А при делении однозначных чисел переменная num обратится в нуль: 1/10 9/10 → 0 → 0 Условие в цикле while станет ложным, и он завершится. 531
В списке digits мы имеем все цифры заданного числа: 102 → 1 0 2 999 → 9 9 9 Мы записали цифры заданного числа в обратном порядке. Поскольку мы ищем палиндромы, то это не имеет значения. Если же вы хотите получить список цифр в правильном порядке, то после заполнения списка примените к нему метод расширения reverse. И нам осталось в функции solve сделать небольшие исправления: # РЕШАЕМ ЗАДАЧУ def solve(): nVar = 0 max = 0 maxi = 0 maxj = 0 for i in range(100, 999 + 1): for j in range(i, 999 + 1): mul = i * j if isPalindrome(getDigits(mul)): print(i, "*", j, "=", mul) if mul > max: max = mul maxi = i maxj = j nVar += 1 print("Всего палиндромов:", nVar) print() print("Наибольший палиндром =", maxi, "*", maxj, "=", max) print() Проверка показывает, что новый вариант программы работает безукоризненно (Рис. 2). Рис. 2 532
Пифагоровы тройки чисел Если вы хотя бы раз посетили урок геометрии, то наверняка запомнили теорему про пифагоровы штаны, которые во все стороны равны. Выражаясь «по-научному», теорема Пифагора (Рис. 1) гласит: квадрат гипотенузы прямоугольного треугольника равен сумме квадратов его катетов. Начертим для наглядности чертёж (Рис. 2). Рис. 1. Πυθαγόρας ὁ Σάμιος (лат. Pythagoras, 570—490 гг. до н. э.) - древнегреческий математик Это соотношение сторон прямоугольного треугольника было известно ещё древним вавилонянам за 2 тысячи лет до Пифагора. Более подробную информацию о задаче Пифагора вы найдёте в книге [ОО80], на страницах 10-12. Пользуясь теоремой Пифагора, мы легко вычислим длину третьей стороны прямоугольного треугольника, если две нам известны. Например: a = √𝒄𝟐 + 𝒃𝟐 Поскольку в этой формуле присутствует квадратный корень, то число a вполне может оказаться и не целым даже при целых c и b. Конечно, особенно интересны такие прямоугольные треугольники, у которых длины всех сторон выражаются целыми числами. Самый простой из таких треугольников известен каждому: 533
a = 3 b = 4 c = 5 Рис. 2. Теорема Пифагора Вопрос в том, как найти другие целочисленные прямоугольные треугольники. Ответ на него знали ещё древние греки, а способ их нахождения подробно изложен в упомянутой выше книге Оре, в пятой главе. Всякая тройка целых чисел, удовлетворяющих уравнению на Рис. 2⬆, называется пифагоровой тройкой. Одну такую тройку мы уже знаем - (3, 4, 5). В общем виде все пифагоровы тройки можно записать так: (a, b, c) Из того же уравнения следует, что равенство не нарушится, если мы умножим длину всех сторон на некоторое натуральное число k. Следовательно, если (a, b, c) – пифагорова тройка, то и (ka, kb, kc) – также пифагорова тройка. Например, при k=2 мы получим из пифагоровой тройки (3,4,5) такую пифагорову тройку - (6, 8, 10), а при k=3 - (9,12,15). Поэтому обычно отыскивают простейшие пифагоровы тройки, которые состоят из попарно взаимно простых чисел. Некоторые свойства пифагоровых троек: 534
1. Из двух чисел a и b – одно чётное, а второе нечётное. 2. Число с всегда нечётное. 3. Если числа m и n удовлетворяют условиям: - они взаимно простые, т.е. НОД(m,n)=1; - m больше n; - одно из чисел m, n – чётное, а второе нечётное, то числа a,b,c, образующие простейшую пифагорову тройку, можно найти по формулам: a = 2mn b = m2-n2 c = m2+n2 (1) Иногда удобнее пользоваться условием: - оба числа m, n – нечётные. Тогда выполняются соотношения: a = mn b = (m2-n2 )/2 c = (m2+n2)/2 (2) Проект Пифагоровы тройки Исходный код программы находится в файле Пифагоровы тройки.py. Поскольку даже простейших пифагоровых троек бесконечно много, то вполне разумно ограничить поиски каким-либо достаточно большим числом. Например, тысячей: # ГЛАВНАЯ ФУНКЦИЯ def main(): # макс. пифагорово число: MAX_NUM = 1000 В функции main мы вводим число в диапазоне 5..MAX_NUM. Мы знаем, что хотя бы одно из чисел пифагоровой тройки не меньше пяти, поэтому совершенно бесполезно задавать числа ещё меньше. А число пользователя max означает, что все числа пифагоровой тройки должны быть не больше 535
этого числа. Например, при max = 5 мы найдём единственную – первую – тройку пифагоровых чисел: # ГЛАВНАЯ ФУНКЦИЯ def main(): # макс. пифагорово число: MAX_NUM = 1000 # бесконечный цикл ввода данных # пока пользователь не закроет программу: while True: print() print("Пифагоровы тройки") print() print("Макс. пифагорово число (5 ..", MAX_NUM, ") > ", end = '') max = int(input()) if (max < 5): return # ищем тройки: var = Pythagor(max) print("Всего найдено троек -", var) print() main() Затем функция Pythagor получает число max и по формулам (2) находит все пифагоровы тройки чисел, не превышающие max (Рис. 1): # # # # -*- coding: Windows-1251 -*Пифагоровы тройки ПРОГРАММА ДЛЯ НАХОЖДЕНИЯ ПРОСТЕЙШИХ ПИФАГОРОВЫХ ТРОЕК # РЕШАЕМ ЗАДАЧУ def Pythagor(max): var = 0 n = 1 m = 3 while ((m * m + n * n) / 2 <= max): while ((m * m + n * n) / 2 <= max): # числа m и n должны быть # взаимно простыми: if (nod(m, n) == 1): print("a =", m * n) print("b =", (m * m - n * n) // 2) print("c =", (m * m + n * n) // 2) print() 536
var += 1 m += 2 n += 2 m = n +2 return var Рис. 1. Пифагоровы тройки чисел Для начала мы задаём минимальные значения переменным m и n, исходя из того, что это натуральные нечётные числа, причём m > n. Поскольку обе переменные изменяются, то нам потребуется пара вложенных циклов. Лучше всего подходят циклы while, потому что в них удобно проверять условие: наибольшая сторона не должна превышать заданного значения max. В прямоугольном треугольнике самая длинная сторона – гипотенуза, так что условие должно выглядеть так (см. формулу 2): c = (m2+n2)/2 <= max Внутри циклов проверяем, что числа m и n взаимно простые: # числа m и n должны быть # взаимно простыми: if (nod(m, n) == 1): 537
И если это так, то мы по тем же формулам находим стороны треугольника и выписываем их на экране. В нашем случае это будет первый пифагоров треугольник со сторонами 3,4,5. Затем во внутреннем цикле мы увеличиваем переменную m на два, чтобы получить следующее нечётное число: m += 2 Значение переменной n остаётся без изменений (сейчас n=1, m=5). И так мы увеличиваем переменную m до тех пор, пока выполняется условие цикла. Когда внутренний цикл закончится, мы перейдём во внешний цикл и увеличим значение переменной n на два: n += 2 То есть в нашем примере n = 3. Так как m > n, то для m мы выбираем следующее нечётное число, большее n, то есть n+2. После завершения обоих циклов все простейшие пифагоровы тройки, в которых наибольшее число не превышает max, будут найдены. Задания для самостоятельного решения Числа Армстронга Числа Армстронга существуют не только в десятичной системе, но и во многих других. Причём количество чисел Армстронга в любой системе счисления конечно: Основание Наибольшая степень Количество чисел Армстронга 2 3 4 1 3 4 1 5 11 538
5 6 7 8 9 10 11 12 13 14 15 16 14 18 23 29 30 39 45 51 61 64 57 73 17 30 59 62 58 88 134 86 201 103 201 295 Напишите программу для их поиска. Нарциссические числа Иногда нарциссическими называют не только числа Армстронга, но и другие, которые обладают схожими свойствами. А. Степени цифр образуют возрастающую последовательность чисел, начинающуюся с единицы (не обязательное требование): 89 = 81 + 92 135 175 518 598 = = = = 11 11 51 51 + + + + 32 72 12 92 + + + + 53 53 83 83 1306 = 11 + 32 + 03 + 64 1676 = 11 + 62 + 73 + 64 2427 = 21 + 42 + 23 + 74 2646798 = 21 + 62 + 43 + 64 + 75 + 96 + 87 43 = 42 + 33 63 = 62 + 33 47016 = 42 + 73 + 04 + 15 + 66 542186 = 52 + 43 + 24 + 15 + 86 + 67 670737197 = 62 + 73 + 04 + 75 + 36 + 77 + 18 + 99 + 710 6714 = 63 + 74 + 15 + 46 Б. Или убывающую: 539
24 = 23 + 42 1676 = 15 + 64 + 73 + 62 4975929 = 48 + 97 + 76 + 55 + 94 + 23 + 92 332 = 35 + 34 + 23 В. Но интереснее всего, конечно, найти числа, равные сумме степеней цифр, когда степень цифры совпадает с самой цифрой! 1 = 11 3435 = 33 + 44 + 33 + 55 Напишите программу для поиска таких чисел. 540
Глава #11. Комбинаторика и теория вероятностей Комбинаторика - это раздел математики, который изучает множества (совокупности, наборы) каких-либо элементов. Первая книга по комбинаторике вышла в 1666 году под названием Рассуждения о комбинаторном искусстве. Её написал известный немецкий математик Готфрид Вильгельм фон Лейбниц, который и придумал название комбинаторика этому разделу математики. Как в жизни, так и в программировании комбинаторные задачи встречаются очень часто. Например, сколько различных слов можно составить из букв русского алфавита? Сколько существует различных комбинаций при игре в кости двумя или тремя кубиками? Сколько разных нарядов можно составить из трех юбок и четырёх блузок и так далее. Все комбинаторные задачи решаются с помощью комбинаторных конфигураций: размещений, перестановок, сочетаний, композиций и разбиений. Перестановки А начнём мы наши комбинаторные забавы с перестановок элементов. Возьмём множество, состоящее из трёх разных элементов, например, русских букв - {К, О, Т}. Всякое «слово», составленное из всех этих букв без повторений, и называется перестановкой элементов множества. Поскольку в множестве элементы не упорядочены, то мы выпишем их сначала в произвольном порядке, например так: 1. КОТ Вот мы и получили первую перестановку, а значит, и первое слово – КОТ. Оно «случайно» совпало с настоящим русским словом. Чтобы найти вторую перестановку, поменяем местами вторую и третью буквы: 2. КТО Тоже получилось неплохо, ведь кто - это русское местоимение. Давайте поменяем местами теперь первую и вторую буквы: 3. ТКО 541
Такого слова нет (в старой речи была частица –тко, имеющая тот же смысл, что и современная -ка: бери-тко, читай-тко), а вот четвёртая перестановка снова удачная. Чтобы её получить, поменяем местами вторую и третью буквы: 4. ТОК Снова меняем местами первую и вторую буквы - и получаем пятую перестановку: 5. ОТК Не ахти какое слово, но Отдел технического контроля тоже сгодится. И последнюю перестановку мы найдём, переставив две последние буквы: 6. ОКТ ОКТ – тоже сокращение от Оптическая когерентная томография, так что все наши перестановки из трёх букв К, О, Т оказались не совсем бессмысленными. Конечно, с другими буквами результат был бы другим. Но почему мы можем утверждать, что нашли все перестановки множества из трёх элементов? – Давайте рассуждать логически. На первом месте в слове может стоять любая из трёх букв. На второе место можно поставить любую из двух оставшихся, а для последнего места останется только одна буква. Таким образом, всего можно составить 3 х 2 х 1 = 6 разных слов. Но мы ровно столько и составили, значит, других слов из этих букв составить нельзя (естественно, мы используем каждую букву только один раз!). Если взять множество из четырёх разных элементов, то, рассуждая аналогично, мы придём к выводу, что из них можно составить 4 х 3 х 2 х 1 = 24 разных слова. Этот ряд легко продолжить сколь угодно далеко, а произведение чисел от единицы до заданного называется факториалом. Как его вычислять, вы уже знаете. 542
Проект Генерируем перестановки Исходный код программы находится в файле Генерируем перестановки.py. Бесконечный цикл while Метод int Условный оператор if Оператор return Списки Цикл for Функция с параметрами Условный оператор if-else Рекурсивная функция Мы научились подсчитывать общее число перестановок. Ну, пусть мы знаем, что из трёх разных букв можно составить 3! разных слов, но вопрос в том, как получить эти перестановки? С множеством из трёх элементов мы легко справились, а если взять больше – четыре, пять, а то и восемь? Оказывается, мы не первые, кто задался этим вопросом. Ещё в семнадцатом веке английские звонари научились выбивать на нескольких разных колоколах «мелодии», состоящие из всех перестановок этих колоколов. Например, для трёх колоколов нужно было сыграть такую мелодию: Первый колокол – второй – третий. Второй колокол – первый – третий. Дальше вы и сами продолжите эту музыку. Колоколов, конечно, было больше, а последовательность колокольных ударов нужно было держать в голове. Например, в Книге рекордов Гиннеса рассказывается о том, что в 1963 году за 17 с лишним часов удалось выбить на восьми колоколах все 8! = 40320 перестановок. В семнадцатом веке, конечно, эти музыкально-комбинаторные экзерсисы были короче, но, тем не менее, запомнить многие сотни перестановок было совсем непросто, поэтому звонари придумывали свои способы для «генерирования» всех колокольных перестановок. Один из таких способов мы и положим в основу компьютерной программы, которая быстро и правильно выпишет все перестановки элементов заданного множества. 543
Переменная MAX_ELEM – это ограничитель числа элементов в множестве, иначе их печать на экране займёт очень много времени. В переменной nPerm мы будем хранить числа перестановок, а в списке а – собственно перестановки: # -*- 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 " 544
# число перестановок: nPerm = 0 # список для хранения очередной перестановки --> # начальная перестановка: a = list(range(1, nElem + 1)) Генерирование всех остальных перестановок, а также их печать происходит в рекурсивной функции permutationRec, которой мы передаём три аргумента: 1. всегда 0 2. число элементов в списке nElem 3. список a nPerm = 0 # генерируем перестановки: permutationRec(0, nElem, a) print("Число перестановок =", 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] Запускаем программу и проверяем её работу при различных значениях числа элементов в множестве. Рис. 1 убеждает нас, что программа верно генерирует перестановки. 545
Рис. 1. Перестановочная программа в работе! Проект Функция permutations Исходный код программы находится в файле Функция permutations.py. В модуле itertools есть функция permutations, которая возвращает все перестановки заданного набора элементов без повторений в лексикографическом порядке. Импортируем эту функцию в проект: # -*- coding: Windows-1251 -*# Функция permutations from itertools import permutations В главной функции создаём список элементов заданной длины: # макс. число элементов в множестве: MAX_ELEM = 8 # ГЛАВНАЯ ФУНКЦИЯ def main(): print('Функция permutations') 546
print() # бесконечный цикл ввода данных # пока пользователь не закроет программу # или не введет 0: while True: s = "Число элементов (1.." + str(MAX_ELEM) + ") > print(s, end='') # число элементов: nElem = int(input()) # если пользователь ввёл 0, # то программу закрываем: if (nElem == 0): return " # список элементов для перестановки: a = list(range(1, nElem + 1)) Получаем список всех перестановок (каждая перестановка – это кортеж) и печатаем перестановки в Консольном окне: # генерируем перестановки: perm = list(permutations(a)) # распечатываем перестановки: for p in perm: print(p) print("Число перестановок =", len(perm)) print() if __name__ == "__main__": main() При сравнении перестановок легко заметить, что функция permutations выдаёт перестановки в лексикографическом порядке (Рис. 1) в отличие от нашей функции permutationRec. Точно так же можно получить перестановки элементов кортежа (Рис. 2): # перестановки кортежа: a = (0, 2, 4) for p in permutations(a): print(p) a = ('К', 'О', 'Т') for p in permutations(a): print(p) 547
Рис. 1 Рис. 2 Буквы в слове (Рис. 3): # перестановки букв в слове: for p in permutations("КРОТ"): # буквы: print(p) Так можно найти анаграммы к заданному слову. Например, в этом списке имеется парная анаграмма КРОТ – КОРТ (Рис. 4): # перестановки букв в слове: for p in permutations("КРОТ"): # буквы: #print(p) # слово: print("".join(p)) 548
По умолчанию длина перестановок, генерируемых функцией permutations, равна длине набора элементов. Но вы можете задать длину перестановок, передавая в функцию permutations второй аргумент: perm = list(permutations(a, 3)) Рис. 3 Рис. 4 И даже список слов можно переставлять на все лады (Рис. 5): # список слов: lst = ["РАЗ", "ДВА", "ТРИ"] for p in permutations(lst): print(p) 549
Рис. 5 Мы получили перестановки набора из четырёх элементов длиной 3 (Рис. 6). Рис. 6 550
Проект «Правильные» перестановки Исходный код программы находится в файле «Правильные» перестановки.py. Бесконечный цикл while Метод int Функция с параметрами Список Цикл for Цикл while Оператор return Давайте напишем собственную функцию, которая выдаёт перестановки в правильном, лексикографическом порядке, то есть в таком, в каком стоят слова в словаре. Например, перестановка 2 4 1 3 должна предшествовать перестановке 2 4 3 1, поскольку единица меньше тройки. Точно так же перестановка 4 1 2 3 должна занимать место выше перестановки 4 1 3 2. Функцию main мы опять используем для ввода числа элементов в списке и вызова функции permutation: # ГЛАВНАЯ ФУНКЦИЯ def main(): print('«Правильные» перестановки') print() # бесконечный цикл ввода данных # пока пользователь не закроет программу # или не введет 0: while True: s = "Число элементов (1.." + str(MAX_ELEM) + ") > print(s, end='') # число элементов: nElem = int(input()) # если пользователь ввёл 0, # то программу закрываем: if (nElem == 0): return " # генерируем перестановки: n = permutation(nElem) print("Число перестановок =", n) print() 551
if __name__ == "__main__": main() Эта функция нерекурсивная, поэтому создание и инициализацию списка можно перенести непосредственно в неё. Как обычно, первую перестановку образуют последовательные числа 1..n, которые затем переставляются так, чтобы сохранялся лексикографический порядок перестановок: # -*- coding: Windows-1251 -*# Генерация перестановок в лексикографическом порядке # макс. число элементов в # множестве: MAX_ELEM = 8 # ГЕНЕРИРУЕМ ПЕРЕСТАНОВКИ 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 return nPerm 552
На Рис. 1 вы видите, что все перестановки заняли свои места. Рис. 1. Перестановки по порядку Обратите внимание, что в списке а первый и последний элементы всегда равны нулю, поэтому при печати не используются. Проект Премия за изобретение Исходный код программы находится в файле Премия за изобретение.py. Список Функция без параметров Функцмя с параметрами Оператор return Цикл for Генерирование перестановок – процесс занимательный и познавательный уже сам по себе, но нам перестановки нужны для решения задач. Задача 15 (16) из книги Удивительный мир чисел [КА86], страница 67: Четыре изобретателя независимо друг от друга придумали 10 различных приспособлений: Андрей - 1, Борис - 2, Виктор - 3 и Григорий - 4. 553
Когда внедрили их в производство, то выяснилось, что каждое приспособление, разработанное одним и тем же изобретателем, даёт одну и ту же годовую экономию: • • • • чьё-то по 25 тыс. р., чьё-то по 125 тыс. р., чьё-то по 625 тыс. р., а чьё-то даже по 3125 тыс. р. В результате годовая экономия составила 8350 тыс. р. 10% этой суммы выделили на премию, которую распределили между изобретателями пропорционально вкладу каждого в общую годовую экономию. Сколько рублей получил каждый из них в качестве премии? Создадим список для хранения годовой экономии по приспособлениям каждого изобретателя: # РЕШАЕМ ЗАДАЧУ def solve(): # список годовой экономии приспособлений: eco = [0, 25, 125, 625, 3125] Чтобы используемые значения имели индексы 1..4, мы добавляем нулевой элемент с нулевым же значением. Тогда: 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 554
Итого: 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 25 + 250 + 2500 + 9375 = 12150 Опять не получилось! Мы должны составить новую перестановку изобретателей, найти для неё годовую экономию – и так продолжать до тех пор, пока сумма зелёных чисел в таблице не совпадёт с заданной. Поскольку алгоритм мы придумали, и главную роль в нём играют перестановки чисел 1..4, то вполне разумно обратиться к функции permutation, который умеет генерировать перестановки. При этом мы должны учесть, что нам нужны не сами перестановки, а общая годовая экономия, так что эту функцию нужно доработать. Начнём новый проект с объявления переменной Summa, значение которой равно общей годовой экономии: # -*- coding: Windows-1251 -*# Кордемский, с.67, Задача 15 # годовая экономия: 555
Summa = 8350 Функция main вызывает функцию solve для решения поставленной задачи, получает от неё число найденных решений и печатает его на экране: # ГЛАВНАЯ ФУНКЦИЯ def main(): print('Премия за изобретение') print() nVar = solve() print("Найдены все варианты решения -", nVar) print() if __name__ == "__main__": main() Функция solve создаёт список eco, о котором мы говорили выше, и передаёт его функции permutation: # РЕШАЕМ ЗАДАЧУ def solve(): # список годовой экономии приспособлений: eco = [0, 25, 125, 625, 3125] return permutation(4, eco) На этот раз функция permutation получает не только число элементов в списке, но и список годовой экономии для приспособлений каждого изобретателя. Так мы учитываем специфику конкретной задачи. Мы не знаем наверняка, что задача имеет единственное решение, поэтому будем посчитывать варианты: # ГЕНЕРИРУЕМ ПЕРЕСТАНОВКИ def permutation(n, eco): # число вариантов: nVar = 0 Для каждой перестановки а мы находим общую годовую экономию и сравниваем её с заданной. Для этого пишем функцию getSumma, которой передаём очередную перестановку чисел 1..4 и список годовых экономий eco: 556
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 в противном случае: 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 557
И последняя функция проекта - 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='') 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 тыс. р. Также вы видите на Рис. 1, что каждый изобретатель получил 10% от суммы головой экономии: • • • • Григорий - 10 тыс. р. Андрей – 12,5 тыс. р. Виктор – 187,5 тыс. р. Борис – 625 тыс. р. А задача имеет единственное решение, напрасно мы сомневались… 558
Рис. 1. Изобретатели выявлены Проект Массивные перестановки Исходный код программы находится в файле Массивные перестановки.py. Функцияс параметрами Оператор return Цикл for Список списков Цикл while Бесконечный цикл while Метод int Последний проект показал несовершенство функции permutation: при решении каждой новой задачи её приходится изменять. Гораздо лучше иметь такую функцию, которая возвращает все перестановки в вызывающую функцию, а та делает с ними всё, что захочет. Тогда функция permutation будет совершенно одинакова во всех программах! Итак, функция permutation должна возвращать все перестановки. Их число легко найти по формуле для вычисления факториала: # ВЫЧИСЛЯЕМ ФАКТОРИАЛ ЗАДАННОГО ЧИСЛА def factorial(num): if (num == 0): return 1 559
fact = num for i in range(2, num): fact *= i return fact Перестановки можно хранить в списке. Но каждую перестановку мы также храним в списке. Это значит, что для хранения перестановок нужен список списков, который напоминает двумерный массив. Общее число перестановок, а значит и число элементов в первом списке равно: # -*- coding: Windows-1251 -*# Генерируем перестановки # макс. число элементов в # множестве: MAX_ELEM = 8 # ГЕНЕРИРУЕМ ПЕРЕСТАНОВКИ def permutation(n): # число перестановок: allPerm = factorial(n) Число элементов во втором списке в точности равно числу элементов множества (в перестановке). Теперь мы можем создать список списков для хранения всех перестановок: # список перестановок: 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): 560
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 Для проверки новой функции отправляемся в функцию main, в которой нужно изменить всего несколько строк: # ГЛАВНАЯ ФУНКЦИЯ 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)) 561
print() if __name__ == "__main__": main() На Рис. 1 отчётливо видно, что новая функция работает без ошибок! Рис. 1. Списки перестановок Проект Сумма пятизначных чисел Исходный код программы находится в файле Сумма пятизначных чисел.py. Функция без параметров Список списков Вложенные циклы for Применим новый способ генерирования перестановок в этом проекте. Задача 11 (4-5) из книги Удивительный мир чисел [КА86], страница 65: Предположим, что из цифр 1, 2, 3, 4, 5, 6 составлены всевозможные пятизначные числа, причём все цифры в записи каждого числа различны. 562
Чему равна сумма всех таких пятизначных чисел? Легко посчитать, что пятизначных чисел из цифр 1..6 можно составить столько же, сколько и перестановок из этих цифр. Первое место в пятизначном числе может занимать одна из 6 цифр, второе – одна из 5 оставшихся, и так далее. Всего: 6 * 5 * 4 * 3 * 2 = 6! Следовательно, мы должны сначала получить все перестановки из цифр 1..6, затем составить из них пятизначные числа, и наконец, найти их сумму. Все перестановки генерирует функция permutation, которая и возвращает их в список perms (Рис. 1): # ГЛАВНАЯ ФУНКЦИЯ def main(): print('Сумма пятизначных чисел') print() solve() print() if __name__ == "__main__": main() # -*- coding: Windows-1251 -*# Кордемский, с.65, Задача 11 # РЕШАЕМ ЗАДАЧУ def solve(): # генерируем перестановки: perms = permutation(6) Поскольку перестановки состоят из 6 цифр, а нам нужны только 5, то мы будем составлять 5-значные числа из первых пяти цифр (или из последних пяти цифр – кто как пожелает). Зная цифры, само число легко вычислить, последовательно умножая число, составленное из 1..n-1 первых цифр на 10 и прибавляя к произведению следующую цифру: 563
# находим сумму всех чисел: sum = 0 for p in range(0, factorial(6)): num = 0 for n in range(0, 5): num = num * 10 + perms[p][n] Рис. 1. Пятизначные числа Полученное 5-значное число добавляем к промежуточной сумме: sum += num print("Сумма пятизначных чисел равна", sum) Запускаем программу и получаем ответ (Рис. 2). Рис. 2. Сумма найдена 564
Проект Как собрать Команду мечты? Исходный код программы находится в файле Как собрать Команду мечты.py. Список Бесконечный цикл while Метод int Условный оператор if Оператор return Оператор continue Функцияс параметрами Цикл while Оператор break Условный оператор if-else Функция без параметров Давайте решим ещё одну жизненно важную задачу. Представим себе, что из 16 футболистов нам нужно выпустить на поле 11. Общее число футболистов для комбинаторики - это число элементов в множестве, а число игроков в команде – число элементов в подмножестве. Таким образом, нам нужно найти все подмножества заданного множества. Каждое подмножество какого-либо множества иначе называют набором из заданного числа элементов, или сочетанием. В сочетании порядок элементов не играет роли, поэтому мы отберём только 11 игроков в команду, а как между ними поделить номера на футболках (а, значит, и их амплуа на поле), - это уже задача тренера. Объявляем глобальные переменные: # -*- coding: Windows-1251 -*# Сочетания # ПРОГРАММА ДЛЯ ГЕНЕРИРОВАНИЯ # ВСЕХ ПОДМНОЖЕСТВ k МНОЖЕСТВА ЧИСЕЛ n=1..nElem # макс. число элементов в # множестве: MAX_ELEM = 30 # число элементов в # подмножестве: k = 0 565
# список, содержащий # очередное подмножество: a = [] # номер подмножества: 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("Число сочетаний =", n) print() if __name__ == "__main__": main() Затем функция subSet генерирует все подмножества: # Генерируем все сочетания множества 1..nElem # из k элементов def subSet(nElem): 566
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 разных команд (Рис. 1)! Комбинаторную задачу мы решили, а вот какая из этих команд действительно станет командой мечты, тут комбинаторика бессильна! 567
Рис. 1. Футбольный комбинатор Иногда полезно заранее знать, сколько же получится сочетаний при тех или иных исходных данных - не печатать же весь список подмножеств, если нам интересно узнать только их число! На этот случай в комбинаторике припасена несложная формула: В нашей программе n = nElem. Как видите, в комбинаторике без факториала не обойтись! 568
Проект Функция combinations Исходный код программы находится в файле Функция combinations.py. В модуле itertools есть функция combinations, которая возвращает все подмножества заданного множества элементов. Импортируем эту функцию в проект: # -*- coding: Windows-1251 -*# Функция combinations from itertools import combinations В функции main действуем, как в предыдущем проекте: # макс. число элементов в множестве: MAX_ELEM = 30 # ГЛАВНАЯ ФУНКЦИЯ def main(): print('Функция combinations') print() # бесконечный цикл ввода данных # пока пользователь не закроет программу # или не введет 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()) " # список элементов для перестановки: a = list(range(1, nElem + 1)) Получаем список сочетаний и печатаем его на экране: 569
# генерируем сочетания: comb = list(combinations(a, k)) # распечатываем сщчетания: for c in comb: print(c) print("Число сочетаний =", len(comb)) print() if __name__ == "__main__": main() Запускаем программу и проверяем её на простых примерах (Рис. 1). Рис. 1 Проект Функция combinations_with_replacement Исходный код программы находится в файле Функция combinations_with_replacement.py. В модуле itertools есть также функция combinations_with_replacement, которая возвращает все подмножества заданного множества элементов с повторениями. Импортируем эту функцию в проект: # -*- coding: Windows-1251 -*# Функция combinations_with_replacement 570
from itertools import combinations_with_replacement Остальную часть кода берём из предыдущего проекта и заменяем название функции: # макс. число элементов в множестве: MAX_ELEM = 30 # ГЛАВНАЯ ФУНКЦИЯ def main(): print('Функция combinations_with_replacement') print() # бесконечный цикл ввода данных # пока пользователь не закроет программу # или не введет 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()) " # список элементов для перестановки: a = list(range(1, nElem + 1)) # генерируем сочетания: comb = list(combinations_with_replacement(a, k)) # распечатываем сщчетания: for c in comb: print(c) print("Число сочетаний =", len(comb)) print() if __name__ == "__main__": main() Опять выполняем проверку и убеждаемся, что программа работает без ошибок (Рис. 1). 571
Рис. 1 Как и следовало ожидать, число сочетаний с повторениями элементов значительно больше, чем без оных. 572
Проект Дартс Исходный код программы находится в файле Дартс1.py. Дартс – отличная игра для настоящих мужчин! Ни беготни, ни шума, ни пыли. Знай себе мечи дротики прямо в цель. А цель – это круглая мишень, разделённая на 20 равных секторов. В каждой игре нужно набрать быстрее соперника 501 очко. За 1 подход игрок бросает 3 дротика. Желательно попасть ими в мишень. При попадании непосредственно в сектор (сектора окрашены в чёрный и белый цвет попеременно) игроку начисляется столько очков, сколько написано рядом с сектором. Если присмотреться к Рис. 1, то можно разглядеть, что по периферии круга расставлены числа от 1 до 20. Рис. 1 573
Таким образом, одним броском можно выбить любое число из диапазона 1..20. Но это ещё не всё! При попадании во внешнее зелёно-красное кольцо очки соответствующего сектора удваиваются, а при попадании во внутреннее зелёно-красное кольцо - утраиваются. Но и это ещё не всё! Центральный красный глазок даёт 50 очков, а зелёное кольцо вокруг него – 25. Легко подсчитать, что тремя дротиками можно выбить минимум 0 очков, а максимум – 180. Забавно, но в конце игры запрещено набирать 180 очков, даже если это и можно сделать… В начале игры важно и выгодно набирать по 180 очков за один раз, что особенно ценится игроками и зрителями. Это магическое число можно встретить на многих картинках, посвящённых этой замечательной и броской игре (Рис. 2). Рис. 2. Идеал! В конце игры ситуация другая. Игрок должен набрать ровно 501 очко, ни больше ни меньше, поэтому необходимо чётко распределить дротики по секторам, чтобы получить нужную сумму. Может показаться, что тремя дротиками можно выбить любую сумму от 1 до 180, но это не так. Некоторые суммы тремя дротиками выбить невозможно. Какие? – Вот в чём вопрос этого проекта. 574
В функции solve мы сначала записываем в множество score все очки, которые можно выбить одним дротиком: # -*- coding: Windows-1251 -*from itertools import combinations_with_replacement # РЕШАЕМ ЗАДАЧУ def solve(): # сектора и BULL: score1 = list(range(0, 20 + 1)) + [25, 50] # двойные сектора: score2 = [2 * i for i in range(1, 20 + 1)] # тройные сектора: score3 = [3 * i for i in range(1, 20 + 1)] # избавляемся от повторов: score = set(score1 + score2 + score3) Так как некоторые очки можно получить несколькими способами, то мы используем множество, чтобы они не повторялись. Одним дротиком можно выбить любое число очков из множества score: # возможные суммы --> # одна стрелка: summa = set(score) Двумя дротиками – любую сумму, которую можно составить из двух чисел в множестве score: # две стрелки: for p in combinations_with_replacement(score, 2): summa.add(sum(p)) Тремя дротиками – любую сумму, которую можно составить из трёх чисел в множестве score: # три стрелки: for p in combinations_with_replacement(score, 3): summa.add(sum(p)) 575
Теперь в множестве summa находятся все возможные очки, которые можно набрать тремя дротиками. Если мы удалим их из множества 1..180, то в множестве not_numbers останутся как раз те очки, которые получить невозможно: # печатаем невозможные суммы: not_numbers = set(range(1, 180 + 1)) - set(summa) print("Невозможные суммы:", not_numbers) print() # ГЛАВНАЯ ФУНКЦИЯ def main(): print() print('Дартс') print() solve() print() main() На Рис. 3 вы видите, что 9 сумм тремя дротиками выбить нельзя! Рис. 3. Вот такие пироги! Исходный код программы находится в файле Дартс2.py. Задачу можно решить и другим способом – со списком: 576
# -*- coding: Windows-1251 -*from itertools import combinations_with_replacement # РЕШАЕМ ЗАДАЧУ def solve(): # сектора, двойные сектора, тройные сектора и BULL score = [[i, i * 2, i * 3] for i in range(20 + 1)] score = sum(score, [25, 50]) # избавляемся от повторов: score = set(score) # все суммы --> all_sum = list(range(180 + 1)) for p in combinations_with_replacement(score, 3): sp = sum(p) if sp in all_sum: all_sum.remove(sp) # печатаем невозможные суммы: print('Невозможные суммы:', all_sum) print() # ГЛАВНАЯ ФУНКЦИЯ def main(): print() print('Дартс') print() solve() print() main() 577
Проект Разменный пункт Исходный код программы находится в файле Разменный пункт.py. Список Бесконечный цикл 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 578
P(4) = 5 > 4 31 22 211 1111 P(5) = 7 > 5 41 32 311 221 2111 11111 Эту процедуру можно продолжить, но уже сейчас явно прослеживается её рекурсивная сущность. Однако мы воспользуемся итерационным алгоритмом, описанным Витольдом Липским в книге [ЛВ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 579
# генерируем все разбиения: generate(num) print("Число разбиений =", nVar) print() if __name__ == "__main__": main() В функции 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) 580
Всякий раз, когда функция 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) И вот уже без особого напряжения мы получаем полный список разбиений числа (Рис. 1). Рис. 1. Все разбиения числа 12 Функция partitions выдаёт такие же разбиения (Рис. 2), но она очень маленькая: 581
def partitions(n, m=None): if m is None or m >= n: yield [n] for f in range(n - 1 if (m is None or m >= n) else m, 0, -1): for p in partitions(n - f, f): yield [f] + p n = 0 for p in partitions(num): print(p) n += 1 print("Число разбиений =", n) print() Рис. 2 Второй параметр этой функции ограничивает величину максимального элемента в разбиении. Например, мы хотим, чтобы все элементы были не больше трёх (Рис. 3): n = 0 for p in partitions(num, 3): print(p) 582
n += 1 print("Число разбиений =", n) print() Рис. 3 Функция partitions_sized генерирует разбиения числа n на заданное число элементов k: def partitions_sized(n, k, m=None): if k == 1: yield [n] return for f in range(n - k + 1 if (m is None or m > n - k + 1) else m, (n - 1) // k, -1): for p in partitions_sized(n - f, k - 1, f): yield [f] + p Разбиваем число 12 на группы по 3 элемента в каждой (Рис. 4): 583
n = 0 for p in partitions_sized(num, 3): print(p) n += 1 print("Число разбиений =", n) print() Рис. 4 Дополнительно можно задать максимальный элемент в группах. Например, семь (Рис. 5): n = 0 for p in partitions_sized(num, 3, 7): print(p) n += 1 print("Число разбиений =", n) print() Функция partitions_len генерирует разбиения числа n. Если задан аргумент max_val, то это максимальный размер элементов в группах. Если задан аргумент max_len, то это максимальное число элементов в группах: def partitions_len(n, max_val=100000, max_len=100000): if n <= max_val: yield [n] for first in range(min(n - 1, max_val), max(0, (n - 1) // max_len), -1): 584
for p in partitions_len(n - first, first, max_len - 1): yield [first] + p Рис. 5 Разбиваем число 12 на группы, не длиннее 5 элементов. Максимальный элемент в группах не больше 4 (Рис. 6): n = 0 for p in partitions_len(num, 4, 5): print(p) n += 1 print("Число разбиений =", n) print() Рис. 6 585
Проект Спортлото Исходный код программы находится в файле Спортлото.py. Бесконечный цикл 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 вы можете посмотреть ролик о поиске владельца потерянного зонтика (Рис. 1). 586
Рис. 1. Учебно-познавательный ролик В Германии, точно так же, как и в России, многие годы играют в Лото 6 из 49 (Рис. 2). Рис. 2. Карточка немецкого «Спортлото» В книге утверждается, что при покупке 1 билета вы имеете всего 1 шанс из 13 983 816 угадать все 6 номеров. Это даже меньше, чем найти незадачливого владельца зонтика в большом городе. Однако вряд ли кто-нибудь станет звонить по телефону в надежде попасть на него, а вот в лото играют многие и охотно. И это легко понять: выиграть чужое гораздо приятнее, чем его вернуть. Но давайте проверим, правильно ли подсчитал вероятность автор книги. Поскольку 6 номеров из 49 можно выбрать многими способами, то распечатывать их нет никакого смысла. 587
А вот сохранить все сочетания в списке смысл есть. Действительно, шарики выпадают из барабана совершенно случайно, поэтому все комбинации совершенно равноправны, и это значит, что вам не нужно ломать голову над составлением числовых комбинаций – выбирайте случайно любую из списка и смело зачёркивайте числа! Если учесть, что в каждом билете может быть до 12 комбинаций (см. Рис. 3), то таким нехитрым способом вы быстро избавитесь от головной боли. Рис. 3. 12-кратная кароточка лото Хотя, надо признать, этот способ был бы хорош, если бы только вы играли в лото. Но кроме вас, ещё несколько миллионов человек надеются на удачу, поэтому даже в случае выигрыша вам придётся делиться с такими же везунчиками, как и вы. Чтобы избежать дележа, нужно выбирать «редкие», «маловероятные» комбинации чисел, чтобы никто другой их не нашёл. Как вы помните, героиня комедии Спортлото-82 Таня поступила именно так, зачеркнув 6 первых чисел (Рис. 4). Рис. 4. Удачный выбор чисел 588
И выиграла (Рис. 5)! Рис. 5. Увы, жизнь – это не кино! Таким образом, наша задача упрощается – мы не будем распечатывать сочетания, а ограничимся только их подсчётом: # -*- coding: Windows-1251 -*# Спортлото # ПРОГРАММА ДЛЯ ПОДСЧЁТА # ВСЕХ ПОДМНОЖЕСТВ k МНОЖЕСТВА ЧИСЕЛ n=1..nElem # макс. число элементов в # множестве: MAX_ELEM = 90 # 49 # число элементов в # подмножестве: 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()) " 589
# если пользователь ввёл 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() if __name__ == "__main__": main() Для этого вполне годится готовая функция subSet из наших предыдущих проектов, в которой достаточно закомментировать всего одну строку: # Генерируем все сочетания множества 1..nElem # из 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 590
Запускаем программу – и убеждаемся в правоте автора: 6 чисел из 49 можно выбрать 13 983 816 способами (Рис. 6). Рис. 6. Маловероятно! Если не нужно сохранять комбинации чисел в списке, то можно найти число сочетаний по формуле, которая нам известна из проекта Как нам собрать Команду мечты? К сожалению, для подсчётов она малопригодна, так как в числителе стоит огромный факториал. Но нетрудно догадаться, что первые (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 591
Вызываем функцию getNumSubSets в функции main и получаем тот же результат, что и раньше: # считаем все подмножества: #n = subSet(nElem) n = getNumSubSets(nElem, k) print("Число сочетаний = " + str(n)) print() Аналогично мы можем узнать число раскладов в игре скат (Рис. 7). Или число рукопожатий, которыми обменяются 14 человек, расходясь после праздника (примеры из книги) (Рис. 8). Рис. 7. Карточные расклады Рис. 8. Число рукопожатий В Чемпионате России по футболу участвуют 16 команд (Рис. 9). Сколько всего матчей они проведут между собой? Задача решается аналогично предыдущей, но следует учесть, что каждая пара команд сыграет дважды – дома и на выезде. Рис. 9. Таблица розыгрыша Чемпионата России по футболу 20212022 592
Поэтому команды проведут не 120 (Рис. 10), а 120 х 2 = 240 матчей. Рис. 10. Число матчей В современной России играют также в Гослото 6 из 45 и 5 из 36 (Рис. 11). Рис. 11. Новое российское лото Подсчитаем число вариантов и для них (Рис. 12). По сравнению с иноземными лотереями, российские более обнадёживающие! В Спортлото играют и в Италии – но по другим правилам. В одном из вариантов нужно угадать 5 чисел из 90. Легко подсчитать, что вероятность такого радостного события равна 1 / 43 949 268 (Рис. 13). Рис. 13. Небольшая надежда всё-таки есть! 593
Рис. 14. Итальянское лото Не забудьте поправить переменую: MAX_ELEM = 90 В варианте SuperEnaLotto (Рис. 15) нужно угадать уже 6 чисел из 90, что уменьшает шансы игроков до 1 / 622 614 630 (Рис. 16). «Нет, я так не играю!» - сказал бы Карлсон. В итальянском лото все неразыгранные деньги переходят в следующий тираж. А поскольку вероятность выигрыша главного приза невелика, то постепенно джек-пот может достигать огромных значений – до 100 миллионов евро (Рис. 17)! Рис. 15. Попробуй угадай! Рис. 16. Везёт же итальянцам! 594
Рис. 17. Вот это джекпот! И тогда из соседних стран в Италию движутся караваны автобусов с жаждущими наживы джентльменами удачи. Вот так, сравнительно дёшево можно развивать туризм и познавательный интерес к своей стране… Чтобы показать, насколько мала вероятность выигрыша в немецкое Спортлото, Эрхард Берендс прибегает к такому наглядному примеру. Вы, конечно, понимаете, что в «цивилизованных» странах всё построено на личной наживе, а не на заботе о развитиии спорта, поэтому я употребляю слово Спортлото только по аналогии с советской версией лото. Расстояние между Берлином и Котбусом составляет около 140 км (Рис. 18) или в сантиметрах – 14 000 000. Как вы помните, практически столько же разных карточек можно заполнить в немецком лото. Рис. 18. Дорога дальняя! 595
Теперь представьте, что вы едете на автомобиле по этой дороге с завязанными глазами (опасаясь побочных эффектов и последствий, автор книги уточняет: вас везут по этой дороге). Где-то на обочине стоит столб диаметром в 1 см (Рис. 19). Вы в любой момент времени можете бросить одноцентовую монету в надежде попасть в этот столб (или шест). Рис. 19. Иллюстрация из книги Вероятность этого события приблизительно равна вероятности выигрыша в немецкое лото. Для итальянского лото SuperEnaLotto потребуется дорога в 6000 километров: из Рима через Берлин в Москву и обратно. По адресу http://www.youtube.com/watch?v=ODwm29lIt0E вы можете посмотреть ролик с экспериментом по бросанию монеты из автомобиля в шест (Рис. 20). Рис. 20. Видеоэксперимент 596
Играйте, дорогие люди, в Спортлото, За это не осудит вас никто! (душераздирающий призыв из комедии Спортлото-82) 597
Проект Жребий брошен! Исходный код программы находится в файле Жребий брошен!.py. Цикл for Функция с параметрами Условный оператор if Оператор return Форматированный вывод Кто отжеребился, могут пройти к своим машинам. Кто отжеребился, не мешайте спортсменам, участвующим в жеребьёвке. Из спортивных репортажей Жребием может быть любой предмет, который можно бросить в лицо судьбе. У нас это будет монетка. Она очень удобна, когда, например, нужно распределить ворота команд на футбольном поле. А на третьем Чемпионате Европы по футболу в 1968 году именно монетка определила судьбу сборной СССР в полуфинале. Матч со сборной Италии, которая и принимала чемпионат, закончился вничью, а победителя тогда определяли таким немудрёным способом. Повезло итальянцам… Монета хороша тем, что у неё 2 стороны, на которые она с одинаковой вероятностью может упасть. Впрочем, студенты нашли у монеты и скрытые возможности: - Давайте монетку кинем. Если орлом упадёт, то пивка выпьем, если решка - то водочки. Если ребром упадёт - доспим, ну а если в воздухе зависнет, тогда учиться пойдём. Понятно, что если мы подбросим монетку 1 раз, то выпадет либо орёл, либо решка. Судить о вероятности их выпадения невозможно. Это значит, что число подбросов нужно значительно увеличить. А это уже утомительно, поэтому пусть виртуальный эксперимент проводит компьютер – ему 598
ничего не стоит подбросить монету и 1 миллион, и даже 10 миллионов раз кряду! Естественно, компьютер и одного раза не сможет подбросить настоящую монетку, но зато с помощью генератора псевдослучайных чисел он может имитировать (или симулировать – в хорошем смысле этого слова!) выпадение орлов и решек. В функции main мы задаём параметры эксперимента – число серий из 10 экспериментов и число подбрасываний монет в каждой серии: # ГЛАВНАЯ ФУНКЦИЯ def main(): print('Подбрасываем монетку') print() # число серий: ns = 10 for i in range(0, ns): # число бросаний: n = 1000000 orlov = experiment(n) print(f"Орлов : Решек {orlov} : {n - orlov}") print(f"Вероятность {orlov / n} : {1.0 - orlov / n}") print() if __name__ == "__main__": main() Метод randint класса random с аргументами 0, 1 выдаёт случайную последовательность нулей и единиц. Если мы примем 1 за орла, 0 – за решку, то только успевай считать! # -*- coding: Windows-1251 -*import random def experiment(n): orlov = 0 for i in range(0, n): if (random.randint(0, 1) == 1): orlov += 1 return orlov 599
Я провёл тестирование нашей программы при 1 миллионе подбрасываниях в каждой серии. Рис. 1 показывает неплохое качество нашего экспериментального устройства! Рис. 1. Подбрасываем виртуальные деньги Конечно, было бы странно, если бы при случайных бросаниях число орлов и решек в точности совпадало, но эти числа должны быть довольно близки друг к другу, что мы и видим на Рис. 1⬆. Задания для самостоятельного решения Сочетания Напишите программу, которая вычисляла бы и печатала в списке число сочетаний. Ладейное окончание Научившись генерировать перестановки, мы, сами того не подозревая, решили знаменитую комбинаторную задачу: найти все варианты рас- 600
становки восьми ладей на шахматной доске так, чтобы они не били друг друга. Когда речь идёт о поиске всех вариантов, то обычно для решения задачи используют метод перебора с возвратами (по-английски backtracking). Однако число всех расстановок ладей 8! = 40320 невелико, и мы можем решить задачу полным перебором (brute force), учтя, конечно, тот факт, что на одной горизонтали может находиться единственная ладья. Тогда каждая перестановка чисел 1..8 служит решением задачи о ладьях. Например, перестановке 1 2 3 4 5 6 7 8 соответствует решение, представленное на Рис. 1. Действительно, если каждую ладью ставить на отдельную горизонталь Г, а в ней – на ту вертикаль, номер которой совпадает с числом, стоящим в Г-той позиции перестановки, то ни одна ладья не будет угрожать другим ладьям - и задача решена. Тысячная перестановка, которую выдаёт наша программа Генерируем перестановки, такая: 1 4 3 7 2 6 5 8. Если мы расставим ладьи согласно этой перестановке, то также получим решение задачи (Рис. 2). Рис. 1. Перестановка ладей Таким образом, мы умеем находить все решения ладейной задачи, и вам нужно только и всего, что красиво вывести их на экран. Можно ограничиться крестиками X в Консольном окне, хотя в картинках было бы лучше… 601
Рис. 2. Одно из решений ладейной задачи Ферзевой гамбит Если мы заменим ладей ферзями, то оба наших решения окажутся неверными. Ферзи – более серьёзные шахматные фигуры и могут больно ударить и по диагонали! С другой стороны, совершенно очевидно, что среди 40320 решений для ладей отыщутся и все 92 решения для ферзей. Например, такое (Рис. 3). Так что для решения проблемы с ферзями вам необходимо устроить проверку всех перестановок и отобрать только те, в которых ферзи не угрожают друг другу. Конечно, не лучший способ для решения этой задачи, но вполне пригодный. Рис. 3. Безобидные ферзи 602
Глава #12. Рекурсия Рекурсией называют вызов функции из самой же функции. Рекурсия имеет немало преимуществ и недостатков по сравнению с обычными функциями. Мы рассмотрим их подробно на конкретных примерах. Проект Рекурсия, или Сказочка про белого бычка Исходный код программы находится в файле Рекурсивная сказочка.py. - Сказать ли тебе сказку про белого бычка? - Скажи. - Ты скажи, да я скажи, да сказать ли тебе сказку про белого бычка? - Скажи. - Ты скажи, да я скажи, да чего у вас будет, да докуль это будет! Сказать ли тебе сказку про белого бычка? Докучная сказка Хорошим примером рекурсии в литературе могут служить докучные сказки, которые находчивый русский народ придумал для того, чтобы изводить ими своих врагов, поскольку они никогда не заканчиваются. Такую рекурсию называют бесконечной. В программах она вызывает «зависание» компьютера до тех пор, пока не будут полностью исчерпаны его ресурсы. Естественно, такое поведение приложения совершенно недопустимо, поэтому для рекурсивных подпрограмм необходимо предусмотреть условие выхода из рекурсии. Попробуем запрограммировать докучную сказку – про то, как поп убил собаку. Число повторений сказки ограничивает переменная n. Она просто указывает функции pop, сколько раз мы хотим услышать сию душещипательную историю. Это и будет тот счётчик повторений, который избавит нас от бесконечной рекурсии. А вот и сама сказочка: # РЕКУРСИВНАЯ ПРОГРАММА ДЛЯ РАССКАЗЫВАНИЯ СКАЗОЧЕК 603
# Функция печати сказочки def pop(n): # n - число повторов if n <= 0: return else: print('У попа была собака, он её любил,') print('Она съела кусок мяса, он её убил.') print('В землю закопал и надпись написал:') print() pop(n - 1) def main(): print('Рекурсивная сказочка') print('Сказочка про попа и его собаку') print() # n - число повторов сказочки; n = 3 pop(n) print() if __name__ == '__main__': main() В начале функции как раз и находится обязательное условие, гарантирующее завершение сказочки. Как только переменная n станет равной нулю, вызовы функции закончатся. Но до этого сказочка будет напечатана 1 раз, после чего функция pop снова вызовет себя со значением переменной n, уменьшенным на единицу: pop(n - 1) Таким образом, функция pop будет выполнена 3 раза (если вы не изменили начальное значение), а значение параметра n всякий раз будет уменьшаться на единицу, пока не достигнет нуля, после чего вызовы прекратятся: n=3 n=2 n=1 n=0 На последнем вызове сработает условие n <= 0, и страдания читателя этих милых строк закончатся (Рис. 1). 604
Рис. 1 Проект Нерекурсивная сказочка Исходный код программы находится в файле Нерекурсивная сказочка.py. Функция pop напоминает оператор цикла while, поэтому давайте напишем нерекурсивную функцию, что иногда бывает полезно: # НЕРЕКУРСИВНАЯ ПРОГРАММА ДЛЯ РАССКАЗЫВАНИЯ СКАЗОЧЕК # Функция печати сказочки def popWhile(n): # n - число повторов while n > 0: print('У попа была собака, он её любил,') print('Она съела кусок мяса, он её убил.') print('В землю закопал и надпись написал:') print() n -= 1 def main(): print('Нерекурсивная сказочка') print('Сказочка про попа и его собаку') print() 605
# n - число повторов сказочки; n = 3 popWhile(n) print() if __name__ == '__main__': main() Результат рассказывания сказки точно такой же, как и в рекурсивном варианте программы (Рис. 1). Рис. 1 Конечно, не всегда бывает так просто избавиться от рекурсии, как в этом примере. Проект Ханойские башни Исходный код программы находится в файле Ханойские башни.py. Другой классический пример рекурсивных программ – решение головоломки Ханойские башни. 606
Эту головоломку придумал французский математик Эдуар Люка (Edouard Lucas) в 1883 году. Тогда же её начали продавать как игрушку, а её изобретателем был назван некий профессор Клаус (Prof. Claus) из коллежа Li-SouStian. Впрочем, люди, сведущие в анаграммах, быстро «вычислили» настоящего автора – профессора Люка из Сен-Луи (Prof. Lucas, Saint Louis). Историю этой головоломки описал непревзойдённый Мартин Гарднер в книге Hexaflexagons and Other Mathematical Diversions: The First Scientific American Book of Puzzles and Games, обложку которой и украшает Ханойская башня. Впрочем, это только фрагмент головоломки. На картинке отсутствуют ещё два стрежня без колец. Целиком она представлена на странице 58. Как вы видите, Ханойская башня – это набор из восьми колец, нанизанных на стержень. Кольца имеют разный диаметр, и в начальном положении самое большое кольцо находится в основании башни, а самое маленькое – на его вершине. Все остальные кольца располагаются согласно их диаметру. Цель решения головоломки состоит в том, чтобы переложить за минимальное число ходов все кольца с одного стержня на другой так, чтобы их порядок сохранился. Совершенно очевидно, что при двух стержнях задачу решить невозможно, поэтому имеется ещё один - вспомогательный - стержень. За каждый ход разрешается снять одно кольцо с какого-либо стержня и пере- 607
местить его на один из двух оставшихся. При этом следует неукоснительно выполнять правило: нельзя на маленькое кольцо класть большое. Методом индукции нетрудно доказать, что при n кольцах минимальное число ходов равняется 2n-1, то есть для перемещения восьми колец потребуется 28-1 = 255 ход. Но давайте вернёмся к головоломке Люка, в которой он продолжил свои мистификации и в инструкции к игре указал, что Ханойские башни – это уменьшенный вариант мифической Башни Брамы, которая находится в замке индийского города Бенарес. Эта башня построена из 64 дисков, которые жрецы должны переложить по описанным выше правилам. Когда они закончат свою работу, замок превратится пыль, а мир исчезнет. Как остроумно замечает Мартин Гарднер, за мир можно быть спокойным, поскольку на 264-1 = 18 446 744 073 709 551 615 ходов потребуется несколько миллиардов лет. Что касается первого утверждения, то замок превратится в пыль гораздо раньше означенного срока. Для ручного решения головоломки кольца-диски можно изготовить из бумаги либо заменить их игральными картами – от туза до восьмёрки. Но нас, естественно, больше интересует компьютерное решение. Чтобы не утомлять себя многочисленными перемещениями колец, мы ограничим их число, что ничуть не изменит самой сути рекурсивного процесса. Итак, начните новый проект с объявления констант и переменных: 608
# ПРОГРАММА ДЛЯ РЕШЕНИЯ ГОЛОВОЛОМКИ # ХАНОЙСКИЕ БАШНИ # число дисков: NUM_DISC = 5 # названия дисков: NAME = ['Стержень1', 'Стержень2', 'Стержень3'] # число ходов: nMoves = 0 # колышки: peg1, peg2, peg3 = 0, 1, 2 Главная функция программы очень простая. Мы вызываем рекурсивную функцию solve, а затем печатаем число ходов, которые сделала программа: # ГЛАВНАЯ ФУНКЦИЯ def main(): print('ХАНОЙСКИЕ БАШНИ') print('Решаем головоломку Ханойские башни') print() global nMoves nMoves = 0 solve(NUM_DISC, peg1, peg2, peg3); print('Всего сделано ходов', nMoves) print() if __name__ == '__main__': main() Поскольку функция solve – рекурсивная, то она совсем короткая, но непростая. Чтобы лучше понять, как она действует, нужно внимательно проследить за всеми перемещениями дисков и обратить внимание, что первоначальное назначение стержней - исходный, промежуточный и конечный – изменяется по ходу решения головоломки: # РЕШАЕМ ЗАДАЧУ ДЛЯ nDisks ДИСКОВ def solve(nDisks, source, inter, dest): if nDisks > 1: solve(nDisks - 1, source, dest, inter) moveDisk(source, dest) solve(nDisks - 1, inter, source, dest) else: moveDisk(source, dest) 609
Нам поможет в этом функция moveDisk, которая усердно печатает номера ходов и стержней, которые участвуют в них: # ПЕРЕНОСИМ ДИСК СО СТЕРЖНЯ source # НА СТЕРЖЕНЬ dest def moveDisk(source, dest): global nMoves nMoves += 1 # ход: print('Ход ', end='') if nMoves < 10: print(' ', end='') print(str(nMoves) + '. ', end='') # откуда: print(NAME[source], '> ', end='') # куда: print(NAME[dest]) Как и предписано теорией, на решение понадобится 31 ход (Рис. 1). Рис. 1 Пользуясь этим протоколом, вы легко решите задачу с настоящими стержнями и кольцами, вот только хорошо было бы иметь перед глазами не только список ходов, но и все промежуточные позиции, как это обычно 610
делают при разборе шахматных партий. Для этого нужно представить каждый стержень в виде стека. Проект Рекурсивный факториал Исходный код программы находится в файле Рекурсивный факториал.py. Самый классический пример рекурсии – вычисление факториала, то есть произведения всех чисел от 1 до заданного: Число 0 1 2 3 4 5 6 Вычисление По определению 1*1 1*2 2*3 6*4 24 * 5 120 * 6 Факториал 1 1 2 6 24 120 720 Как следует из таблицы, факториал нуля принимается равным единице! Так как число сомножителей известно, то факториал проще всего вычислить в цикле for: # НЕРЕКУРСИВНАЯ ФУНКЦИЯ def fact2(n): f = 1 if (n <= 0): return f for i in range(2, n + 1): f *= i return f # ГЛАВНАЯ ФУНКЦИЯ def main(): print() n = 5 print(f'Факториал {n}! = {fact2(n)}') 611
print() main() Здесь нужно выполнить только (n-1) умножение, поэтому функция очень быстро выдаёт результат. Переход к рекурсивной функции легче проследить, когда используется цикл while: # НЕРЕКУРСИВНАЯ ФУНКЦИЯ def fact3(n): if (n <= 0): return 1 f = 1 while n > 1: f *= n n -= 1 return f В рекурсивных функциях важно учесть основной случай, когда результат известен заранее и не требует рекурсивного вызова. Если этого не сделать, то рекурсивные вызовы будут продолжаться «бесконечно». На самом деле программа завершится с ошибкой, когла возможности системного стека будут исчерпаны. Тогда вы получите вот такое сообщение: RecursionError: maximum recursion depth exceeded in comparison Из этого, кстати, следует, что большие факториалы, например, числа 1000 вычислить рекурсивно не удастся по той же самой причине. И это один из недостатков рекурсии. Основной случай называют прекращения рекурсии. также терминальным или условием При вычислении факториала основной случай возникает, когда нужно вычислить факториал нуля. Его вычислять не нужно, поскольку он по определению равен 1. Итак, если функция fact вызывается с нулевым аргументом, то она должна тут же вернуть 1: 612
# -*- coding: Windows-1251 -*""" Рекурсивный способ вычисления факториала """ # РЕКУРСИВНАЯ ФУНКЦИЯ def fact(n): if (n == 0): return 1 Если аргумент равен 1, то единицу нужно умножить на факториал предыдущего числа: return n * fact(n - 1) В таблице это хорошо видно. В данном случае n = 1, и это число нужно умножить на fact(n-1) = f(0) = 1. В результате функция fact вернёт 1 * 1 = 1. Обратите внимание, что вызов функции с нулевым аргументом f(0) прекращает рекурсивные вызовы. А что же будет при n = 2? – Так как основной случай не возник, то функция вернёт произведение 2 * fact(1). Но при n = 1 основной случай опять не возникает, и функция снова вернёт произведение 1 * fact(0). И тут, наконец, возникнет основной случай, и функция вернёт 1. Эта 1 будет умножена на предыдущую 1, а та, в свою очередь, на 2. Всё произведение равно 2, и это значение вернёт функция fact. Чтобы проследить за всеми вызовами рекурсивной функции, дополним её сообщениями: # РЕКУРСИВНАЯ ФУНКЦИЯ def fact_ex(n): print('Начало функции при n =', n) if (n == 0): print('Основной случай n =', n) return 1 f = n * fact_ex(n - 1) print('Функция возвращает', f) return f При n =0 мы получим такие сообщения: 613
Начало функции при n = 0 Основной случай n = 0 Факториал 0! = 1 При n = 1 число сообщений увеличится: Начало функции при n = 1 Начало функции при n = 0 Основной случай n = 0 Функция возвращает 1 Факториал 1! = 1 При n = 2 их станет ещё больше: Начало функции при n = 2 Начало функции при n = 1 Начало функции при n = 0 Основной случай n = 0 Функция возвращает 1 Функция возвращает 2 Факториал 2! = 2 При n = 5 сообщения становятся ещё интереснее: Начало функции при n = 5 Начало функции при n = 4 Начало функции при n = 3 Начало функции при n = 2 Начало функции при n = 1 Начало функции при n = 0 Основной случай n = 0 Функция возвращает 1 Функция возвращает 2 Функция возвращает 6 Функция возвращает 24 Функция возвращает 120 Факториал 5! = 120 Хорошо видно, что функция fact вызывает сама себя 5 раз с аргументами 4, 3, 2, 1 и 0. Когда возникает основной случай, все функции начинают возвращать свои значения в вызывающую функцию, где они умножаются на уже полученные значения. 614
Проект Рекурсивное умножение Исходный код программы находится в файле Рекурсивное умножение.py. Функция с параметрами Условный оператор if - else Оператор return Форматированный вывод Оператор or Рассмотрим курьёзный способ применения рекурсии – умножение двух чисел: # -*- coding: Windows-1251 -*""" Рекурсивный способ умножения двух чисел """ # РЕКУРСИВНО УМНОЖАЕМ ЗАДАННЫЕ ЧИСЛА def mult(m, n): # основной случай: if ((n <= 0) or (m <= 0)): return 0 # рекурсия: else: return m + mult(m, n - 1) # ГЛАВНАЯ ФУНКЦИЯ def main(): print() print('Рекурсивное умножение') print() # числа: n1 = 12345679 n2 = 9 # произведение: mul = mult(n1, n2) print('Произведение чисел {0} и {1} = {2}'.format(n1, n2, mul)) print() main() 615
По сути, здесь вместо умножения двух чисел используется сложение. Если второе число большое, то стек переполнится, и никакого результата вы не получите. Если второе число отрицательное, то произведение будет равно нулю, так что таким способом умножения лучше не пользоваться! Интересный способ умножения (Рис. 1)! Рис. 1. «Сложное» умножение Проект Рекурсивный модуль Исходный код программы находится в файле Рекурсивный модуль.py. Функция с параметрами Условный оператор if - else Оператор return Форматированный вывод Другой рекурсивный курьёз – вычисление модуля, то есть остатка от деления первого числа на второе. Здесь также следует быть осторожным, поскольку деление на нуль вызовет ошибку при выполнении программы (Рис. 1): # -*- coding: Windows-1251 -*""" Рекурсивный способ вычисления модуля """ # РЕКУРСИВНО ВЫЧИСЛЯЕМ МОДУЛЬ def mod_rec(m, n): # основной случай: if (m < n): return m # рекурсия: else: 616
return mod_rec(m - n, n) # ГЛАВНАЯ ФУНКЦИЯ def main(): print() print('Рекурсивное вычисления модуля') print() # числа: n1 = 12 n2 = 5 # модуль: modul = mod_rec(n1, n2) print('Модуль чисел {0} и {1} = {2}'.format(n1, n2, modul)) print() main() Рис. 1. Рекурсивный модуль Проект Рекурсивные перевороты Исходный код программы находится в файле Рекурсивные перевороты.py. Цикл for Функция с параметрами Условный оператор if - else Оператор return Форматированный вывод Списки Срезы Более полезный случай применения рекурсии – переворачивание списков и строк: # -*- coding: Windows-1251 -*""" Рекурсивный способ переворачивания списков и строк """ 617
# ПЕРЕВОРАЧИВАЕМ СПИСОК def reverse_lst(lst, id): # основной случай: if (id >= len(lst)): return [] # рекурсия: else: res = reverse_lst(lst, id + 1) res.append(lst[id]) return res # ПЕРЕВОРАЧИВАЕМ СТРОКУ def reverse_str(lst, id): # основной случай: if (id >= len(lst)): return '' # рекурсия: else: res = reverse_str(lst, id + 1) res += lst[id] #print(res) return res # ГЛАВНАЯ ФУНКЦИЯ def main(): print() print('Рекурсивные перевороты') print() # список: lst = range(1, 10 + 1) print(reverse_lst(lst, 0)) lst = 'КАТОК' print(reverse_lst(lst, 0)) # строка: lst = 'ШАЛАШ' #rev = reverse_str2(lst) rev = reverse_str(lst, 0) print(rev) if (lst == rev): print('Палиндром') lst = 'А РОЗА УПАЛА НА ЛАПУ АЗОРА' print(reverse_str(lst, 0)) print() main() 618
Функция reverse_lst возвращает новый список, в котором элементы заданного списка располагаются в обратном порядке. Метод reverse_str действует аналогично, но только для строк и возвращает строку, записанную задом наперёд (Рис. 1). Рис. 1. Перевёртыши Легко заметить, что обе функции действуют одинаково. Они получают последовательность и номер (индекс) очередного элемента в последовательности. Сначала это первый элемент (с нулевым индексом). Так как этот элемент принадлежит последовательности, то основной случай не наступает, а функция рекурсивно вызывает себя со следующим индексом. И так до тех пор, пока индекс не выйдет за границы последовательности. Тогда наступит основной случай, и рекурсивные вызовы прекратятся. Все функции начнут возвращать свои значения, то есть символы строки, начиная с пустого (его вернёт вызов с основным случаем). К нему добавится последний, затем предпоследний. И так далее до самого первого. Так как символы добавляются к пустой строке от конечного к начальному, то в строке res мы получим обращённую строку. Если вы хотите понаблюдать за этим процессом, добавьте раскомментируйте функцию print, например, в функции reverse_str (Рис. 2): # ПЕРЕВОРАЧИВАЕМ СТРОКУ def reverse_str(lst, id): # основной случай: if (id >= len(lst)): return '' # рекурсия: else: res = reverse_str(lst, id + 1) res += lst[id] print(res) return res 619
Рис. 2. Обратимый процесс Эти же операции можно выполнить и без рекурсивных премудростей: # НЕРЕКУРСИВНАЯ ФУНКЦИЯ def reverse_str2(lst): res = '' for i in range(len(lst) - 1, 0 - 1, -1): res += lst[i] print(res) return res Но и эту функцию можно заменить простым срезом, который самостоятельно вывернет строку: return lst[::-1] 620
Проект Рекурсивные палиндромы Исходный код программы находится в файле Рекурсивные палиндромы.py. Цикл for Функция с параметрами Условный оператор if - else Оператор return Форматированный вывод Списки Файлы Применим нашу неказистую функцию reverse_str для поиска палиндромов в словаре: # -*- coding: Windows-1251 -*# Программа для поиска палиндромов # ПЕРЕВОРАЧИВАЕМ СТРОКУ def reverse_str(s, id): if (id >= len(s)): return "" else: res = reverse_str(s, id + 1) res += s[id] return res В главной функции мы сообщаем функции palindrome имя файла со списком слов для поиска: # ГЛАВНАЯ ФУНКЦИЯ def main(): print() print('Рекурсивные палиндромы') print() # словарь: fileName = 'makarov_frc.txt' # ищем палиндромы: palindrome(fileName) print() main() 621
В функции palindrome загружаем файл, слегка обрабатываем его и получаем готовый список слов words: # ИЩЕМ ПАЛИНДРОМЫ def palindrome(filename): # открываем файл: file = open(filename) # загружаем словарь: spisok = file.readlines() # закрываем файл: file.close() # убираем символ переноса строки: words = [s.strip() for s in spisok] Теперь мы просматриваем весь словарь сверху донизу и для каждого слова получаем от функции reverse_str перевёрнутое слово. Если оно совпадает с прямым, значит, очередной палиндром найден: # нашли слов: nWords = 0; # просматриваем словарь: for wrd in words: if wrd == reverse_str(wrd, 0): print(wrd) nWords += 1 print('Всего слов: ', nWords) Так как список слов небольшой, то и скорости нашей функции для этого вполне достаточно (Рис. 1). Рис. 1. Все русские палиндромы 622
Проект Нерекурсивные палиндромы Исходный код программы находится в файле Нерекурсивные палиндромы.py. Цикл for Функция с параметрами Условный оператор if Оператор return Форматированный вывод Списки Файлы Более простой, естественный и быстрый способ поиска палиндромов в файле заключается в замене медленного рекурсивного метода переворачивания строки срезом: # -*- coding: Windows-1251 -*# Программа для поиска палиндромов # ИЩЕМ ПАЛИНДРОМЫ def palindrome(filename): # открываем файл: file = open(filename) # загружаем словарь: spisok = file.readlines() # закрываем файл: file.close() # убираем символ переноса строки: words = [s.rstrip('\n') for s in spisok] # нашли слов: nWords = 0; # просматриваем словарь: for wrd in words: if wrd == wrd[::-1]: print(wrd) nWords += 1 print("Всего слов: ", nWords) # ГЛАВНАЯ ФУНКЦИЯ def main(): 623
print() print('Нерекурсивные палиндромы') print() # словарь: # fileName = 'makarov_frc.txt' fileName = 'EnDictionary_frc.txt' # ищем палиндромы: palindrome(fileName) print() main() Все отечественные палиндромы мы уже нашли, поэтому давайте поищем английские. Их оказалось больше (Рис. 1). Рис. 1. Все английские палиндромы 624
Проект Рекурсивная считалка Исходный код программы находится в файле Рекурсивная считалка.py. Цикл for Функция с параметрами Условный оператор if - else Оператор return Оператор not Модуль random Списки Лямбда-функции Рассмотрим занимательный, но не очень полезный пример рекурсии. Пусть у нас имеется список из n чисел, которые мы в данном проекте выбираем случайно: # -*- coding: Windows-1251 -*import random # генератор псевдослучайных чисел: rand = random.Random() # СОЗДАЁМ СЛУЧАЙНЫЙ СПИСОК def make_list(n): lst = [] for i in range(n): lst.append(rand.randint(1, 100)) return lst # ГЛАВНАЯ ФУНКЦИЯ def main(): # число элементов: num = 10 # создаём список: lst = make_list2(num) # печатаем его: print(lst) print() main() 625
И мы хотим подсчитать, сколько нечётных чисел в этом списке. Рекурсивная функция может быть такой: # СЧИТАЕМ def count(lst): # основной случай: if not lst: return 0 # рекурсия: else: return (lst[0] % 2) + count(lst[1:]) Если первый элемент списка – нечётное число, то функция возвращает 1 плюс число таких чисел в оставшейся части списка. Так мы продолжаем до тех пор, пока не дойдём до конца списка. В итоге наша функция сообщит нам, сколько нечётных чисел оказалось в списке. Дописываем в функцию main строки: # считаем нечётные числа: num_odd = count(lst) print('Нечётных чисел:', num_odd) print() И получаем верный ответ (Рис. 1). Рис. 1. Посчитали Нам могут потребоваться и другие подсчёты, поэтому мы напишем вторую версию функции, которая будет получать не только список, но и лямбдафункцию для проверки его элементов: # СЧИТАЕМ С ЛЯМБДОЙ def count2(lst, l): return l(lst[0]) + count2(lst[1:], l) if lst else 0 Проверяем нашу новую функцию: 626
# считаем нечётные числа с лямбдой: l = lambda n: n % 2 num_odd = count2(lst, l) print('Нечётных чисел:', num_odd) print() Она даёт такие же результаты, что и первая версия (Рис. 2). Рис. 2. Версия с лямбда-функцией Теперь давайте подсчитаем чётные числа: # считаем чётные числа l = lambda n: not (n % num_even = count2(lst, print('Чётных чисел:', print() с лямбдой: 2) l) num_even) Всё сходится (Рис. 3)! Рис. 3. Считаем чётные числа И наконец, посчитаем, сколько в списке оказалось чисел, которые больше 50 (Рис. 4): # считаем числа > 50: l = lambda n: n > 50 num51 = count2(lst, l) print('Чисел > 50:', num51) print() 627
Рис. 4. Считаем «большие» числа Задавая разные лямбда-функции, вы можете сосчитать, что угодно! И ещё одна функция для создания случайного списка чисел: def make_list2(n): lst = [random.randint(0, 100) for r in range(n)] return lst Проект Рекурсивные разбиения Исходный код программы находится в файле Рекурсивные разбиения.py. Цикл for Функция с параметрами Условный оператор if Оператор return Списки Оператор yield Рекурсивный вариант функции для разбиения чисел: # -*- coding: Windows-1251 -*# РЕКУРСИВНЫЙ ГЕНЕРАТОР РАЗБИЕНИЙ # ЗАДАННОГО ЧИСЛА 628
def part_gen(n): # основной случай: if n == 0: yield [] return # рекурсия: for p in part_gen(n - 1): p.append(1) yield p p.pop() if p and (len(p) < 2 or p[-2] > p[-1]): p[-1] += 1 yield p # ГЛАВНАЯ ФУНКЦИЯ def main(): print() print('Рекурсивные разбиения') print() # число: num = 20 # генерируем: parts = part_gen(num) # print(parts) # число разбиений: np = 0 # печатаем разбиения: for p in parts: np += 1 print(p) # if max(p) > 3: # print('----- БОЛЬШЕ 3 -----') print('Всего:', np) print() main() Обратите внимание, что функция part_gen представляет собой генератор разбиений, то есть она не возвращает список разбиений заданного числа, а ждёт, когда вы захотите получить их, например, в цикле for. Для сравнения с нашей предыдущей функцией мы найдём все разбиения числа 12 (Рис. 1). 629
Рис. 1. Разбили дюжину Все разбиения сгенерированы быстро и правильно, и порядок разбиений здесь лексикографический, то есть «по алфавиту». Проект Двадцать пятёрок Исходный код программы находится в файле Двадцать пятёрок.py. Цикл for Функция с параметрами Условный оператор if Оператор return Списки Оператор yield 630
В журнале Квантик, №3 за 2016 год, на странице 33 напечатана такая конкурсная задача (Рис. 1). Рис. 1. Условие задачи Давайте аккуратно выпишем все пятёрки в ряд: 55555555555555555555 Теперь их нужно разбить на группы и поставить между ними знак сложения. • Если поставить 0 знаков, то получится огромное число. • Если поставить 19 плюсов, то получится 100. Следовательно, нужно разбивать ряд из 20 пятёрок на разные группы и находить сумму получившихся чисел. Разбивать числа мы уже умеем и из любопытства можем напечатать все разбиения числа 20 (Рис. 2). Их совсем немного, но мы можем рассматривать и гораздо меньше разбиений, если отбросим заведомо неподходящие. Например, все разбиения, в которых имеется группа из четырёх пятёрок, дадут сумму больше 1000. Разбиение с тремя пятёрками 555 вполне может быть хорошим. Итак, мы будем проверять только разбиения с небольшими группами. Подправляем главную функцию из предыдущего проекта: # ГЛАВНАЯ ФУНКЦИЯ def main(): print() print('Двадцать пятёрок') print() # число: num = 20 # генерируем: parts = part_gen(num) # число разбиений: np = 0 631
# печатаем разбиения: for p in parts: if max(p) < 4: np += 1 print(p) print('Всего:', np) print() main() Рис. 2. Все разбиения числа 20 И узнаём, что таких разбиений всего 44 (Рис. 3). 632
Рис. 3. Все нужные разбиения Из этого рисунка видно, что сумма чисел постоянно увеличивается, поэтому решение будет единственным. Его можно найти и вручную, но зачем тратить время на вычисления? Вызываем из главной функции main функцию solve для решения задачи: # ГЛАВНАЯ ФУНКЦИЯ def main(): print() print('Двадцать пятёрок') print() # решаем задачу: solve() print() Функция solve получает разбиения заданного числа и проверяет те из них, которые не содержат групп более чем из трёх пятёрок: # РЕШАЕМ ЗАДАЧУ def solve(): # число: num = 20 633
# генерируем: parts = part_gen(num) # число решений: np = 0 # печатаем разбиения: for p in parts: if max(p) < 4: Одна пятёрка – это число 5, две – 55 и три – 555. Эти числа мы добавляем к сумме: # находим сумму чисел: summa = 0 for i in p: # print(i) if i == 1: summa += 5 elif i == 2: summa += 55 else: summa += 555 Если полученная сумма равняется 1000, то мы печатаем результат на экране: if summa == 1000: print(p) np += 1 print('Сумма =', summa) print('Всего:', np) print() Запускаем программу и получаем единственное решение (Рис. 4). Рис. 4. Вот и вся задача В более привычной записи ответ такой: 555 + 55 + 55 + 55 + 55 + 55 + 55 + 55 + 55 + 5 = 1000 634
Здесь мы, естественно, не учитываем решений, которые можно получить перестановкой чисел. Как говорится, доверяй, но проверяй. Заходим в Консольное окно, от которого и получаем подтверждение правильности нашего решения (Рис. 5). Рис. 5. Всё верно Когда ответ известен, то нетрудно найти и логическое решение этой задачи… Проект Двадцать пятёрок и больше Исходный код программы находится в файле Двадцать пятёрок и больше.py. Цикл for Функция с параметрами Условный оператор if – elif - else Оператор return Оператор break Списки Оператор yield Зададимся интересным вопросом: если взять не 20 пятёрок, а больше или меньше, то будет ли задача иметь решение? Легко сообразить, что 200 пятёрок дадут тысячу, поэтому задача может быть решена не только для 20 пятёрок. Так же очевидно, что больше 200 пятёрок брать бесполезно, так как их сумма всегда больше 1000. Наша задача облегчается: найти число пятёрок в диапазоне 1..200, для которых существует решение «квантовой» задачи. Программу легко написать, воспользовавшись нашими наработками: 635
# -*- coding: Windows-1251 -*""" Решаем задачу №14 из Квантик, №3 за 2016 год, страница 33 """ # РЕКУРСИВНЫЙ ГЕНЕРАТОР РАЗБИЕНИЙ # ЗАДАННОГО ЧИСЛА def part_gen(n): # основной случай: if n == 0: yield [] return # рекурсия: for p in part_gen(n - 1): p.append(1) yield p p.pop() if p and (len(p) < 2 or p[-2] > p[-1]): p[-1] += 1 yield p # РЕШАЕМ ЗАДАЧУ def solve(num): # генерируем: parts = part_gen(num) # число решений: np = 0 # печатаем разбиения: for p in parts: if max(p) > 3: break # print(p) # находим сумму чисел: summa = 0 for i in p: # print(i) if i == 1: summa += 5 elif i == 2: summa += 55 else: summa += 555 if summa == 1000: print(p) np += 1 print('Сумма =', summa) if np: 636
print('Всего:', np) return np # ГЛАВНАЯ ФУНКЦИЯ def main(): print() print('Двадцать пятёрок') print() # число пятёрок: for n5 in range(1, 200 + 1): # решаем задачу: np = solve(n5) if np: print('Число пятёрок =', n5) print() print() main() Вот сокращённый отчёт о работе нашей программы: Число Число Число Число Число Число Число Число Число Число Число Число Число Число Число Число Число Число Число Число Число пятёрок пятёрок пятёрок пятёрок пятёрок пятёрок пятёрок пятёрок пятёрок пятёрок пятёрок пятёрок пятёрок пятёрок пятёрок пятёрок пятёрок пятёрок пятёрок пятёрок пятёрок = = = = = = = = = = = = = = = = = = = = = 20 29 38 47 56 65 74 83 92 101 110 119 128 137 146 155 164 173 182 191 200 - 2 2 2 2 2 2 2 решения решения решения решения решения решения решения 637
Закономерность очевидна. Первое решение – для 20 пятёрок, а затем нужно каждый раз добавлять по 9 пятёрок. Задачу можно решить для четвёрок. Для получения 1000 необходимо взять 16 + 9n четвёрок, где n = 0...250 Двоек потребуется взять 23 + 9n. Единиц потребуется взять 28 + 9n (с исключениями). Восьмёрок – 8 + 9n. Исходный код программы находится в файле Не только пятёрки.py. А вот интересные примеры с текущим годом: 1111 + 111+ 111 + 111 + 111 + 111 + 111 + 111 + 111 + 11 + 11 + 1 = 2022 222 + 222 + 222 + 222 + 222 + 222 + 222 + 222 + 222 + 22 + 2 = 2022 333 + 333 + 333 + 333 + 333 + 333 + 3 + 3 + 3 + 3 + 3 + 3 + 3 + 3 = 2022 666 + 666 + 666 + 6 + 6 + 6 + 6 = 2022 Вы можете поискать суммы и для других годов: # -*- coding: Windows-1251 -*""" Решаем задачу №14 из Квантик, №3 за 2016 год, страница 33 """ # РЕКУРСИВНЫЙ ГЕНЕРАТОР РАЗБИЕНИЙ # ЗАДАННОГО ЧИСЛА def part_gen(n): # основной случай: if n == 0: yield [] 638
return # рекурсия: for p in part_gen(n - 1): p.append(1) yield p p.pop() if p and (len(p) < 2 or p[-2] > p[-1]): p[-1] += 1 yield p # РЕШАЕМ ЗАДАЧУ def solve(num, n): # генерируем: parts = part_gen(num) # число решений: np = 0 # печатаем разбиения: for p in parts: if max(p) > 4: break # находим сумму чисел: summa = 0 for i in p: # print(i) if i == 1: summa += n elif i == 2: summa += n * 11 elif i == 3: summa += n * 111 else: summa += n * 1111 if summa == 2022: print(p) np += 1 print('Сумма =', summa) return np # ГЛАВНАЯ ФУНКЦИЯ def main(): print() print('Не только пятёрки') print() # число: n = 3 for num in range(1, 80 + 1): # решаем задачу: np = solve(num, n) 639
if np: print('Вариантов:', np) print() print() main() Проект Треугольные числа Исходный код программы находится в файле Треугольные числа.py. Ряд треугольных чисел представляет собой простейшую арифметическую прогрессию, поэтому любое треугольное число можно легко вычислить по формуле (Рис. 1): Рис. 1. Tn = ½ n(n + 1) (1) 640
Однако из наших нарисованных треугольник видно, что второе треугольное число получается, если к первому прибавить 2. Третье – если ко второму прибавить 3. Для любого треугольного числа верна такая рекуррентная формула: Tn = Tn-1 + n Из неё хорошо видна прямая аналогия с рекурсивным вычислением факториалов. Нам нужно только заменить умножение сложением (Рис. 2): # -*- coding: Windows-1251 -*""" Рекурсивный способ вычисления треугольных чисел """ # РЕКУРСИВНАЯ ФУНКЦИЯ def tri(n): if (n == 0): return 0 return n + tri(n - 1) # ГЛАВНАЯ ФУНКЦИЯ def main(): print() #for n in range(1000000, 1000010): for n in range(11): print(f'Треугольное число {n} = {tri2(n)}') print() main() Рис. 2 641
Если вам вдруг понадобятся очень большие треугольные числа, то их можно получить из нерекурсивной функции (Рис. 3): # НЕРЕКУРСИВНАЯ ФУНКЦИЯ def tri2(n): if (n <= 1): return n t = n * (n + 1) // 2 return t Рис. 3 Интересно, что сумма двух последовательных треугольных чисел это квадратное число: Tn + Tn+1 = (n+1)2 На Рис. 4 это свойство хорошо видно. Рис. 4 А точное доказательство этого утверждения легко получить из формулы (1), если найти сумму чисел Tn и Tn+1. 642
Менее очевидно такое свойство треугольных чисел: любое целое неотрицательное число можно представить в виде суммы не более трёх треугольных чисел. Это предположение высказал Пьер Ферма в1638 году, а доказал его в 1796 году Карл Гаусс. Проект Рекурсивные числа Фибоначчи Исходный код программы находится в файле Рекурсивные числа Фибоначчи.py. Классический пример плохой рекурсии – нахождение чисел Фибоначчи по рекуррентной формуле: Fn = Fn-2 + Fn-1 То есть каждое число Фибоначчи, начиная с третьего, равно сумме двух предыдущих. Первые два числа Фибоначчи: F0 = 0 F1 = 1 Дальше легко продолжить этот ряд: 1, 2, 3, 5, 8, 13, 21, 34, 55, … Этот способ легко реализовать в функции fibo: # -*- coding: Windows-1251 -*""" Рекурсивный способ вычисления чисел Фибоначчи Очень медленный. Не годится для вычислений! """ # РЕКУРСИВНАЯ ФУНКЦИЯ def fibo(n): if (n < 2): return n else: return fibo(n - 2) + fibo(n - 1) # НЕРЕКУРСИВНАЯ ФУНКЦИЯ 643
def fibo_iter(n): f, b = 0, 1 for i in range(n): f, b = b, f + b return f # ГЛАВНАЯ ФУНКЦИЯ def main(): print() for n in range(11): print(f'Число Фибоначчи {n} = {fibo(n)}') print() main() В функции fibo мы учитываем основной случай для двух первых чисел Фибоначчи, которые не вычисляются, а даются в готовом виде. Все остальные числа находим, складывая 2 предыдущих (Рис. 1). Рис. 1 Недостаток этого способа в том, что мы не сохраняем уже найденные числа, а каждый раз вычисляем их снова. В результате для больших чисел получается огромная цепочка вызовов функции fibo, и время вычисления быстро растёт. Кроме как для демонстрации рекурсии, это способ никуда не годится. Вообще говоря, если нам нужно получить только заданное число Фибоначчи, то не обязательно хранить в памяти все найденные числа – достаточно двух последних. 644
На каждом шаге предпоследнее число становится последним, а сумма двух последних чисел – текущим числом Фибоначчи, которое становится предпоследним для следующего: # НЕРЕКУРСИВНАЯ ФУНКЦИЯ def fibo_iter(n): f, b = 0, 1 for i in range(n): f, b = b, f + b return f # ГЛАВНАЯ ФУНКЦИЯ def main(): print() for n in range(1000, 1001): #for n in range(11): #print(f'Число Фибоначчи {n} = {fibo(n)}') print(f'Число Фибоначчи #{n} = {fibo_iter(n)}') print() Так как в новой функции fibo_iter остались только простые арифметические операции, то даже огромные числа Фибоначчи находятся практически мгновенно (Рис. 2). Рис. 2 Проект Рекурсивные анаграммы Исходный код программы находится в файле Рекурсивные анаграммы.py. Анаграммы - это слова, которые состоят из одних и тех же букв, но записанных в разной последовательности. С комбинаторной точки зрения, анаграммы – это перестановки букв. Перестановки удобно генерировать с помощью функция permutations из модуля itertools. Но сейчас нас интересуют рекурсивные перестановки, и 645
мы напишем свою функцию, которая умеет переставлять буквы в заданном слове. Возьмём, для примера, слово КАРП: # ГЛАВНАЯ ФУНКЦИЯ def main(): print() global s, n_var s = 'КАРП' n_var = 0 get_anagrams(len(s)) print() main() В рекурсивной функции get_anagrams мы переставляем буквы в слове s и печатаем найденные анаграммы: # -*- coding: Windows-1251 -*""" Рекурсивный способ генерирования перестановок """ # ГЕНЕРИРУЕМ АНАГРАММЫ def get_anagrams(size): global s, n_var if size == 1: return for _ in range(size): get_anagrams(size - 1) # печатаем анаграмму: if (size == 2): n_var += 1 print('вариант', n_var, '>', s) # сдвигаем буквы: rotate(size) Здесь основной случай очевиден: если длина слова size равна 1 (слово состоит из единственной буквы), то других анаграмм у заданного слова нет: if size == 1: return 646
Итак, мы научились находить все анаграммы для однобуквенных слов! А что, если слово состоит из двух букв. Возьмём слово РА. В нём как раз 2 буквы. Из комбинаторики следует, что двухбуквенные слова имеют 2! = 2 анаграммы. Первая анаграмма – исходное слово, второе – слово, в котором первая буква переставлена в конец слова: РА → АР Значит, мы должны научиться переставлять (или сдвигать) первую букву в конец слова. Для этого напишем функцию rotate. Так как нам придётся переставлять буквы в словах разной длины, то мы сообщаем этой функции число конечных букв size в слове s, в которых мы хотим переставить первую букву в конец: # СДВИГАЕМ ЗАДАННЫЕ БУКВЫ def rotate(size): global s # первые len - size символов: first = s[: len(s) - size] # сдвигаем последние size символов: s = s[-size:] s = first + s[1:] + s[0] Если слово состоит из двух букв, то size = 2. Как вы уже знаете, одну букву переставлять бессмысленно. Добавим в функцию rotate отладочные функции print для печати промежуточных результатов работы функции: # СДВИГАЕМ ЗАДАННЫЕ БУКВЫ def rotate(size): global s # первые len - size символов: first = s[: len(s) - size] print('first =', first) # сдвигаем последние size символов: s = s[-size:] print('s[-size:] =', s) print('s[1:] + s[0] =', s[1:] + s[0]) s = first + s[1:] + s[0] print(s) print() И вызовем функцию rotate с аргументом 2. Тогда мы получим такую информацию для размышления (Рис. 1). 647
Рис. 1 Наше слово состоит из 2 букв, поэтому слово first содержит 0 букв, так как переставлять буквы нужно во всём слове целиком, и не останется ни одной буквы перед ними, которые мы должны оградить от изменения. Остаток двухбуквенного слова – это само двухбуквенное слово РА. Срез от первой буквы (считаем с нуля!) до конца слова – Р, первая буква – А. Переносим её в конец и получаем слово АР. Цикл for выполнится 2 раза, так как длина всего слова 2 буквы. В первой итерации будет напечатано слово РА, во второй – АР (Рис. 2). Рис. 2 Трёхбуквенные слова имеют 6 анаграмм. Возьмём наше любимое слово КОТ. Здесь цикл for будет выполнен 3 раза для 3-буквенных слов, в результате на первом месте окажутся буквы К, О, Т. Для каждого из этих циклов дважды выполнится цикл for для двухбуквенных слов, получаемых из последних двух букв 3-буквенного слова. Буквы в двухбуквенных словах поменяются местами, а первая буква сохранится. Как и предписано теорией, мы получим 3 * 2 = 6 анаграмм (Рис. 3). Для 4-буквенного слова КАРП цикл for выполнится 4 раза для 4буквенных слов, и в начале слов последовательно окажутся буквы К, А, Р, П. Для 3-буквенных слов цикл for выполнится 3 раза, как мы рассмотрели 648
выше. В итоге наша программа напечатает все 24 перестановки указанных букв (Рис. 4). Рис. 3 Рис. 4 649
Среди них окажутся и слова КРАП и ПАРК – осмысленные анаграммы исходного слова. Проект Числовые рекурсии Исходный код программы находится в файле Числовые рекурсии.py. В задачах часто бывает нужно найти, сколько цифр в заданном числе. Мы решим задачу только для натуральных чисел. Нуль и отрицательные числа потребуют дополнительной обработки ввода. Итак, основное условие: # СЧИТАЕМ ЦИФРЫ def get_num_digits(num): if num <= 0: return 0 То есть, когда функция get_num_digits получит нуль или отрицательное число, она вернёт нуль, и на этом закончит свою работу. В противном случае число натуральное имеет по крайней мере 1 цифру. Если мы разделим нацело заданное число, то оно укоротится на 1 цифру или обратится в нуль. При следующем рекурсивном вызове функция get_num_digits получит либо укороченное число, либо нуль. Если это нуль, то все цифры числа уже посчитаны, в противном случае к сумме цифр добавится ещё 1: return 1 + get_num_digits(num // 10) По этому сценарию можно подсчитать цифры и в нерекурсивной функции: # ИТЕРАЦИОННАЯ ВЕРСИЯ def get_num_digits_iter(num): num_digit = 0 while num: num_digit += 1 650
num //= 10 return num_digit В главной функции мы организуем бесконечный цикл ввода чисел и печатаем длину чисел, полученных от обеих функций (Рис. 1): # ГЛАВНАЯ ФУНКЦИЯ def main(): num = 1 # бесконечный цикл ввода чисел, # пока пользователь вводит # натуральные числа: while num: print('Введите число: ', end='') num = int(input()) print(get_num_digits(num)) print(get_num_digits_iter(num)) print() main() Рис. 1 Другая часто встречающаяся задача состоит в перевёртывании заданного числа. Например, число 123 должно превратиться в 321. Последнюю цифру числа мы легко получим как остаток от деления заданного числа num на 10. Её нужно перенести в начало числа. В числе 123 по651
следняя цифра 3. Она должна занять первое место в перевёрнутом числе, то есть место сотен. И для этого мы должны умножить тройку на 100, а в общем случае на 10 в степени на 1 меньше числа цифр в исходном числе. В числе 123 имеется 3 цифры, значит, тройку следует умножить на 10 в степени 2, то есть как раз на сотню. Подсчитывать цифры мы уже научились, так что с первой цифрой мы успешно справились. Итак, тройка заняла место сотен, и теперь нам нужно повторить операцию на оставшихся двух цифрах исходного числа, то есть на 12. Чтобы его получить, достаточно разделить исходное число на 10 без остатка, а затем рекурсивно вызвать функцию reverse_num для числа 12. Она переставит двойку на место десятков, а от числа останется 1. Она займёт место единиц. Таким образом, число 123 превратится в 321, что нам и нужно. Функция reverse_num реализует разработанный нами алгоритм: # -*- coding: Windows-1251 -*""" Рекурсивный способ переворачивания чисел """ # ПЕРЕВОРАЧИВАЕМ ЧИСЛО def reverse_num(num): if num == 0: return 0 return (num % 10) * 10 ** (get_num_digits(num) - 1) + reverse_num(num // 10) Добавляем в функцию main вызов этой функции и убеждаемся, что она работает верно (Рис. 2): # ГЛАВНАЯ ФУНКЦИЯ def main(): num = 1 # бесконечный цикл ввода чисел, # пока пользователь вводит # натуральные числа: while num: print('Введите число: ', end='') num = int(input()) #print(get_num_digits(num)) #print(get_num_digits_iter(num)) print(reverse_num(num)) print() 652
Рис. 2 Для переворачивания чисел также существует нерекурсивная версия: # ИТЕРАЦИОННАЯ ВЕРСИЯ def reverse_num_iter(num): rev_num = 0 while num: # очередная цифра сзади: last_digit = num % 10 rev_num = rev_num * 10 + last_digit num //= 10 return rev_num И ещё 2 способа нерекурсивного переворачивания чисел с предварительным их преобразованием в строку: # СТРОКОВАЯ ВЕРСИЯ def reverse_num_str(num): return int(str(num)[::-1]) # СТРОКОВАЯ ВЕРСИЯ 2 def reverse_num_str2(num): return int(''.join(reversed(str(num)))) В первом случае мы используем срез с отрицательным шагом, который возвращает перевёрнутую строку. Во втором случае - функцию reversed, 653
которая возвращает символы строки в обратном порядке. Мы добавляем их к пустой строке и получаем строку с перевёрнутым числом. Так как нам нужны не строки, а числа, то мы затем конвертируем строки в числа. Проект Великолепная четвёрка Исходный код программы находится в файле Великолепная четвёрка.py. В книге Algorithmen und Problemlösungen mit C++ (Рис. 1), на страницах 300-302 решается такая задача. Рис. 1 Из четвёрки с помощью трёх простых операций получить любое натуральное число. Операции такие: - добавить 4 в конец - добавить 0 в конец - разделить число на 2, если оно чётное Эту задачу проще решить с конца, то есть заданное число превратить в четвёрку. Тогда и операции станут противоположными: - удалить 4, если она стоит в конце числа 654
- удалить 0, если он стоит в конце числа - умножить число на 2 В главной функции мы вводим любое целое число. Если это нуль или отрицательное число, то программа закрывается. В противном случае число num передаётся функции solve. Кроме того, она получает сообщения, которые поясняют действие функции. Если число num - четвёрка (основной случай), то решение заканчивается. В противном случае мы рекурсивно выполняем операции над текущим числом num. Если число num заканчивается на нуль, то мы удаляем его. Для этого достаточно разделить число на 10. Аналогично мы поступаем, если число заканчивается на четвёрку. Если число заканчивается на другие цифры, то мы умножаем его на 2. Эти действия мы продолжаем до тех пор, пока число не обратится в четвёрку. Тогда мы печатаем все действия с комментариями в обратном порядке – по мере возврата из вызванных функций. Обратите внимание, что в функции solve операции противоположные, а комментарии «прямые», так как функция напечатает все действия в прямом порядке. # -*- coding: Windows-1251 -*# РЕШАЕМ ЗАДАЧУ def solve(num, message): if (num == 4): return # последняя цифра числа: last_dig = num % 10 # добавляем 0 в конец: if last_dig == 0: solve(num // 10, '(добавляем 0 в конец)') # добавляем 4 в конец: elif last_dig == 4: solve(num // 10, '(добавляем 4 в конец)') # делим на 2: else: solve(num * 2, '(делим на 2)') print('->', num, message, end='') # ГЛАВНАЯ ФУНКЦИЯ 655
def main(): print() print('Великолепная четвёрка') print() # бесконечный цикл ввода данных # пока пользователь не закроет программу # или не введёт отрицательное число: while True: s = 'Введите число > ' print(s, end='') num = int(input()) # если пользователь ввёл 0 или отрицательное число, # то программу закрываем: if (num <= 0): return # решаем задачу: solve(num, '') print() print() main() На Рис. 2 вы можете видеть, как работает наша программа. Рис. 2. Оперативно! 656
Проект Рекурсивный тортик Исходный код программы находится в файле Рекурсивный тортик.py. Метод с параметрами Оператор return Оператор if Оператор if-else Цикл while Бесконечный цикл while Оператор input В книге Математические изюминки [ХР92], на страницах 9-11 Росс Хонсбергер показывает, как решить такую задачу: Расположим n точек на окружности и соединим их попарно хордами. Предположим, что никакие три хорды не имеют общей точки внутри круга. На сколько областей разобьётся круг этими хордами? Вывод формулы вы можете проследить по книге, нас же интересует только формула как таковая: Здесь: • • • NR – число областей, на которые хорды разбивают круг n – число точек на окружности и - число сочетаний по 2 и по 4 из n Подобную задачу решает и Мартин Гарднер в книге Математические досуги [ГМ72], на страницах 82-87. Она облечена в более занимательную форму: 657
На какое максимальное число кусков можно разделить круглый пирог, если сделать n разрезов, каждый из которых пересекает все остальные? Муж спрашивает у жены: - Милая, на сколько частей разрезать тортик – на шесть или на восемь? Жена отвечает: - Режь на шесть – восемь я не съем… На Рис. 1 вы можете внимательно проследить судьбу тортика, подвергнутого означенным разрезам. Рис. 1. Режем тортик По этим данным с помощью метода конечных разностей легко выводится формула для подсчёта кусков: NR = ½ n2 + ½ n +1 = ½ n(n + 1) + 1 (1) Существует и рекуррентная формула: NR(0) = 1 NR(n) = n + NR(n-1) при n > 0 658
Давайте в помощь домохозяйкам и кондитерам напишем программу, умеющую подсчитывать куски торта, которые получаются при заданном числе разрезов (Рис. 2). Поскольку в реальной жизни вряд ли придётся разрезать даже самый большой торт на тысячи частей (то есть в пыль!), то для практических нужд вполне достаточно рекуррентной формулы: # -*- coding: Windows-1251 -*# ПРОГРАММА ДЛЯ РЕКУРСИВНОГО РАЗРЕЗАНИЯ ТОРТА # РЕЖЕМ ТОРТИК РЕКУРСИВНО def tortik_rec(num): if(num < 1): return 1 else: return num + tortik_rec(num - 1) # ГЛАВНАЯ ФУНКЦИЯ def main(): print('Рекурсивный тортик') print() # бесконечный цикл ввода данных # пока пользователь не закроет программу # или не введёт отрицательное число: while True: s = 'Введите число разрезов > ' print(s, end = '') num = int(input()) # если пользователь ввёл отрицательное число, # то программу закрываем: if (num < 0): return # находим число разрезов: num = tortik_rec(num) #num = tortik(num) # печатаем число кусков: print('Максимальное число кусков =', num) print() main() Но если вас интересуют и теоретические изыскания в кондитерской науке, то нужно прибегнуть к формуле (1) (Рис. 3): 659
Рис. 2. Полезная программа # РЕЖЕМ ТОРТИК НЕРЕКУРСИВНО def tortik(num): res = num*(num + 1) // 2 + 1 return res # находим число разрезов: #num = tortik_rec(num) num = tortik(num) Рис. 3. Очень мелкие кусочки! 660
Исходный код программы находится в файле Рекурсивный тортик 2.py. Впрочем, хозяйке гораздо интереснее и полезнее знать, сколько нужно сделать разрезов, чтобы получить вполне определённое число кусков торта. Опять бросаемся на помощь домохозяйке! В функции main она задаёт нужное число кусков и получает от функции tortik необходимое число разрезов: # ГЛАВНАЯ ФУНКЦИЯ def main(): print('Рекурсивный тортик 2') print() # бесконечный цикл ввода данных # пока пользователь не закроет программу # или не введёт отрицательное число: while True: s = 'Введите число кусков > ' print(s, end='') tile = int(input()) # если пользователь ввёл отрицательное число # или 0, то программу закрываем: if (tile <= 0): return # находим число разрезов: num = tortik(tile) # печатаем число разрезов: print('Минимальное число разрезов =', num) unwanted = num * (num + 1) // 2 + 1 - tile print('Число лишних кусков =', unwanted) print() main() В функции tortik нам удобнее пользоваться нерекуррентной формулой. Она получает необходимое число кусков n и начинает последовательно делать num разрезов – до тех пор, пока число кусков не сравняется или не превысит заданного числа кусков: # -*- coding: Windows-1251 -*# ПРОГРАММА ДЛЯ РЕКУРСИВНОГО РАЗРЕЗАНИЯ ТОРТА 661
# РЕЖЕМ ТОРТИК def tortik(num): res = 0 while (num > (res * (res + 1) // 2 + 1)): res += 1 return res На Рис. 4 вы можете видеть работу этой во всех отношениях полезной для дома и для семьи программы. Рис. 4. Тоже полезная программа В некоторых случаях остаются «лишние» куски. Вот почему так важно знать математику и лично нарезать тортики кусками! Проект Red and Black Исходный код программы находится в файле Red and Black.py. Вложенные циклы for Функция с параметрами Условный оператор if - elif Оператор return Оператор and 662
Списки Глобальные переменные Функция copy В книге Data Structure Practice for Collegiate Programming Contests and Education (Рис. 1), на страницах 48-51 разбирается интересная задача POJ 1979 Red and Black. Рис. 1. Обложка книги Прямоугольное помещение разделено на клетки, окрашенные в красный и чёрный цвет. Человек стоит на чёрной клетке и может передвигаться на соседние клетки по четырём направлениям, но заходить на красные клетки ему запрещено. Нужно написать программу для подсчёта числа чёрных клеток, которые может обойти человек. Обозначения на карте: . – чёрная клетка # - красная клетка @ - человек (стоит на чёрной клетке) Задания хранятся в текстовых файлах в таком формате: 11 9 .#......... .#.#######. 663
.#.#.....#. #.#.###.#. .#.#..@#.#. .#.#####.#. .#.......#. .#########. ........... На олимпиадах это общепринятый способ ввода данных, но мы просто скопируем задания в программу, чтобы не заниматься лишней работой. Глобальные переменные и условие первой задачи можно записать так: # -*- coding: Windows-1251 -*""" Задача POJ 1979 - Red and Black """ from copy import * from pprint import pprint # поле: field = [] # посещённые клетки: visited = [] # число посещённых клеток: count = 0 # размеры поля: w = 6 h = 9 problem1 = [ '....#.', '.....#', '......', '......', '......', '......', '......', '#@...#', '.#..#.' ] Прежде всего, нужно скопировать условие задачи в список field (мы будем называть его массивом – для удобства). Эта операция необязательная, поскольку условие задачи уже хранится в списке problem1. Но, если бы мы загружали задачу с диска, нам пришлось бы заполнять массив field данными, так что мы просто имитируем этот процесс. 664
Одновременно функция prepare возвращает координаты клетки, на которой стоит человек, чтобы мы смогли начать обход помещения: # РЕШАЕМ ЗАДАЧУ def solve(problem): # печатаем задачу: print('h =', h, 'w =', w) pprint(problem) print() # готовимся к решению: starty, startx = prepare(problem) # решаем задачу: dfs(starty, startx) print() print('Число клеток =', count) print_position() В функции prepare мы копируем массив из условия задачи в массив поля field. Эта операция простая и безболезненная. А дальше мы должны просмотреть все клетки поля и отметить красные клетки единицей в массиве visited. Она обозначает, что путь туда нашему человечку запрещён. И точно также мы обозначаем и первую чёрную клетку, на которой стоит человечек, чтобы не забрести в неё ещё раз. По ходу обхода мы запоминаем координаты клетки с человечком в переменных starty и startx: # ГОТОВИМСЯ К РЕШЕНИЮ ЗАДАЧИ def prepare(problem): # обнуляем счётчик клеток: global count count = 0 # создаём копию поля: global field field = copy(problem) # создаём массив для обозначения посещённых клеток: global visited visited = [[0] * w for i in range(h)] # заполняем массив visited: for y in range(h): for x in range(w): if (field[y][x] == '#'): visited[y][x] = 1 elif (field[y][x] == '@'): visited[y][x] = 1 # координаты стартовой позиции: starty = y 665
startx = x # печатаем исходную позицию посещённых клеток: print('starty =', starty, 'startx =', startx) print_position() return starty, startx Теперь можно распечатать текущее состояние массива visited: # ПЕЧАТАЕМ ТЕКУЩУЮ ПОЗИЦИЮ def print_position(): pprint(visited) print() Для первой задачи будет напечатана такая информация (Рис. 2). Рис. 2. Подготовительные операции Проверяем и убеждаемся, что наши функции работают исправно! Приступаем непосредственно к решению задачи в функции dfs, которой мы передаём координаты начальной клетки (y, x): # СЧИТАЕМ КЛЕТКИ МЕТОДОМ DFS def dfs(y, x): global count count += 1 print(count, end=' ') Сразу же считаем ту чёрную клетку, на которой стоит человечек. Так как наше поле принципиально не отличается от лабиринта, то для его обхода можно использовать обычные для лабиринтов методы DFS и BFS. 666
DFS - это сокращение полного английского названия этого метода depthfirst search, что по-русски означает поиск в глубину. Второй метод – BFS – расшифровывается как breadth-first search, а на русский язык переводится как поиск в ширину. Его также можно использовать для решения этой задачи. Оба метода показывают преимущества рекурсивных методов и алгоритмов по сравнению с итерационными. Обход клеток лабиринта любой сложности укладывается в несколько строк кода: # идём вверх: if (y > 0 and visited[y - 1][x] == 0): visited[y - 1][x] = 1 dfs(y - 1, x) # идём налево: if (x > 0 and visited[y][x - 1] == 0): visited[y][x - 1] = 1 dfs(y, x - 1) # идём направо: if (x < w - 1 and visited[y][x + 1] == 0): visited[y][x + 1] = 1 dfs(y, x + 1) # идём вниз: if (y < h - 1 and visited[y + 1][x] == 0): visited[y + 1][x] = 1 dfs(y + 1, x) Но, прежде чем переступить на соседнюю клетку, мы должны убедиться, что: • она находится в границах поля • мы её ещё не посещали (либо это клетка красного цвета, которую мы как бы уже посетили) Если клетка находится на поле и свободна, то мы переходим на неё. Порядок выбора направления для перехода в соседнюю клетку может быть любым. В данном случае мы идём вверх. Если клетка сверху свободна, мы переходим на неё и снова пытаемся идти вверх, вызывая функцию dfs с координатами соседней верхней клетки. И так мы продолжаем идти вверх, пока над нами есть свободные клетки. Но рано или поздно над нами окажется: 667
• посещённая (или красная) клетка • граница поля. Тогда СТОП – дальше в этом направлении идти нельзя. Что делать? – Как и в любом лабиринте, главное – не растеряться и не потерять голову. Осматриваемся по сторонам и идём налево, пока это возможно. Затем снова пытаем пойти вверх. Не получится – налево. Когда все пути в этих направлениях закончатся, мы идём направо, и наконец, вниз. Когда мы попадём в тупик, то функция dfs вернётся в вызывающую функцию – в ту самую клетку, из которой мы начали движение в тупик. Если из этой клетки остались пути в других направлениях, то мы идём по ним. В случае нового тупика функция dfs снова возвращается назад, и так далее, пока мы не обойдём все доступные нам клетки. Поскольку каждую из посещённых клеток мы посчитали ровно 1 раз, то в переменной count окажется ответ на задачу. Запускаем программу и получаем полную информацию о странствиях в лабиринте (Рис. 3). Рис. 3 Обратите внимание, что мы не посчитали 3 чёрные клетки, но не по лености, а только потому, что они окружены со всех сторон красными клетками, и человечек в них попасть не может (Рис. 4). Если бы человечек мог беспрепятственно посетить все чёрные клетки, мы просто пересчитали бы их в обычном цикле… 668
Рис. 4. Хода нет! Чтобы решить вторую олимпиадную задачу, раскомментируйте её: """ w = 11 h = 9 problem1 = [ '.#.........', '.#.#######.', '.#.#.....#.', '.#.#.###.#.', '.#.#..@#.#.', '.#.#####.#.', '.#.......#.', '.#########.', '...........' ] """ Вы можете и самостоятельно придумать подобные «лабиринты» - это несложно. 669
Литература [Нагибин88] Нагибин Ф.Ф., Канин Е.С. Математическая шкатулка М.: Просвещение, 1988. – 160 с. [ОО80] Оре О. Приглашение в теорию чисел М.: Наука, 1980 г. - 128 с. Библиотечка Квант, Выпуск 3 670
[КА86] [КА96] Б.А. Кордемский, А.А.Ахадов Удивительный мир чисел (МАТЕМАТИЧЕСКИЕ ГОЛОВОЛОМКИ И ЗАДАЧИ ДЛЯ ЛЮБОЗНАТЕЛЬНЫХ) М.: Просвещение, 1986. – 144 с. М.: Просвещение, 1996. – 159 с 671
[ЗП88] Абрамов С.А. и др. Задачи по программированию Наука, 1988. – 224 с. ISBN: 5-02-013774-Х Серия: Библиотечка программиста [100] В. А. Дагене, Г. К. Григас, К. Ф. Аугутис 100 задач по программированию М.:Просвещение, 1993. – 251 с. ISBN: 5-09-003864-3 672
[ВНН88] Воробьёв Н.Н. Признаки делимости Наука. - 1988, 96 с. ISBN 5-02-013731-6 [БК85] Брудно А. Л. Каплан Л. И. Олимпиады по программированию для школьников Наука. - 1985, 96 с. 673
[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 с. 674
[ЛВ88] Липский В. Комбинаторика для программистов Москва: Мир, 1988. – 200 с. [РВ11] Рубанцев Валерий Delphi в примерах, играх и программах. От простых приложений, решения задач и до программирования интеллектуальных игр Наука и Техника, 2011. – 672 с. ISBN: 978-5-94387-664-6 Серия: Самоучитель 675