/
Author: Рубанцев В. Рубанцева Л.
Tags: задачи по программированию искусство программирования компьютерные технологии язык программирования python
ISBN: ㅤ
Year: 2016
Text
1
Валерий Рубанцев
Практикум по решению задач на
языке Питон 3
Базовый уровень
2
Бесплатное издание
Все права защищены. Никакая часть этой книги не может быть воспроизведена в
любой форме без письменного разрешения правообладателей.
Автор книги не несёт ответственности за возможный вред от использования информации, составляющей содержание книги и приложений.
Copyright 2016 Валерий Рубанцев
Лилия Рубанцева
3
От автора
В книге подробно рассматривается решение более 100 задач: математических, словесных, комбинаторных, вероятностных, игровых. И это не учебные, надуманные задачи, которые обычно предлагаются в книгах по программированию, а вполне конкретные, которые можно встретить в журналах, книгах и на конкурсах по программированию. Изучать теорию можно
бесконечно, но без практического применения знаний она ничего не стоит.
Только ежедневные упражнения в решении самых разнообразных задач помогут вам стать настоящими программистами.
Любую задачу можно решить несколькими способами, и не всегда в книге
приводится лучшее из них. Цель книги более скромная: показать, как
можно решить задачу наиболее простым и понятным способом. Начинающим важно научиться решать задачи именно так, потому что более быстрые/эффективные решения обычно гораздо сложнее и совершенно непонятны начинающим программистам. Тем не менее очень полезно искать
собственные решения предложенных задач – только так вы научитесь разрабатывать проекты самостоятельно.
Кроме собственно решения задач, мы разработаем и «вспомогательные»
проекты:
Делимость чисел
Наибольший общий делитель
Наименьшее общее кратное
Простые числа. Решето Эратосфена
Факторизация чисел
Совершенные числа
Числовые ребусы
Факториал
Числа Фибоначчи
Генерирование перестановок
Генерирование сочетаний
Разбиение числа на слагаемые
Книга охватывает все ключевые элементы языка Питон:
Числа
Строки
Логический тип
4
Локальные и глобальные переменные
Арифметические и логические выражения
Условный оператор if – elif - else
Списки
Кортежи
Списки
Словари
Циклы for и while
Вложенные и бесконечные циклы
Операторы return, continue, break
Функции с параметрами и без параметров
Лямбда-функции
Классы
В самом начале книги находится Тематический указатель, который поможет вам ориентироваться в проектах и легко находить нужный. В конце
многих глав имеются задания для самостоятельного решения.
Для решения задач используются следующие методы и алгоритмы:
метод грубой силы, полный перебор (brute force)
поиск в глубину depth-first search, DFS)
динамическое программирование (dynamic programming)
алгоритм Эратосфена (The Sieve of Eratosthenes)
простой и быстрый алгоритм Евклида
рекурсия (recursion)
Все проекты разрабатывались в Microsoft Visual Studio 2015 с Python
Tools for Visual Studio 2015 и в PyCharm 5. Поскольку исходный код программ с расширением *.py не содержит никакой специфической информации, то может быть запущен в любой другой среде разработки программ на
Питоне, хотя бы в IDLE.
Везде используется версия Python 3.5, но исходный код будет работать и в
более ранних версиях Питона (но не в 2.7!).
Валерий Рубанцев
5
Условные обозначения, принятые в книге:
Дополнение или замечание
Ненавязчивое требование или указание
Исходный код
Задание для самостоятельного решения
Папка с исходным кодом программы
Исходные коды всех проектов находятся в папке _Projects
6
Оглавление
Практикум по решению задач на языке Питон 3 ............... 2
От автора ................................................................ 4
Оглавление ............................................................. 7
Тематический указатель ............................................. 11
Глава 1. Числа, числа, числа… .................................... 14
Признаки делимости чисел ................................................................................. 17
Проект Делится - не делится? ............................................................................ 23
Проект Назойливый остаток ................................................................................ 29
Проект Первый числовой фокус........................................................................ 35
Проект Второй числовой фокус.......................................................................... 38
Проект Шестизначное число ................................................................................ 41
Проект Тройка, семёрка и... только ................................................................... 44
Проект Любопытное свойство чисел ............................................................... 49
Проект Как определил ошибку Чохбилмиш? ................................................. 53
Проект Шестизначный перенос ........................................................................... 55
Задания для самостоятельного решения.......................................................... 57
Глава 2. НОД, НОК и компания .................................. 58
Проект Наибольший общий делитель .............................................................. 58
Проект Наименьшее общее кратное .................................................................. 64
Проект НОК нескольких чисел ........................................................................... 66
Проект Всезнающая статистика .......................................................................... 70
Проект Восстановите потерянную цифру........................................................ 73
Проект Снимите маску с одной цифры ............................................................ 75
Проект На одно делится, на другое нет ........................................................... 77
Проект Кто где живёт? ........................................................................................... 79
Проект Три велосипедиста .................................................................................. 82
Глава 3. Простые числа ............................................ 84
Проект Чудеса в решете Эратосфена ................................................................. 88
Проект Простые числа .......................................................................................... 92
Проект Простые числа 2 ...................................................................................... 95
Взаимно простые числа ......................................................................................... 99
Проект Разложение числа на простые множители .................................... 100
Проект Совершенные числа ............................................................................... 103
7
Задания для самостоятельного решения........................................................ 106
Глава 4. Числовые ребусы ......................................... 107
Проект Каковы жуки? ........................................................................................... 108
Проект Четыре "пари" ........................................................................................... 111
Проект Тайна трёх слагаемых ...........................................................................114
Проект Меняем четыре буквы на четыре цифры .........................................118
Проект Коварная задача папы ............................................................................121
Проект Универсальный решатель .................................................................... 124
Задания для самостоятельного решения........................................................ 132
Глава 5. Степени и корни ......................................... 134
Проект Кубическое число ................................................................................... 134
Проект И «хвост», и «грива» ............................................................................. 137
Проект Возведение в квадрат без операции умножения ..................... 140
Проект Возведение в куб без операции умножения ............................. 143
Проект Зашифрованные жуки ............................................................................ 148
Проект Ж-Ж-Ж!.........................................................................................................151
Проект Девять в квадрате .................................................................................... 153
Проект Пара чисел: 3149 и 3151....................................................................... 155
Проект Число 698 896 ......................................................................................... 158
Проект Числа 11 826, 12 363, 14 676 ................................................................161
Проект Числа 32 043 и 99 066 ......................................................................... 167
Проект Число 117 649 .......................................................................................... 168
Проект Красивые цепочки равенств ................................................................ 172
Задания для самостоятельного решения........................................................ 175
Глава 6. Числовые ряды и другие задачи ....................... 176
Проект Факториал .................................................................................................. 176
Проект Факториальные нули .............................................................................. 180
Проект Числа Фибоначчи..................................................................................... 182
Проект Числа Фибоначчи 2 ................................................................................. 186
Проект «Избранные» числа ................................................................................ 189
Проект Безошибочный прогноз......................................................................... 194
Проект Ошибочный прогноз .............................................................................. 199
Проект Нумерация страниц ............................................................................... 203
Проект Сколько страниц в книге?.................................................................... 205
Проект И такие есть числа ................................................................................ 207
Проект Трёхзначное число ................................................................................ 209
8
Проект Таких чисел только два .........................................................................211
Проект Ещё два числа .......................................................................................... 213
Проект Отгадать число, ничего не спрашивая ............................................. 215
Проект Три лягушки ............................................................................................. 219
Проект Гаусс .......................................................................................................... 222
Проект Плюс-минус ............................................................................................ 226
Проект Минус-плюс ............................................................................................ 228
Проект Дробный ряд ............................................................................................ 230
Проект Ещё один дробный ряд......................................................................... 232
Проект Трёхзначное число 2 ............................................................................ 234
Проект Вычисляем пи и е ................................................................................. 236
Проект Вычисляем пи по методу Архимеда............................................... 253
Проект Тригонометрические функции .......................................................... 259
Проект Сотая цифра ............................................................................................. 265
Проект Случайные ферзи................................................................................... 268
Проект Три числа ................................................................................................. 273
Проект Четыре числа .......................................................................................... 279
Проект Город .......................................................................................................... 281
Проект Наименьшее число ................................................................................ 284
Проект Трёхзначное число ................................................................................ 286
Задания для самостоятельного решения....................................................... 288
Глава 7. Диофантовы уравнения и линейное программирование .. 290
Проект На ферме .................................................................................................. 293
Проект Решите систему уравнений ................................................................. 295
Проект Сооружение для лаборатории............................................................ 297
Проект И такие есть числа 3 ............................................................................. 300
Проект И такие есть числа 4 ............................................................................. 304
Проект Ящики ........................................................................................................ 306
Проект Путёвки ..................................................................................................... 309
Проект На базаре.................................................................................................... 312
Проект Дедушка и внучка .................................................................................... 314
Проект Сколько у мамы дочерей и сыновей? .............................................. 316
Проект Из жизни Дефурнеля ............................................................................. 318
Проект Счётные палочки .................................................................................... 321
Задания для самостоятельного решения....................................................... 323
Глава 8. Компьютерные игры ..................................... 324
Проект Игра Охота на Скалоеда ...................................................................... 324
9
Проект Игра Охота на Скалоедов .................................................................... 343
Проект Игра Угадай число ................................................................................ 356
Проект Игра Крестики-нолики ......................................................................... 363
Задания для самостоятельного решения....................................................... 380
Глава 9. Комбинаторика и теория вероятностей ................. 383
Перестановки ......................................................................................................... 383
Проект Генерируем перестановки .................................................................. 385
Проект «Правильные» перестановки ........................................................... 389
Проект Функция permutations .......................................................................... 392
Проект Премия за изобретение ....................................................................... 396
Проект Сумма пятизначных чисел .................................................................. 402
Проект Как собрать Дрим тим? ........................................................................ 405
Проект Дартс .......................................................................................................... 409
Проект Разменный пункт ..................................................................................... 414
Проект Спортлото.................................................................................................. 418
Проект Жребий брошен! ....................................................................................... 431
Задания для самостоятельного решения....................................................... 434
Глава 10. Рекурсия .................................................. 437
Проект Рекурсивный факториал ...................................................................... 437
Проект Рекурсивное умножение ...................................................................... 442
Проект Рекурсивный модуль............................................................................. 444
Проект Рекурсивные перевороты .................................................................... 446
Проект Рекурсивные палиндромы .................................................................. 450
Проект Нерекурсивные палиндромы ............................................................. 453
Проект Рекурсивная считалка ........................................................................... 455
Проект Рекурсивные разбиения........................................................................ 459
Проект Двадцать пятёрок .................................................................................... 461
Проект Двадцать пятёрок и больше................................................................ 466
Проект Треугольные числа ............................................................................... 470
Проект Рекурсивные числа Фибоначчи .......................................................... 474
Проект Рекурсивные анаграммы ...................................................................... 477
Проект Числовые рекурсии ............................................................................... 482
Проект Великолепная четвёрка ....................................................................... 487
Проект Рекурсивный тортик .............................................................................. 490
Проект Red and Black ........................................................................................... 496
Литература ........................................................... 504
10
Тематический указатель
Элементы языка
Проекты
Идентификаторы
*
Выражения
Операторы
*
*
Комментарии #
*
Целый тип int
*
Вещественный тип float
Проект Дробный ряд
Проект Ещё один дробный ряд
Проект Вычисляем пи и е
Проект Тригонометрические функции
Множества
Проект Alphametics
Проект Дартс
Словари
Проект Alphametics
Списки
Проект Делится - не делится?
Проект Alphametics
Проект Тройка, семёрка и... только . . .
Списки списков
Проект Игра Охота на Скалоеда
Проект Игра Охота на Скалоедов
Проект Игра Крестики-нолики
Проект Массивные перестановки
Проект Сумма пятизначных чисел
Переменные
Локальные переменные
*
Операторы и операции Проект Тройка, семёрка и... только,
присваивания
Проект Наибольший общий делитель. . .
= =+ =- *= /= %=
11
Операции отношения
< > == != <= >=
*
Логические операции
and, or и not
Проект Три велосипедиста,
Проект Чудеса в решете Эратосфена . . .
Арифметические опера- *
ции
+ - * // / %
*
Условный оператор if
Условный оператор ifelse
Вложенные
условные
операторы
Цикл for
Проект Делится - не делится? . . .
Цикл while
Проект Тройка, семёрка и... только,
Проект Наибольший общий делитель . . .
Вложенные циклы
Проект Назойливый остаток,
Проект Простые числа . . .
Бесконечные циклы
Проект Делится - не делится?
Проект Назойливый остаток . . .
Оператор break
Проект Делится - не делится? . . .
Оператор continue
Проект Делится - не делится? . . .
Классы class
Конструкторы
Проект Игра Угадай число, Проект Игра Крестики-нолики
Поля
Методы
*
Ключевое слово return
*
Модуль math
Проект Делится - не делится? . . .
Модуль random
Проект Безошибочный прогноз
Проект Ошибочный прогноз . . .
12
Модуль itertools
Проект Функция permutations
Проект Дартс
Форматированный вывод
Проект Ящики,
Проект Путёвки
Если после название проекта стоит многоточие ..., значит элемент
многократно используется и в следующих проектах.
Звёздочка * в графе Проекты означает, что соответствующий элемент языка
используется во многих проектах.
13
Глава 1. Числа, числа, числа…
Говорят, что числа правят миром.
Нет, они только показывают, как правят миром.
Иоганн Гёте
Детский компьютер
Как вы знаете из уроков математики, чисел бесконечно много, но их можно
разбить на отдельные (под)множества по тем или иным признакам.
Самые первые числа, которые придумали ещё первобытные люди, называются натуральными. Они используются для подсчёта различных предметов, например, яблок или палочек, на которых вы и сами учились считать
в первом классе.
Папа спрашивает у сына:
- Скажи, сколько будет, если к трём грушам прибавить
ещё две груши?
Сын отвечает:
- Не знаю, папа, мы в школе решаем задачи только
про яблоки!
Множество натуральных чисел обозначается большой латинской буквой N, поэтому само множество можно записать так: N = {1, 2, 3, ...}. Иногда
к множеству натуральных чисел относят и нуль (отсутствие предметов вообще): N0 = {0, 1, 2, 3, ...}. Множество натуральных чисел является подмножеством всех чисел и также бесконечно.
Если к натуральным числам добавить отрицательные числа (и нуль), то
получится множество целых чисел. Оно обозначается большой латинской буквой Z = {... -2, -1, 0, 1, 2, ...}. Нетрудно догадаться, что и целых чисел
бесконечно много.
14
В арифметике обычно используют именно целые числа, но встречаются
алгебраические и геометрические задачи, которые невозможно решить
без дробных чисел.
Рациональные числа можно представить в виде простой (обыкновенной)
дроби:
m/n
где:
m - целое число;
n - натуральное число, не равное нулю (вы, конечно, помните, что на
нуль делить нельзя!).
Множество рациональных чисел обозначается буквой Q. Если знаменатель дроби равен 1, то вся дробь равна числителю, то есть целому числу n.
Таким образом, все целые числа являются в то же время и рациональными
(множество целых чисел - это подмножество рациональных). Но не наоборот!
Рациональные числа можно представить также в виде конечной десятичной дроби (1/2 = 0,5) или бесконечной периодической десятичной дроби (1/7
= 0,1428571...).
Иррациональные числа не могут быть представлены в виде простой
дроби (а также в виде конечной или бесконечной десятичной периодической дроби). Таким образом, иррациональным числом называют любое
число, представимое в виде бесконечной непериодической десятичной
дроби. Примером такой дроби служит корень квадратный из двойки. Иррациональность этого числа была известна уже древним математикам, которые доказали несоизмеримость стороны и диагонали квадрата.
Иррациональные числа обозначают буквой I.
Множество действительных, или вещественных чисел объединяет
множества рациональных и иррациональных чисел. Их принято наглядно
представлять в виде точек на числовой прямой (Рис. 1.1).
Рис. 1.1. Числовая прямая
15
Множество действительных чисел обозначают буквой R (от их латинского
названия numerus realis).
К иррациональным числам относятся знаменитые числа - π (пи, отношение длины окружности к диаметру) и е (основание натуральных логарифмов).
В программах на языке Питон чаще всего используют такие числовые
типы данных:
int/long – для целых чисел произвольной длины
float – для вещественных чисел
В третьей версии Питона появился класс Decimal, c помощью которого легко
манипулировать вещественными числами произвольной точности.
Кроме собственно чисел, нам понадобятся и два «числовых» модуля. Модуль math нужен для математических вычислений, а модуль random – для
генерирования случайных чисел.
16
Признаки делимости чисел
Целые числа и их свойства изучает теория чисел, или высшая арифметика. В математических задачах (да и в жизни тоже) нередко нужно
быстро определить, делится ли одно число на другое или нет. При этом сам
результат деления неважен. У каждого натурального числа имеется, по
крайней мере, два делителя - это единица и само число (у единицы они
совпадают!). Если других делителей нет, то число называется простым. К
ним мы вернёмся немного позже, а сейчас давайте вспомним признаки (то
есть правила) делимости.
Если делитель – натуральное число, то на него можно разделить любое другое
натуральное число – либо нацело, либо с остатком:
делимое : делитель = частное + остаток
Нас интересует только деление чисел, при котором остаток от деления равен
нулю.
Признак делимости на 2
Самый простой признак: число делится на 2 только тогда, когда его последняя цифра равна 0, 2, 4, 6 или 8. Если число делится на 2, то оно
называется чётным, если не делится - нечётным. Примеры чётных чисел:
2016, 92, 4, 76, 58. Нечётных: 2015, 91, 5, 77, 61.
Иногда этот признак формулируют проще: число делится на 2 тогда и только
тогда, когда его последняя цифра чётная.
Признак делимости на 3
Число делится на 3 тогда и только тогда, когда сумма его цифр делится на 3. Например, число 2016 кратно 3, поскольку сумма его цифр
равна 6: 2 + 1 + 6 = 9 (при подсчёте нули не учитываем).
Если сумма цифр также выражается не однозначным числом, то следует найти
сумму его цифр. Если в результатае сложения цифр получится одно из чисел
3, 6 или 9, то число делится на 3. В противном случае – не делится. Например,
сумма цифр числа 123456789 равняется 45. Число двузначное – опять
17
находим сумму его цифр: 4 + 5 = 9. Получили девятку – значит, исходное число
123456789 делится на три.
Признак делимости на 4
Очевидно, что числа, кратные четырём, должны быть чётными. Но этого
мало, поэтому мы оставляем от числа только две последние цифры и рассматриваем получившееся двузначное число.
Если число сразу двузначное, то ничего отбрасывать не нужно. А если
однозначное, то достаточно вспомнить таблицу умножения.
Если это двузначное число делится на 4, то и всё число также делится
на четыре.
Почему достаточно рассмотреть только последние две цифры числа? – На этот
вопрос легко ответить, если вспомнить, что сотня делится на 4 без остатка.
Естественно, любое число сотен также разделится на 4, поэтому разряды
сотен, тысяч и так далее в проверяемом числе можно не учитывать.
Чтобы ещё упростить проверку, сложите число десятков с половиной единиц. Если сумма чётная, то исходное число делится на 4, в противном случае не делится.
Опять проверим год 2016. Число из последних двух цифр равно 16. Оно на
4 делится, значит, 2016 кратно четырём. Возьмём другое число - 4567896.
Оставляем две цифры - 96. Складываем 9 с половиной от 6, то есть
тройкой и получаем 12. Это число кратно четырём, значит, число
4567896 делится на 4.
Признак делимости на 5
Это правило очень похоже на признак делимости на двойку. Число делится на 5, если оно оканчивается на 0 или 5.
Признак делимости на 6
Число делится на 6, если одновременно выполняются признаки делимости на 2 и 3.
18
Признак делимости на 7
Хорошего признака делимости чисел на 7 не существует, зато имеется немало достаточно сложных и запутанных. Из них мы выберем один – самый
простой и «универсальный».
Разбиваем заданное число, начиная с конца, на группы, состоящие из трёх
цифр. Например, если мы проверяем число 4567896, то получим три
группы цифр:
4 567 896
3
+
2
-
1
+
Кстати говоря, в книгах так зачастую и печатают длинные числа, чтобы легче
было распознать разряды сотен, тысяч и так далее.
Теперь первое (считаем сзади!) число мы берём со знаком плюс, второе со знаком минус, третье – снова со знаком плюс. То есть знаки плюс и минус чередуются. Составляем из чисел с их знаками арифметическое выражение и вычисляем его значение:
896 – 567 + 4 = 333
Если результат делится на 7, то и всё число также делится на 7. В противном случае не делится.
В нашем примере число 333 на 7 не делится, значит, этот вывод
относится и к исходному числу 4567896.
Признак делимости на 8
Число делится на 8, если оно чётное, а число, составленное из трёх последних цифр, делится на 8. Так как делить трёхзначное число на 8 тоже
нелегко, то можно воспользоваться тем же приёмом, что и в признаке дели
- мости на 4.
Тысяча делится на 8 без остатка. Любое число тысяч также разделится на 8,
поэтому разряды тысяч и далее в проверяемом числе можно не учитывать.
19
Рассмотрим три последние цифры. К числу, образованному первыми
двумя цифрами, добавьте половину единиц, а затем к числу десятков добавьте половину единиц получившегося числа. Если результат - чётное
число, то исходное число делится на 8.
Проясним этот алгоритм на примере. Начнём с того же числа 2016. Последние три цифры дают двузначное число 16, которое на 8 делится. Следовательно, делится и число года. Возьмём другое число - 123457928.
Оставляем для проверки трёхзначное число 928. Число из первых двух
цифр равно 92. Складываем его с половиной единиц - 4 - и получаем 96.
Дальше действуем, как в признаке делимости на 4: 9 + 3 = 12. Это число
кратно двум, поэтому всё число 123457928 делится на 8.
Признак делимости на 9
Этот признак напоминает правило для тройки. Число делится на 9 тогда
и только тогда, когда сумма его цифр делится на 9. Раньше мы установили, что число 201 6 делится на 3, а сумма его цифр равна девяти. Поэтому, согласно этому признаку делимости, оно делится и на 9.
Если сумма цифр выражается не однозначным числом, то следует найти
сумму его цифр. То есть действовать так же, как и в признаке делимости на 3.
Признак делимости на 10
Ещё проще, чем признак делимости на 5. Число делится на 10 тогда и
только тогда, когда оно заканчивается на 0. Например, число 2010 делится на 10, а число 2016 не делится.
Признак делимости на 11
Самое любопытное правило; не все его знают, но оно помогает очень просто определить, делится ли, например,
номер автобусного билета на 11.
Чтобы узнать, делится ли число на 11, нужно подсчитать
отдельно сумму цифр, стоящих на нечётных и чётных местах в исходном числе. Если они равны, то число кратно
11. В противном случае нужно из первой суммы вычесть
вторую. Если разность делится на 11, то и всё число делится на 11.
20
Если сумма первых трёх цифр равна сумме трёх последних, то такой билет
называется счастливым.
Для краткости мы везде употребляем слово цифры, но вы должны понимать,
что речь идёт об однозначных числах, которые записываются этими цифрами.
Например, число 123453 делится на 11, так как 1 + 3 + 5 = 2 + 4 + 3 = 9. А
число 123456 не делится (проверьте сами!).
Другой признак делимости на 11 полностью совпадает с признаком делимости на 7, но делить сумму чисел нужно на 11.
Признак делимости на 12
Число делится на 12 тогда и только тогда, когда одновременно выполня
- ются признаки делимости на 3 и 4.
Признак делимости на 13
Признак делимости на 13 тот же самый, что для чисел 7 и 11 (второй способ), но делить сумму чисел
нужно на 13. Поэтому я недаром назвал этот
признак универсальным.
Интересно, что наименьшее число, которое одновременно делится на 7, 11 и 13, равняется 7 х 11 х 13 =
1001, то есть сказочному числу арабских ночей.
Признак делимости на 19
Признак делимости чисел на 19 хорошо описан в книге Якова Перельмана
Занимательная алгебра.
Число делится без остатка на 19 тогда и только тогда, когда число его
десятков, сложенное с удвоенным числом единиц, кратно 19.
Опять проверим большое число 123457928.
Оно содержит 12345792 десятка и 8 единиц. По правилу, получаем:
21
12345792 + 16 = 12345808
Опять находим число десятков и единиц: 1234580 и 8 – и сумму:
1234580 + 16 = 1234596
И так продолжаем дальше:
123459 + 12 = 123471
12347 + 2 = 12349
1234 + 18 = 1252
1252 + 4 = 1256
125 + 12 = 137
13 + 14 = 27
Вывод: число 123457928 на 19 не делится.
Добавим к исходному числу шестёрку и проверим сумму на делимость:
123457934
12345793 + 8 = 12345801
1234580 + 2 = 1234582
123458 + 4 = 123462
12346 + 4 = 12350
123 + 10 = 133
13 + 6 = 19
Вывод: число 123457934 на 19 делится.
Интересные математические фокусы, связанные с признаками делимости, вы
найдёте в книге Мартина Гарднера Математические досуги [ГМ72], Глава 19.
Если вы серьёзно интересуетесь свойствами чисел, то прочитайте книгу Н.Н.
Воробьёва Признаки делимости [ВНН88].
22
Проект Делится - не делится?
Исходный код программы находится в папке Делимость.
Бесконечный цикл while
Функции с параметрами
Оператор деления по модулю %
Оператор деления //
Цикл for
Условныq операторы if
Оператор return
Списки
Функция для извлечения квадратного корня math.sqrt
Метод печати в консольном окне print
Метод ввода строки в консольном окне input
Преобразование строки в число типа int с помощью метода int
Компьютер должен считать,
а человек - думать.
Программистская пословица
Мы вспомнили признаки делимости чисел, без которых человеку обойтись
трудно, а вот компьютеру они совсем не нужны, потому что он и без них
считает охотно и быстро.
Начните новый проект и сохраните его в папке Делимость.
C помощью операции деления по модулю можно легко проверить, делится
ли одно число на другое нацело или нет. Например, мы хотим узнать, делится ли число 2016 на 7. Пишем:
Рис. 1.2. Делится – не делится
23
Запускаем программу и тут же узнаём, что делится (Рис. 1.2). То есть нам
только и нужно, что проверить остаток от деления. Если он равняется
нулю, то первое число делится на второе. Вот и вся премудрость!
В функции main пользователь самостоятельно выбирает число, для которого программа найдёт все его делители:
# ГЛАВНАЯ ФУНКЦИЯ
def main():
print()
print('Делимость чисел')
print()
# бесконечный цикл ввода данных # пока пользователь не закроет программу
# или не введёт 0:
while True:
print("Введите натуральное число > ", end = '')
num = int(input())
#print(num)
if (num == 0):
return
solve1(num)
#solve2(num)
#solve3(num)
#solve4(num)
print()
main()
После того как пользователь ввёл число, мы передаём его функции solve1,
которая в цикле for пытается поделить его на числа из диапазона 1..num и
печатает в консольном окне все делители заданного числа (Рис. 1.3):
# НАХОДИМ ВСЕ ДЕЛИТЕЛИ ЗАДАННОГО ЧИСЛА
def solve1(num):
# печатаем результаты в консольном окне:
print()
print('Число', num, 'делится на:')
for i in range(1, num + 1):
if (num % i == 0):
print(i, end = ' ')
print()
print()
24
Рис. 1.3. Программа в действии
Задавая параметры цикла, мы исходим из того, что любое натуральное
число делится на единицу и само на себя. На нуль делить нельзя, отрицательных натуральных чисел нет, а на числа, большие заданного, делить
нет смысла.
Таким образом, наша программа не только проверяет делимость заданного числа на все числа из диапазона 1..num, но и находит все его делители в порядке возрастания.
Для проверки очень больших чисел нашу функцию можно оптимизировать. Достаточно заметить, что заданное число num не может делиться на
числа, большие num/2, исключая, естественно, само число.
Немного подправим код – и пользователь может находить делители
огромных чисел (Рис. 1.4):
# ОПТИМИЗИРОВАННАЯ ФУНКЦИЯ
def solve2(num):
# печатаем результаты в консольном окне:
print()
print('Число', num, 'делится на:')
for i in range(1, num // 2+1):
if (num % i == 0):
25
print(i, end = ' ')
print(num)
print()
Делимость чисел
Введите натуральное число >
111
Число 111 делится на:
1 3 37 111
Введите натуральное число >
111111111
Число 111111111 делится на:
1 3 9 37 111 333 333667 1001001 3003003 12345679 37037037 111111111
Введите натуральное число >
222222222
Число 222222222 делится на:
1 2 3 6 9 18 37 74 111 222 333 666 333667 667334 1001001 2002002 3003003
6006006 12345679 24691358 37037037 74074074 111111111 222222222
Введите натуральное число >
123456789
Число 123456789 делится на:
1 3 9 3607 3803 10821 11409 32463 34227 13717421 41152263 123456789
Рис. 1.4. Оптимизированная программа
Мы ещё во много раз ускорим поиск делителей, если заметим, что произведение симметричных относительно середины ряда делителей равно заданному числу. Например, для числа 64 мы получили такой ряд
делителей:
1 2 4 8 16 32 64
Проверяем утверждение:
1 х 64 = 2 x 32 = 4 x 16 = 8 x 8 = 64
Всё верно! Единственное «неудобство» причиняют восьмёрки – они дважды входят в произведение, поэтому нам следует подумать, как оставить
только одну из них.
26
Теперь легко заметить, что нам достаточно найти делители заданного
числа в диапазоне 1..√𝑛𝑢𝑚. Вторую половину делителей мы найдём, поделив заданное число на очередной делитель.
Для хранения делителей мы заведём список res:
# ОПТИМИЗИРОВАННАЯ ФУНКЦИЯ СО СПИСКОМ
def solve3(num):
# список для записи делителей:
res = []
for i in range(1, round(math.sqrt(num)) + 1):
if (num % i == 0):
res.append(i)
# не допускаем повторов делителей:
if (i*i != num):
res.append(num // i)
# печатаем результаты в консольном окне:
print()
print('Число', num, 'делится на:')
print(*res, sep=' ', end='')
print()
И вот почему. Для первого делителя – единицы – мы найдём парный делитель, равный заданному числу num, для второго парный делитель равен
(num / второй делитель). То есть делители мы напечатаем не по порядку
(Рис. 1.5).
Рис. 1.5. Неупорядоченные делители
Чтобы выправить ситуацию, мы до печати результатов отсортируем список res с помощью метода sort списочного класса, а затем напечатаем делители строго по ранжиру:
27
# ОПТИМИЗИРОВАННАЯ ФУНКЦИЯ С ОТСОРТИРОВАННЫМ СПИСКОМ
def solve4(num):
# список для записи делителей:
res = []
for i in range(1, int(math.sqrt(num)) + 1):
if (num % i == 0):
res.append(i)
# не допускаем повторов делителей:
if (i*i != num):
res.append(num // i)
# печатаем результаты в консольном окне:
res.sort()
print()
print('Число', num, 'делится на:')
print(*res, sep=' ', end='')
print()
Теперь даже для огромных чисел мы мгновенно выписываем все делители
(Рис. 1.6).
Рис. 1.6. Молниеносное нахождение делителей
28
Проект Назойливый остаток
Исходный код программы находится в папке Кордемский 086
08.
Вложенные циклы for
Бесконечный цикл while
Условный оператор if
Оператор %
Оператор break
Оператор continue
В книге Бориса Кордемского и Аскера Ахадова Удивительный мир чисел
[КА86] вы найдёте немало интересных задач, в том числе и на делимость.
На странице 86 авторы предлагают решить задачу Назойливый остаток:
Некоторые числа, кратные числу 7, при делении на 2, на 3, на 4, на 5 и на
6 дают остаток 1.
Найдите наименьшее из таких чисел.
Эта же задача напечатана в книге Фёдора Нагибина и Евгения Канина Математическая шкатулка [Нагибин88], задача 42, страницы 18-19, но в более занимательной форме:
Колхозница привезла на рынок для продажи корзину яиц. Продавала она
их по одной и той же цене. После продажи яиц колхозница пожелала проверить, верно ли она получала деньги. Но вот беда: она забыла, сколько у
неё было яиц. Вспомнила она только, что когда перекладывала яйца по 2,
то оставалось одно яйцо; одно яйцо оставалось также при перекладывании яиц по 3, по 4, по 5, по 6. Когда же она перекладывала яйца по 7, то не
оставалось ни одного.
Помоги колхознице сообразить, сколько у неё было яиц.
Бросаемся на помощь бестолковой колхознице и в функции main вызываем
функцию solve для решения этой головоломки:
# ГЛАВНАЯ ФУНКЦИЯ
def main():
29
print()
print('Назойливый остаток')
print()
solve()
main()
Поскольку речь идёт о натуральных числах, то мы можем начать наши поиски с единицы:
# РЕШАЕМ ЗАДАЧУ
def solve():
# мин. число:
minnum = 1
Если число 1 не является решением задачи, то мы переходим к двойке, к
тройке, и так далее – пока не найдётся искомое число num.
Так как мы всякий раз добавляем к начальному значению переменной minnum единицу, то вполне разумно делать это в цикле for, в котором переменная num и будет играть роль переменной цикла.
Обычно к циклу for прибегают тогда, когда число повторов точно известно.
В нашем же случае, мы знаем только начало цикла (min = 1), но не знаем, на
каком числе он закончится. Впрочем, мы вполне разумно можем предположить, что у колхозницы было не очень много яиц, и задать заведомо большее значение для верхней границы цикла. Так мы организуем почти бесконечный цикл, который прервём сразу же, как только искомое число будет
обнаружено. Для этого мы воспользуемся флажком – логической переменной flg. Если очередное число num выдержит все проверки, значение флага
останется верным (True), а мы с помощью оператора break прервём цикл for
и напечатаем решение задачи в консольном окне:
for num in range(minnum, 1000):
flg = True
# число не кратно семи:
if num % 7 != 0:
continue;
Проверку очередного числа на остаток, равный единице, также можно проводить в цикле, чтобы не выписывать несколько одинаковых условий. Если
30
число num при делении хотя бы на одно из чисел 2..6 не даёт в остатке 1,
то мы сбрасываем флажок и прерываем внутренний цикл for:
for d in range(2, 6 + 1):
if (num % d != 1):
flg = False
break
if flg:
break
print('Искомое число равно', num)
print()
На Рис. 1.7 вы видите ответ на эту задачу.
Рис. 1.7. Яиц было немало!
Настоящий бесконечный цикл легко получить с помощью конструкции
while True:
# РЕШАЕМ ЗАДАЧУ С ЦИКЛОМ while
def solve_while():
# мин. число:
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('Искомое число равно', num)
print()
31
Не знаю, как вам, но мне интересно, а есть ли ещё и другие числа, которые
имеют назойливый остаток?
Имея компьютер, мы легко утолим своё любопытство – достаточно написать новую функцию 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('Искомое число равно', num)
print()
Тут, конечно, следует учесть, что бесконечный цикл уже не годится, потому
что он действительно станет бесконечным, если искомых чисел очень
много (а это нетрудно предвидеть).
Запускаем приложение и видим, что среди первой десятки тысяч натуральных чисел довольно много подходящих под условие задачи (Рис. 1.8).
Также нетрудно подметить такую закономерность. Если обозначить через
n номер искомого числа, то все числа с назойливыми остатками можно
легко найти по формуле:
num = 301 + 420(n-1)
Любопытно, что если решать задачу для чисел, кратных не 7, а большим
числам, то это непременно должны быть простые числа. Например, решения для чисел 11 и 13 показаны на Рис. 1.9.
32
Рис. 1.8. Неназойливый список назойливых чисел
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):
flg = False
break
if (flg):
print('Искомое число равно', num)
print()
def solve13():
print('Решаем задачу для чисел, кратных 13:')
33
# мин. число:
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('Искомое число равно', num)
print()
Рис. 1.9. Исследования продолжаются
Чтобы понять, почему так происходит, нам нужно научиться вычислять
НОД, НОК и находить простые числа!
34
Проект Первый числовой фокус
Исходный код программы находится в папке Кордемский 034
02.
Метод int
Оператор return
Условный оператор if
Функция с параметром
Оператор деления по модулю %
Оператор деления //
Операторы < > or !=
А вот и фокус из книги Удивительный мир чисел [КА86], страница 34, задача 2:
Скажите другу: «Любое трёхзначное число умножь на 37, потом на 27. К
полученному шестизначному числу прибавь удвоенное первоначальное
число. Покажи мне результат, и я угадаю задуманное трёхзначное число».
Секрет фокуса. Пусть задумано трёхзначное число
100x + 10y + z,
где х, y, z - цифры сотен, десятков и единиц соответственно. Выполнив
указанные действия, получим:
100 100x + 10 010у + 1001z= 1001 ⦁ (100x + 10у + z).
Теперь ясно, что достаточно разделить результат на 1001, чтобы получилось задуманное трёхзначное число.
Пример. Пусть задумано число 173. После выполнения указанных действий получилось 173 173. Наблюдаем, что в его записи трёхзначное
число 173 повторяется. Вычеркнем 173; это равносильно тому, что 173
173 разделили на 1001; в результате остается задуманное число 173.
Замечание. При повторении фокуса легко обнаружится, что если задумано число abc, то результат, показываемый фокуснику, имеет вид
abcabc . Чтобы это скрыть, надо добавить еще одно действие в конце фокуса, например потребовать прибавить 1111. Тогда фокуснику скажут не
35
173 173, а 174 284. Теперь закономерность скрыта, а фокуснику ничего не
стоит в уме вычесть 1111, а затем угадать задуманное число.
В функции main мы предлагаем всем желающим загадать трёхзначное
число и произвести с ним требуемые манипуляции:
# ГЛАВНАЯ ФУНКЦИЯ
def main():
print('Первый числовой фокус')
print()
# бесконечный цикл ввода данных # пока пользователь не закроет программу
# или не введёт 0:
while True:
print('Любое трёхзначное число умножь на 37, \nпотом на 27.
\nК полученному шестизначному числу прибавь \nудвоенное первоначальное число. \nВведи результат, \nи я угадаю задуманное трёхзначное
число. > ', end = '')
num = int(input())
if (num == 0):
return
solve(num)
print()
Сам фокусник притаился в функции solve, которая получает от загадчика 6значное число. Но эта кио или акопяно не шибко доверчива, поэтому, прежде
всего, проверяет число загадчика и отказывается фокусничать, если ей пытаются подсунуть негодное число:
# -*- coding: Windows-1251 -*# Кордемский, страница 34, задача 2
# ПОКАЗЫВАЕМ ФОКУС
def solve(num):
min6 = 100000
max6 = 999999
print()
if (num < min6 or num > max6):
print('Число должно быть шестизначным!')
print()
return
if (num % 1001 != 0):
36
print('Ты ошибся в вычислениях!')
print()
return
А добросовестному загадчику наш виртуальный фокусник безошибочно сообщает загаданное трёхзначное число:
print('Ты задумал число %i' %(num // 1001))
print()
Например, моё число не поставило фокусника в тупик (Рис. 1.10)!
Рис. 1.10. Компьютерные фокусы
Фокус интересный, но очень простой. Постарайтесь учесть замечание и улучшить программу!
37
Проект Второй числовой фокус
Исходный код программы находится в папке Кордемский 035
03.
Цикл 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 =
122877
38
123 * 13 * 77 =
246000 : 2000 =
123123
246000
123
Всё сходится – фокус удался!
Вторую фокусную программу мы легко получим из первой. Главное - грамотно написать речь фокусника:
# -*- coding: Windows-1251 -*# Кордемский, страница 35, задача 3
# ПОКАЗЫВАЕМ ФОКУС
def solve(num):
if (num % 2000 != 0):
print('Ты ошибся в вычислениях!')
print()
return
print()
print('Ты задумал число %i!' %(num // 2000))
print()
# ГЛАВНАЯ ФУНКЦИЯ
def main():
print()
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)
39
print()
main()
Кто бы сомневался: конечно, компьютер запросто разделит любое число
на 2000 (Рис. 1.11), а вот загадчику придётся хорошенько потрудиться,
умно- жая и складывая числа!
Рис. 1.11. И тут без промаха!
40
Проект Шестизначное число
Исходный код программы находится в папке Кордемский 044
04.
Функция без параметров
Цикл for
Оператор деления по модулю %
Оператор continue
Оператор break
Задача 4 из книги Удивительный мир чисел
[КА86], страница 44:
Ученику понадобилось написать наибольшее из шестизначных чисел,
кратных 11 и чтобы цифра 6 была первой слева. Как надо действовать
ученику для быстрого выполнения задания, если признаков делимости
на 11 он ещё не знает?
Сообщим, что искомое число обладает забавной особенностью: если каждую его цифру повернуть на 180° в плоскости бумаги, оставляя её на
прежнем месте, то образовавшееся число окажется дважды кратным 11
(делится на 11 и частное также делится на 11).
Выявите ещё одну особенность чисел: найденного и с повёрнутыми
цифрами.
# ГЛАВНАЯ ФУНКЦИЯ
def main():
print()
print('Шестизначное число')
print()
solve()
main()
Легко догадаться, что нужно искать решение задачи среди шестизначных
чисел, начинающихся с шестёрки:
41
# РЕШАЕМ ЗАДАЧУ
def solve():
# мин. число:
minnum = 600000
# макс. число:
maxnum = 699999
Причём перебирать числа следует задом наперёд, то есть от большего к
меньшему, чтобы сразу же найти наибольшее из всех возможных чисел в
заданном диапазоне:
for num in range(maxnum, minnum-1, -1):
# чиcло должно быть кратно 11:
if num % 11 != 0:
continue
s = ‘Наибольшее число равно ‘ + str(num)
print(s)
print()
break
А вот и ответ (Рис. 1.12).
Рис. 1.12. Крути-верти
По условию задачи, поворачиваем все цифры найденного числа:
699996 966669
Проверяем, делится ли перевёрнутое число на 11 * 11 = 121. Это легко сделать в интерактивной консоли. Рис. 1.13 подтверждает: перевёрнутое
число делится на 121.
42
Рис. 1.13. Поделилось!
А ещё одной особенностью этих чисел является их палиндромичность –
они одинаково читаются и в прямом, и в обратном направлении.
43
Проект Тройка, семёрка и... только
Исходный код программы находится в папке Кордемский 045
08.
Функция без параметров
Функция с параметром
Цикл for
Оператор continue
Условный оператор if
Оператор деления по модулю %
Цикл while
Комбинированные операторы присваивания
Оператор return
Списки
Задача 8 (13) из книги Удивительный мир чисел [КА86], страница 45:
Найдите наименьшее число, обладающее следующими свойствами:
состоит только из цифр 7 и 3,
оно само и сумма его цифр делятся на 7 и на 3.
В отличие от Пиковой дамы, туза в этой задаче нет, но тройка и семёрка присутствуют в полном объёме!
# ГЛАВНАЯ ФУНКЦИЯ
def main():
print()
print('Тройка, семёрка и... только')
print()
solve()
print()
main()
Предположим, что у нас имеется функция get37, которая возвращает числа,
составленные из цифр 3 и 7, и мы запоминаем в переменной min37 самое
маленькое из них:
44
# -*- 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:
if (summa(ds) % 21 != 0):
continue
Если это так, то мы печатаем найденное число на экране и, если оно меньше
текущего значения переменной min37, то запоминаем его:
s = 'Десятичное число равно ' + str(num)
print(s)
s = 'Двоичное число равно ' + str(ds)
print(s)
print()
# запоминаем наименьшее число:
if (min37 > ds):
min37 = ds
Просмотрев все числа в заданном диапазоне, мы печатаем ответ на задачу:
print('Наименьшее число равно', min37)
print()
45
Функцию 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 ...
Так как между двоичными и 3,7-числами существует взаимно-однозначное
соответствие (точно так же, как и между десятичными числами и их двоичным представлением), то на вход функции get37 нужно подавать числа,
начиная с нуля, и проверять в функции solve, не является ли очередное 3,7число решением задачи. Но поскольку нас интересует не любое число, а
именно наименьшее, а они генерируются не последовательно, то необходимо в функции solve перебрать числа в некотором диапазоне, в котором
заведомо окажется искомое число.
Перевести десятичное число в двоичное нетрудно, однако мы должны
учесть, что нас интересует не оно, а соответствующее ему 3,7-число, поэтому каждый нуль в десятичном числе нужно заменить тройкой, а
единицу – семёркой. Это легко сделать с помощью списка idigs, в который
следует записать нужные цифры.
def get37(num):
if (num < 1):
return -1
# цифры числа:
46
idigs = [3, 7]
Также мы должны проследить, чтобы в числе обязательно присутствовали
обе цифры – и тройка, и семёрка.
Например, двоичным числам 00, 11, 000, 111 соответствуют 3,7-числа 33, 77,
333 и 777, которые целиком состоят из одинаковых цифр.
Для этого можно завести логический список, подобный idigs, а можно просто объявить 2 логические переменные:
# число из троек и семёрок:
num37 = 0
# флажки для цифр 3 и 7:
flg3 = False
flg7 = False
И последняя тонкость, которую необходимо «утолстить»: при конвертировании входного десятичного числа в двоичную форму мы никогда не получим ведущих нулей, поскольку число обратится в нуль, и деление на двойку
закончится:
# формируем число из троек и семёрок:
dec = 1
while (num > 2):
# очередная цифра:
dig = num % 2
# отмечаем 3 и 7:
flg3 |= idigs[dig] == 3
flg7 |= idigs[dig] == 7
num37 = num37 + idigs[dig]*dec
dec *= 10
num //= 2
# строка должна включать и тройку, и семёрку:
if (not flg3 or not flg7):
return -1
return num37
47
Из этого следует, что нужно пожертвовать первой единицей в двоичном
числе, тогда оставшиеся цифры дадут все комбинации нулей и единиц в
двоичном числе и троек и семёрок – в 3,7-числе.
Решение задачи получилось не очень коротким, но зато программа нашла
ответ за одно мгновение (Рис. 1.14)!
Рис. 1.14. Немаленькое число!
Как и в книге, это число – 3333377733.
Кроме этого, минимального 3,7-числа, существует бесконечно много 3,7чи- сел, которые больше его.
Можно формировать 3,7-число сзади, но тогда нужно отбросить последнюю
единицу.
48
Проект Любопытное свойство чисел
Исходный код программы находится в папке Кордемский 102
15.
Функция без параметров
Цикл for
Оператор деления по модулю %
Оператор деления //
Условный оператор if
Оператор break
Задача 15 (16 – номер задачи в издании 1996 года) из книги Удивительный мир чисел [КА86], страница 102:
Возьмите какое-либо б-значное число, делящееся на 7, например 325 836.
Перенесите последнюю цифру в начало записи числа. Образуется новое
число 632 583. Оно также делится на 7.
Докажите самостоятельно, что таким свойством обладает любое 6значное число, делящееся на 7.
Рекомендация. Представьте заданное число так: 7k = 10а+ 6 (1). Тогда новое число примет вид 100 0006 +а (2). Используя (1), докажите делимость
(2) на число 7.
Доказать - значит, убедиться, что все 6-значные числа обладают указанным
свойством. Это можно сделать «теоретически», то есть с помощью математических выкладок, а можно и «практически» - с помощью метода грубой
силы, или полного перебора. Оба способа доказательства можно считать
строгими, ведь они исчерпывают все возможные варианты. Поскольку 6значных чисел совсем немного, то метод грубой силы здесь вполне уместен.
Тем более что алгоритмы, построенные на этом методе почти всегда достаточно простые.
Традиционно из функции main мы вызываем функцию solve для непосредственного решения задачи:
# ГЛАВНАЯ ФУНКЦИЯ
def main():
49
print()
print('Любопытное свойство чисел')
print()
solve()
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, мы пропускаем
:
# число не кратно семи:
if (num % 7 != 0):
continue
Всем остальным назначаем проверки.
50
Чтобы перенести последнюю цифру числа в начало, нужно её выделить. Это
легко сделать, применив к числу операцию деления по модулю:
# последняя цифра числа:
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.16):
# все числа проверены:
51
if (flg):
print('Утверждение верное')
else:
print('Утверждение неверное')
print()
Рис. 1.16. Доказали!
Так как мы проверили все 6-значные числа, то можем уверенно
утверждать, что любое 6-значное число удовлетворяет условиям
задачи.
52
Проект Как определил ошибку Чохбилмиш?
Исходный код программы находится в папке Кордемский 057
23.
Функция без параметров
Цикл for
Оператор деления по модулю %
Оператор деления //
Условный оператор if
Оператор break
Задача 23 (25) из книги Удивительный мир чисел [КА86], страница 57:
Чохбилмиш предложил каждому из двух учеников задумать какое-либо
шестизначное число и переставить первую цифру в конец записи числа.
Одному сказал: «Найди сумму получившихся чисел». Другому сказал:
«Найди разность».
Ученики выполнили действия и написали:
913 485 и 167 860.
Чохбилмиш не знал, какие числа были задуманы учениками, но сразу
определил: «Вы оба ошиблись».
Как рассуждал Чохбилмиш?
В ответе на задачу утверждается, что сумма любого 6-значного числа с дру
- гим 6-значным числом, полученным из исходного переносом первой
цифры в конец числа, кратна 11 и аналогично полученная разность –
кратна 9.
Мы можем проверить эти утверждения с помощью полного перебора, тем
более что самая трудная часть задачи – перенос цифры – нами уже решена
в предыдущем проекте.
# -*- coding: Windows-1251 -*# Кордемский, с.57, Задача 23
# РЕШАЕМ ЗАДАЧУ
53
def solve():
# мин. и макс.
min6 = 100000
max6 = 999999
flg = True
6-значные числа:
for num in range(min6, max6 + 1):
# число не кратно семи:
if (num % 7 != 0):
continue
# последняя цифра числа:
end = num % 10
# 5-значное число из первых 5 цифр числа num:
start5 = num // 10
# новое 6-значное число:
newnum = end * 100000 + start5
# сумма и разность чисел num и newnum:
sum = num + newnum
razn = num - newnum
# это число не кратно 11:
if (sum % 11 != 0 or razn % 9 != 0):
flg = False
break
# все числа проверены:
if (flg):
print('Утверждения верные')
else:
print('Утверждения неверные')
print()
# ГЛАВНАЯ ФУНКЦИЯ
def main():
print()
print('Как определил ошибку Чохбилмиш?')
print()
solve()
main()
Проверив все 6-значные числа, мы можем с уверенностью констатировать
факт: оба утверждения верные (Рис. 1.17)!
Рис. 1.17. И здесь всё верно
54
Проект Шестизначный перенос
Исходный код программы находится в папке Нагибин 557.
Функция без параметров
Цикл for
Оператор деления по модулю %
Оператор деления //
Задача 557 из книги Математическая шкатулка
[Нагибин88], страница 94:
Первая слева цифра шестизначного числа – 1. Если её перенести с первого
места в конец числа, сохранив порядок остальных цифр, то вновь полученное число будет втрое больше первоначального.
Восстановите первоначальное число.
Эта задача отличается от двух предыдущих только тем, что переносить
нужно не последнюю цифру, а первую:
# -*- coding: Windows-1251 -*# Нагибин, с.94, Задача 557
# РЕШАЕМ ЗАДАЧУ
def solve():
# мин. и макс.
min6 = 100000
max6 = 299999
flg = True
6-значные числа:
for num in range(min6, max6 + 1):
# первая цифра числа:
start = num // 100000
# 5-значное число из последних 5 цифр числа num:
end5 = num % 100000
# новое 6-значное число:
newnum = end5 * 10 + start
# это число должно быть втрое больше исходного:
if (newnum != 3 * num):
continue
55
print('Число равно', num)
print()
# ГЛАВНАЯ ФУНКЦИЯ
def main():
print()
print('Шестизначный перенос')
print()
solve()
main()
Среди 6-значных чисел, начинающихся с единицы, первоначальное число –
единственное. Есть ещё одно подобное число, но оно начинается с двойки (
Рис. 1.18).
Рис. 1.18. Перенос закончен
56
Задания для самостоятельного решения
Вы ошиблись в подсчёте
Удивительный мир чисел.. Задача 4 (2.2) ], страница 50
Ученик покупает 18 карандашей, 6 тетрадей, 12 ластиков, 9 блокнотов и
несколько тетрадей для рисования по 15 к. Девушка-продавец выписала
чек на 1 р. 52 к. Взглянув на чек, мальчик сразу же сказал продавцу: «Вы
ошиблись в подсчёте». Девушка пересчитала и исправила свою ошибку.
Как удалось пареньку так быстро обнаружить просчёт?
Ответ: Стоимость каждой покупки, а значит и общая сумма кратна трём,
но 1 р. 52 к. на три не делится.
Задача #24
Математическая шкатулка
Какое целое число делится (без остатка) на любое целое число, отличное от 0?.
Ответ: 0
Задача #25
Математическая шкатулка
Сумма каких двух натуральных чисел равна их произведению?
Ответ: 2 + 2 = 2 x 2-
57
Глава 2. НОД, НОК и компания
В начале второй главы мы напишем две родственные программы – для
вычисления НОД (наибольшего общего делителя двух чисел) и НОК
(наименьшего общего кратного).
Проект Наибольший общий делитель
Исходный код программы находится в папке НОД.
Функция с параметром
Фуекция без параметров
Цикл for
Оператор деления по модулю %
Оператор деления //
Бесконечный цикл while
Метод int
Оператор return
Условный оператор if
Условный оператор if-else
Цикл while
Комбинированные операторы присваивания
Для вычисления НОД мы воспользуемся простым и быстрым алгоритмами
древнегреческого математика Евклида (Рис. 2.1).
Рис. 2.1. Евклид (Εὐκλείδης, ок. 325 года до н.э. - до 265 года до н.э.)
58
По-английски НОД называется 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 = '')
number1 = int(input())
print('Введите второе число > ', end = '')
number2 = int(input())
if (number1 + number2 < -1): return
# если первое число меньше второго,
59
# то меняем их значения:
if (number1 < number2):
number1, number2 = number2, number1
# находим НОД:
if (number2 == 0):
nod = number1
else:
nod = Euklid(number1, number2)
# печатаем НОД:
print('НОД = (' + str(number1) + ',' + str(number2) + ') = ' +
str(nod))
print()
main()
Если меньшее из чисел (или оба) равны нулю, то за НОД следует принять
первое число. Если же оба числа положительные, то начинаем действовать
по простому алгоритму Евклида:
# -*- coding: Windows-1251 -*# ПРОГРАММА ДЛЯ НАХОЖДЕНИЯ НАИБОЛЬШЕГО
# ОБЩЕГО ДЕЛИТЕЛЯ ДВУХ НАТУРАЛЬНЫХ ЧИСЕЛ (НОД)
# ПРОСТОЙ АЛГОРИТМ ЕВКЛИДА
def Euklid(n1, n2):
while (n2 != n1):
if (n1 >= n2):
n1 -= n2
else:
n2 -= n1;
return n1
Если числа очень большие, то лучше пользоваться быстрым, а не простым
алоритмом.
В цикле while мы на каждой итерации вычитаем из большего числа меньшее
– до тех пор, пока значения переменных n1 и n2 не сравняются. Поскольку
с каждым разом одно из чисел уменьшается, то рано или поздно это условие
будет выполнено.
60
Вычисленное значение НОД мы возвращаем функции main, которая и публикует его в окне консоли. При небольших числах алгоритм работает очень
быстро (Рис. 2.2).
Рис. 2.2. Вычисляем НОД двух чисел
Нагнетаем обстановку, задавая всё более «солидные» числа, и простой алгоритм начинает «буксовать» (Рис. 2.3).
А при дальнейшем увеличении чисел он попросту впадает в кому! И тут нам
на выручку приходит быстрый алгоритм Евклида:
# БЫСТРЫЙ АЛГОРИТМ ЕВКЛИДА
def speedEuklid(n1, n2):
while (n2 > 0):
n1, n2 = n2, n1 % n2
return n1
Он отличается от простого алгоритма тем, что на каждой итерации он находит остаток от деления первого числа на второе, значение второго числа
присваивает первому числу, а значение остатка - второму. Эти операции
61
продолжаются до тех пор, пока остаток от деления не обратится в нуль. Как
и в первом случае, это условие обязательно выполнится.
Рис. 2.3. Вычисляем НОД больших чисел
Быстрый алгоритм Евклида основан на свойстве:
НОД(m,n) = НОД(n, m % n) при m > n
Этот алгоритм не собьёшь с толку никакими числами (Рис. 2.4)! Вы можете
взять любые числа и убедиться, что алгоритм работает практически мгновенно.
Для лучшего понимания работы быстрого алгоритма давайте вручную
найдём НОД чисел 42 и 14:
number1 = 42
number2 = 14
Так как второе число больше нуля, то находим остаток от их деления:
62
n = 42 % 14 = 0
И присваиваем новые значения переменным:
number1 = 14
number2 = 0
Второе число теперь равно нулю, значит, НОД найден – он равен второму числу, то есть 14.
Рис. 2.4. Наша программа легко справляется и с очень большими числами!
Рассмотрим другой пример:
number1 = 56
number2 = 42
n = 56 % 42 = 14
number1 = 42
number2 = 14
Уже после одного цикла мы пришли к первому примеру.
63
Проект Наименьшее общее кратное
Исходный код программы находится в папке НОК.
Бесконечный цикл while
Метод int
Оператор return
Условный оператор if
Функция с параметрами
Зная наибольший общий делитель двух чисел, мы очень просто вычислим
их наименьшее общее кратное по такой формуле:
НОК = число1 * число2 / НОД (число1, число2)
(1)
По-английски НОК называется lcd – least common denominator.
Для нахождения НОД мы воспользуемся быстрым алгоритмом Евклида, а
НОК вычислим по формуле (1):
# -*- coding: Windows-1251 -*# ПРОГРАММА ДЛЯ ВЫЧИСЛЕНИЯ НАИМЕНЬШЕГО ОБЩЕГО
# КРАТНОГО ДВУХ ЧИСЕЛ (НОК)
# БЫСТРЫЙ АЛГОРИТМ ЕВКЛИДА
def speedEuklid(n1, n2):
while (n2 > 0):
n1, n2 = n2, n1 % n2
return n1
# ГЛАВНАЯ ФУНКЦИЯ
def main():
print()
print('НОК двух чисел')
print()
# бесконечный цикл ввода данных # пока пользователь не закроет программу
# или не введёт отрицательные числа:
while True:
print('Введите первое число > ', end = '')
number1 = int(input())
print('Введите второе число > ', end = '')
64
number2 = int(input())
if (number1 + number2 < -1):
return
# если первое число меньше второго,
# то меняем их значения:
if (number1 < number2):
number1, number2 = number2, number1
# находим НОK:
if (number1 * number2 == 0):
nok = 0
else:
# вычисляем НОК:
nok= number1 * number2 // speedEuklid(number1, number2)
# печатаем НОK:
print('НОK = (' + str(number1) + ',' + str(number2) + ') = ' +
str(nok))
print()
main()
Поскольку НОД вычисляется очень быстро, то и НОК мы получаем в считанные мгновения (Рис. 2.5).
Рис. 2.5. Вычисляем НОК
65
Проект НОК нескольких чисел
Исходный код программы находится в папке НОК2.
Функция с параметром
Цикл for
Оператор return
Цикл while
Оператор деления по модулю %
При решении задачи о назойливом остатке необходимо знать НОК последовательных чисел от 2 до 6 (см. [КА86], решение на странице 133).
Найти НОК нескольких чисел можно так.
Сначала вычисляем НОК2 первых двух чисел, затем вычисляем НОК3 для
НОК2 и третьего числа и так далее.
Но давайте решим эту задачу для ряда чисел произвольной длины – от 2
до maxnok. В функции main мы вызываем функцию NOK 2, которой и
передаём верхнюю границу ряда:
# ГЛАВНАЯ ФУНКЦИЯ
def main():
print()
print('НОК НЕСКОЛЬКИХ ЧИСЕЛ')
print()
NOK2(30)
#solve(17)
main()
В функции NOK2 мы задаём начальное значение переменной nok, равное 1,
что вполне разумно для наименьшего общего кратного чисел 1 и 1. Далее
мы находим НОК для 1 и 2, затем для полученного НОК и тройки – как и
было описано выше:
# -*- coding: Windows-1251 -*# ВЫЧИСЛЕНИЕ НОК НЕСКОЛЬКИХ ЧИСЕЛ
# НАХОДИМ НОК РЯДА ЧИСЕЛ
def NOK2(maxnok):
66
nok = 1
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
Все промежуточные значения НОК мы печатаем на экране (Рис. 2.6).
Рис. 2.6. Вычисляем НОК ряда чисел
67
Из полученного списка видно, что НОК ряда чисел 2..6 равно 60. При делении чисел вида 60n на числа 2..6 в остатке мы всегда будем получать 0. А
при делении чисел вида (60n + 1) – единицу, что и требуется в условии задачи. Но это число (60n + 1) должно делиться на 7 без остатка, то есть:
(60n + 1) = 7k
(1)
Поскольку мы умеем вычислять НОК для любого ряда чисел, то можем обобщить задачу о назойливых остатках для любых рядов, записав формулу так:
(nok * n + 1) = maxd * k
(2)
Решаем задачу с помощью новой функции solve:
def main():
print('НОК НЕСКОЛЬКИХ ЧИСЕЛ')
print()
#NOK2(30)
solve(17)
Этой функции мы должны передать то число maxd, на которое делится без
остатка искомое число. В задаче это семёрка, но нас, конечно, интересуют
более крупные числа!
На Рис. 2.6 хорошо видно, что наименьшее искомое число быстро растёт с
увеличением maxd, поэтому нужно либо задавать значение переменной
max достаточно большим, либо искать только заданное количество искомых чисел.
В функции solve мы находим НОК ряда чисел 2..(maxd-1), а для этого нам
нужна модифицированная функция для вычисления НОК, которая возвращает, а не печатает значения:
def NOK3(maxnok):
nok = 1
for i in range(2, maxnok+1):
nok = NOK(nok,i)
return nok
По формуле (2) мы ищем в цикле while числа, которые отвечают условиям
задачи и печатаем их в консольном окне:
68
# РЕШАЕМ ЗАДАЧУ
def solve(maxd):
# макс. число:
max = 200000000
print('Решаем задачу для чисел, кратных %i: ' %maxd)
nok = NOK3(maxd-1)
print('НОК чисел 2..%i равно %i' %(maxd-1, nok))
# искомые числа имеют вид:
# (nok * n + 1)
n=1
num = nok * n + 1
while(num <= max):
# число не кратно maxd:
if (num % maxd == 0):
print('Искомое число равно', num)
n += 1
num = nok * n + 1;
print()
Например, взяв вместо числа 7 на десяток больше, мы получим такие реше
- ния задачи (Рис. 2.7).
Рис. 2.7. Обобщили
Конечно, без компьютера решать такие задачи было бы крайне затруднительно!
А теперь поэкспериментируйте и подумайте, почему для составных чисел
maxd задача не решается.
69
Проект Всезнающая статистика
Исходный код программы находится в папке Кордемский 086
14.
Функция без параметров
Оператор continue
Цикл for
Оператор деления по модулю %
Задача 14 (16) из книги Удивительный мир чисел
[КА86], страницы 86-87:
Попал как-то мне в руки обрывок прошлогодней газеты. Моё внимание привлекло чернильное пятно, закрывшее последние три цифры
шестизначного числа (Рис. 2.8). По сохранившемуся кусочку текста я вспомнил: это была заметка, в которой сообщалось, что к концу минувшего года население нашего города возросло до этого числа. В заметке говорилось также о том, что это шестизначное число уникально
среди шестизначных: оно делится на 2, 3, 4, 6, 7, 8 и 9. Ого! Не правда ли?
Чтобы восстановить все цифры этого числа, нет нужды обращаться в реставрационную лабораторию. Собственная сообразительность подскажет вам математический метод быстрого решения этой задачи.
Рис. 2.8. Обрывочные сведения
70
Опять всё решение задачи переносим в функцию solve.
# ГЛАВНАЯ ФУНКЦИЯ
def main():
print()
print('Всезнающая статистика')
print()
solve()
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('Искомое число равно', num)
71
print()
Ответ вы можете видеть на Рис. 2.9.
Рис. 2.9. Информация восстановлена
72
Проект Восстановите потерянную цифру
Исходный код программы находится в папке Кордемский 085
07.
Функция без параметров
Цикл 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('Восстановите потерянную цифру')
print()
solve()
main()
Цифра, обозначенная буквой x, может принимать значения от 0 до 9. Их
легко перебрать в цикле for:
73
# -*- 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('Искомое число равно 170 141 183 460 469 231 %i31 687 303
715 884 105 727' %x)
print()
На Рис. 2.10 вы можете видеть, что x = 7.
Рис. 2.10. Нашли х
74
Проект Снимите маску с одной цифры
Исходный код программы находится в папке Кордемский 084
06.
Функция без параметров
Цикл for
Оператор целочисленного деления %
Условный оператор if
Задача 6 из книги Удивительный мир чисел
[КА86], страница 84:
В записи знаменитого «шахматного» числа
М64= 1y446 744 073 709 551 615
на его вторую цифру накинута маска у. Сняв маску, расшифруйте значение у, зная, что достаточно увеличить заданное число на 3 единицы, как
оно становится кратным числу 19.
Примечание. По легенде, именно такое число пшеничных зёрен следовало выдать в награду изобретателю шахмат, попросившему положить
всего одно зерно на первую клетку шахматной доски, а на каждую следующую клетку вдвое большее число зёрен, чем на предыдущую.
Эта задача сродни предыдущей, но признак делимости чисел на 19 не
очень удобен:
# ГЛАВНАЯ ФУНКЦИЯ
def main():
print()
print('Снимите маску с одной цифры')
print()
solve()
main()
Поэтому в функции solve мы просто находим остаток от деления числа num
+ 3 на 19:
75
# -*- 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('Искомое число равно 1%i 446 744 073 709 551 615' %y)
print()
Других сложностей в задаче нет, и мы быстро находим, что y = 8 (Рис. 2.11).
Рис. 2.11. Нашли зерно задачи
76
Проект На одно делится, на другое нет
Исходный код программы находится в папке Кордемский 084
02.
Функция без параметров
Бесконечный цикл for
Оператор целочисленного деления %
Оператор break
Задача 2 из книги Удивительный мир чисел
[КА86], страница 84:
Найдите наименьшее число, которое делится на 77, а при делении на
74 даёт в остатке 48.
Простая переборная задача – как раз для решения на компьютере:
# ГЛАВНАЯ ФУНКЦИЯ
def main():
print()
print('На одно делится, на другое нет')
print()
solve()
main()
Поскольку искомое число кратно 77, то его можно представить в виде
:
num = 77 * n
Значение переменной n нужно изменять от 1 до … - пока задача не будет
решена. Здесь вполне уместно использовать бесконечный цикл while, прерываемый оператором break, как только искомое число будет найдено:
# -*- coding: Windows-1251 -*# Кордемский, с.84, Задача 2
77
# РЕШАЕМ ЗАДАЧУ
def solve():
n = 1
while True:
# искомое число кратно 77:
num = 77 * n
n += 1
if (num % 74 != 48):
continue
# печатаем искомое число:
print('Искомое число равно %i' %num)
break
print()
Ответ на задачу показан на Рис. 2.12.
Рис. 2.12. Лёгкая задача
78
Проект Кто где живёт?
Исходный код программы находится в папке Кордемский 050
03.
Функция без параметров
Списки
Цикл for
Цикл while
Оператор return
Задача 3 из книги Удивительный мир чисел [КА86], страница 50:
Лайне, Майму, Юта, Белла, Елена и Элеонора живут в одном блоке здания.
Возвращаясь из школы домой, Лайне проходит внутри здания 84 ступеньки, Майму -126, Юта -147, Белла - 189, Елена -210, а Элеонора -231
ступеньку. До квартиры на первом этаже ступенек нет, а между этажами
одинаковые количества ступенек.
Кто на каком этаже живёт?
Число ступенек, ведущих на заданный этаж можно представить в виде:
ЧИСЛО СТУПЕНЕК = ПРОЛЁТ * (ЭТАЖ-1)
(1)
Откуда:
ЭТАЖ = (ЧИСЛО СТУПЕНЕК) / ПРОЛЁТ + 1
(2)
Единицу мы добавляем потому, что на первый этаж можно подняться без
ступенек.
Число ступенек для каждого этажа нам известно из условия задачи. Эти данные, равно как и странные имена девочек, можно поместить в список, которым пользоваться удобнее, чем несколькими отдельными переменными:
# -*- coding: Windows-1251 -*-
79
# Кордемский, с.50, Задача 3
# РЕШАЕМ ЗАДАЧУ
def solve():
# число степенек:
stupeni = [ 84, 126,147,189,210,231 ]
# имена:
names = [ 'Лайне', 'Майму', 'Юты', 'Беллы', 'Елены', 'Элеоноры']
Число ступенек между всеми этажами ПРОЛЁТ одинаково во всём здании, а
номер этажа выражается целым числом. Это значит, что число ступенек для
каждого этажа должно делиться на одно и то же число ПРОЛЁТ. Все числа
кратны их наибольшему общему делителю, который мы легко найдём с помощью функции NOD:
# находим НОД всех чисел:
nod = 84
for i in stupeni:
nod = NOD(nod,i)
print('НОД всех чисел =', nod)
print()
Этажи, на которых находятся квартиры, теперь легко определить по формуле (2):
# решение задачи:
for i in range(0, len(stupeni)):
print('Этаж %s = %i' %( names[i],
print()
stupeni[i]//nod + 1))
def NOD(n1, n2):
while (n2 > 0):
n1, n2 = n2, n1 % n2
return n1
# ГЛАВНАЯ ФУНКЦИЯ
def main():
print()
print('Кто где живёт?')
print()
solve()
print()
80
main()
Ответ на задачу показан на Рис. 2.13.
Рис. 2.13. Нашли этажи
Так как 21 = 3 х 7, то число ступенек успешно можно разделить и на 3, и на
7. Номера этажей значительно увеличатся, но здравый смысл нам всё-таки
подсказывает, что вряд ли между этажами всего 3 или 7 ступенек.
81
Проект Три велосипедиста
Исходный код программы находится в папке Кордемский 055
16.
Функция без параметров
Функция с параметрами
Оператор return
Условный оператор if
Цикл while
Оператор целочисленного деления % Два вилисипидиста виихали навстричу
Бесконечный цикл while
друг другу из пунктов А и В…
Оператор and
Типичная задача по физике
и в исполнении нашего учителя
Задача 16 из книги Удивительный мир чисел [КА86], страница 55:
Три велосипедиста начали с общего старта движение по круговой дорожке. Первый делает полный круг за 21 мин, второй - за 35 мин, а третий - за 15 мин.
Через сколько минут они ещё раз окажутся вместе в начальном
пункте?
Задача решается элементарно, если догадаться, что велосипедисты окажутся в точке старта через целое число кругов. А это случится, когда пройдёт время t (в минутах), которое нацело делится на 21, 35 и 15. Из этого
сле- дует, что искомое время в точности равно НОК этих чисел:
# -*- coding: Windows-1251 -*# Кордемский, с.55, Задача 16
# РЕШАЕМ ЗАДАЧУ
def solve():
# находим НОК трёх чисел:
nok = NOK(NOK(21,35),15)
print('Велосипедисты встретятся через %i мин.' % nok)
print()
82
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 main():
print()
print('Три велосипедиста')
print()
solve()
solve2()
print()
main()
Если вы хотите решить задачу короче, то нужно просто запустить часы и
ждать, пока не пройдёт столько времени, чтобы оно делилось без остатка
на означенные числа:
# РЕШАЕМ ЗАДАЧУ
def solve2():
t = 0
while True:
t += 1
if (t % 21 == 0 and t % 35 == 0 and t % 15 == 0):
break
print('Велосипедисты встретятся через %i мин.' % t)
print()
Но, как ни решай и ни выражайся, а встреча произойдёт через 105 минут
(Рис. 2.14).
Рис. 2.14. Время встречи изменить нельзя
83
Глава 3. Простые числа
Будьте проще – и к вам потянутся люди.
Кредо простых чисел
Простыми называются натуральные числа, имеющие в точности два разных делителя. Из этого определения следует, что ни нуль, ни единица к простым числам не относятся. Также очень легко установить, что первое простое число - это двойка, потому что она делится на единицу и на саму себя
(двойка – единственное чётное простое число!). Дальше вы легко найдёте
тройку, пятёрку, семёрку. Вам не составит труда продолжить этот ряд: 11,
13, 17, 23, 29, 31. Чтобы отбросить многие составные (то есть не простые)
числа, достаточно воспользоваться признаками делимости. Но проверять
большие числа таким способом «опасно», потому что легко ошибиться при
делении (да и вообще делить большие числа затруднительно). На этот случай греческий математик Эратосфен (Рис. 3.1) придумал надёжный способ
поиска простых чисел, который в его честь назвали решетом Эратосфена.
Рис. 3.1. Ἐρατοσθένης ὁ Κυρηναῖος (276 - 194 до н.э.)
84
Действует он так [ЗП88, Задача 557].
Поиск простых чисел начинается с засыпки натуральных чисел в «решето»,
которое удобно представить в виде прямоугольной таблицы. Наименьшее
число - это двойка, поскольку единица к простым числам не относится. А
наибольшее – любое, по вашему выбору. Мы ограничимся первой сотней чисел (Рис. 3.2).
Рис. 3.2. Числа в решете
Находим в таблице двойку – первое простое число – и обводим её кружком.
Очевидно, что все последующие числа, кратные двойке, простыми быть не
могут, поэтому мы их вычёркиваем. В данном примере мы будем просто закрашивать клетки с составными числами (Рис. 3.3).
Как видите, уже после первого, грубого просеивания в решете осталась
только половина чисел. Продвигаемся дальше по таблице и находим первое
после двойки невычеркнутое число. Это тройка – второе простое число. Вычёркиваем все ещё не вычеркнутые числа, кратные тройке (Рис. 3.4).
Ну а затем всё повторяется. Находим следующее простое число – пятёрку и вычёркиваем числа, кратные пяти. Переходим к семёрке, затем к числам
11 и 13. И так далее, пока не дойдём до конца таблицы. Числа в кружках простые, все прочие – составные (Рис. 3.5).
85
Рис. 3.3. Вычеркнули чётные числа
Рис. 3.4. И кратные тройке
86
Рис. 3.5. Просеивание закончено!
87
Проект Чудеса в решете Эратосфена
Исходный код программы находится в папке Решето Эратосфена.
Бесконечный цикл 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)
print()
88
main()
Здесь мы дополнительно позаботились о том, чтобы пользователь мог закрыть программу, введя в качестве конца диапазона нуль. Это необязательно, но и не помешает.
Пользователь может и не знать об этой особенности вашего приложения, поэтому сообщите ему о ней.
Непосредственно поиск простых чисел мы поместим в функцию primes, которой передаём число – конец диапазона end:
# -*- coding: Windows-1251 -*# Решето Эратосфена
import math
# ИЩЕМ ПРОСТЫЕ ЧИСЛА
def primes(end):
print()
print('Простые числа в заданном диапазоне:')
Решето вполне естественно представить списком number. А дальше мы
действуем по алгоритму Эратосфена:
1. Записываем в список number числа, начиная с двойки и заканчивая концом диапазона:
# создаём список натуральных чисел 2..end:
number = list(range(2,end + 1))
2. Переменную prime мы отведём под текущее простое число. Сначала это
будет двойка:
prime = 2
89
3. «Вычёркиваем» из списка число prime * prime, а затем все числа, начиная
с этого числа через prime:
4 – 6 – 8 - … для prime= 2,
9 – 12 – 15 - … для prime= 3.
И так далее:
while (prime <= math.sqrt(end)):
for i in range(prime * prime, end + 1, prime):
number[i - 2] = 0
Конечно, мы не можем реально зачеркнуть число в списке, поэтому присваиваем соответствующему элементу списка значение нуль, которое будет
означать, что это число составное. Из индекса элемента списка нужно вычесть двойку, поскольку нулевой индекс принадлежит этому числу!
4. Ищем первое «невычеркнутое» число – его значение в списке должно отличаться от нулевого:
# ищем следующее простое число:
prime += 1
while (prime <= math.sqrt(end) and number[prime - 2] == 0):
prime += 1
Теперь переменная prime содержит следующее простое число.
5. Если prime не превосходит корня квадратного из максимального числа
end, то мы переходим к пункту 3.
В противном случае все простые числа в заданном диапазоне уже найдены,
их значения в списке - ненулевые. Перебираем весь список, находим по
этому признаку простые числа и печатаем их на экране:
print()
# печатаем простые числа:
for i in number:
if (i != 0):
print(i, ' ', end='')
90
print()
print()
Поскольку в функции main действует бесконечный цикл while, то пользова
- тель может и дальше «решетить» простые чисел (Рис. 3.6). Несмотря на «
древность » алгоритма, он действует довольно быстро, но очень жаден до
памяти компьютера.
Этот пример наглядно показывает нам, что для написания эффективной
программы нужен хороший алгоритм. А если алгоритм имеется, то перевести его на любой язык программирования совсем несложно.
Рис. 3.6. Решето Эратосфена в действии!
91
Проект Простые числа
Исходный код программы находится в папке Простые числа.
Бесконечный цикл while
Метод int
Условный оператор if
Оператор return
Функция с параметрами
Вложенные циклы for
Оператор целочисленного деления %
Всем хорош алгоритм Эратосфена, но не годится для больших чисел - слишком неэкономно он расходует память
компьютера. Мы, конечно, и с помощью решета легко
узнаем, что следующим простым годом будет 2017-й. Но вот с числами
514229 или 39916801 нам уже придётся повозиться! В таких случаях
правильнее один раз написать программу, чем каждый раз считать вруч
- ную.
Чтобы сделать программу более универсальной, мы добавим ещё одну
переменную - begin, чтобы пользователь мог задавать и нижнюю, и
верхнюю границу диапазона для поиска простых чисел:
# ГЛАВНАЯ ФУНКЦИЯ
def main():
print()
print('Простые числа')
print()
# бесконечный цикл ввода данных # пока пользователь не закроет программу
# или не введёт 0:
while True:
s = 'Введите начало диапазона > '
print(s, end = '')
begin = int(input())
# если начало диапазона равно 0, то программу закрываем:
if (begin == 0):
return
#начало диапазона не меньше двойки:
if (begin < 2):
begin = 2
92
s = 'Введите конец
print(s, end = '')
end = int(input())
диапазона
>
'
# если конец диапазона равен 0,
# то программу закрываем:
if (end == 0):
return
# конец диапазона не меньше двойки:
if (end < 2):
end = 2
# ищем простые числа:
primes(begin, end)
print()
main()
Функция main действует почти так же, как и «эратосфеновская», но вызывает функцию primes с двумя параметрами:
# -*- coding: Windows-1251 -*# ПРОГРАММА ДЛЯ ПОИСКА ПРОСТЫХ ЧИСЕЛ
# В ЗАДАННОМ ДИАПАЗОНЕ
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='')
# и записываем в файл:
93
w.write(str(j) + ' ')
w.write('\n')
w.close()
print()
print('Всего ' + str(n))
print()
Здесь мы в цикле for последовательно перебираем числа от n1 = begin до n
2 = end и проверяем их на простоту.
Проверка проходит так: очередное число j мы поочерёдно делим на все
числа, начиная с двойки и кончая корнем квадратным из самого числа j.
Так как любое натуральное число делится на единицу и само на себя, то
два разных делителя у него имеются в любом случае (кроме, единицы, разумеется). Если мы обнаружим хотя бы ещё один делитель, то это будет
перебор: число заведомо составное, и проводить испытания дальше нет
смысла - мы переходим к проверке следующего числа. Если же число простое, то мы печатаем его на экране (Рис. 3.7) и записываем в файл.
Рис. 3.7. Простые числа найдены!
94
Проект Простые числа 2
Исходный код программы находится в папке Простые числа 2.
Бесконечный цикл 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())
# если пользователь ввёл 0, то программу закрываем:
if (num == 0):
95
return
# ищем простые числа:
primes(num)
print()
main()
В функции primes мы создаём список prime для хранения найденных простых чисел и сразу же помещаем в него двойку:
# -*- coding: Windows-1251 -*# ПРОГРАММА ДЛЯ ПОИСКА ПРОСТЫХ ЧИСЕЛ
import math
# ИЩЕМ ПРОСТЫЕ ЧИСЛА
def primes(end_num):
print()
print('Простые числа:')
# список для хранения простых чисел:
prime = []
# первое простое число - двойка:
prime.append(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
96
Обратите внимание, что цикл while - бесконечный. Дело в том, что мы
наперёд не знаем, какое из чисел num окажется по счёту end_num, поэтому
мы прекращаем цикл, когда найдём nPrimes = end_num простых чисел.
Проверку очередного числа 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()
По ходу поиска простых чисел функция primes печатает все простые числа
на экране, а в конце «выступления» сообщает, что простое число с заданным
номером такое-то (у нас оно всегда последнее из найденных). Например,
101-ое простое число равно 547 (Рис. 3.8).
97
Рис. 3.8. Простая задача с простыми числами
98
Взаимно простые числа
Если наибольший общий делитель двух чисел 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 рассматриваются
шестерни с взаимно простыми числами зубьев, что уменьшает их износ.
99
Проект Разложение числа на простые множители
Исходный код программы находится в папке Факторизация.
Бесконечный цикл while
Метод int
Условный оператор if
Оператор return
Функция с параметром
Вложенные циклы
Цикл while
Цикл for
Оператор целочисленного деления %
Основная теорема арифметики утверждает, что любое натуральное
число (составное), большее единицы, можно представить в виде произведения простых чисел, причём единственным способом (если не учитывать
их порядок). Например:
21 = 3 * 7
24 = 2 * 12
125 = 5 * 5 * 5
Единичный (со)множитель в разложении можно не указывать, поскольку он
имеется у всех чисел. Поэтому простые числа представляются единственным
сомножителем – самим числом.
Разложение числа на произведение простых сомножителей, называется
его факторизацией.
В этом проекте мы используем самый простой метод для разложения чисел – полный перебор всех чисел от двух до квадратного корня из заданного числа. Он очень простой, но довольно быстро справляется с задачей
100
даже для очень больших чисел (Рис. 3.9). Однако при встрече с большими
простыми числами может и спасовать.
# ГЛАВНАЯ ФУЕКЦИЯ
def main():
print()
print('Факторизация числа')
print()
# бесконечный цикл ввода данных # пока пользователь не закроет программу
# или не введёт 0:
while True:
s = 'Введите натуральное число (больше единицы) > '
print(s, end = '')
number = int(input())
# если пользователь ввёл 0, то программу закрываем:
if (number == 0):
return
# находим разложение:
razl(number)
#print()
main()
В функцию razl мы передаём испытуемое число, а само разложение числа
даётся нам очень просто:
#
#
#
#
-*- coding: Windows-1251 -*Факторизация
ПРОГРАММА ДЛЯ РАЗЛОЖЕНИЯ НАТУРАЛЬНЫХ ЧИСЕЛ
НА ПРОСТЫЕ МНОЖИТЕЛИ
# НАХОДИМ РАЗЛОЖЕНИЕ ЗАДАННОГО ЧИСЛА НА ПРОСТЫЕ МНОЖИТЕЛИ
def razl(num):
print()
print('Разложение числа:')
print(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='')
101
print()
print()
Рис. 3.9. Разлагаем на простые множители большие числа!
102
Проект Совершенные числа
Исходный код программы находится в папке Совершенные
числа.
Бесконечный цикл 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
103
Легко заметить, что первые 4 совершенных числа достаточно маленькие, а
затем они стремительно увеличиваются!
Из этого наблюдения следует, что первые совершенные числа можно искать
методом грубой силы:
# ГЛАВНАЯ ФУНКЦИЯ
def main():
print()
print('Совершенные числа')
print()
# бесконечный цикл ввода данных # пока пользователь не закроет программу
# или не введёт 0:
while True:
s = 'Введите верхнюю границу > '
print(s, end = '')
num = int(input())
# если пользователь ввёл 0, то программу закрываем:
if (num == 0):
return
perfectNumbers(num);
print()
main()
Практически такой же алгоритм поиска совершенных чисел предлагается и
в книге Брудно и Каплана:
# -*- 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
104
n = num // divisor
if (divisor != n):
summa += n
# печатаем число в консольном окне:
if (summa == num):
print('Число', num, 'совершенное')
print()
Рис. 3.10. Несовершенный алгоритм для поиска совершенных чисел
Увы, дальше четвёртого числа с методом perfectNumbers вы не продвинетесь!
105
Задания для самостоятельного решения
Простые числа
1. Напишите метод, который определяет, являются ли несколько чисел
взаимно простыми.
2. Найдите четвёрки простых чисел, принадлежащих одному десятку.
Например, 11, 13, 17, 19. [ЗП88, Задача 558].
3. Натуральное число называется полусовершенным, если оно равно
сумме всех или некоторых своих делителей, исключая само число: 6, 12,
18, 20, 24, 28, 30, 36, 40. Например, 12 = 1 + 2 + 3 + 6 или 12 = 2 + 4 + 6.
Из этого определения следует, что всякое совершенное число является и
полусовершенным, то есть полусовершенных чисел в заданном диапазоне не меньше, чем совершенных. Напишите программу, которая находила бы несколько полусовершенных чисел.
4. Два (различных) натуральных числа называются дружественными,
если сумма всех делителей (исключая само число) первого числа равна
второму числу, и наоборот. Первая пара дружественных чисел была
найдена несколько тысячелетий тому назад. Это числа 220 и 284. Следующая пара отыскалась только в 1860 году – 1184 и 1210. Сейчас известно
несколько миллионов дружественных чисел. Напишите программу для
их поиска [ЗП88, Задача 560]. Учитывайте, что числа в паре либо оба чётные, либо оба нечётные.
106
Глава 4. Числовые ребусы
Первый числовой ребус составил Генри Дьюдени:
SEND + MORE = MONEY
В числовых ребусах словами зашифрованы числа, причём одинаковые
буквы обозначают одинаковые цифры, и наоборот, одинаковые цифры обозначены одинаковыми буквами.
Задача состоит в том, чтобы заменить буквы соответствующими им цифрами так, чтобы получилось верное равенство.
Обычно числовые ребусы решаются с помощью логических рассуждений, но
практически никогда не удаётся обойтись без предположений, то есть без
перебора вариантов. А с такой работой даже самый захудалый компьютер
справится быстрее любого из нас. А от нас требуется только одно – написать
простую программу.
107
Проект Каковы жуки?
Исходный код программы находится в папке Кордемский 100
09.
Функция без параметров
Вложенные циклы for
Условный оператор if
Оператор continue
Оператор return
Оператор or
Задача 9 из книги Удивительный мир чисел
[КА86], страница 100:
Решите уравнение в целых неотрицательных числах:
520⦁(Ж⦁У⦁К⦁И + Ж⦁У + Ж⦁И + К⦁И + 1) = 577⦁(У⦁К⦁И + У + И).
Числовые ребусы вполне успешно можно решать методом полного перебора. В них каждая цифра заменена буквой, но, поскольку цифр только десять, то каждая буква может принимать всего 10 разных значений - от 0 до
9. В любом ребусе не более десятка разных букв, то есть в худшем случае
нам придётся проверить 10 000 000 000 вариантов. Современным компью
- терам это вполне по силам. Впрочем, так бездумно компьютер не используют, поэтому даже при полном переборе следует разумно ограничивать
число вариантов. Обычно сделать это очень просто, поскольку некоторые
способы решения криптарифмов лежат на поверхности и не требуют глубо
- ких размышлений. Например, для уменьшения числа вариантов достаточно учесть тот очевидный факт, что все буквы должны иметь разное зна
- чение. Это следует из условия самой головоломки: разным буквам
соответ- ствуют разные цифры. Вот теперь можно смело браться за
любой числовой ребус.
Функция main просто вызывает функцию solve, а затем печатает число
найденных решений:
# ГЛАВНАЯ ФУНКЦИЯ
def main():
print()
108
print('Каковы жуки?')
print()
nVar = solve()
print('Найдены все варианты решения -', nVar)
print()
main()
Функция solve, как это обычно и бывает при полном переборе, очень простая. Записываем столько вложенных циклов for, сколько разных букв в ре
- бусе. В нашем примере всего 4 разные буквы, поэтому и циклов тоже
будет 4. Начиная со второй буквы, делаем проверку: её цифровое
представление не должно совпадать с предыдущими буквами. Найдя
значение каждой буквы, проверяем условие:
520*(Ж*У*К*И + Ж*У + Ж*И + К*И + 1) == 577*(У*К*И + У + И)
Если оно выполняется (то есть левая часть выражения равна правой), то мы
выписываем найденное решение в консольном окне, а затем ищем другие
решения:
# -*- 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('Вариант #', result)
s = 'Ж = ' + str(Ж)
print(s)
s = 'У = ' + str(У)
print(s)
s = 'К = ' + str(К)
print(s)
s = 'И = ' + str(И);
print(s)
print()
return result
109
В итоге мы найдём единственное решение этого ребуса (Рис. 4.1).
Рис. 4.1. Задача решена!
Точно так же вы можете решить любой числобус, НО – каждый раз вам придётся писать новую функцию solve! Такова плата за простоту программы…
110
Проект Четыре "пари"
Исходный код программы находится в папке Кордемский 100
08.
Функция без параметров
Вложенные циклы for
Условный оператор if
Оператор continue
Оператор return
Оператор or
Задача 8 из книги Удивительный мир чисел [КА86], страница 100:
Как велико ПАРИ, если:
В этой системе числовых ребусов также необходимо найти цифровые значения четырёх букв, поэтому большую часть кода мы можем скопировать
из предыдущего проекта.
# ГЛАВНАЯ ФУНКЦИЯ
def main():
print()
print('Четыре "пари"')
print()
nVar = solve()
print('Найдены все варианты решения -', nVar)
print()
main()
111
Однако теперь нам предстоит проверить выполнение не одного, а сразу четырёх условий! Лучше их проверять последовательно, и если какое-либо
условие окажется невыполненным, то с помощью оператора 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('Вариант #', result)
s = 'П = ' + str(П)
print(s)
s = 'А = ' + str(А)
print(s)
s = 'Р = ' + str(Р)
print(s)
s = 'И = ' + str(И);
print(s)
print()
return result
Когда все равенства станут верными, мы печатаем решение задачи на
экране (Рис. 4.2).
112
Рис. 4.2. Мы выиграли ПАРИ!
113
Проект Тайна трёх слагаемых
Исходный код программы находится в папке Кордемский 079
11.
Функция без параметров
Вложенные циклы for
Условный оператор if
Оператор continue
Оператор return
Оператор or
Задача 11 из книги Удивительный мир чисел [КА96],
страница 79:
Переведите на язык арифметики чисел:
Первое слагаемое (БОРЯ) должно быть возможно максимальным.
Этот ребус с БОРЕЙ – вполне классический: все цифры заменены буквами,
и нужно узнать сумму нескольких чисел. Вы найдёте в книгах огромное
множество таких ребусов, и все они решаются одинаково – с помощью вложенных циклов for.
Решая классические ребусы, вы должны учитывать, что первая цифра числа
не может быть нулём. Это значит, что буквы Б, И, Д могут принимать значения от 1 до 9, но не нуль. Это требование легко учесть в циклах for для соот
- ветствующих переменных, что мы и сделали в функции solve.
# -*- coding: Windows-1251 -*# Кордемский, с.79, Задача 11
# РЕШАЕМ ЗАДАЧУ
def solve():
114
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
if (max > Б*1000 + О*100 + Р*10 + Я): continue
result += 1
print('Вариант #', result)
s = 'БОРЯ = ' + str(Б) + str(О) + str(Р) + str(Я)
print(s)
s = ' ИДИ = ' + str(И) + str(Д) + str(И)
print(s)
s = 'БУДЬ = ' + str(Б) + str(У) + str(Д) + str(Ь)
print(s)
s = 'ДОБР = ' + str(Д) + str(О) + str(Б) + str(Р)
print(s)
print()
return result
# ГЛАВНАЯ ФУНКЦИЯ
def main():
print()
print('Тайна трёх слагаемых')
print()
nVar = solve()
print('Найдены все варианты решения -', nVar)
print()
main()
Кроме большого числа циклов и длинных проверок, ничего сложного в подобного рода задачах нет, поэтому вы решите любую из них без проблем!
Рис. 4.3 показывает 72 ответа.
115
Рис. 4.3. 72 БОРИ
Обычно хороший числобус должен иметь единственное решение, что в
этой задаче достигается дополнительным требованием, чтобы число БОРЯ
было максимальным.
Конечно, можно просто вручную найти в списке решений то, в котором это
условие выполняется, но всякий перебор лучше перепоручать компьютеру.
Введём переменную max, которая поначалу имеет нулевое значение (или
любое отрицательное):
max = 0
Перед строчкой
result += 1
вставляем код для проверки и запоминания максимального значения для
БОРИ:
if (max > Б*1000 + О*100 + Р*10 + Я): continue
116
# запоминаем новое максимальное значение:
max = Б*1000 + О*100 + Р*10 + Я
result += 1
Теперь функция solve будет печатать только «рекордные» результаты для
числа БОРЯ. Самое большое значение будет последним в списке.
На Рис. 4.4 вы видите любопытную картину – число БОРЯ увеличивается с
каждой итерацией и достигает своего максимума только в самом конце
списка решений.
Рис. 4.4. Самый большой БОРЯ
Мы можем предположить, что автор задачи решал её так же, как и мы, то
есть на компьютере.
117
Проект Меняем четыре буквы на четыре цифры
Исходный код программы находится в папке Кордемский 073
10.
Функция без параметров
Вложенные циклы 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('Меняем четыре буквы на четыре цифры')
print()
nVar = solve()
print('Найдены все варианты решения -', nVar)
print()
main()
В функции solve, пожалуй, стоит обратить внимание только на выделенные строчки, в которых проверяется условие первой задачи:
118
# -*- coding: Windows-1251 -*# Кордемский, с.73, Задача 10
# РЕШАЕМ ЗАДАЧУ
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('Вариант #', 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
Первая часть задачи решена (Рис. 4.5)!
Рис. 4.5. Первая половина задачи
119
Чтобы решить вторую часть задачи, следует изменить условие:
#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
Вторая часть задачи имеет 2 решения (Рис. 4.6)!
Рис. 4.6. И вторая половина
120
Проект Коварная задача папы
Исходный код программы находится в папке Кордемский 022
11.
Функция без параметров
Вложенные циклы for
Условный оператор if
Оператор continue
Оператор return
Оператор or
Задача 11 из книги Удивительный мир чисел
[КА86], страницы 22-23:
Мой папа - шофёр - часто ездит по трассе, вдоль которой расположены
пять небольших поселков: А, В, С, D, Е. Папа знал точно, сколько домов в
каждом из этих посёлков, и составил для меня такую задачу: «Сколько
всего домов в этих пяти посёлках, если
в А и В вместе 13 домов,
в В и С вместе 31 дом,
в С и Е - 17 домов,
в Е и D - 26 домов,
в А и D - 23 дома?»
Я подметил, что число домов в каждом посёлке подсчитывалось папой
дважды (по тексту задачи), поэтому моё решение было молниеносным:
(13 + 31 + 17 +26+ 23) / 2 = 55 (домов в пяти поселках вместе).
Но... вот уж действительно: поспешишь — людей насмешишь! Оказалось,
что задачу папа сознательно составил так, что она не может быть решена.
И я должен был это доказать. Помогите!
В каждом посёлке не меньше 0 домов и не больше 31 дома – эти числа легко
получить из условия задачи. Достаточно перебрать все варианты числа домов в этом диапазоне для посёлков А, В, С, D, Е, чтобы найти все решения
задачи.
121
# ГЛАВНАЯ ФУНКЦИЯ
def main():
print()
print('Коварная задача папы')
print()
nVar = solve()
print('Найдены все варианты решения -', nVar)
print()
main()
Выписываем 5 вложенных циклов for и по ходу итераций проверяем папины фантазии:
# -*- coding: Windows-1251 -*# Кордемский, с.22-23, Задача 11
# РЕШАЕМ ЗАДАЧУ
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('Вариант #', 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)
122
s = 'E = ' + str(E)
print(s)
print()
return result
Как и отметил торопливый сынуля, папа его дезинформировал (Рис. 4.7).
Рис. 4.7. С полным перебором не поспоришь!
Правда, если в условии задачи нежно подправить условие:
C + E = 17 C + E = 27
то папина задача успешно разрешится единственным способом (Рис. 4.8).
Рис. 4.8. Поправили папу
Из лучших педагогических побуждений будем считать, что папа просто
ошибся.
123
Проект Универсальный решатель
Исходный код программы находится в папке Alphametics.
Функция c параметрами
Функция eval
Функция permutations
Списки
Множества
Словари
Поскольку лень двигает прогресс, то мы должны подумать о
написании универсального решателя числовых ребусов.
Питон имеет очень полезную для этого случая функцию eval, которая
умеет вычислять переданные ей выражения. Это может быть любая строка
программного кода, заключённая в кавычки. Например, она может вычислить «шахматное число»:
print (eval('2**64'))
Числовые ребусы называют также криптарифмами, поскольку цифры в них
зашифрованы – заменены буквами. Естественно, Питон не может вычислить такое выражение, и наша задача заключается в том, чтобы заменять
буквы всеми возможными цифрами и уже числовое выражение отправлять
функции eval на проверку. Так как числобус это равенство, то и функция eval
должна проверять левую и правую части выражения на равенство. Поэтому
обычный знак равенства нужно заменить двойным ==, так как мы сравниваем значения правой и левой частей выражения. Если функция eval вернёт
True, значит, равенство верное, и мы можем напечатать решение на экране.
Вполне может оказаться, что задача имеет не единственное решение, поэтому мы продолжаем проверки до полного исчерпания всех возможных
комбинаций букв и цифр.
Буквы в числобусе можно разделить на 2 группы – начальные и все остальные. Начальные буквы не могут заменять нуль, а все прочие могут. Следовательно, нам нужно иметь 2 списка букв. Также мы должны учесть, что
разных букв не должно быть больше 10, иначе на них не хватит цифр.
124
В начале функции 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 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)
# список всех букв:
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)):
125
# первой цифрой не может быть нуль:
if '0' not in perm[:len(first_chars)]:
Если всё нормально, то мы заменяем все буквы числобуса соответствующими цифрами в перестановке и получаем строку, в которой все буквы заменены цифрами. Знаки арифметических действий при этом остаются без
изменений:
# составляем числовое выражение,
# заменяя буквы цифрами:
equation = puzzle.translate(dict(zip(all_chars, perm)))
#print(equation)
Например, строки equation для числобуса
'IMPRESARIO + PARATROOPS + TETRAMETER == ASPIRATION' будут такими
(Рис. 4.9).
Рис. 4.9. Ищем!
Функция eval проверяет их, и если какая-либо строка окажется верной, то
на экране появляется очередное решение:
# если оно верное,
if eval(equation):
# то печатаем очередное решение:
n_var += 1
print('Вариант #', n_var)
print ('\n'.join((puzzle, equation)))
Общее число найденных решений функция eval возвращает в главную
функцию:
126
return n_var
А в функции main мы вызываем функцию solve со строкой-заданием:
# ГЛАВНАЯ ФУНКЦИЯ
def main():
print()
print('Решаем числовой ребус')
print()
#puzzle = 'ЛОБ + ТРИ == САМ'
#puzzle = 'УА * ВБГ == ИЯ * ЖДА'
puzzle = 'IMPRESARIO + PARATROOPS + TETRAMETER == ASPIRATION'
nVar = solve(puzzle)
print('Найдены все варианты решения -', nVar)
print()
main()
Как и всякое универсальное решение, наша программа не самая быстрая, но
зато позволяет решать любые классические числовые ребусы (Рис. 4.10), не
переписывая каждый раз исходный код программы.
Рис. 4.10. Неспешно, но универсально…
Более того, функция eval справляется с любыми выражениями, поэтому в
числобусе могут быть не только знаки плюс, но также знаки вычитания,
умножения, деления, возведение в степень и другие.
Так что программа у нас получилась даже более универсальной, чем мы планировали.
Чтобы все слова были правильно извлечены из входной строки, ставьте между
ними и арифметическими знаками пробелы!
127
Давайте решим с помощью нашей программы вот такой ребус:
puzzle = 'ABCD * E == FGHIJ'
То есть мы хотим найти решения для примера, в котором участвуют все 10
разных цифр.
Недолго думая, наша программа выдаёт 13 решений поставленной перед
ней задачи:
Решаем числовой ребус
Вариант # 1
ABCD * E == FGHIJ
5694 * 3 == 17082
Вариант # 2
ABCD * E == FGHIJ
6819 * 3 == 20457
Вариант # 3
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
Вариант # 7
ABCD * E == FGHIJ
7039 * 4 == 28156
Вариант # 8
ABCD * E == FGHIJ
9127 * 4 == 36508
Вариант # 9
ABCD * E == FGHIJ
5817 * 6 == 34902
Вариант # 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 решений (Рис. 4.11
).
128
Рис. 4.11. Многовато будет
И даже с математическими функциями наш решатель успешно справляется
(Рис. 4.12)!
puzzle = 'math.sqrt( ABCD ) == EF'
Рис. 4.12. Функционально!
Более интересный пример (Рис. 4.13):
puzzle = 'math.sqrt( ABCDE ) == FGH'
129
Рис. 4.13. Почти готовая задача
А вот так просто можно решать комбинаторные задачи (Рис. 4.14):
puzzle = ' A + B + C + D == 12'
Рис. 4.14. Универсальная комбинаторика
Изменим всего одну строчку кода:
words = [w for w in puzzle.split() if
w.isupper()]
И мы сможем решать «системы уравнений» (Рис. 4.15):
puzzle = 'A + B + C + D == 12 and A * B == 10 and A - B == 3'
130
Рис. 4.15. Системно!
А вот так можно легко и просто решить задачу про ПАРИ (Рис. 4.16):
puzzle = 'П * А * Р * И == 5 * ( П + А + Р ) and П * А * Р * И
* ( И + Р + А ) and 3 * П * А * Р * И == 10 * ( П + И + Р )'
== 3
Рис. 4.16. Здорово!!!
Как вы видите, чтобы решить задачу, четвёртое выражение не нужно.
131
Задания для самостоятельного решения
Поиграем в прятки
Удивительный мир чисел. Задача 8 (9) ], страница 73
Все 10 цифр спрятались под буквами в каждом из пяти равенств:
1)
2)
3)
4)
5)
Д ⦁ Г У В А = Б Ж Я И Е
Е ⦁ Д А И Г = Б ⦁ У В Ж Я
Е Ж ⦁ Г В А = Б У Я Д И
ЖИ ⦁ ДАГ = ЕУВБЯ
УА ⦁ ВБГ = ИЯ ⦁ ЖДА
Задача несложная, но трудоёмкая: чтобы решить её, придётся выписывать 10 вложенных циклов!
ЛОБ ТРИ САМ
Удивительный мир чисел. Задача 2, страница 70
Это трёхзначные числа, такие, что
ЛОБ +ТРИ = САМ
Расшифруйте сложение, обходясь без цифры нуль.
В любом возможном решении должна обнаружиться определенная закономерность в числе «САМ». Какая?
Семейство, спрятавшееся в «БАКУ»
Удивительный мир чисел. .Задача 5, страница 77
а) Расшифруйте произведение чисел БАКУ и §§§§, зная, что в каждом
сомножителе цифры образуют (слева направо) строго убывающую последовательность.
Какое семейство цифр спряталось за буквами Б, А, К, У?
132
б) Расшифруйте в этом ребусе ещё и второй множитель (§§§§), если
строго убывающую последовательность образуют цифры только первого сомножителя (БАКУ) и если в произведении вторая цифра слева нуль.
133
Глава 5. Степени и корни
Зри в корень!
Козьма Прутков
Из множества занимательных и поучительных задач со степенями и корнями мы решим всего 42 - √𝟗, но зато очень интересных!
Проект Кубическое число
Исходный код программы находится в папке Кордемский 089
01.
Функция с параметром
Функция 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
134
# РЕШАЕМ ЗАДАЧУ
def solve(num):
print('Кубическое число =', round(math.pow(num, 1 / 3.0)))
print()
# ГЛАВНАЯ ФУНКЦИЯ
def main():
print()
print('Кубическое число')
print()
solve(996703628669)
solve(1011443374872)
#solve2(996703628669)
#solve2(1011443374872)
print()
main()
На Рис. 5.1 показано решение задачи:
Рис. 5.1. Извлекли корни
Поскольку кубические корни даже из очень больших чисел невелики, то
можно найти их простым перебором, даже начиная с нуля!
Пишем вторую версию функции для решения задачи:
def solve2(num):
i = 0
while True:
if (i*i*i > num):
break
if (i*i*i == num):
print('Кубический корень из числа %i = %i' %(num, i))
break
i += 1
print()
135
И получаем точные значения искомых корней (Рис. 5.2).
Рис. 5.2. И перебор не в тягость!
При этом нам не понадобился функция pow из модуля math!
136
Проект И «хвост», и «грива»
Исходный код программы находится в папке Кордемский 072
08.
Функция без параметров
Функция с параметром
Оператор целочисленного деления %
Оператор return
Цикл while
Вложенные циклы for
Оператор or
Задача 8 из книги Удивительный мир чисел [КА86], страница 72:
Если есть четырёхзначные числа, первые две и последние две цифры
каждого из которых совпадают соответственно с первыми двумя и последними двумя цифрами квадрата и куба искомого числа, то какое из
них наименьшее и какое - наибольшее? Числа, оканчивающиеся единицей и двумя нулями, исключаем.
В зашифрованном виде:
xyzt2= xy...zt и
xyzt3= xy...zt.
В правой и левой частях этих равенств буквой х зашифрована одна и та
же цифра, буквой у - также, буквой z - также и буквой t - также, но эти
цифры могут быть и одинаковыми.
Чтобы получить из квадратов и кубов две первые и две последние цифры
(точнее, двузначные числа, составленные из этих цифр), мы напишем 2
вспомогательные функции.
Две последние цифры легко получить как остаток от деления исходного
числа на сотню:
def last2(num):
return num % 100
137
С первыми двумя цифрами сложнее: мы не знаем «длину» числа, поэтому
и не сможем сразу разделить его на нужную степень числа 10. В этом случае
нужно последовательно делить заданное число на 10, пока оно не станет
двузначным:
def first2(num):
while (num >= 100):
num //= 10
return num
Решить задачу можно так же, как мы решали числовые ребусы – с помощью
четырёх вложенных циклов for. При этом мы учитываем, что переменные x,
y, z, t могут иметь одинаковые значения, что в числовых ребусах недопустимо. Также из условия задачи следует, что t не равно 0 и 1, а z не равно 0.
В остальном функция solve достаточно проста:
# -*- coding: Windows-1251 -*# Кордемский, с.72, Задача 8
def last2(num):
return num % 100
def first2(num):
while (num >= 100):
num //= 10
return num
# РЕШАЕМ ЗАДАЧУ
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):
138
continue
result += 1
print('Вариант #', result)
print('xyzt =', xyzt)
print('xyzt2 =', xyzt2)
print('xyzt3 =', xyzt3)
print()
return result
# ГЛАВНАЯ ФУНКЦИЯ
def main():
print()
print('И "хвост", и "грива"')
print()
nVar = solve()
print('Найдены все варианты решения -', nVar)
print()
main()
Запустив программу, мы тут же получаем ответ (Рис. 5.3).
Рис. 5.3. Решили задачу игриво: и в хвост и в гриву
Так как функции first2 и last2 позволяют извлекать нужные цифры из любых
чисел, то задачу можно решить, проверяя в одном цикле for все четырёхзначные числа. Попробуйте!
139
Проект Возведение в квадрат без операции
умножения
Исходный код программы находится в папке Квадраты .
Бесконечный цикл 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('Квадраты чисел')
print()
# бесконечный цикл ввода данных # пока пользователь не закроет программу
140
#или не введёт 0:
while True:
s = 'Введите число > '
print(s, end='')
num = int(input())
# если пользователь ввёл 0, то программу закрываем:
if (num == 0):
return
# вычисляем квадрат заданного числа:
q = quadrat(num)
print('Квадрат числа', num, ' = ', q)
print()
main()
Функция quadrat вычисляет квадрат заданного числа num по алгоритму,
описанному выше:
# -*- coding: Windows-1251 -*# Дагене, #6, с.27
import math
# ВЫЧИСЛЯЕМ КВАДРАТ ЗАДАННОГО ЯИСЛА
def quadrat(num):
# нечётное число:
nech = 1
# квадрат:
q = 0
# вычисляем квадрат как сумму нечётных чисел:
for i in range(1, num + 1):
q += nech
nech += 2
return q
Несмотря на множество операций сложения, квадраты даже очень больших
чисел функция quadrat находит очень быстро (Рис. 5.4).
141
Рис. .5.4. Решение квадратной задачи
142
Проект Возведение в куб без операции умножения
Исходный код программы находится в папке Кубы.
Бесконечный цикл while
Метод int
Условный оператор if
Оператор return
Функция с параметром
Цикл for
Вложенные циклы for
Задача 6⨀ из книги В. А. Дагене, Г. К. Григас, К. Ф. Аугутис 100 задач по программированию [100], страницы 27-28:
Куб любого натурального числа n равен сумме n нечётных чисел, следующих по порядку за числами, сумма которых составила куб числа n - 1:
1 3= 1
2 3= 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
Затем найдём разность между соседними числами (средняя строка) и повторим эту операцию ещё раз. В нижней строке все числа одинаковые, это
значит, что номер нечётного числа можно вычислить по формуле:
143
a⦁num2 + b⦁num + c
Для нахождения коэффициентов этого выражения, следует воспользоваться методом исчисления конечных разностей. Он описан, например, в
моей книге Как решать комбинаторные задачи на компьютере.
В итоге мы получим вот такую замечательную формулу:
n = num (num-1) / 2 + 1
(1)
По номеру n мы легко найдём и само нечётное число. Оно равняется:
2n – 1
(2)
Эти две формулы можно объединить в одну:
Первое нечётное число = num (num-1) + 1
Если ряд чисел не очень сложный, то можно поискать для него формулу в
Интернете. Например, для нашей последовательности 1, 2, 4, 7, 11 я сразу
нашёл общую формулу:
http://znanija.com/task/141487
http://answers.yahoo.com/question/index?qid=20080903051936AAUvpkC
http://michaelnela.hubpages.com/question/87442/what-is-the-general-term-of-1-2-4-7-11-1622
Функция main требует только небольшой правки:
# ГЛАВНАЯ ФУНКЦИЯ
def main():
print()
print('Кубы чисел')
print()
# бесконечный цикл ввода данных # пока пользователь не закроет программу
# или не введёт 0:
while True:
144
s = 'Введите число > '
print(s, end='')
num = int(input())
# если пользователь ввёл 0, то программу закрываем:
if (num == 0):
return
# вычисляем куб заданного числа:
q = qube(num)
print('Куб числа', num, ' = ', q)
print()
main()
В функции qube мы сначала вычисляем номер n первого нечётного числа
суммы, а затем по формуле (2) и само нечётное число. Остальная часть метода ничем не отличается функции quadrat:
# -*- coding: Windows-1251 -*# Дагене, #6, с.27-28
import math
# ВЫЧИСЛЯЕМ КУБ ЗАДАННОГО ЧИСЛА
def qube(num):
# номер нечётного числа:
n = num*(num-1) // 2 + 1
# нечётное число:
nech = 2*n - 1
# куб:
q = 0
# вычисляем куб как сумму нечётных чисел:
for i in range(1, num + 1):
q += nech
nech += 2
return q
Начинаем осторожно проверять работу нового метода на небольших числах, кубы которых нам точно известны. Но, разохотившись, вы можете заняться вычислением БОЛЬШИХ кубов (Рис. 5.5).
145
Рис. 5.5. Это вам не в кубики играть!
Кубическая задача успешно решена!
Пользуясь функцией qube, можно составить таблицу кубов, о которой идёт
речь в задаче, но лучше написать отдельную функцию, тем более что она
не отнимет у вас много сил:
# ПЕЧАТАЕМ ТАБЛИЦУ КУБОВ
def printTable(num):
print()
nech = 1
# для всех чисел 1..num:
for i in range(1, num + 1):
q = 0
for j in range(1, i + 1):
q += nech
nech += 2
print('Куб числа', i, ' = ', q)
146
Алгоритм, заложенный в эту функцию, буквально повторяет условие задачи. Мы последовательно находим сумму нечётных чисел, продолжая их
ряд с каждым новым числом, для которого находим куб.
На Рис. 5.6 вы можете видеть отчёт о проделанной работе.
Рис. 5.6. Кубическая таблица
147
Проект Зашифрованные жуки
Исходный код программы находится в папке Кордемский 071
04 .
Функция без параметров
Вложенные циклы for
Оператор continue
Условный оператор if
Оператор or
Задача 4 из книги Удивительный мир чисел
[КА86], страница 71:
Эту задачу вполне можно считать числовым ребусом и потому решать
точно так же – с помощью вложенных циклов.
Все буквы, входящие в равенства а) и б), должны быть различными, а буква
Ж не может равняться нулю.
Значение степени И заведомо не меньше трёх и не больше шести.
С задачей а) мы справляемся без особых хлопот и затей:
# -*- coding: Windows-1251 -*# Кордемский, с.71, Задача 4
# РЕШАЕМ ЗАДАЧУ
def solve():
result = 0
148
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('Вариант #', result)
print('ЖУК = ' + str(Ж) + str(У) + str(К))
print('И =', И)
print()
return result
# ГЛАВНАЯ ФУНКЦИЯ
def main():
print()
print('Зашифрованные жуки')
print()
nVar = solve()
print('Найдены все варианты решения -', nVar)
print()
main()
Она имеет единственное решение, показанное на Рис. 5.7.
Рис. 5.7. Первый ЖУК выявлен
149
Для решения задачи б) нужно только изменить проверку:
#if (num != 244140000 + Ж*100 + У*10 + К):
#
continue
if (num != 19987173000 + Ж*100 + У*10 + К):
continue
Она также имеет единственное решение (Рис. 5.8).
Рис. 5.8. И второй ЖУК тоже
150
Проект Ж-Ж-Ж!
Исходный код программы находится в папке Кордемский 071
03 .
Функция без параметров
Цикл for
Оператор return
Условный оператор if
Оператор continue
Задача 3 из книги Удивительный мир чисел
[КА86], страница 71:
Убрав из жучиной программы всё лишнее, мы легко берём очередную задачу:
# -*- 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('Вариант #', result)
print()
print('Ж =', Ж)
print()
151
return result
# ГЛАВНАЯ ФУНКЦИЯ
def main():
print()
print('Ж-Ж-Ж!')
print()
nVar = solve()
print('Найдены все варианты решения -', nVar)
print()
main()
На Рис. 5.9 вы видите, что Ж = 7, то есть полное решение такое:
(7-1)5 = 7776
Рис. 5.9. И с жужжжелицей справились
152
Проект Девять в квадрате
Исходный код программы находится в папке Кордемский 070
01.
Функция без параметров
Цикл for
Оператор целочисленного деления %
Оператор return
Задача 1 из книги Удивительный мир чисел [КА86],
страница 70:
Найдите шестизначное число, зашифрованное в ребусе:
ДЕВЯТЬ2=§§§§§§ДЕВЯТЬ
Мы легко найдём, что наименьшее 6-значное число, состоящее из разных
цифр, равно 102345, а наибольшее – 987654. Таким образом, нужно перебрать все числа в этом диапазоне, возвести их в квадрат и сравнить послед
- ние 6 цифр квадрата с исходным числом. По условию задачи, они должны
совпадать.
# ГЛАВНАЯ ФУНКЦИЯ
def main():
print()
print('Девять в квадрате')
print()
nVar = solve()
print('Найдены все варианты решения -', nVar)
print()
main()
В функции solve мы и выполняем означенные действия:
# РЕШАЕМ ЗАДАЧУ
def solve():
minnum = 102345
153
maxnum = 987654
result = 0
for num in range(minnum, maxnum + 1):
num2 = num * num
if (num2 % 1000000 != num):
continue
result += 1
print('Вариант #', result)
s = 'num = ' + str(num)
s += ' num2 = ' + str(num*num)
print(s)
print()
return result
Задача имеет 2 решения (Рис. 5.10).
Рис. 5.10. Расквадратили
154
Проект Пара чисел: 3149 и 3151
Исходный код программы находится в папке Кордемский 043
03-2.
Функция без параметров
Цикл for
Условный оператор if
Оператор continue
Задача 3.2 из книги Удивительный мир чисел [КА86],
страница 43:
Десятичная запись куба каждого из чисел 3149 и 3151 начинается двумя
его первыми цифрами, а оканчивается двумя его последними цифрами:
31493 = 31 226 116 949
31513 = 31 285 651 951
Есть ещё два последовательных четырёхзначных числа, обладающих таким же свойством.
Какие это числа?
Задача решается полным перебором всех четырёхзначных чисел:
# ГЛАВНАЯ ФУНКЦИЯ
def main():
print()
print('Пара чисел: 3149 и 3151')
print()
nVar = solve()
print('Найдены все варианты решения -', nVar)
print()
main()
# -*- coding: Windows-1251 -*# Кордемский, с.43, Задача 3-2
155
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):
Для каждого четырёхзначного числа мы находим его куб. Затем сравниваем
в этих числах 2 первые и 2 последние цифры. Для этого мы привлекаем
функции first2 и last2, разработанные нами в проекте И «хвост», и «грива»:
num3 = num * num * num
# две последние цифры:
if (last2(num) != last2(num3)):
continue
# две первые цифры:
if (first2(num) != first2(num3)):
continue
result += 1
print('Вариант #', result)
s = 'num = ' + str(num)
s += ' num3 = ' + str(num*num*num)
print(s)
print()
return result
На Рис. 5.11 приведён полный список чисел, выполняющих условие задачи.
В книге указана пара чисел 3200 и 3201, но вы видите, что есть и другие
пары чисел:
1000 и 1001
1024 и 1025
9975 и 9976
156
Рис. 5.11. Уточнили ответ
157
Проект Число 698 896
Исходный код программы находится в папке Кордемский 043
03-3.
Функция без параметров
Цикл for
Функция с параметром
Цикл while
Оператор return
Функция max
Оператор целочисленного деления %
Задача 3.3 из книги Удивительный мир чисел [КА86], страница 43:
Число 698 896 квадратное (8362), палиндромическое. Предполагают, что
оно - наименьшее квадратное палиндромическое число с чётным количеством цифр. Подтвердить это или опровергнуть можно, только покопавшись в таблице квадратов чисел, меньших 836.
И правда, давайте покопаемся!
Например, можно докопаться до чисел, гораздо больших, чем 836
:
# ГЛАВНАЯ ФУНКЦИЯ
def main():
print()
print('Число 698 896')
print()
nVar = solve()
print('Найдены все варианты решения -', nVar)
print()
main()
# РЕШАЕМ ЗАДАЧУ
def solve():
minnum = 4
maxnum = 1000000
result = 0
for num in range(minnum, maxnum + 1):
num2 = num * num
158
Чтобы распознать квадратный палиндром, мы задействуем функцию
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
А вот новая функция для подсчёта цифр в квадрате:
# -*- coding: Windows-1251 -*# Кордемский, с.43, Задача 3-3
# НАХОДИМ ЧИСЛО ЦИФР В ЗАДАННОМ ЧИСЛЕ
def numDigit(num):
num = abs(num)
nd = 0
while (num > 0):
nd += 1
num //= 10
return max(1,nd)
Как и полагается по условию задачи, мы рассматриваем только числа с чётным набором цифр:
# число цифр:
if (numDigit(num2) % 2 != 0):
continue
#s = 'num = ' + str(num)
159
#s += ' num2 = ' + str(num*num)
#print(s)
result += 1
print('Вариант #', result)
s = 'num = ' + str(num)
s += ' num2 = ' + str(num*num)
print(s)
print()
return result
За несколько секунд мы, покопавшись и порывшись, установили, что первое
квадратное палиндромическое число именно 698 896, а второе – гораздо
больше (Рис. 5.12).
Рис. 5.12. Квадратные палиндромы
Если искать квадратные палиндромы с любым числом цифр, то можно откопать и накопать весьма любопытные экземпляры (Рис. 5.13)!
Рис. 5.13. Тоже квадратно
160
Проект Числа 11 826, 12 363, 14 676
Исходный код программы находится в папке Кордемский 043
03-4.
Функция без параметров
Цикл 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()
print('Числа 11 826, 12 363, 14 676')
print()
nVar = solve()
print('Найдены все варианты решения', nVar)
print()
161
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)):
#if (not isNoRepDigit(num2, False)):
#if (not isNoRepDigit(num2, False) or num2 < 1000000000):
continue
result += 1
print('Вариант #', result)
s = 'num = ' + str(num)
s += ' num2 = ' + str(num2)
print(s)
print()
return result
Можно придумать много способов, как проверить, что в числе цифры не повторяются. Самый простой из них – конвертировать число в строку и проверять в ней символы. Но правильнее сделать так.
Создадим для цифр числа num список digs:
# ОПРЕДЕЛЯЕМ, ЧТО ВСЕ ЦИФРЫ
# В ЗАДАННОМ ЧИСЛЕ РАЗНЫЕ
def isNoRepDigit(num, noNull = True):
# список цифр:
digs = []
162
Поведение функции isNoRepDigit мы регулируем с помощью параметра
noNull. По умолчанию он равен True, то есть числа, содержащие нуль, будут
отвергаться.
Если же мы допускаем в числе присутствие нуля, то значение параметра
noNull должно быть False. Если заданное число равно нулю, то функция
isNoRepDigit возвращает True или False в зависимости от того, допускается
ли в заданном числе нуль:
if (num == 0 and not noNull):
return True
num = abs(num)
Если число отличается от нуля, то мы начинаем извлекать из него по одной
цифре, начиная с конца:
while (num > 0):
dig = num % 10
И вот здесь мы должны убедиться, что такая цифра ещё не встречалась, то
есть её нет в списке digs:
if dig in digs:
return False
if (noNull and dig == 0):
return False
digs.append(dig)
num //= 10
#print(digs)
return True
Если вы раскомментируете строчку
#print(digs)
то увидите в консольном окне распечатку списка digs для всех чисел с неповторяющимися цифрами (Рис. 5.14).
163
На этом подготовительные работы заканчиваются, и мы приступаем к решению задачи.
На Рис. 5.15 вы можете видеть часть списка пятизначных чисел, квадраты
которых состоят из разных цифр и не содержат нуля. Всего таких чисел
ровно 30.
Рис. 5.14. Разноциферные числа
Рис. 5.15. Весь пятизначный список
Что касается числа, состоящего из цифр 1, 2, 3, 4, 5, то оно в списке
занимает третье место. Это число 12543.
Если вы пожелаете найти разноциферные числа с нулём, то перенесите
комментарий на строчку выше:
164
# если цифры повторяются:
# if (not isNoRepDigit(num2)):
if (not isNoRepDigit(num2, False)):
Как и следовало ожидать, чисел с неповторяющимися цифрами стало заметно больше, но в некоторых из этих чисел отсутствует, например, цифра
8, так что ряд цифр получается разорванным (Рис. 5.16).
Рис. 5.16. Не все цифры присутствуют
Тут, пожалуй, лучше изменить условие проверки чисел так, чтобы были
напечатаны только 10-значные числа:
if (not isNoRepDigit(num2, False) or num2 < 1000000000):
На удивление их оказалось довольно много (Рис. 5.17).
165
Рис. 5.17. Самые длинные квадраты с неповторяющимися цифрами
166
Проект Числа 32 043 и 99 066
Задача 3.8 из книги Удивительный мир чисел [КА86], страница 44:
Числа 32 043 и 99 066. В запись квадрата каждого из них входят все 10
цифр, по одному разу каждая:
32 0432= 1 026 753 849
99 0662 = 9 814 072 356.
Предполагают, что первое - наименьшее, а второе - наибольшее из пятизначных чисел с таким свойством.
Подтвердить или опровергнуть это утверждение под силу только ЭВМ
или энтузиасту - любителю счёта.
Как верно отмечено в условии задачи, это нам под силу! Более того, мы
ненароком уже решили эту проблему в предыдущем проекте, и на Рис. 5.17
отчётливо видно, что указанные числа действительно наименьшее и
наибольшее из всех пятизначных чисел.
167
Проект Число 117 649
Исходный код программы находится в папке Кордемский 044
03-6.
Функция без параметров
Цикл for
Условный оператор if
Оператор continue
Функция round
Функция с параметром
Задача 3.6 из книги Удивительный мир чисел [КА86], страница 44:
Число 117 649 существует одновременно в трёх качествах. Оно:
квадратное
кубическое
кратное семи:
117 649 = 3432 = 493 = 7k, k ∈ N.
Более того, на отрезке от единицы до миллиона оно единственное с таким свойством.
Докажите!
Чтобы доказать это утверждение, нужно проверить все числа в заданном
диапазоне (но мы расширим диапазон, чтобы найти и другие числа).
# ГЛАВНАЯ ФУНКЦИЯ
def main():
print()
print('Число 117 649')
print()
solve()
print()
main()
# РЕШАЕМ ЗАДАЧУ
168
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 isQube(num)):
continue
Если очередное число num выдержало все проверки, то оно является решением задачи, и мы печатаем его на экране:
result += 1
print('Вариант #', result)
s = 'num = ' + str(num)
print(s)
s = 'Квадратный корень = ' + str(round(math.pow(num, 1/2.0)))
print(s)
s = 'Кубический корень = ' + str(round(math.pow(num, 1/3.0)))
print(s)
print()
return result
Проверку числа на квадратность и кубичность можно выполнить с помощью функций sqrt или pow из модуля math:
# -*- coding: Windows-1251 -*# Кордемский, с.43, Задача 3-6
169
import math
# ОПРЕДЕЛЯЕМ, ЯВЛЯЕТСЯ ЛИ ЗАДАННОЕ ЧИСЛО
#ПОЛНЫМ КВАДРАТОМ
def isQuadrat(num):
r = round(math.sqrt(num))
return r * r == num
# ОПРЕДЕЛЯЕМ, ЯВЛЯЕТСЯ ЛИ ЗАДАННОЕ ЧИСЛО
# ПОЛНЫМ КУБОМ
def isQube(num):
r = round(math.pow(num, 1/3.0))
return r * r * r == num
Рис. 5.18 показывает, что первое число с указанными в задаче свойствами, 117 649, а второе – 7 529 536 – значительно больше 1 миллиона. А всего
среди первого миллиарда чисел только 4 кратны 7 и являются полными
квадратами и кубами.
Если слегка подправить функцию solve, то можно найти ещё несколько любопытных чисел (Рис. 5.19):
#for num in range(minnum, maxnum+1, 7):
for num in range(1, 500000000):
Рис. 5.18. Семикратные корни
170
Рис. 5.19. Квадратно-кубические числа
Или в более наглядной форме:
33752 = 2253 = 11390625
49132 = 2893 = 24137560
И так далее.
171
Проект Красивые цепочки равенств
Исходный код программы находится в папке Кордемский 045
09.
Функция без параметров
Бесконечный цикл 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) = хх
Найдите подходящее значение х.
Так как в условии задачи участвуют очень большие степени, то можно предположить, что число х должно быть, наоборот, очень маленьким.
В бесконечном цикле while мы перебираем значения х, начиная с единицы
, и при этом проверяем выполнение условий задачи:
# ГЛАВНАЯ ФУНКЦИЯ
def main():
print()
print('Красивые цепочки равенств')
print()
solve()
print()
172
main()
# РЕШАЕМ ЗАДАЧУ
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('Число х равно', x)
print()
Для подсчёта суммы цифр чисел мы используем функцию summa:
173
# -*- coding: Windows-1251 -*# Кордемский, с.45, Задача 9
# НАХОДИМ СУММУ ЦИФР ЗАДАННОГО ЧИСЛА
def summa(num):
sum = 0
while (num > 0):
sum += num % 10
num //= 10
return sum
Как мы и предполагали, значение х совсем небольшое (Рис. 5.20).
Рис. 5.20. Нашли икс
174
Задания для самостоятельного решения
Задача-ребус
Удивительный мир чисел, страницы 69-70
Представим задачу в форме такого ребуса:
Эта задача практически ничем не отличается от той, что мы решили в
проекте Девять в квадрате.
Задача #33
Математическая шкатулка
Какую последнюю цифру может иметь квадрат натурального числа?
Куб его? Четвёртая степень?
Задача #34
Математическая шкатулка
Могут ли числа 458, 523, 652 быть квадратами или кубами целого
числа?
175
Глава 6. Числовые ряды и другие
задачи
В этой главе мы займёмся вычислением факториалов, рядов и решением
интересных задач с числами.
Проект Факториал
Исходный код программы находится в папке Факториал.
Бесконечный цикл while
Метод int
Функция с параметром
Условный оператор if
Оператор return
Цикл for
Произведение натуральных чисел от единицы до заданного (пусть это будет n) называется факториалом. Обозначается факториал восклицательным знаком после числа:
n! (читается: эн-факториал)
Чтобы его вычислить, нужно просто перемножить все числа от единицы до
этого числа, включительно:
n! = 1 х 2 х . . . х (n-1) х n
(1)
По определению, 0! = 1.
Формула очень простая, но нетрудно догадаться, что для больших значений
n факториал будет выражаться огромным числом.
Функция factorial действует по формуле (1) - с изменением порядка сомножителей:
176
# -*- coding: Windows-1251 -*# ПРОГРАММА ДЛЯ ВЫЧИСЛЕНИЯ ФАКТОРИАЛОВ
# ВЫЧИСЛЯЕМ ФАКТОРИАЛ ЗАДАННОГО ЧИСЛА
def factorial(num):
if (num == 0):
return 1
fact = num
for i in range(num-1, 1, -1):
fact *= i
return fact
Или без изменения порядка сомножителей:
def fact2(n):
f = 1
if (n == 0):
return 1
for i in range(2, n + 1):
f *= i
return f
В функции main пользователь вводит число, после чего вызывается функция factorial, которая возвращает вычисленное значение факториала:
# ГЛАВНАЯ ФУНКЦИЯ
def main():
print()
print('Факториал')
print()
# бесконечный цикл ввода данных # пока пользователь не закроет программу
# или не введёт отрицательное число:
while True:
s = "Введите число > "
print(s, end='')
num = int(input())
# если пользователь ввёл отрицательное число,
# то программу закрываем:
if (num < 0):
177
return
# находим факториал заданного числа:
fact = factorial(num)
# печатаем факториал:
print(num, '! =', fact)
print()
main()
Умножать на единицу, конечно, смысла нет.
Здесь мы учитываем, что факториал нуля равен единице, это особый случай
и его нужно учесть отдельно (Рис. 6.1)!
Рис. 6.1. Маленькие и большие факториалы
В Питоне имеется и встроенная функция math.factorial для вычисления
факториалов (Рис. 6.2):
178
Рис. 6.2. Встроенные факториалы
179
Проект Факториальные нули
Исходный код программы находится в папке Нагибин 840.
Функция без параметров
Оператор break
Бесконечный цикл while
Задача 840 из книги Математическая
шкатулка [Нагибин88], страница 128:
Сколько нулей в конце записи числа, выражающего произведение
1 ⦁ 2 ⦁ 3 ⦁ 4 ⦁ 5 ⦁ 6 ⦁ . . . ⦁14 ⦁ 15?
Произведение чисел 1..15 равно факториалу числа 15. Факториалы мы вычислять умеем, поэтому достаточно пересчитать нули в конце записи
числа 15!
В данном случае можно просто напечатать значение факториала на экране
и посмотреть, сколькими нулями заканчивается запись. Но вполне вероятно, что вам встретится и другая задача на подсчёт хвостовых нулей, поэтому мы добросовестно пересчитаем их в функции solve:
# РЕШАЕМ ЗАДАЧУ
def solve():
NUM = 15
# находим факториал заданного числа:
fact = factorial(NUM)
# печатаем факториал:
print(str(NUM) + "! = " + str(fact))
# считаем нули:
n = 0
while True:
# очередная цифра сзади:
dig = fact % 10
# не нуль!
if dig:
break
# переходим к следующей цифре:
fact //= 10
180
n += 1
print("Число нулей в заданном числе = ", str(n))
print()
Последнюю цифру легко отделить от числа с помощью оператора %. Чтобы
предпоследняя цифра числа обратилась в последнюю, нужно разделить его
на 10. Как только последняя цифра будет отличаться от нуля, подсчёт
нужно закончить.
# ГЛАВНАЯ ФУНКЦИЯ
def main():
print('Факториальные нули')
print()
solve()
print()
main()
На Рис. 6.3 очень хорошо видно, что факториал числа 15 заканчивается
тремя нулями.
Рис. 6.3. Нулевая считалка
181
Проект Числа Фибоначчи
Исходный код программы находится в папке Числа Фибоначчи.
Бесконечный цикл while
Метод int
Списки
Условный оператор if
Цикл for
Оператор or
Оператор return
Числа Фибоначчи, как нетрудно
догадаться, открыл Фибоначчи (он
же
Леонардо
Пизанский),
средневековый математик (Рис.
6.4), автор Книги абака (Liber
Abaci), которую он написал в 1202 году
.
Рис. 6.4. Леонардо Пизанский (Leonardo Pisano, 1170 - 1250), по прозвищу Фибоначчи (Fibonacci)
Впрочем, дотошные историки утвеждают, что этот ряд чисел был известен в
Индии задолго до Фибоначчи, где он использовался при стихосложении.
182
В трактате Книга абака Фибоначчи рассмотрел математическую модель,
связанную с кроликами. Он взял пару взрослых кроликов (точнее, кролика и
крольчиху) и предположил, что они могут производить на свет потомство
каждый месяц. Причём у них всегда рождается пара крольчат разного пола, у
которых через два месяца также рождаются крольчата. Фибоначчи решил
подсчитать, сколько будет кроликов через год, если за это время ни один
кролик не умрёт. Числа Фибоначчи как раз и отражают рост популяции
кроликов.
В книгах по программированию принято находить числа Фибоначчи с
помощью красивого рекурсивного алгоритма, который основан на
рекуррентном определении самих чисел:
Первое число Фибоначчи = 0
Второе число = 1
Все последующие равны сумме двух предыдущих, то есть:
Третье число = 0 + 1 = 1
Четвёртое = 1 + 1= 2
Пятое = 1 + 2 = 3
и так далее, до бесконечности.
Рекурсивные алгоритмы обычно довольно короткие, но не всегда быстрые.
Поэтому для ускорения вычислений мы воспользуемся методом
динамического программирования, то есть просто запомним уже
найденные числа Фибоначчи в списке. Этим мы убьём сразу двух зайцев
(или кроликов) – и заданное число получим, и все предыдущие числа
сохраним в массиве!
В функции main пользователь вводит любое неотрицательное число, после
чего функция fibo находит числа Фибоначчи от нулевого до заданного.:
# ГЛАВНАЯ ФУНКЦИЯ
def main():
print('Числа Фибоначчи')
print()
# бесконечный цикл ввода данных # пока пользователь не закроет программу
# или не введёт отрицательное число:
while True:
183
s = 'Введите число > '
print(s, end = '')
num = int(input())
# если пользователь ввёл отрицательное число,
# то программу закрываем:
if (num < 0):
return
# находим все числа Фибоначчи от 0 до num:
fib = fibo(num)
# и печатаем их:
for i in range(0, num+1):
print('Число Фибоначчи', str(i), '=', fib[i])
print()
main()
Чтобы сохранить все промежуточные числа, мы создадим список чисел f и
сразу же поместим в него три первых числа Фибоначчи, чтобы можно было
найти следующие. Далее мы передаём функции fibo номер числа
Фибоначчи, которое желает узнать пользователь. Сам алгоритм следует
определению чисел Фибоначчи:
# -*- coding: Windows-1251 -*# ПРОГРАММА ДЛЯ ВЫЧИСЛЕНИЯ ЧИСЕЛ ФИБОНАЧЧИ
# ВЫЧИСЛЯЕМ ЗАДАННОЕ ЧИСЛО ФИБОНАЧЧИ
def fibo(n):
# помещаем в список первые числа Фибоначчи:
f = [0,1,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. Последнее из найденных чисел
соответствует заданному пользователем номеру числа Фибоначчи. Из этого
следует, что если пользователю нужно только оно, то можно возвращать
только последнее из найденных чисел.
184
Наша уловка со списком позволяет практически мгновенно находить
совершенно невероятные числа Фибоначчи (Рис. 6.5)!
Рис. 6.5. Вычисляем мгновенно!
185
Проект Числа Фибоначчи 2
Исходный код программы находится в папке Числа Фибоначчи 2.
Бесконечный цикл while
Метод int
Функция с параметром
Цикл for
Оператор return
Легко заметить, что из всего списка чисел
Фибоначчи для вычисления следующего
числа нам нужны только два последних
элемента. И если все числа Фибоначчи от первого до заданного сохранять
не нужно, то можно обойтись и вообще без списка. И при этом мы не
потеряем скорость вычислений! Вот как это делается.
«Клонируйте» предыдущий проект и исправьте функцию main:
# -*- coding: Windows-1251 -*#ПРОГРАММА ДЛЯ ВЫЧИСЛЕНИЯ ЧИСЕЛ ФИБОНАЧЧИ
# ГЛАВНАЯ ФУНКЦИЯ
def main():
print('Числа Фибоначчи')
print()
# бесконечный цикл ввода данных # пока пользователь не закроет программу
# или не введёт отрицательное число:
while True:
s = "Введите число > "
print(s, end = '')
num = int(input())
# если пользователь ввёл отрицательное число,
# то программу закрываем:
if (num < 0):
return
# находим число Фибоначчи:
f = fibo2(num)
# и печатаем его:
print('Число Фибоначчи', num, '=', f)
print()
186
main()
Кода стало меньше, но теперь мы получим из функции fibo2 только одно
число. Впрочем, вы можете вызывать эту функцию в цикле и получить все
числа Фибоначчи от нулевого до заданного. Конечно, придётся выполнить
лишнюю работу, но скорость программы такова, что вы ничего не заметите.
Функция fibo2 для нахождения чисел Фибоначчи тоже упростилась по
стравнению с первой версией:
# НАХОДИМ ЧИСЛО ФИБОНАЧЧИ БЕЗ СПИСКА
def fibo2(n):
f1, f2 = 0, 1
f = 1
for i in range(1, n + 1):
f = f1 + f2
f2 = f1
f1 = f
return f
Здесь нам понадобились всего три переменные f1, f2 и f.
Первые два числа Фибоначчи мы задаём сразу, а все последующие вычисляем как сумму двух предыдущих. То есть третье (если нулевое число
Фибоначчи считать первым (нередко последовательность чисел Фибоначчи начинается с единицы, а не с нуля, поэтому и возникают подобные «разночтения») число 0 + 1 = 1, четвёртое 1 + 1= 2, пятое 1 + 2 = 3 и так далее:
f = f1 + f2
f2 = f1
f1 = f
Проверка подтверждает наши ожидания – новый алгоритм считает быстро
и аккуратно (Рис. 6.6).
187
Рис. 6.6. Тоже неплохо!
188
Проект «Избранные» числа
Исходный код программы находится в папке Кордемский 065
12.
Функция без параметров
Цикл for
Условный оператор if
Оператор continue
Оператор целочисленного деления %
Функция с параметром
Цикл while
Задача 12 из книги Удивительный мир чисел [КА86], страница 65:
Есть 80 четырёхзначных и 800 пятизначных чисел, не оканчивающихся
нулём, и таких, что если от любого из них вычтем 999 в случае четырёхзначного числа и 9999 в случае пятизначного числа, то всякий раз получим обращённое число, т. е. записанное теми же цифрами, но в обратном
порядке. Какие это числа?
Найдите способ быстрого вычисления суммы этих чисел (без привлечения компьютера).
Мы проигнорируем замечание в скобках и решим сначала задачу для 4значных чисел:
# ГЛАВНАЯ ФУНКЦИЯ
def main():
print('Избранные числа')
print()
nVar = solve()
print('Найдены все варианты решения -', nVar)
print()
main()
Так как условие задачи запрещает числам оканчиваться нулём, то минимальное 4-значное число равно 1001, а максимальное – 9999:
189
# РЕШАЕМ ЗАДАЧУ
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 != reverse_num(num)):
continue
Если они совпадают, то найдено ещё одно решение задачи:
# чмсло найдено:
result += 1
print('Вариант #', result)
s = 'num = ' + str(num)
s += ' > ' + str(num2)
print(s)
print()
print()
return result
Для обращения чисел мы используем функцию reverse_num:
190
# ПЕРЕВОРАЧИВАЕМ ЗАДАННОЕ ЧИСЛО
def reverse_num(num):
rev = 0
while (num > 0):
rev = rev*10 + num % 10
num //= 10
return rev
Как и указано в книге Удивительный мир чисел, всего существует 80 таких
чисел (Рис. 6.7).
Рис. 6.7. Четырёхзначные числа-перевёртыши
Для поиска 5-значных чисел необходимо изменить значения локальных
переменных в функции solve:
#min = 1001
#max = 9999
#minus = 999
min = 10001
max = 99999
minus = 9999
191
5-значных чисел ровно в 10 раз больше, чем 4-значных, что и сообщает
нам Рис. 6.8.
Рис. 6.8. Пятизначные числа-перевёртыши
Поскольку мы решаем задачу на компьютере, то было бы странно, если бы
мы принялись сумму найденных чисел вычислять вручную! Поэтому объявляем переменную для подсчёта суммы:
# сумма чисел:
sum = 0
И каждое найденное число добавляем к общей сумме:
result += 1
sum += num
В конце метода solve печатаем эту сумму:
print('Сумма всех чисел =', sum)
print()
return result
Вот решение задачи для 4- и 5-значных чисел (Рис. 6.9).
192
Рис. 6.9. Числовой сумматор
193
Проект Безошибочный прогноз
Исходный код программы находится в папке Кордемский 067
16.
Класс random
Функция с параметром
Условный оператор if
Оператор return
Операторы or и and
Списки
Цикл for
Функция с параметром-массивом
Цикл while
Функция abs
Задача 16 (17) из книги Удивительный мир чисел [КА86], страница 67:
Расположите по кругу 4 произвольных натуральных числа а1, а2, а3, а4. Замените их абсолютными значениями разностей (Рис. 6.10):
|а1 — а2|, |а2 — а3|, |а3 — а4|, |а4 — а1|.
Рис. 6.10. Иллюстрация к задаче
С получившимися разностями поступите так же, как с исходными числами. Повторите процедуру вычисления разностей несколько раз, и на
некотором шаге все разности одновременно станут нулями.
Можете начать вычисления не с 4, а с 8 или с 16, вообще с 2k (k = 2, 3, ...)
чисел - финал будет таким же.
Докажите хотя бы для 4 исходных чисел, что прогноз безошибочен.
194
Дополнительную информацию об этой задаче смотрите на странице 6 этой
книги.
Задача очень интересная, и мы за неё возьмёмся!
Поскольку нам потребуются «произвольные», то есть случайные числа, то
без генератора таких чисел нам не обойтись:
# -*- coding: Windows-1251 -*# Кордемский, с.67, Задача 16
import random
В функции main мы передаём функции solve то количество чисел, которые
хотим испытать:
# ГЛАВНАЯ ФУНКЦИЯ
def main():
print('Безошибочный прогноз')
print()
solve(4)
print()
По условию задачи, это число не меньше 4 и должно быть степенью
двойки, поэтому, прежде всего, следует проверить значение параметра n:
# РЕШАЕМ ЗАДАЧУ
def solve(n):
# число n должно быть степенью двойки:
if (n < 4 or n & (n - 1) != 0):
return
Если условие задачи нарушено, то следующую часть кода выполнять
нельзя, и мы возвращаемся в главную функцию программы.
Если все числа изначально равны нулю, то условие задачи будет выполнено, но, скорее всего, нам потребуется сделать для этого несколько шагов,
которые мы, ради интереса, подсчитываем в переменной step:
195
# номер шага:
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('Шаг %i:' % step)
# печатаем список:
print(*a[1:], sep=' ', end='')
print()
В задаче утверждается, что через некоторое число шагов все элементы
списка одновременно станут нулями. На самом деле это не так: некоторые
элементы массива могут обратиться в нули раньше других, поэтому мы будем контролировать процесс по сумме всех элементов в массиве. Как только
сумма обратится в нуль, цикл while будет закончен:
# сумма элементов списка:
sum_el = -1
# заменяем элементы списка
# абсолютными значениями разностей:
196
while (sum_el != 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('Шаг %i:' %step)
print(*a[1:], sep=' ', end='')
print()
# находим сумму элементов списка:
sum_el = sum(a)
Важно учесть, что последнюю разность
a4 – a1
нельзя вычислить в цикле for. Действительно, значение первого
элемента списка будет заменено абсолютным значением разности
a1 = a1 – a 2
и последняя разность будет вычислена неверно!
Поэтому мы перед началом цикла изменения значений элементов списка
запоминаем во вспомогательной переменной a1 текущее значение первого
элемента. Его мы и используем для замены значения последнего элемента
списка.
Результаты проверки «гипотезы» для четырёх чисел показаны на Рис. 6.11.
Рис. 6.11. Прошагали
197
Как вы видите, для обнуления элементов списка может потребоваться разное число шагов – в зависимости от начальных значений элементов массива.
С числами 8, 16 и далее поэкспериментируйте самостоятельно!
198
Проект Ошибочный прогноз
Исходный код программы находится в папке Кордемский 067
16-2.
Класс random
Функция без параметров
Списки
Цикл for
Цикл while
Функция abs
Операторы and и or
Пример из книги Удивительный мир
чисел [КА86], страница 6:
Иной результат наблюдается для серии разностей в случае комплекта из
трёх произвольных натуральных чисел: в финале всегда получаются две
единицы и нуль в том или ином чередовании.
Пример. Пусть исходная тройка чисел R0 = (7, 12, 1). Тогда последовательность разностей будет:
R1 = (5, 11, 6)
R2 = (6, 5, 1)
R3 = (1, 4, 5)
R4 = (3, 1, 4)
R5 = (2, 3, 1)
R6 = (1, 2, 1)
R7 = (1, 1, 0)
Поскольку один пример ничего не доказывает, то мы напишем программу
и подвергнем серьёзному сомнению и проверке это утверждение.
# ГЛАВНАЯ ФУНКЦИЯ
def main():
print('Безошибочный прогноз')
print()
solve()
print()
199
main()
Небольшие изменения в функции solve – и можно приниматься за научноисследовательские работы!
# -*- coding: Windows-1251 -*# Кордемский, с.6, Пример
import random
# РЕШАЕМ ЗАДАЧУ
def solve():
# тройка чисел:
n = 3
# номер шага:
step = 0
# список чисел:
a = [0]
# заполняем список случайными числами:
max = 20
for i in range(1, n+1):
a.append(random.randint(0,max))
#a += [7,12,1]
print('Шаг %i:' % step)
# печатаем список:
print(*a[1:], sep=' ', end='')
print()
flg = False
# заменяем элементы списка
# абсолютными значениями разностей:
while (not flg):
a1 = a[1]
for i in range(1, n):
a[i] = abs(a[i] - a[i+1])
a[n] = abs(a[n] - a1)
step += 1
print('Шаг %i:' %step)
print(*a[1:], sep=' ', end='')
print()
if (a[1] * a[2] * a[3] == 0 and
(a[1] - a[2] - a[3] == 0 or
a[2] == a[3])):
flg = True
200
Раскомментируйте строчку с условиями примера, закомментируйте заполнение списка случайными значениями и запустите программу. Рис. 6.12
подтверждает, что на 7 шаге одно число превратится в нуль, а остальные
два – в единицу.
Рис. 6.12. Пока всё верно
Теперь закомментируйте раскомментированное и пуститесь в свободное
плавание по числовому океану!
Сначала всё было тихо и штильно (Рис. 6.13, слева), но вскоре заштормило
(Рис. 6.13, в середине). И не на шутку (Рис. 6.13, справа и ещё правее)!
Рис. 6.13. А дальше скверно
201
Как вы видите, элементы списка а всегда превращаются в элегантные
числа: 1 нуль и 2 «ненуля». Причём ненули это не всегда пара единиц!
Вывод: Не говори гоп, пока не проверишь решение на компьютере!
202
Проект Нумерация страниц
Исходный код программы находится в папке Кордемский 064
06.
Функция без параметров
Цикл for
Условный оператор if-elif-else
Задача 6 из книги Удивительный мир чисел
[КА86], страница 64:
Я спросил наборщика типографии:
- Сколько отдельных литер с цифрами потребуется для нумерации 1000
страниц книги?
- Легко вычислить,- ответил наборщик. - Вот моё решение:
3000 - 18 – 90 +1= 2893.
Я решил иначе, но получил такой же результат.
Придумайте свой план решения и найдите объяснение способу,
предложенному наборщиком.
Число литер легко найти так:
для записи чисел 1..9 нужно по 1 литере
для записи чисел 10..99 нужно по 2 литеры
для записи чисел 100..999 нужно по 3 литеры
для записи чисел 1000..9999 нужно по 4 литеры
В функции solve мы «пролистываем» книгу от 1 до 1000 страницы и считаем литеры, как описано выше:
# -*- coding: Windows-1251 -*# Кордемский, с.64, Задача 6
# РЕШАЕМ ЗАДАЧУ
203
def solve():
# число литер:
sum = 0
for i in range(1, 1000+1):
if (i < 10):
sum += 1
elif (i < 100):
sum += 2
elif (i < 1000):
sum += 3
else:
sum += 4
s = 'Потребуется литер: ' + str(sum)
print(s)
print()
# ГЛАВНАЯ ФУНКЦИЯ
def main():
print()
print('Нумерация страниц')
print()
solve()
print()
main()
Все правы: потребуется 2893 литеры (Рис. 6.14).
Рис. 6.14. Литерная задача
204
Проект Сколько страниц в книге?
Исходный код программы находится в папке Кордемский 064
05.
Функция без параметров
Бесконечный цикл for
Вложенные условные операторы if-else
Оператор break
Задача 5 из книги Удивительный мир чисел
[КА86], страница 64:
Для нумерации страниц книги наборщик типографии использовал 2529
литер с цифрами. На каждой литере одна цифра.
Сколько страниц в этой книге?
Задача очень похожа на предыдущую, поэтому нам нужно только подправить её код:
# -*- coding: Windows-1251 -*# Кордемский, с.64, Задача 5
# РЕШАЕМ ЗАДАЧУ
def solve():
# число использованных литер:
SUMMA = 2529
# число литер:
sum = 0
# число страниц:
i = 1
Мы знаем, что первая страница обозначена числом 1, вторая – числом 2, и
так далее. Если мы будем последовательно находить сумму литер, затраченных на нумерацию страниц, то рано или поздно она сравняется с заданной – SUMMA (а если задача составлена неверно, то это событие может и не
произойти!). На этом бесконечный цикл while заканчивается, и мы
печатаем ответ на задачу:
205
# перелистываем страницы и считаем литеры:
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()
# ГЛАВНАЯ ФУНКЦИЯ
def main():
print()
print('Сколько страниц в книге?')
print()
solve()
print()
main()
В книге оказалось 879 страниц (Рис. 6.15), а задачу автор составил верно!
Рис. 6.15. Посчитали все страницы
206
Проект И такие есть числа
Исходный код программы находится в папке Кордемский 063
04-1.
Функция без параметров
Цикл for
Условный оператор if
Оператор continue
Оператор return
Задача 4-1 из книги Удивительный мир
чисел [КА86], страница 63:
Какое двузначное число в 19 раз
больше числа его единиц?
Чтобы решить задачу, достаточно перебрать все двузначные числа и проверить условие задачи:
# -*- coding: Windows-1251 -*# Кордемский, с.63, Задача 4-1
# РЕШАЕМ ЗАДАЧУ
def solve():
result = 0
for n in range(10, 99+1):
if (n != n % 10*19):
continue
result += 1
print('Вариант #', result)
s = 'Число = ' + str(n)
print(s)
print()
return result
# ГЛАВНАЯ ФУНКЦИЯ
def main():
207
print()
print('И такие есть числа')
print()
nVar = solve()
print('Найдены все варианты решения -', nVar)
print()
main()
Как вы видите на Рис. 6.16, задача имеет единственное решение: искомое
число равно 95.
Рис. 6.16. Число найдено
208
Проект Трёхзначное число
Исходный код программы находится в папке Кордемский 063
03.
Функция без параметров
Цикл for
Условный оператор if
Оператор continue
Оператор целочисленного деления %
Оператор деления //
Задача 3 из книги Удивительный мир чисел [КА86], страница 63:
Найдите трёхзначное число, обладающее следующими свойствами:
число десятков на 4 меньше числа единиц, но на 4 больше числа сотен;
если цифры этого числа разместить в обратном порядке, то новое полученное число будет на 792 больше искомого.
Чтобы найти трёхзначное число с требуемыми свойствами, необходимо перебрать все трёхзначные числа и выбрать те из них, которые удовлетворяют условиям задачи.
# -*- coding: Windows-1251 -*# Кордемский, с.63, Задача 3
# РЕШАЕМ ЗАДАЧУ
def solve():
# трёхзначные числа:
MIN3 = 100
MAX3 = 999
# перебираем трёхзначные числа:
for n in range(MIN3, MAX3+1):
Прежде всего, нужно найти, сколько единиц, десятков и сотен содержит текущее число. Это нетрудно сделать с помощью двух операций – целочисленного деления и деления с остатком:
209
# перебираем трёхзначные числа:
for n in range(MIN3, MAX3+1):
# число единиц:
e = n % 10
# число десятков:
d = n // 10 % 10
# проверяем условие:
if (d != e - 4):
continue
# число сотен:
s = n // 100 % 10
if (d != s + 4):
continue
Выполнить проверки и того проще. И вот мы уже получаем ответ на эту
задачу (Рис. 6.17):
s = 'Число = ' + str(n)
print(s)
print()
# ГЛАВНАЯ ФУНКЦИЯ
def main():
print()
print('Трёхзначное число')
print()
solve()
print()
main()
Рис. 6.17. Нашли трёхзначное число
Поскольку мы перебрали все трёхзначные числа и нашли только одно, которое удовлетворяет условиям задачи, то последняя проверка на разность
с перевёрнутым числом оказалась бы лишней!
210
Проект Таких чисел только два
Исходный код программы находится в папке Кордемский 063
01.
Функция без параметров
Цикл for
Оператор целочисленного деления %
Оператор деления //
Условный оператор if
Оператор continue
Задача 1 из книги Удивительный мир чисел [КА86], страница 63:
Есть только два двузначных числа, каждое из которых равно неполному
квадрату разности своих цифр.
Найдите эти числа.
Чтобы облегчить решение, подскажем, что одно число на 11 больше другого.
Так как двузначных чисел очень мало, то мы и без подсказки найдём оба
искомых числа!
Выделяем из каждого числа его единицы и десятки и сравниваем само
число с неполным квадратом разности его цифр:
# -*- coding: Windows-1251 -*# Кордемский, с.63, Задача 1
# РЕШАЕМ ЗАДАЧУ
def solve():
# двузначные числа:
MIN2 = 10
MAX2 = 99
for n
#
e
#
d
in range(MIN2, MAX2 + 1):
число единиц:
= n % 10
число десятков:
= n // 10 % 10
211
# проверяем условие:
if (e * e + d * d - e * d != n):
continue
s = 'Число = ' + str(n)
print(s)
print()
# ГЛАВНАЯ ФУНКЦИЯ
def main():
print()
print('Таких чисел только два')
print()
solve()
print()
main()
Задача решена (Рис. 6.18)!
Рис. 6.18. Искомая парочка
212
Проект Ещё два числа
Исходный код программы находится в папке Кордемский 063
02.
Функция без параметров
Условный оператор if
Цикл for
Оператор continue
Оператор деления //
Оператор целочисленного деления %
Задача 2 из книги Удивительный мир чисел
[КА86], страница 63:
Сходным свойством обладают ещё два двузначных числа: каждое равно
неполному квадрату суммы своих цифр.
Найдите эти числа, зная, что одно число на 50 больше другого.
Эта задача решается точно так же, как предыдущая, только в функции solve
нужно исправить одну строчку:
# -*- coding: Windows-1251 -*# Кордемский, с.63, Задача 2
# РЕШАЕМ ЗАДАЧУ
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)
213
print(s)
print()
# ГЛАВНАЯ ФУЕКЦИЯ
def main():
print()
print('Ещё два числа')
print()
solve()
print()
main()
Правда, к нашему удивлению чисел оказалось не два, а три (Рис. 6.19)!
Рис. 6.19. А таких чисел три!
Да, с полным перебором состязаться невозможно!
214
Проект Отгадать число, ничего не спрашивая
Исходный код программы находится в папке Кордемский 036
04.
Функция без параметров
Вложенные циклы for
Условный оператор if
Оператор continue
Оператор деления //
Оператор целочисленного деления %
Ради счастья, ради нашего,
Если хочешь ты его,
Ни о чем меня не спрашивай,
Не расспрашивай, не выспрашивай,
Не выведывай ничего.
Песня из комедии Свадьба в Малиновке
Задача 4 из книги Удивительный мир чисел [КА86], страница 36:
Пусть кто-нибудь задумает двузначное число в виде 10x + y, где х —
цифра десятков, у - цифра единиц, х – у ≥ 2, у ≠ 0. Потребуйте теперь,
чтобы он переставил цифры в обратном порядке и вычел меньшее число
из большего. Полученную разность пусть сложит с нею же, но написанной
в обратном порядке следования цифр. Ничего не спрашивая у загадавшего, вы сообщаете, что у него получилось 99.
Пример. Пусть задумано 75. Загадавший должен выполнить следующие
действия: 75 - 57=18, 18 + 81=99.
В общем виде: пусть задумано 10x + y, где х – у ≥ 2. При обратном расположении цифр число имеет вид 10у + х.
Абсолютная величина разности:
R =|10x + y — (10у + х)| = 9⦁|х – у|.
Так как х – у ≥ 2, то R = 9⦁(х – у) = 10⦁(х – у) - (х – у) + 10 - 10.
То есть разность можно представить в виде:
R = 10(x – y - 1) + (10 - (x - y)).
(1)
Запишем число с обратным расположением цифр:
10(y – x + 10) + (x – y - 1) (2)
215
Сложив (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.
# -*- 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
Получив пару верных цифр, мы составляем из них число, переворачиваем
его, находим абсолютную разность прямого и обратного чисел, а затем сумму полученной разности и её «зеркального отражения»:
216
# прямое число:
num = 10*x + y
# обратное число:
numr = 10*y + x
# разность чисел:
r = abs(num - numr)
# число единиц в разности:
e = r % 10
# число десятков в разности:
d = r // 10
# сумма:
sum = r + e*10 + d
Для контроля печатаем каждое двузначное число, удовлетворяющее условиям задачи, а также результат наших действий над ним:
print('Исходное число равно: ', num)
print('Полученное число равно:', sum)
print()
print()
# ГЛАВНАЯ ФУНКЦИЯ
def main():
print()
print('Отгадать число, ничего не спрашивая')
print()
solve()
print()
main()
Часть списка представлена на Рис. 6.20. Из него видно, что все двузначные
числа обращаются в 99.
Трёх- и четырёхзначные числа проверьте самостоятельно!
217
Рис. 6.20. Удачный фокус
218
Проект Три лягушки
Исходный код программы находится в папке Кордемский 051
06.
Функция без параметров
Списки
Бесконечный цикл for
Вложенные циклы for
Условный оператор if
Оператор break
Оператор continue
Комбинированные операторы присваивания
Задача 6 из книги Удивительный мир чисел [КА86], страница 51:
Три лягушки находятся на дне колодца глубиной 60 м. За день они поднимаются на 18 м каждая, а потом спускаются первая на 12 м, вторая на
16 м, третья на 17 м и остаются на своих местах до следующего дня. На
следующий день каждая лягушка проделывает снова такой же маршрут
и т. д.
Через сколько дней лягушки выйдут из колодца?
Для начала объявим необходимые переменные и списки:
# -*- coding: Windows-1251 -*# Кордемский, с.51, Задача 6
# РЕШАЕМ ЗАДАЧУ
def solve():
# глубина колодца:
GLUBINA = 60
# высота подъёма:
UP = 18
# глубина опускания:
DOWN = [12, 16, 17]
# текущее расстояние до поверхности:
frogs = [GLUBINA, GLUBINA, GLUBINA]
# освободившиеся лягушки:
free = [False, False, False]
219
Приключения начинаются! По значению элементов списка free мы определяем, все ли лягушки-квакушки выбрались из колодца. Если это радостное
событие состоялось, значит, все элементы в списке обратились в True:
n = 1
while True:
# все лягушки свободны:
if not False in free:
print()
print('Все лягушки свободны!')
print()
break
На этом решение задачи заканчивается, и мы с чистой совестью отпускаем
их на свободу, а бесконечный цикл while прерываем оператором break.
Каждый день лягушки поднимаются на UP метров:
# лягушки поднялись вверх:
for i in range(len(frogs)):
# эта лягушка уже свободна:
if (free[i]):
continue
frogs[i] -= UP
Если текущее значение лягушки в списке frogs не больше нуля, значит, лягушка выкарабкалась из колодца:
# лягушка выбралась из колодца?
free[i] = frogs[i] <= 0
if (free[i]):
print('Лягушка %i свободна! День: %i' %(i+1, n))
Если лягушкам выбраться не удалось, то они опускаются вниз – каждая на
свой «размер»:
# лягушки опускаются:
for i in range(len(frogs)):
# эта лягушка уже свободна:
if (free[i]):
continue
frogs[i] += DOWN[i]
220
n += 1
print()
# ГЛАВНАЯ ФУНКЦИЯ
def main():
print()
print('Три лягушки')
print()
solve()
print()
main()
Запускаем программу и освобождаем лягушек (Рис. 6.21)!
Рис. 6.21. Свободу французским лягушкам!
Ура-ква-ква! Квак преквасен этот мир!
221
Проект Гаусс
Исходный код программы находится в папке Нагибин 005.
Функция с параметрами
Цикл for
Задача 5 из книги Математическая шкатулка [Нагибин88], страница 15:
Рассказывают, что в начальной школе, где учился
мальчик Карл Гаусс, ставший потом знаменитым математиком, учитель, чтобы занять класс на продолжительное время самостоятельной работой, дал детям такое задание - вычислить сумму всех
натуральных чисел от 1 до 100. Но маленький Гаусс это задание моментально выполнил.
Попробуй и ты быстро выполнить это задание.
Нетрудно в этом ряде чисел увидеть арифметическую прогрессию, начинающуюся с единицы и насчитывающую
100 членов. Разность
арифметиче- ской прогрессии равна 1. Зная все параметры этого ряда, мы
быстро найдём его сумму:
Сумма = (1 + 100)*100/2 = 5050
# ГЛАВНАЯ ФУНКЦИЯ
def main():
print()
print('Гаусс')
print()
solve(1,100,1)
print()
main()
222
Учитель же, конечно предполагал, что школяры будут вычислять сумму
ряда последовательным прибавлением очередного члена ряда к текущей
сумме.
Давайте в функции 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
При этом не забываем вычислять следующий член последовательности,
прибавляя к текущему разность арифметической прогрессии:
n += r
print('Сумма ряда равна %i' % sum)
print()
223
В нашей задаче:
start = 1
num = 100
r=1
Наша глуповатая, но прилежная функция правильно подсчитала сумму заданного ряда (Рис. 6.22).
Рис. 6.22. Методический сумматор
Зато наша функция получилась универсальной! Подставляя нужные значения в функцию solve, вы быстро найдёте сумму любой арифметической прогрессии. Например, давайте решим такую задачу.
Задача 7.1 из книги Математическая шкатулка [Нагибин88], страница
15:
Как быстро вычислить сумму 1 + 3 + 5 + 7 + 9 + ... + 997 + 999?
Добавьте в функцию main выделенную строку:
solve(1,100,1)
solve(1,999//2+1,2)
И получите ответ на задачу (Рис. 6.23).
Рис. 6.23. Гауссиана продолжается!
224
Здесь главное – не ошибиться со значениями параметров функции solve!
start = 1
num = 999//2+1
r=2
При вычислении num следует добавить 1, так как при целочисленном деле
- нии результат будет округлён отбрасыванием целой части, поэтому
число членов окажется на 1 меньше действительного.
Задача 838 из книги Математическая шкатулка [Нагибин88], страница
128:
Вычислите: 5 + 10 + 15 + 20 + 25 + ... + 100.
И опять одна строка программы решает задачу (Рис. 6.24):
solve(1,100,1)
solve(1,999//2+1,2)
solve(5,100//5,5)
Рис. 6.24. Арифметике нас учить не надо!
В этой задаче:
start = 5
num = 100/5
r=5
225
Проект Плюс-минус
Исходный код программы находится в папке Нагибин 007-2.
Функция без параметров
Цикл for
Комбинированные операторы присваивания
Задача 7-2 из книги Математическая шкатулка [Нагибин88], страница 15:
Как быстро вычислить:
99 - 97 + 95 - 93 + 91 - 89 + ... + 7 - 5 + 3 – 1?
Конечно, быстро вычислить можно на компьютере!
По сравнению с обычной арифметической прогрессией (здесь она убывающая) знак членов ряда чередуется: плюс-минус-плюс-минус…
Но достаточно ввести переменную sign, чтобы решить и эту задачу (Рис.
6.25):
# -*- 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(num):
sum += n*sign
n += r
226
# меняем знак:
sign *= -1
print('Сумма ряда равна %i' % sum)
print()
# ГЛАВНАЯ ФУНКЦИЯ
def main():
print()
print('Плюс-минус')
print()
solve()
print()
main()
Рис. 6.25. Плюсы и минусы – это наши плюсы!
К сожалению, в издании 1988 года отсутствует и ответ, и решение этой задачи, а вот в более раннем издании 1958 года приведено решение – и
весьма остроумное!
227
Проект Минус-плюс
Исходный код программы находится в папке Нагибин 482-8..
Функция без параметров
Цикл for
Задача 482-8 из книги Математическая шкатулка [Нагибин88], страница 88:
Найдите простой приём вычисления:
1002 – 992 + 982 – 972 + 962 – 952 + ... + 42 – 32 + 22 – 12
Здесь мы видим практически тот же ряд, что и в предыдущей задаче, только
ещё проще!
Задача решается без труда, но довольно интересно, что ответ на неё полностью совпадает с тем ответом, который мы получили, решая задачу Гаусса
(Рис. 6.26).
# -*- coding: Windows-1251 -*# Нагибин, с.88, Задача 482-8
# РЕШАЕМ ЗАДАЧУ
def solve():
# сумма ряда:
sum = 0
# знак алгебраической суммы:
sign = 1
for i in range(100, 1-1, -1):
sum += i*i*sign
# меняем знак:
sign *= -1
print('Сумма ряда равна %i' % sum)
print()
228
# ГЛАВНАЯ ФУНКЦИЯ
def main():
print()
print('Минус-плюс')
print()
solve()
print()
main()
Рис. 6.26. Гаусс вечен!
229
Проект Дробный ряд
Исходный код программы находится в папке Нагибин 009-1.
Функция без параметров
Тип данных float
Цикл for
Комбинированные операторы присваивания
Оператор деления /
Задача 9-1 из книги Математическая шкатулка [Нагибин88], страница
16:
Найдите простой приём вычислений и воспользуйтесь им для вычисления суммы:
Этот ряд – почти арифметическая прогрессия: в знаменателе первый сомножитель изменяется от 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(num):
sum += 1.0 / (n * (n + 1))
n += r
230
print('Сумма ряда равна %f' % sum)
print()
# ГЛАВНАЯ ФУНКЦИЯ
def main():
print()
print('Дробный ряд')
print()
solve()
print()
main()
Рис. 6.27 показывает, что сумма равна 0,9, и подсказывает нам, что у этой
задачи имеется и красивое некомпьютерное решение. Найдите его!
Рис. 6.27. Дробовая задача!
231
Проект Ещё один дробный ряд
Исходный код программы находится в папке Нагибин 009-2.
Функция без параметров
Тип данных float
Цикл for
Комбинированные операторы присваивания
Оператор деления /
Задача 9-2 из книги Математическая шкатулка [Нагибин88], страница
16:
Найдите простой приём вычислений и воспользуйтесь им для вычисления суммы:
Задача очень похожа на предыдущую, поэтому решается исправлением
двух строк в коде (Рис. 6.28):
# -*- 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.0 / (n * (n + 1))
n += r
print('Сумма ряда равна %f' % sum)
232
print()
# ГЛАВНАЯ ФУНКЦИЯ
def main():
print()
print('Ещё один дробный ряд')
print()
solve()
print()
main()
Рис. 6.28. Дробим всё!
233
Проект Трёхзначное число 2
Исходный код программы находится в папке Нагибин 597.
Функция с параметрами
Цикл for
Вложенные операторы if
Оператор деления //
Оператор целочисленного деления %
Оператор break
Задача 597 из книги Математическая шкатулка [Нагибин88], страницы
97-98:
Сколько слагаемых суммы 1 + 2 + 3 + 4 + 5 + … надо взять, чтобы получить трёхзначное число, состоящее из одинаковых цифр?
Поскольку числовой ряд представляет собой арифметическую прогрессию,
сумму которой мы умеем находить, то нам нужно только проследить, когда
эта сумма превратится в трёхзначное число с одинаковыми цифрами.
Искать заданное число можно в бесконечном цикле 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
# число десятков:
234
d = sum // 10 % 10
# число сотен:
s = sum // 100
if (e == d and e == s):
print('Число слагаемых равно %i' %i)
print('Трёхзначное число равно %i' %sum)
# трёхзначные числа закончились:
if (sum > 999):
break
n += r
i += 1
print()
# ГЛАВНАЯ ФУНКЦИЯ
def main():
print()
print('Трёхзначное число')
print()
solve(1,100,1)
print()
main()
Правда, по условиям задачи, цикл можно закончить сразу, как только будет
найдено первое трёхзначное число с одинаковыми цифрами, но мы проверим все трёхзначные суммы – в надежде, что среди них найдутся и другие
подходящие. Ан нет: Рис. 6.29 разочаровывает нас – задача имеет единственное решение!
Рис. 6.29. Поиски оказались бесплодными
235
Проект Вычисляем пи и е
Исходный код программы находится в папке Вычисляем пи и
е.
Функция без параметров
Тип данных float
Цикл for
Оператор деления /
Комбинированные операторы
присваивания
Функция math.pow
Ряды используются не только в
занимательных задачах, но и при
решении вполне серьёзных проблем. Например, с их помощью можно вычислить одни из самых знаменитых иррациональных чисел – π и е (поскольку они выражаются бесконечной
десятичной дробью, то мы сможем найти только приближённое значение).
Современное обозначение этого числа греческой буквой пи предложил в 1706
году английский математик У.Джонсон. Он воспользовался первой буквой
греческого слова periferia (конечно, в оригинальном написании, а не в более
удобном - латинскими буквами), что значит окружность. Но
общепризнанным в научном мире этот символ стал после того как в 1736 году
Леонард Эйлер использовал его в своих работах.
История вычислений числа пи, которое равно отношению длины окружности
к её диаметру, началась много тысячелетий назад.
Так, египетские математики считали пи равным (16/9)2, то есть примерно
3,1604938…, а индийские – √10 = 3,16227766… В третьем веке до нашей эры
Архимед установил, что пи меньше, чем 3 1/7 и больше, чем 3 10/71 3,1428
… 3,1408. Среднее значение этого диапазона равно 3,14185, что больше пи уже
в четвёртом десятичном знаке. Но ещё до Архимеда, в пятом веке до нашей
эры китайский математик Цзу Чунчжи нашёл более точное значение 3,1415927... В первой половине пятнадцатого века ал-Каши, астроном и
математик из Самарканда вычислил пи с точностью до 16 знаков после
запятой. В 1615 году голландский математик Лудольф ван Цейлен довёл
точность вычислений до 32 знаков. В 1873 году Вильям Шенкс после 20 лет
расчётов нашёл 707 знаков числа пи, но в 1944 году Д.Фергюсон с помощью
236
механического калькулятора выяснил, что верны только первые 527 знаков
числа Шенкса. Для своих расчётов Шенкс использовал формулу Дж. Мачина:
А арктангенс он вычислял по формуле
Сам Мачин (Рис. 6.30) ещё в 1706 году по этой формуле вычислил 100 знаков
числа пи.
Рис. 6.30. John Machin (1686? - 1751)
С появлением ЭВМ скорость вычислений значительно выросла. В 1949 году
электронная машина ЭНИАК за 70 часов работы вычислила более двух тысяч
десятичных знаков числа пи. Через некоторое время были найдены 3000
знаков всего за 13 минут. В 1959 году компьютеры преодолели рубеж десяти
тысяч знаков, а сейчас известно несколько десятков миллионов знаков числа
пи. Чтобы их напечатать, потребуется несколько толстенных книг.
Число пи с точностью 500 знаков после запятой:
π ≈ 3,141 592 653 589 793 238 462 643 383 279 502 884 197 169 399 375 105 820
974 944 592 307 816 406 286 208 998 628 034 825 342 117 067 982 148 086 513
282 306 647 093 844 609 550 582 231 725 359 408 128 481 117 450 284 102 701
938 521 105 559 644 622 948 954 930 381 964 428 810 975 665 933 446 128 475
648 233 786 783 165 271 201 909 145 648 566 923 460 348 610 454 326 648 213
393 607 260 249 141 273 724 587 006 606 315 588 174 881 520 920 962 829 254
091 715 364 367 892 590 360 011 330 530 548 820 466 521 384 146 951 941 511
237
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…
Конечно, все эти знаки не упомнить (да и не надо!), а вот несколько первых
знать совсем не помешает. Помню, в школьной стенной газете был такой
стишок для запоминания первых цифр числа пи:
Чтобы нам не ошибаться,
Надо правильно прочесть:
Три, четырнадцать, пятнадцать,
Девяносто два и шесть.
Я его запомнил и надеюсь, что и вам это удастся!
Формула Валлиса
Английский математик Джон Валлис (Рис. 6.31) вывел эту красивую формулу в 1655 году, когда вычислял площадь круга. Она представляет собой
бесконечное произведение дробей.
Рис. 6.31. John Wallis by Sir Godfrey Kneller (1616 - 1703)
238
К сожалению, чтобы вычислить даже несколько правильных знаков числа
пи по этой формуле, нужно затратить немало времени и сил. Однако, имея
компьютер, мы можем облегчить себе задачу. Итак, пишем программу:
# -*- 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()
После десяти миллионов итераций получаем ответ (Рис. 6.32).
Рис. 6.32. Да! Уже седьмой знак после запятой неверный
239
Ряд Лейбница
Попробуем зайти с другого конца и воспользуемся знакочередующимся рядом, который предложил немецкий математик Лейбниц (Рис. 6.33).
Рис. 6.33. Gottfried Wilhelm von Leibniz (1646 – 1716)
А ряд вот такой:
Он хорош тем, что его легко приспособить под наши нужды:
# ВЫЧИСЛЯЕМ ПИ - Ряд Лейбница
# 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()
240
После миллиона итераций число пи найдено (Рис. 6.34).
Рис. 6.34. К сожалению, результат не лучше первого!
Вообще говоря, точность вычислений по этим формулам и не может быть
высокой из-за того, что тип float грубоват для этих целей.
Вычисляем число е
Число е –основание натуральных логарифмов - равно:
е ≈ 2,7182818284 5904523536 0287471352 6624977572 4709369995 9574966967
6277240766 3035354759 4571382178 5251664274 2746639193 2003059921
8174135966 2904357290 0334295260 5956307381 3232862794 3490763233
8298807531 9525101901 1573834187 9307021540 8914993488 4167509244
7614606680 8226480016 8477411853 7423454424 3710753907 7744992069
5517027618 3860626133 1384583000 7520449338 2656029760 6737113200
7093287091 2744374704 7230696977 2093101416 9283681902 5515108657
4637721112 5238978442 5056953696 7707854499 6996794686 4454905987
9316368892 3009879312 7736178215 4249992295 7635148220 8269895193
6680331825 2886939849 6465105820 9392398294 8879332036 2509443117
3012381970 6841614039 7019837679 3206832823 7646480429 5311802328
7825098194 5581530175 6717361332 0698112509 9618188159 3041690351
5988885193 4580727386 6738589422 8792284998 9208680582 5749279610
4841984443 6346324496 8487560233 6248270419 7862320900 2160990235
3043699418 4914631409 3431738143 6405462531 5209618369 0888707016
7683964243 7814059271 4563549061 3031072085 1038375051 0115747704
1718986106 8739696552 1267154688 9570350354…
Его можно вычислить как сумму бесконечного ряда:
e = 1 + 1/1! + 1/2! + 1/3! + ...
241
Очень удобная формула для вычисления на компьютере: знаменатель каждой следующей дроби равен знаменателю предыдущей дроби, умноженному на 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()
Уже через 17 итераций мы получим 15 правильных десятичных знаков
числа е после запятой (Рис. 6.35).
В той же школьной стенной газете было и такое прозаическое правило для
запоминания первых цифр числа е:
2 – 7 – 1828 – 1828 (год рождения Льва Толстого) – 45 - 90 – 45 (углы
прямоугольного равнобедренного треугольника).
Вполне достаточно, чтобы очаровать учительницу математики (на учителей
математики этот приём, увы, не действует)!
242
Эти же формулы используются в книге [100], страницы 24-26 для нахождения
чисел пи и е.
Рис. 6.35. Неплохое е!
В книге [100] предлагается вычислить пи и по такой формуле:
Новый метод легко получить из метода Лейбница:
# ВЫЧИСЛЯЕМ ПИ
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()
243
Как и следовало ожидать, точность вычислений невысокая (Рис. 6.36).
Рис. 6.36. Тоже пи
Другая формула для вычисления пи из этой книги:
Здесь мы опять наталкиваемся на ограничение при выборе типа данных
(Рис. 6.37):
# ВЫЧИСЛЯЕМ ПИ
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()
Рис. 6.37. Точности не хватает!
В приведённой выше формуле из книги допущена опечатка – степень пи не
2, а 3, поэтому мы должны извлечь кубический корень из суммы ряда.
244
Следующий ряд состоит как бы из двух рядов, сумму которых можно вычислить отдельно, а затем найти пи:
В целом ряд напоминает ряд Лейбница, поэтому новую функцию написать
не очень сложно (Рис. 6.38):
# ВЫЧИСЛЯЕМ ПИ
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()
Рис. 6.38. Повышаем точность
Здесь все знаки числа пи (кроме последнего) верные!
И наконец, вычислите самостоятельно пи по такой формуле:
245
Поскольку здесь участвуют квадратные корни, то точность вычислений
также будет невысокой.
Учительница математики спрашивает у Вовочки:
- Сколько будет пи в квадрате?
Вовочка не растерялся:
- Пи-пи!
Для вычислений с произвольной точностью имеется специальный модуль
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, 1000+1):
sum1 += neg * 4 /n5 / (i*2+1)
sum2 += neg / n239 / (i*2+1)
neg = -neg
n5 *= 25
n239 = n239*239*239
print('i =', i, 'pi =', 4*(sum1 - sum2))
print()
Как вы видите, прежде всего, нужно задать желаемую точность вычислений:
246
getcontext().prec = 1000+2
На сайте https://ru.wikipedia.org можно найти табличку, в которой указаны
1000 знаков числа пи после запятой. Вместе с целой частью получаем 1001
цифру. И ещё одну цифру мы добавляем для достижения нужной точности.
Затем нужно создать все переменные, которые должны иметь высокую точность, как объекты класса Decimal. Сами же вычисления проводятся так же,
как и раньше.
Зато уже после 1000 итераций мы получаем 1000 правильных знаков
числа пи после запятой (Рис. 6.39).
Рис. 6.39. Хорошая точность
Аналогично вы можете вычислить и 1000 десятичных знаков числа е
после запятой (Рис. 6.40).
# ВЫЧИСЛЯЕМ e
def calc_e_2():
# число цифр:
NUM_DIG = 1000+2
getcontext().prec = NUM_DIG
e = Decimal(0)
i = 0
MAX_VALUE = 10**NUM_DIG
247
while True:
fact = math.factorial(i)
e += Decimal(1)/fact
i += 1
if fact > MAX_VALUE:
break
print()
print('i = ', i)
print('e =', e)
print()
Рис. 6.40. Отличная точность
Но вычисления с высокой точностью выполняются значительно медленнее
обычных, и это следует учитывать при разработке программ!
По формуле Чудновского можно очень быстро вычислить множество
цифр числа пи:
Формула очень сложная, поэтому мы не будем обсуждать все тонкости её
перевода на компьютерный язык. За подробностями обращайтесь на сайт:
http://www.craig-wood.com/nick/articles/pi-chudnovsky/
А вот код для вычисления числа пи:
248
# ВЫЧИСЛЯЕМ КВАДРАТНЫЙ КОРЕНЬ С ЗАДАННОЙ ТОЧНОСТЬЮ
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
Tab = Qmb * Tam + Pam
return Pab, Qab, Tab
* (2 * a - 1) * (6 * a - 1)
C3_OVER_24
+ 545140134 * a)
m)
b)
* Tmb
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
249
Тысячу знаков функция 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
03975951567715770042033786993600723055876317635942187312514712053292819182618612586732
15791984148488291644706095752706957220917567116722910981690915280173506712748583222871
83520935396572512108357915136988209144421006751033467110314126711136990865851639831501
97016515116851714376576183515565088490998985998238734552833163550764791853589322618548
96321329330898570642046752590709154814165498594616371802709819943099244889575712828905
92323326097299712084433573265489382391193259746366730583604142813883032038249037589852
43744170291327656180937734440307074692112019130203303801976211011004492932151608424448
59637669838952286847831235526582131449576857262433441893039686426243410773226978028073
18915441101044682325271620105265227211166039666557309254711055785376346682065310989652
69186205647693125705863566201855810072936065987648611791045334885034611365768675324944
16680396265797877185560845529654126654085306143444318586769751456614068007002378776591
250
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
22629348600341587229805349896502262917487882027342092222453398562647669149055628425039
12757710284027998066365825488926488025456610172967026640765590429099456815065265305371
82941270336931378517860904070866711496558343434769338578171138645587367812301458768712
66034891390956200993936103102916161528813843790990423174733639480457593149314052976347
57481193567091101377517210080315590248530906692037671922033229094334676851422144773793
93751703443661991040337511173547191855046449026365512816228824462575916333039107225383
74218214088350865739177150968288747826569959957449066175834413752239709683408005355984
91754173818839994469748676265516582765848358845314277568790029095170283529716344562129
64043523117600665101241200659755851276178583829204197484423608007193045761893234922927
96501987518721272675079812554709589045563579212210333466974992356302549478024901141952
12382815309114079073860251522742995818072471625916685451333123948049470791191532673430
251
28244186041426363954800044800267049624820179289647669758318327131425170296923488962766
84403232609275249603579964692565049368183609003238092934595889706953653494060340216654
43755890045632882250545255640564482465151875471196218443965825337543885690941130315095
26179378002974120766514793942590298969594699556576121865619673378623625612521632086286
92221032748892186543648022967807057656151446320469279068212073883778142335628236089632
08068222468012248261177185896381409183903673672220888321513755600372798394004152970028
78307667094447456013455641725437090697939612257142989467154357846878861444581231459357
19849225284716050492212424701412147805734551050080190869960330276347870810817545011930
71412233908663938339529425786905076431006383519834389341596131854347546495569781038293
09716465143840700707360411237359984345225161050702705623526601276484830840761183013052
79320542746286540360367453286510570658748822569815793678976697422057505968344086973502
01410206723585020072452256326513410559240190274216248439140359989535394590944070469120
91409387001264560016237428802109276457931065792295524988727584610126483699989225695968
8159205600101655256375678
За несколько секунд вы получите 100 000 знаков числа пи, но я не нашёл в
книге места, чтобы напечатать их…
252
Проект Вычисляем пи по методу Архимеда
Исходный код программы находится в папке PiArchimedes.
Функция с параметрами
Тип данных Decimal
Цикл for
Бесконечный цикл while
Оператор деления //
Как вы знаете, Архимед нашёл приближённое значение
числа пи, вписывая в окружность и описывая вокруг неё
правильные многоугольники (Рис. 6.41 из Википедии).
Рис. 6.41. Методично!
Чем больше сторон у многоугольника, тем точнее получается значение
числа пи. При бесконечном числе сторон мы получили бы точное значение
числа пи, но практически это сделать невозможно.
В этом проекте мы имитируем построение правильных многоугольников с
очень большим числом сторон, вписанных в окружность.
Мы начинаем с правильного четырёхугольника (квадрата), у которого 4
стороны. Если радиус окружности равен r = 1, то из прямоугольного треугольника ОАВ мы найдём длину стороны квадрата √ 2 и длину
окружности 4√2 = 2π, откуда π = 2√2 (Рис. 6.42).
253
Рис. 6.42. «Квадратура круга»
Теперь на каждой стороне квадрата построим равнобедренный треугольник. В итоге из квадрата мы получим 8-угольник. Чтобы найти длину стороны 8-угольника, рассмотрим треугольник АВС (Рис. 6.43).
Рис. 6.43. Вычисляем длину стороны
Из теоремы Пифагора следует, что AC2 = AD2 + CD2.
Обозначим длину стороны квадрата буквой d, тогда:
254
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
4
(2)
Если мы её добросовестно вычислим, то для пи получим такое значение:
Если мы теперь на каждой стороне 8-угольника построим равнобедренный
треугольник, то получим 16-угольник. Длину его стороны можно вычислить по той же формуле (2), только теперь d – это длина стороны 8-угольника. Для 16-угольника длина окружности вполне приемлемая:
Таким образом, по формуле (2) мы можем вычислить длину стороны любого правильного 2n- угольника.
Для вычислений с большой точностью мы импортируем модуль decimal:
# -*- coding: Windows-1251 -*from decimal import Decimal, getcontext
255
В функции pi_archimedes мы действуем по методу Архимеда. Длину сторон
правильного многоугольника будем подсчитывать по формуле (2):
dn+1 = √2 − 2√1 −
𝑑2
4
Начинаем вычисления с квадрата, у которого 4 стороны. Их длину также
можно вычислить по формуле (2), но для этого нужно знать длину сторон
предшествующего многоугольника, у которого сторон в 2 раза меньше. Однако многоугольников с двумя сторонами не существует, поэтому возникает вопрос: если dn +1 – это длина сторон квадрата, то чему равно d? – Посмотрите на Рис. 6.44. Ясно видно, что треугольник АВС построен на диаметре окружности, то есть символическим предшественником квадрата
можно считать диаметр окружности.
Рис. 6.44. Прямолинейный предшественник прямоугольника
Благодаря этому допущению мы можем вычислять стороны всех многоугольников по одной и той же формуле (2).
Других «хитростей» в функции pi_archimedes нет.
# ВЫЧИСЛЯЕМ ЧИСЛО ПИ ПО МЕТОДУ АРХИМЕДА
def pi_archimedes(n):
length = Decimal(2)
sides = 4
for i in range(n):
length = 2 - 2 * (1 - length / 4).sqrt()
256
sides *= 2
return sides * length.sqrt()/2
В главной функции мы задаём нужную нам точность вычислений и номер
начальной итерации, которая определяется по эмпирической формуле. При
желании вы можете посмотреть все итерации, но это займёт немало времени.
# ГЛАВНАЯ ФУНКЦИЯ
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)
# печатаем с заданной точностью,
# округляя текущий результат:
getcontext().prec = NUM_DIG
res = +res
# печатаем промежуточные результаты:
#print(n, res)
# достигли заданной точности:
if res == pred:
break
pred = res
n += 1
# печатаем результат с заданной точностью:
print(n, res)
print()
main()
С каждой итерацией число сторон многоугольника удваивается, так что он
всё больше приближается к окружности. И таким образом мы можем вычис-
257
лить значение числа пи очень точно. Например, после 1662 удвоений
мы по- лучим 1000 правильных десятичных знаков числа пи после
запятой (Рис. 6.45).
Рис. 6.45. Хороший метод!
Если бы у Архимеда был компьютер, он нашёл бы очень точное значение
числа пи ещё две с половиной тысячи лет тому назад!
258
Проект Тригонометрические функции
Исходный код программы находится в папке Тригонометрические функции.
Функция с параметром
Оператор return
Тип данных float
Оператор деления /
Константа math.pi
Бесконечный цикл while
Метод math.sin
Метод math.cos
Метод math.atan
Цикл for
Оператор return
Условный оператор if
Комбинированные операторы присваивания
Задача 5⨀⨀ из книги 100 задач по программированию [100], страница 26:
Составьте функции для вычисления с указанной точностью значений
тригонометрических функций путём сложения членов следующих рядов:
В последней формуле |x| < 1.
Ряд для вычисления синуса должен смутно напомнить вам ряд для вычисления основания натуральных логарифмов е:
259
1 + 1/1! + 1/2! + 1/3! + ...
А поскольку это так, то вам остаётся только учесть, что ряд
знакопеременный, а числитель увеличивается в х2 раз.
Но сначала нужно решить проблему с вводом угла х. Так как пользователю
удобнее вводить угол в градусах, а формула предпочитает радианы, то мы
должны позаботиться о переводе градусов в радианы. Вспомним, что
полной окружности соответствует угол 360˚, или 2π радианов. Чтобы найти,
сколько радианов содержится в а градусах, составим несложную
пропорцию:
2π радианов = 360˚
x радианов = a˚
Откуда находим:
2π a˚ = 360˚ x
x радианов = a˚* π /180˚ , или
x радианов = a˚ /180˚* π
Переводим эту формулу на язык Питон и получаем функцию grad_to_rad:
def grad_to_rad(a):
return a / 180.0 * math.pi
Впрочем, в модуле math имеется функция radians, которая умеет делать то
же самое, поэтому мы будем пользоваться и этой функцией.
В функции main мы вызываем функцию sinus для вычисления заданного
угла и сравниваем полученное значение с тем, что выдаёт функция sin из
модуля math:
# -*- coding: Windows-1251 -*# Тригонометрические функции
import math
from decimal import *
260
# ГЛАВНАЯ ФУНКЦИЯ
def main():
print('Тригонометрические функции')
print()
# бесконечный цикл ввода данных # пока пользователь не закроет программу:
while True:
print('Градусы: ', end='')
# угол в градусах:
grad = float(input())
# переводим в радианы:
rad = math.radians(grad)
print('Синус =
', end='')
sin = sinus(rad)
print('', sin)
print("math.sin =", math.sin(rad))
print()
main()
Приведённая в книге формула для вычисления синуса – это ряд Тейлора.
Так как числитель дробей постоянно и резко уменьшается, а знаменатель
очень быстро растёт, то точности встроенного типа float хватает всего на
пару десятков итераций:
# ВЫЧИСЛЯЕМ СИНУС ЗАДАННОГО УГЛА
def sinus(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
261
Запускаем программу и задаём хорошо известные углы в градусах. Рис. 6.46
убеждает нас, что наша небольшая функция sinus почти не уступает в
точности встроенной фугкции модуля math!
Рис. 6.46. Отсинусились
Переходим к вычислению косинусов:
# ГЛАВНАЯ ФУНКЦИЯ
def main():
. . .
print('Косинус =
', end='')
cos = cosinus(rad)
print(str(cos))
print('math.cos = ', math.cos(rad))
print()
Чтобы получить функцию cosinus, нужно в функции для вычисления
синуса исправить всего несколько строчек:
262
# ВЫЧИСЛЯЕМ КОСИНУС ЗАДАННОГО УГЛА
def cosinus(x):
cos = 1.0
xn = x * x
f = 1.0
neg = -1
for i in range(2, 22+1, 2):
f = f / i / (i - 1)
cos += neg * xn * f
neg = -neg
xn = xn * x * x
return cos
Здесь мы иногда наблюдаем расхождение с функцией cos из модуля math в
последнем знаке (Рис. 6.47), но в целом и этот метод можно признать
успешным!
Ряд для вычисления арктангенса заданного угла мало отличается от ряда
для синуса, он даже проще:
print('Арктангенс =', end='')
atan = arctan(rad)
print(atan)
print('math.atan =', math.atan(rad))
print()
Рис. 6.47. И откосинусились
263
И мы быстро справляемся с новой функцией:
# ВЫЧИСЛЯЕМ АРКТАНГЕНС ЗАДАННОГО УГЛА
def arctan(x):
atan = 0.0
xn = x
neg = 1
for i in range(0, 1000+1, 2):
atan += neg * xn / (i + 1)
neg = -neg
xn = xn * x * x
return atan
В данном ряду знаменатели увеличиваются довольно медленно, поэтому
для достижения максимально возможной точности потребуется
значительно больше итераций.
Также следует учитывать, что ряд правильно работает только для углов,
которые меньше 1 радиана (Рис. 6.48)!
Рис. 6.48. Вполне точные арктангенсы
264
Проект Сотая цифра
Исходный код программы находится в папке Нагибин 300.
Функция без параметров
Цикл while
Вложенные циклы while
Условный оператор if
Оператор break
Списки
Комбинированные операторы присваивания
Задача 300 из книги Математическая шкатулка [Нагибин88], страница
45:
Все натуральные числа, начиная с 1, записаны в порядке их возрастания:
1 2 3 4 5 6 7 8 9 10 11…
Какая цифра в этой записи стоит на сотом месте?
Проще всего решить задачу «строковым» способом:
# -*- coding: Windows-1251 -*# Нагибин, с.45, Задача 300
# РЕШАЕМ ЗАДАЧУ
def solve1():
s = ''
i = 1
while(len(s) < 100):
s += str(i)
i += 1
print('s = %s' %s)
print('На сотом месте стоит цифра: %s' % s[99])
print()
# ГЛАВНАЯ ФУНКЦИЯ
def main():
print()
265
print('Сотая цифра')
print()
solve1()
print()
main()
Пока в строке меньше 100 знаков, мы добавляем к ней следующее число.
Поскольку при этом длина строки может оказаться больше 100 символов,
то мы просто интересуемся, какая цифра стоит на сотом месте (её индекс 99).
На Рис. 6.49 вы видите, что сотое место в числовой записи занимает цифра
5.
Рис. 6.49. Даёшь сотню!
Но строковый способ решения задачи не совсем честный, поэтому давайте
обойдёмся без строк - только числами!
Функция solve2 при этом, конечно, станет длиннее и сложнее, но зато всё
по-честному!
# РЕШАЕМ ЗАДАЧУ
def solve2():
num = 1
nd = 0
anum = []
while (len(anum) <= 100):
n = num
anum.append(n % 10)
n //= 10
while (n > 0):
if (nd >= 100):
break
# очередная цифра:
anum.insert(len(anum)-1, n % 10)
n //= 10
266
nd += 1
num += 1
if (nd >= 100):
break
print(anum)
print('На сотом месте стоит цифра: %i' % anum[99])
print()
Здесь число num увеличивается от 1 и до тех пор, пока число цифр в
списке anum не достигнет сотни.
Теперь нам нужно самостоятельно выделять из каждого числа его цифры и
заносить в список anum. Здесь важно учесть, что цифры мы выделяем с
конца числа, а заносить их в список нужно в обратном порядке.
Ответ мы получим, естественно, тот же самый, что и раньше (Рис. 6.50).
Рис. 6.50. Дали сотню!
267
Проект Случайные ферзи
Исходный код программы находится в папке Queens.
Функция с параметрами
Цикл while
Цикл for
Условный оператор if
Класс Random
Расстановка ферзей на шахматной доске – излюбленная программистская
задача. Обычно стремятся найти все возможные решения. Для небольших
досок это нетрудно, но с увеличением их размеров число расстановок
быстро растёт. Предположим, что нам нужна всего одна, случайная расстановка ферзей.
Если мы найдём все расстановки и сохраним их, то сможем случайным образом выбирать нужные. Но тогда нам нужно затратить время на поиск всех
решений, а также память для хранение этих расстановок.
Можно поступить иначе: расставить ферзей на доске в случайном порядке.
Если при этом хотя бы одна пара ферзей окажется под боем, то снова расставить ферзей. И так до победного конца.
Конечно, если расставлять ферзей бездумно, то удачной расстановки ждать
придётся очень долго. Однако достаточно заметить, что в каждой
горизонтали и вертикали может находиться единственный ферзь, как
задача существенно облегчается!
Пронумеруем горизонтали и вертикали числами от 0 до 7, как это принято
в программировании (Рис. 6.51).
Пройдёмся по горизонталям сверху вниз и отметим вертикали, в которых
стоят ферзи:
25317460
Понятно, что номера вертикалей – это также числа 0..7. Если бы числа повторялись, то в одной вертикали стояло бы несколько ферзей, что недопустимо.
268
Рис. 6.51. Программистская оцифровка
Таким образом, всякая расстановка ферзей описывается перестановкой
чисел 0..7. Если бы мы расставляли ладей, то любая перестановка этих чисел
давала бы решение задачи. Всего таких перестановок 8! = 40320. Однако мы
точно знаем, что ферзей можно расставить всего 92 способами, поэтому вероятность удачной расстановки равна 92 / 40320, или, что понятнее, нам потребуется в среднем 40320 / 92, то есть чуть больше 438 попыток для получения удачной позиции (Рис. 6.52).
Рис. 6.52. Попытка - не пытка
Для стандартной доски это совсем немного. Для доски 9 х 9 клеток число
попыток составляет больше тысячи:
1030.909090909091
А для доски 10 х 10 клеток уже больше 5 тысяч:
5012.154696132597
269
Для досок больших размеров рассматриваемый способ непригоден.
В главной функции программы мы создаём генератор псевдослучайных
чисел 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):
found_solutions += 1
print("Решение # {0}: {1}. Число попыток = {2}"
.format(found_solutions, pos, tries))
tries = 0
270
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
Если два ферзя стоят на одной горизонтали, то абсолютные величины разности их координат равны, что хорошо видно на следующем рисунке (Рис.
6.53).
271
Рис. 6.53. Диагональные угрозы
Если это не так, значит, ферзи не бьют друг друга:
import random
# ПРОВЕРЯЕМ ЗАДАННУЮ ДИАГОНАЛЬ
def test_diagonal(с, r, сol, row):
dr = abs(row - r)
dc = abs(сol - с)
# в разных диагоналях:
return dr != dc
На этом решение задачи закончено (Рис. 6.54).
Рис. 6.54. Случайные расстановки ферзей
272
Проект Три числа
Исходный код программы находится в папке Три числа.
Функция без параметров
Вложенные циклы for
Условный оператор if - else
Оператор continue
Списки
В 1983 году вышла книга The Commodore Puzzle Book: BASIC Brainteasers (
Рис. 6.55).
Рис. 6.55. Обложка книги
Это было в те далёкие времена, когда только начали появляться первые
персональные компьютеры. Одним из самых известных был Commodore.
Люди в то время были гораздо любознательнее, чем нынче, поэтому книга
по решению занимательных математических задач на компьютере была
как раз впору и к месту.
В этой книге собрано немало задач, которые могут послужить хорошим
упражнением и в современном программировании.
Самая первая задача называется Hymn Numbers (с. 3).
273
Её суть состоит в том, чтобы найти тройки чисел, которые имеют следующие свойства:
все три числа трёхзначные
все цифры в них разные (то есть эти числа должны состоять из 9 разных цифр)
второе число в 2 раза больше первого (если считать первым самое маленькое из трёх)
третье число в 3 раза больше первого.
Одно решение показано на картинке (Рис. 6.56).
Рис. 6.56. Подсказка!
Нужно найти остальные.
Решить эту задачу можно разными способами, но лучший из них - простой
перебор (brute force).
Прежде всего, мы должны найти первое число.
Так как оно состоит из трёх цифр, то для этого нам понадобятся 3 вложенных цикла for. Если переменные цикла обозначить буквами a, b, c, то переменная а изменяется в диапазоне 1..9 (трёхзначное число не может начинать с нуля). А b и c – в диапазоне 0..9.
274
Так как все цифры первого числа различны, то дополнительно необходимо
проверять b на неравенство с а, а число с ещё и с b.
Очевидно, что третье число не может быть больше 999 – 998 – 997 – 996 … -987, так как это наибольшее трёхзначное число, состоящее из разных
цифр. Но тогда первое число не может быть больше 987 : 3 = 329. Это значит, что поиск новых чисел следует закончить, когда первое число превысит этот предел.
Решение мы начнём с вызова в главной функции программы функции
solve, которая и решит предложенную проблему:
# ГЛАВНАЯ ФУНКЦИЯ
def main():
solve()
print()
main()
Начало функции solve мы уже подробно разобрали:
# -*- coding: Windows-1251 -*-
# РЕШАЕМ ЗАДАЧУ
def solve():
for a in range(1, 10):
for b in range(10):
if(b == a):
continue
for c in range(10):
if (c == b or c == a):
continue
Теперь мы имеем 3 разные цифры первого (наименьшего) числа
. Обозначим его num1, вычислим и проверим:
# первое трёхзначное число:
num1 = a * 100 + b * 10 + c
275
if (num1 > 329):
return
Пришла пора подумать, как найти оставшиеся 2 числа. Не стоит поддаваться соблазну и выписывать ещё 3 вложенных цикла, ведь второе число
мы легко найдём из условия задачи: для этого достаточно умножить
первое число на двойку:
# второе трёхзначное число:
num2 = 2 * num1
Если бы в задаче не требовалась неповторяемость цифр, то дальше и решать было бы нечего. Но мы должны учесть, что во втором числе num2
цифры не только отличаются друг от друга, но и не повторяют цифр уже
найденного нами числа num1.
Зная число num2, мы легко найдём все его цифры. А закавыка в том, что их
нужно сравнивать не только между собой, но и с цифрами первого числа.
Проверок получается много, а это некрасиво.
Чтобы сделать себе красиво, мы создадим список уже вышедших цифр и
перво-наперво занесём в него уже известные нам цифры первого числа:
# список цифр первого числа:
digits = [ a, b, c ]
В каком порядке «отщипывать» цифры второго числа, совершенно безразлично, но нам удобнее начать с хвоста:
# третья цифра:
d3 = num2 % 10
if ( d3 in digits ):
continue
else:
digits += [d3]
276
Найдя цифру, мы тут же проверяем, а не имеется ли она в списке цифр digits.
Если это так, то дальнейшие действия теряют всякий смысл, и мы возвращаемся в начало второго цикла для получения следующего числа-кандидата.
Если проверяемая цифра в списке отсутствует, мы её туда торжественно помещаем и переходим к проверке оставшихся цифр второго трёхзначного
числа. Здесь ничего нового нет:
# вторая цифра:
d2 = num2 // 10 % 10
if ( d2 in digits):
continue
else:
digits += [d2]
# первая цифра:
d1 = num2 // 100
if ( d1 in digits):
continue
else:
digits += [d1]
Управившись со вторым числом, мы бодро приступаем к проверке третьего
числа, которое втрое больше первого:
# третье трёхзначное число:
num3 = 3 * num1
# третья цифра:
d3 = num3 % 10
if ( d3 in digits):
continue
else:
digits += [d3]
# вторая цифра:
d2 = num3 // 10 % 10
if ( d2 in digits):
continue
else:
digits += [d2]
# первая цифра:
277
d1 = num3 // 100
if ( d1 in digits):
continue
else:
digits += [d1]
Если все цифры в трёх числах оказались различными, мы печатаем решение
на экране:
# печатаем ответ:
print(num1, num2, num3)
Так как мы решаем задачу полным перебором, то можем быть вполне уверены, что нашли все возможные тройки чисел.
Как вы видите, улов оказался не очень богатым – всего 5 решений (Рис. 6.
57).
Рис. 6.57. Пять троек
Первое решение вы имели удовольствие видеть на картинке из книжки.
Сразу напрашивается мысль, что мы использовали только 9 цифр из 10, и,
следовательно, третье, самое большое число может быть и 4-значным. Про
- верить это предположение нетрудно, однако ни одного решения эта
задача, увы, не имеет.
278
Проект Четыре числа
Исходный код программы находится в папке Extra Homework.
Функция без параметров
Цикл for
Условный оператор if
Списки
В той же книге, на странице 4 нас ждёт «дополнительное домашнее задание»: найти максимальное число n, которое при делении на него чисел
1731
5363
7179
9903
даёт одинаковый остаток r.
Понятно, что искомое число не превышает наименьшего из этих чисел, то
есть 1731.
Если n = 1731, то r = 0, но ни одно из остальных чисел не делится нацело
на 1731. Значит, нужно проверить число на 1 меньше, чем 1731, то есть
1730: 1731 % 1730 = 1
Однако 5363 % 1730 = 173
Опять неудача! Но теперь мы точно знаем, что нужно последовательно вычитать из числа 1731 единицу и находить остатки от деления предложенных чисел на разницу.
Когда все остатки от деления совпадут, мы напечатаем ответ (Рис. 6.58):
# -*- coding: cp1251 -*-
# РЕШАЕМ ЗАДАЧУ
def solve():
n = max([n for n in range(1, 1731+1)
279
if 1731 % n == 5363 % n == 7179 % n == 9903 % n])
print(n)
# ГЛАВНАЯ ФУНКЦИЯ
def main():
solve()
print()
main()
Рис. 6.58. Скромный улов
В этом решении немало лишних действий, но зато оно простое. А при небольшом переборе задача решается очень быстро любым способом.
280
Проект Город
Исходный код программы находится в папке Город.
Функция без параметров
Бесконечный цикл while
Оператор break
Условный оператор if - else
В журнале Квантик, № 1 за 2016
год опубликованы задачи Первого
тура математического конкурса.
Мы ре- шим первую задачу (Рис. 6.
59).
Рис. 6.59. Условие задачи
«Докажите» здесь значит «решите задачу». Задача очень простая, но не
сразу это сообразишь, поэтому мы напишем короткую программу, которая
выдаст нам правильный ответ, который покажет нам, как можно решить
эту задачу исключительно в уме.
Функция solve начинается с объявления переменных, известных вам по
условию задачи:
# -*- coding: cp1251 -*-
# РЕШАЕМ ЗАДАЧЦ
def solve():
# население:
281
gorod1 = 1000000
gorod2 = 1000000
# переселение:
iz1 = 2015
iz2 = 2016
А дальше мы симулируем процесс переселения населения с насиженных
мест в места не столь отдалённые, то есть в другую часть города.
Поскольку число переселений нам неизвестно (иначе мы бы знали ответ на
задачу), то цикл while будет бесконечным, а прервём мы его тогда, когда
вы- полнится требование задачи: в каждой половине города вновь
окажется по 1 миллиону жителей.
В первый год, а также во все последующие нечётные года iz1 человек переселяется из одной половины города в другую. Во второй год и во все чётные
года, наоборот, из второй половины города переселяется iz2 человек в
первую.
Через n лет население обеих половин города сравняется, и мы напечатаем
ответ:
# счётчик лет:
n = 0
while True:
n += 1
if n % 2: # из первого во второй
gorod1 -= iz1
gorod2 += iz1
else: # из второго в первый
gorod1 += iz2
gorod2 -= iz2
# проверяем численность:
if gorod1 == 1000000:
print('Лет: ', n,'\n')
break
# ГЛАВНАЯ ФУНКЦИЯ
def main():
solve()
print()
main()
282
Запускаем программу и тут же узнаём, что долгодолгодолгожданное событие наступит через 4031 год (Рис. 6.60).
Рис. 6.60. Ждать придётся долго!
Если бы найти такой город, в котором люди живут по 4000 лет, то можно
было бы и попереселяться ежегодно.
Понятно, что эта задача совершенно искусственная, а число переселенцев
просто обозначает предыдущий и текущий года.
Ответ 4031 нетрудно получить так: 2015 х 2 + 1. Откуда и следует элементарное, как Ватсон, решение.
Каждые 2 года население правой части города уменьшается на 1 человека.
Но при этом из левой части города каждый нечётный год к ним добавляются 2015 человек. Когда в правой части останется 1000000 – 2015
человек, то на следующий год в него прибудут 2015 человек из левой
части города, и население возрастёт до 1000000 человек. Это будет как
раз в тот год, о котором и спрашивается в условии задачи.
Как мы уже выяснили, население правой части города уменьшается на 1 че
- ловека за 2 года, поэтому на 2015 человек население уменьшится за
2015 х 2 = 4030 лет. А на следующий, 4031- й год 2015 человек из левой
части го- рода увеличат его до 1 миллиона.
Хорошее дело – программирование, но иногда и мозгами нужно пораскинуть!
283
Проект Наименьшее число
Исходный код программы находится в папке Наименьшее
число.
Функция без параметров
Бесконечный цикл while
Цикл for
Оператор break
Условный оператор if
В журнале Наука и жизнь, №2 за 1968 год, на странице
57 напечатана такая задача:
Задача исключительно на сообразительность. Вот журнальное решение задачи:
Не каждый этим похвальным даром обладает, поэтому мы пишем простенькую программу:
# -*- coding: Windows-1251 -*-
# РЕШАЕМ ЗАДАЧУ
def solve():
284
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. Он заканчивается, когда найдено первое число, удовлетворяющее всем условиям задачи (Рис. 6.61).
Рис. 6.61. Программирование – для несообразительных
Вопреки ожиданиям наш ответ полностью совпал с «сообразительным» …
285
Проект Трёхзначное число
Исходный код программы находится в папке Трёхзначное
число.
Функция без параметров
Бесконечный цикл 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('Трёхзначное число')
print()
286
n = solve()
print('Искомое число =', n)
print()
main()
Ответ такой (Рис. 6.62).
Рис. 6.62. Опять смекалки не хватило…
Он, безусловно, правильный, но, чтобы его получить, вполне можно обойтись и смекалкой.
Действительно, если число n – 7 нацело делится на 7, то и число n кратно 7.
Аналогично для чисел 8 и 9. Стало быть искомое число одновременно делится на 7, 8 и 9.
Минимальное число, обладающее такими свойствами равно:
7 * 8 * 9 = 504
Следующее такое число 504 * 2 = 1008, но оно не трёхзначное.
287
Задания для самостоятельного решения
Задача #297
Математическая шкатулка
Чтобы пронумеровать страницы некоторой книги, понадобилось 1164
цифры.
Сколько в этой книге страниц?
Ответ: 424
Задача #298
Математическая шкатулка
Сколько цифр нужно употребить для нумерации книги, в которой 634
страницы?
Ответ: 1794
Формула Бине
[ЗП88]. Задача 556
n-ное число Фибоначчи можно вычислить по формуле Бине:
В ней буквой фи обозначено золотое сечение:
Из неё следует, что Fn равно ближайшему к
.
целому числу.
288
Так как абсолютная величина выражения
меньше единицы, то
при больших n числа Фибоначчи можно вычислять по приближённой
формуле:
Напишите функцию, вычисляющую заданное число Фибоначчи по
формуле Бине.
289
Глава 7. Диофантовы уравнения и
линейное программирование
Под диофантовыми понимают неопределённые уравнения, которые решаются в целых числах (часто – в натуральных). Они названы в честь древнегреческого математика Диофанта, жившего в третьем веке нашей эры (Рис.
7.1).
Рис. 7.1. Диофант Александриийский
Διόφαντος ὁ Ἀλεξανδρεύς, Diophantus
В честь Диофанта назван и кратер на Луне (на рисунке – в правом нижнем
углу).
290
В тринадцатитомном труде Арифметика (до нас дошли только 6
первых книг) он показывает, как решать подобные уравнения.
В 1974 году была издана книга Арифметика и книга о многоугольных
числах, содержащая перевод на русский язык всех трудов Диофанта (Рис.
7.2, слева). В 2007 году книга была переиздана (Рис. 7.2, справа).
Рис. 7.2. Книги Диофанта
Более подробно о Диофанте вы можете прочитать в книге Якова Перельмана Занимательная математика (Рис. 7.3, слева) и Изабеллы Башмаковой
(Рис. 7.4, справа).
Рис. 7.3. И книги о Диофанте
Впрочем, диофантовы уравнения появились задолго до самого Диофанта.
Первое из таких уравнений было известно ещё в Древнем Вавилоне:
291
x2 + y2 = z2
Оно было решено пифагорейцами.
Второе уравнение:
x2 – ay2 = 1
решил в целых числах Евклид.
О способах решения диофантовых уравнений можно узнать в книге Башмаковой, а также в Справочном пособии к решению задач: Диофантовы
уравнения Дмитрия Базылева (Рис. 7.4).
Рис. 7.4. Справочник по диофантовым уравнениям
Самая известная занимательная задача, связанная с диофантовыми уравнениями, это, безусловно, Кролики и фазаны.
292
Проект На ферме
Исходный код программы находится в папке Кордемский 053
12.
Функции без параметров
Вложенные циклы for
Условный оператор if
Оператор continue
Задача 12 (13) из книги Удивительный мир чисел [КА86], страница 53:
На ферме выращивают кроликов и фазанов. В настоящее время их
столько, что у всех вместе 740 голов и 1980 ног.
Сколько же в настоящее время находится на ферме кроликов и фазанов?
Поскольку и число голов, и число ног у кроликов и фазанов выражается целыми числами, то достаточно перебрать все варианты распределения 740
голов между кроликами и фазанами.
# ГЛАВНАЯ ФУНКЦИЯ
def main():
print()
print('На ферме')
print()
solve()
print()
main()
293
# -*- 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()
И вмиг кролики и фазаны пересчитаны и занесены в консольное окно
нашей программы (Рис. 7.5).
Рис. 7.5. Пересчитали поголовье и поножье
294
Проект Решите систему уравнений
Исходный код программы находится в папке Кордемский 100
07.
Функция без параметров
Вложенные циклы for
Условный оператор if
Оператор continue
Оператор return
Задача 7 из книги Удивительный мир чисел
[КА86], страница 100:
Решите систему уравнений:
Далеко неочевидно, но значения переменных x и y должны быть целыми,
иначе верхнее равенство вряд ли выполнится. Его лучше записать в другом
виде:
x + y = 2x
Пределы изменения переменных x и y можно взять достаточно большими,
но если решение не будет найдено, то верхнюю границу следует ещё увеличить.
Больше никаких трудностей в решении задачи не наблюдается, поэтому в
двух вложенных циклах for мы перебираем значения переменных x и y и
проверяем выполнение условий задачи.
# ГЛАВНАЯ ФУНКЦИЯ
def main():
print()
295
print('Решите систему уравнений')
print()
nVar = solve()
print('Найдены все варианты решения -', nVar)
print()
main()
# -*- coding: Windows-1251 -*# Кордемский, с.100, Задача 7
import math
# РЕШАЕМ ЗАДАЧУ
def solve():
result = 0
for x in range(0, 100 + 1):
for y in range(0, 100 + 1):
if (x+y != math.pow(2, x)):
continue
if ((x + y) * math.pow(6, x) != 248832):
continue
result += 1
print('Вариант #', result)
s = 'x = ' + str(x)
print(s)
s = 'y = ' + str(y)
print(s)
print()
return result
На Рис. 7.6 вы видите ответ на задачу. Значения переменных оказались совсем небольшими!
Рис. 7.6. Системная задача
296
Проект Сооружение для лаборатории
Исходный код программы находится в папке Кордемский 064
07.
Функция без параметров
Вложенные циклы for
Условный оператор if
Оператор continue
Оператор return
Задача 7 из книги Удивительный мир
чисел [КА86], страница 64:
Для одной лаборатории изготовили конструкцию из двух спаянных пустотелых параллелепипедов (см. рисунок справа). Длины всех рёбер (в
дециметрах) - целые числа. Основания параллелепипедов — квадраты. Высота первого параллелепипеда равна стороне основания второго, а
высота второго равна стороне основания первого параллелепипеда.
Поместится ли эта конструкция, объём которой 3900 дм3, в лаборатории, если расстояние
от пола лаборатории до потолка равно 2 м 75 см?
Поскольку x и y – целые числа, то мы можем просто перебрать все их возможные комбинации. Например, можно сразу заключить, что оба эти числа
больше нуля.
Из уравнения
x2*y + x*y2 = 3900
(1)
297
следует, что ни одно из этих чисел не больше корня квадратного из 3900.
Точное значение корня нам не нужно, пусть это будет 70.
Осталось в двух вложенных циклах for составить все пары чисел x и y и для
каждой пары проверить выполнение условия (1).
# ГЛАВНАЯ ФУНКЦИЯ
def main():
print()
print('Сооружение для лаборатории')
print()
nVar = solve()
print('Найдены все варианты решения -', nVar)
print()
main()
# -*- 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('Вариант #', result)
s = 'x = ' + str(x)
print(s)
s = 'y = ' + str(y)
print(s)
Общая высота сооружения равна x+y. Значение этой суммы мы печатаем на
экране:
s = 'Высота сооружения равна =' + str(x+y)
298
print(s)
print()
return result
Как показывает Рис. 7.7, либо x = 12, y = 13, либо y = 12, x = 13, но в обоих
случаях высота конструкции составляет 12 + 13 = 25 дециметров = 2,5
метра, что меньше высоты лаборатории. Значит, конструкция в ней поместится.
Рис. 7.7. Конструкторская задача
299
Проект И такие есть числа 3
Исходный код программы находится в папке Кордемский 063
04-3.
Функция без параметров
Вложенные циклы for
Условный оператор if
Оператор continue
Функция с параметром
Оператор return
Оператор целочисленного деления %
Оператор and
Задача 4-3 из книги Удивительный
мир чисел [КА86], страница 63:
Два простых числа обладают свойством: если от каждого из них вычесть
половину другого, то одна разность будет в 5 раз больше другой.
Найдите эти числа.
Пусть первое число n1 меньше второго числа n2. Тогда n2 как минимум на
1 больше n1, но не более, чем в 2 раза.
Первое число мы ищем в бесконечном цикле while, начиная с двойки - первого простого числа. Второе число мы ищем в цикле for, начиная со следующего числа и заканчивая числом, вдвое большим, чем первое:
# ГЛАВНАЯ ФУНКЦИЯ
def main():
print()
print('И такие есть числа')
print()
solve()
print()
#test()
main()
300
# -*- coding: Windows-1251 -*# Кордемский, с.63, Задача 4-3
import math
# РЕШАЕМ ЗАДАЧУ
def solve():
n1 = 2-1
while True:
n1 += 1
for n2 in range(n1 + 2, 2 * n1 + 1):
Для каждой пары чисел проверяем условие задачи:
if (2 * n2 - n1 != 5 * (2 * n1 - n2)):
continue
Чтобы избежать дробных чисел, мы оба числа умножили на двойку.
Как только оно выполнится, мы печатаем решение на экране и заканчиваем поиски:
s = 'Первое число = ' + str(n1)
print(s)
s = 'Второе число = ' + str(n2)
print(s)
return
Обратите внимание, что для решения этой задачи нам даже не потребовалось проверять числа n1 и n2 на простоту (Рис. 7.8).
Рис. 7.8. Парочисловая задача решена
301
Но если вы закомментируете строку:
#return
то найдёте ещё множество пар чисел, которые удовлетворяют условиям задачи:
если от каждого из них вычесть половину другого, то одна разность будет в 5 раз больше другой.
Но при этом не являются простыми (Рис. 7.9).
Рис. 7.9. Непростое решение простой задачи
Подкорректируем функцию solve:
# РЕШАЕМ ЗАДАЧУ
def solve():
n1 = 2-1
while True:
n1 += 1
if (not isPrime(n1)):
continue
for n2 in range(n1 + 2, 2 * n1 + 1):
if (not isPrime(n2)):
continue
if (2 * n2 - n1 != 5 * (2 * n1 - n2)):
302
continue
s = 'Первое число = ' + str(n1)
print(s)
s = 'Второе число = ' + str(n2)
print(s)
return
Теперь каждое число 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 «правильная».
303
Проект И такие есть числа 4
Исходный код программы находится в папке Кордемский 063
04-4.
Функция без параметров
Вложенные циклы for
Условный оператор if
Оператор continue
Оператор or
Задача 4-4 из книги Удивительный мир чисел
[КА86], страница 63:
Сумма первого числа и квадрата второго равна 57, а сумма второго числа
и квадрата первого равна 71.
Найдите эти числа.
Из условия задачи следует, что оба числа не меньше 1 и не больше 9. Этого
вполне достаточно, чтобы во вложенных циклах for проверить все возможные пары чисел:
# -*- coding: Windows-1251 -*# Кордемский, с.63, Задача 4-3
# РЕШАЕМ ЗАДАЧУ
def solve():
for n1 in range(1, 9 + 1):
for n2 in range(1, 9 + 1):
if (n1 + n2 * n2 != 57 or n1 * n1 + n2 != 71):
continue
s = 'Первое число =' + str(n1)
print(s)
s = 'Второе число = ' + str(n2)
print(s)
return
# ГЛАВНАЯ ФУНКЦИЯ
def main():
304
print()
print('И такие есть числа')
print()
solve()
print()
main()
Перебор оказался очень небольшим, поэтому ответ мы получаем практически мгновенно (Рис. 7.10).
Рис. 7.10. И такие числа мы нашли
305
Проект Ящики
Исходный код программы находится в папке Нагибин 424.
Функция без параметров
Вложенные циклы for
Условный оператор if
Оператор continue
Форматированный вывод
Задача 424 из книги Математическая шкатулка [Нагибин88], страница 81:
Завод должен переслать заказчику 1100 деталей, которые для пересылки
упаковываются в ящики трёх типов. Один ящик первого типа вмещает
70 деталей, второго типа – 40 деталей, третьего типа – 25 деталей. Стоимость пересылки одного ящика первого, второго и третьего типов
равна соответственно 20, 10 и 7 р.
Какие ящики должен использовать завод, чтобы стоимость пересылки бала наименьшей? Недогрузка ящиков не допускается.
Эта типичная задача линейного (точнее - целочисленного) программирования может быть успешно решена простым перебором вариантов.
Примеры задач целочисленного программирования:
задача о назначениях
задача коммивояжера
задача почтальона
задача о максимальном паросочетании
Обозначим через k1, k2 и k3 число ящиков каждого типа. Минимальное
число этих ящиков равно 0. Верхнюю границу легко определить, поделив
общее число деталей на вместимость каждого ящика.
# ГЛАВНАЯ ФУНКЦИЯ
def main():
print()
306
print('Ящики')
print()
solve()
print()
main()
# -*- coding: Windows-1251 -*# Нагибин, с.81, Задача 424
# РЕШАЕМ ЗАДАЧУ
def solve():
DETALI = 1100
K1 = DETALI // 70
K2 = DETALI // 40
K3 = DETALI // 25
Для хранения текущего значения минимальной стоимости пересылки мы
заведём переменную minCost, которой сначала необходимо присвоить какое-либо большое значение:
#мин. стоимость пересылки:
minCost = 1000000
Во вложенных циклах for мы перебираем все варианты «расфасовки» деталей по ящикам, учитывая, что в сумме в них должно оказаться 1100
деталей:
# мин. стоимость пересылки:
minCost = 1000000
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
307
if (minCost >= cost):
minCost = cost
print('k1 = %i k2 = %i k3 = %i' %(k1, k2, k3))
print('Минимальная стоимость = %i' % minCost)
print()
Если разные расфасовки дадут одинаковую стоимость, то мы напечатаем
все, чтобы узнать общее число решений задачи.
Поскольку результаты нашей упаковочной деятельности не ухудшаются по
ходу работы программы, то минимальная стоимость окажется в самом низу
списка, который вы видите на Рис. 7.11.
Итак, решение задачи единственное:
Детали нужно упаковать в 25 ящиков второго типа и в 4 ящика третьего типа. При этом минимальная стоимость пересылки составит 278
рублей.
Рис. 7.11. Экономная упаковка
308
Проект Путёвки
Исходный код программы находится в папке Нагибин 425.
Функция без параметров
Вложенные циклы for
Условный оператор if
Оператор continue
Форматированный вывод
Задача 425 из книги Математическая
шкатулка [Нагибин88], страница 81:
Предполагается использовать 2000 р. на путёвки в дома отдыха.
Путёвки имеются на 15, 27 и 45 дней, стоимость их соответственно 21, 40
и 60 р.
Сколько и каких путёвок нужно купить, чтобы общее число дней отдыха было наибольшим?
Эту задачу можно решить за пару минут, просто слегка изменив предыдущий проект:
# -*- coding: Windows-1251 -*# Нагибин, с.81, Задача 425
# РЕШАЕМ ЗАДАЧУ
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
309
# число дней отдыха:
days = k1*15 + k2*27 + k3*45
if (maxDays <= days):
maxDays = days
print('k1 = %i k2 = %i k3 = %i' %(k1, k2, k3))
print('Максимальное число дней = %i' % maxDays)
print()
# ГЛАВНАЯ ФУНКЦИЯ
def main():
print()
print('Путёвки')
print()
solve()
print()
main()
Замечательно, что, решив одну задачу, потом можно использовать её код
как заготовку при решении множества других подобных задач!
Наша программа советует не покупать путёвки на 15 дней, а сэкономленные деньги потратить на приобретение двух 27- и тридцати двух 45-дневных путёвок. Они позволят трудящимся заслуженно отдыхать 1494 дня (
Рис. 7.12).
Рис. 7.12. Отдыхай с умом!
В книге приведён другой ответ. Вероятно, автор не учёл, что можно вообще
не покупать путёвки какого-либо вида. Чтобы проверить эту догадку, закомментированную строку замените другой:
#if (maxDays <= days):
if (maxDays <= days and k1*k2*k3 != 0):
310
Теперь нулевые значения числа путёвок будут проигнорированы, и программа выдаст «книжные» результаты (Рис. 7.13).
Рис. 7.13. Наши люди отдыхают дольше!
У автора задачи получилось на 15 дней меньше, чем у нас. Да, без компьютера толком не отдохнёшь!
311
Проект На базаре
Исходный код программы находится в папке Кордемский 051
07.
Функция без параметров
Вложенные циклы for
Условный оператор if
Оператор continue
Форматированный вывод
Управляющие последовательности
Задача 7 (2.3) из книги Удивительный
мир чисел [КА86], страница 51:
На базаре за 9 кг орехов и 2 кг фейхоа заплатили столько же денег,
сколько за 6 кг гранатов. А за 6 кг орехов, 5 кг фейхоа и 4 кг гранатов заплатили 43 р.
Сколько стоит 1 кг орехов, фейхоа и гранатов отдельно, если известно, что стоимость каждого продукта выражается целым числом рублей?
Эту базарную задачу легко решить по аналогии с упаковочной - достаточно
учесть её фруктовый контекст.
# -*- 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
312
if (orehi*9 + feihoa*2 != granat*6):
continue
print('Орехи = %i \nФейхоа = %i \nГранаты = %i'
%(orehi, feihoa, granat))
print()
# ГЛАВНАЯ ФУНКЦИЯ
def main():
print()
print('На базаре')
print()
solve()
print()
main()
Теперь вы можете смело отправляться на рынок 1986 года – вас никто не
обманет (Рис. 7.14)!
Рис. 7.14. Деньги любят счёт в банке
313
Проект Дедушка и внучка
Исходный код программы находится в папке Кордемский 052
10.
Функция без параметров
Цикл while
Форматированный вывод
Оператор деления //
Задача 10 (10.1) из книги Удивительный мир
чисел [КА86], страница 52:
Сколько дедушке лет, столько месяцев внучке. Дедушке с внучкой вместе
91 год.
Сколько лет дедушке и сколько внучке?
Самое сложное в этой задаче – сформулировать проверку в операторе
while.
# -*- coding: Windows-1251 -*# Кордемский, с.52, Задача 10
# РЕШАЕМ ЗАДАЧУ
def solve():
# возраст внучки в месяцах:
x = 0
while (x + 12*x != 91*12):
x += 1
print('Возраст внучки в годах = %i' % (x // 12))
print('Возраст дедушки в годах = %i' % (91 - x // 12))
print()
# ГЛАВНАЯ ФУНКЦИЯ
def main():
print()
print('Дедушка и внучка')
print()
314
solve()
print()
main()
Обозначив через х возраст внучки в месяцах, мы найдём возраст дедушки в
годах – тоже х, а в месяцах – 12х. Дальше задача решается – как сыр по маслу
(Рис. 7.15).
Рис. 7.15. Внучка совсем ещё зелёная
315
Проект Сколько у мамы дочерей и сыновей?
Исходный код программы находится в папке Кордемский 055
15.
Функция без параметров
Вложенные циклы for
Условный оператор if
Оператор continue
Оператор деления //
Форматированный вывод
Управляющие последовательности
Это и есть фейхоа!
Задача 15 (4) из книги Удивительный мир чисел [КА86], страница 55:
В семье 12 детей. Мама принесла для них 70 штук фейхоа. Половину всех
фейхоа она раздала дочерям поровну, остальные отдала сыновьям, которые разделили их между собой также поровну. Каждый мальчик получил
на 2 фейхоа больше, чем каждая девочка.
Сколько было у этой мамы дочерей и сыновей?
Оne day англичанин и шотландец нашли клад и решили его поделить.
- Давай поделим его по-честному, - предложил англичанин.
- Нет, давай лучше поровну! – возразил шотландец.
С точки зрения математики, мальчики и девочки ничем не отличаются от
кроликов и фазанов, поэтому эту задачу мы решаем так же, как и про животных на ферме:
# ГЛАВНАЯ ФУНКЦИЯ
def main():
print()
print('Сколько у мамы дочерей и сыновей?')
print()
solve()
print()
316
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 штук. Всё остальное – дело техники, то есть компьютера.
print('Мальчиков = %i
print()
\nДевочек = %i' %(m, d))
А на Рис. 7.16 представлены плоды раздела плодов и полов в семье плодовитой мамы.
Рис. 7.16. Честный делёж
317
Проект Из жизни Дефурнеля
Исходный код программы находится в папке Кордемский 057
24.
Функция без параметров
Цикл while
Комбинированные операторы присваивания
Оператор and
Задача 24 (26) из книги Удивительный мир чисел [КА86],
страница 57:
Некогда в отрывном календаре были приведены любопытные факты из
биографии француза Пьера Дефурнеля. Он был отцом трёх сыновей, родившихся в разных веках. Он сам и старший сын родились в XVII веке. Женился он на девятнадцатом году жизни. Через год стал отцом первого
сына. Спустя 37 лет женился второй раз. Через год стал отцом второго
сына. А ещё через 43 года родилась его будущая третья жена. Через 19 лет
после этого они поженились. Через год родился третий сын. Спустя
8 лет Пьер Дефурнель умер.
Установите годы рождения Пьера Дефурнеля, сыновей, третьей
жены и годы, когда женился Дефурнель и когда он умер?
Поскольку XVII век начинается с 1601 года, то мы можем в цикле while
начать перебор с предыдущего года и добавлять по 1 к текущему числу
year – году рождения Дефурнеля:
# ГЛАВНАЯ ФУНКЦИЯ
def main():
print()
print('Из жизни Дефурнеляа')
print()
solve()
print()
main()
# -*- coding: Windows-1251 -*318
# Кордемский, с.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('Год рождения Дефурнеля: = %i' % year)
print('Год рождения первого сына: = %i' % son1)
print('Год рождения второго сына: = %i' % son2)
print('Год рождения третьего сына: = %i' % son3)
print('Год рождения третьей жены: = %i' %(son2 + 43))
print('Год первой женитьбы: = %i' % (son1-1))
print('Год второй женитьбы: = %i' %(son2-1))
print('Год третьей женитьбы: = %i' %(son3-1))
print('Год смерти Дефурнеля: = %i' %(son3 + 8))
print('Дефурнель прожил: %i' %(son3 + 8 - year))
print()
319
Она представлена в полном объёме на Рис. 7.17.
Рис. 7.17. Компьютерный ЗАГС в действии
320
Проект Счётные палочки
Исходный код программы находится в папке Нагибин 594.
Функция без параметров
Вложенные циклы for
Условный оператор if
Оператор continue
Форматированный вывод
Управляющие последовательности
Задача 594 из книги Математическая шкатулка [Нагибин88], страница
97:
Из 36 счётных палочек построили треугольники, квадраты и домики
(Рис. 7.18) – всего 10 фигур.
Рис. 7.18. Палочные фигуры
Найдите число фигур каждого вида.
Ясно, что из 36 палочек можно построить от 0 до 36//3 треугольников, от
0 до 36//4 квадратов и от 0 до 36//6 домиков. Все варианты строительства
мы, как обычно, перебираем во вложенных циклах for.
# -*- coding: Windows-1251 -*# Нагибин, с.97, Задача 594
# РЕШАЕМ ЗАДАЧУ
def solve():
PALOCHKI = 36
321
FIGUR = 10
T = PALOCHKI // 3
K = PALOCHKI // 4
D = PALOCHKI // 6
for t in range(0, T + 1):
for k in range(0, K + 1):
for d in range(0, D + 1):
if (t + k + d != FIGUR):
continue
if (t*3 + k*4 + d*6 != PALOCHKI):
continue
print('Треугольники = %i \nКвадраты = %i \nДомики =
%i' %(t, d, k))
print()
# ГЛАВНАЯ ФУНКЦИЯ
def main():
print()
print('Счётные палочки')
print()
solve()
print()
main()
Рис. 7.19 уверенно демонстрирует нам 3 решения задачи, хотя в книге ука
- зано только второе.
Рис. 7.19. Строительство закончено
Мы опять можем предположить, что нужно построить все фигуры хотя бы
по одному разу, хотя в книге это требование отсутствует.
322
Задания для самостоятельного решения
Задача 11, страница 52
Удивительный мир чисел
Овчарка погналась за лисой, когда между ними было расстояние 99 м.
Скачок лисы 1,1 м, скачок овчарки 2,2 м. Когда овчарка делает 19 скачков,
лиса делает 29 скачков.
Сколько метров проскачут они, пока овчарка догонит лису?
323
Глава 8. Компьютерные игры
Типичная современная компьютерная игра –
жуть и гадость!
Несмотря на некоторую легковесность названия, программирование компьютерных игр – задача совсем непростая! И даже игры без графики, которыми мы будем здесь заниматься, потребуют от вас недюжинного ума и
мощных усилий!
Проект Игра Охота на Скалоеда
Исходный код программы находится в папке Охота на Скалоеда.
Бесконечный цикл while
Условный оператор if
Оператор break
Цикл for
Вложенные циклы for
Класс random
Операторы or и and
Оператор return
Форматированный вывод
Условный оператор if-else
Вложенные операторы if-else
Бесконечный цикл while
«Компьютер» докомпьютерной эры
В компьютерные игры сражались уже тогда, когда даже самые простые компьютеры были недоступны большинству граждан тогдашнего Советского
Союза.
324
Их место занимали программируемые калькуляторы (ПМК): БЗ-34, МК-61,
МК-52. В журнале Наука и жизнь регулярно публиковались статьи о программировании, а в журнале Техника – молодёжи несколько лет существовал Клуб электронных игр (Рис. 8.1), где читатели делились своими программами.
Рис. 8.1. Эмблема рубрики
В первом номере журнала за 1987 год я нашёл интересную игру Охота на
Скалоеда, которую мы вполне можем «портировать» на современные компьютеры.
Где-то в глубоком космосе, на неведомой планете жило-было-не-тужило суровое существо – Скалоед.
Скалоед непрерывно буровит землю и гранит, выгрызая в них подземные
ходы, что роднит его со Студентами, которые тоже постоянно чего-нибудь
грызут.
По этим ходам Скалоеда может преследовать отважный Охотник - с вполне
естественной целью – уничтожить Скалоеда.
Поле битвы, или - по-научному – Лабиринт можно представить в виде таблицы (Рис. 8.2).
Нумерация горизонталей противоречит нашим представлениям о верхе и
низе, поэтому мы будем нумеровать их сверху вниз и начиная с нуля. Естественно, сам чужеземный мир от этого не перевернётся, а продолжит своё
бренное существование в полном объёме.
325
Рис. 8.2. План Лабиринта
В начале охоты Скалоед занимает место в середине Лабиринта. На Рис. 8.2
его клетка отмечена штриховкой.
Охотник затаился с подветренной стороны в нижнем левом углу, который
у нас неминуемо превратится в верхний левый – с координатами (0,0). На
Рис. 8.2 Охотник никак не отмечен, но память о нём навсегда останется в
наших сердцах, душах и извилинах.
Клетки Лабиринта могут быть двух видов:
Проходимые (свободные ходы) – обозначены единицами
Непроходимые (скальная порода) – обозначены нулями
Начальная позиция Охотника должна находиться в проходимой клетке, и
двигаться Охотник может только по проходимым клеткам.
А вот Скалоед может перемещаться по любым клеткам. При этом он, выгрызая в скальной породе ходы, делает непроходимые клетки проходимыми.
Но, наткнувшись на проходимую клетку, он заваливает её скальной породой и превращает в непроходимую.
Начальная позиция игры может отличаться от той, что показана на Рис. 8.2,
но Охотник обязательно должен занимать проходимую клетку.
326
Ходы Охотник и Скалоед выполняют поочерёдно: сначала ходит Охотник,
затем – Скалоед. Он может переместиться в соседнюю свободную клетку
слева, справа, сверху или снизу. Скалоед также может переместиться на соседнюю клетку по этим же направлениям, но он не ограничен в выборе клеток, поскольку для него они все – проходимые. К сожалению, Скалоед ограничен в другом – он сильно обделён интеллектом, поэтому бродит по Лабиринту совершенно случайно, целиком полагаясь на свою планиду, то есть
на судьбу.
Если в соседней клетке паче чаяния окажется Скалоед, то Охотник торжественно вступает в эту клетку, наносит сопернику удар в темя и отшибает
оба рога, из чего можно сделать поспешный вывод, что Охотника зовут Шурик, а Скалоед живёт вовсе не на неведомой планете, а на Кавказе.
Шурик записывает тосты по случаю победы
Журнальная статья умалчивает о драматической развязке истории, когда
Скалоед первым оказывается в клетке с незадачливым Охотником и пожирает его глазами. Мы исправим эту оплошность, чтобы усимметрить и угармонизировать взаимоотношения особей и сущностей в природе. Если Скалоед опережает Охотника в борьбе за жизненное пространство, то мы будем
вынуждены признать торжество случая над трезвым расчётом Охотника.
327
Охота начинается!
Из этой долгой истории следует, что, в первую очередь,
нам потребуется Лабиринт, в
коем и разворачиваются эти самые исторические события. Поскольку Лабиринт не простой, а
с клетками двух видов – проходимыми и непроходимыми, - то
вполне и очень правильно использовать для него символьный список списков:
# лабиринт:
labyrinth = [[символ] * size for i in range(size)]
Хотя в оригинальной программе клетки обозначены числами, но символы
покажут нам состояние клеток более понятно, чем малоосмысленные числа
– нуль и единица:
# непроходимые и прoходимые клетки:
SKALA_SYM = ':'
HOD_SYM = '.'
Мы не можем ожидать, что прямоугольные лабиринты окажутся занимательнее квадратных, поэтому будем считать, что наш Лабиринт имеет
квадратную форму заданного размера.
Если места для инопланетного сафари окажется мало, то игрок-Охотник,
сможет самопроизвольно выбрать размеры Лабиринта из ограниченного
разумным смыслом диапазона:
# мин. макс. размеры лабиринта:
MIN_SIZE = 8
MAX_SIZE = 30
328
В функции main жаждущий приключения Охотник создаёт игру желаемого
размера и запускает игровой цикл:
# ГЛАВНАЯ ФУНКЦИЯ
def main():
print()
print('Охота на Cкалоеда')
print()
# создаём игру:
game = Game(9)
# начинаем игровой цикл:
game.play()
main()
Как вы видите, здесь почти ничего нового для вас нет, поэтому переходим
в конструктор класса игры, который принимает размер Лабиринта:
# КЛАСС ИГРЫ
class Game():
# КОНСТРУКТОР
def __init__(self, size):
# контролируем ввод пользователя:
if (size >= MIN_SIZE and size <= MAX_SIZE):
self.size = size
else:
self.size = MIN_SIZE
# создаём лабиринт:
self.labyrinth = [[HOD_SYM] * self.size for i in range(self.size)]
Счёт по партиям мы будем хранить в списке result:
# счёт игры:
result = [0] * 2
result[OHOTNIK] = 0
result[SKALOED] = 0
print('Счёт Охотник: %i Скалоед: %i' %(0,0))
Как обычно, мы скрупулёзно контролируем действия пользователя, который в предстартовой горячке может задать несуразные размеры игрового
поля!
329
Игровой цикл начинается с подготовки к новой игре в методе newGame, а
затем Охотник и Скалоед выполняют свои ходы, преисполненные охотничьего азарта и вдохновения:
# НОВАЯ ИГРА
def newGame(self):
# готовимся к новой игре:
self.prepare()
print()
print('Новая игра')
print()
# ИГРОВОЙ ЦИКЛ
def play(self):
while True:
self.newGame()
while True:
# ход Охотника:
self.printPosition()
#return
self.ohotnikMove()
if (self.isGameOver(OHOTNIK)):
break
# ход Скалоеда:
self.printPosition()
self.skaloedMove()
if (self.isGameOver(SKALOED)):
break
Сколько Лабиринту ни виться, а конец у игры будет: либо Охотник завалит
Скалоеда, либо сам попадёт в зубы более удачливому сопернику. Поскольку
игра идёт понарошку, то Охотник может – даже будучи съеденным без
остатка - продолжить свои похождения и всё-таки добыть р(б)огатые трофеи:
# игра окончена - следующая партия?
print()
print('Играем дальше? (1 - да) > ', end='')
s = input()
if (s != '1'):
break
print()
330
Подготовка к игре должна быть весьма основательной, поэтому её нужно
вынести в отдельный метод prepare.
По заданным размерам мы создаём лабиринт. Если вы взглянете на оригинальную картинку из журнала Техника – молодёжи, то увидите, что большую часть его занимают свободные проходы, а скальная порода образует
аккуратные горизонтальные полосы. Именно расположение и длина этих
полос будут разнообразить игру.
Сначала мы заполняем Лабиринт свободными проходами:
# ГОТОВИМСЯ К ИГРЕ
def prepare(self):
# создаём лабиринт:
labyrinth = [[HOD_SYM] * self.size for i in range(self.size)]
Затем в каждой горизонтали заваливаем скальной породой несколько последовательно расположенных клеток, начинающихся в клетке с горизонтальной координатой begin и заканчивающихся в клетке end:
# скальная порода:
for j in range(1, self.size):
# начало:
begin = random.randint(0, self.size-1)
# конец:
end = random.randint(begin, self.size-1)
for i in range(begin, end + 1):
self.labyrinth[i][j] = SKALA_SYM
Так как эти координаты мы задаём случайно, то массив скальной породы
может занимать от 0 до size клеток в каждой горизонтали.
Для генерации случайных чисел мы используем модуль random:
import random
Координаты наших героев удобно хранить в экземплярах класса Coords:
# координаты:
class Coords():
# КОНСТРУКТОР
def __init__(self, x , y):
331
self.x = x
self.y = y
# координаты Скалоеда:
skaloedCoods = Coords(0,0)
# координаты Охотника:
ohotnikCoords = Coords(0,0)
Согласно классическим правилам, Скалоед изначально занимает центральную клетку Лабиринта:
# координаты Скалоеда:
x = self.size // 2
y = self.size // 2
skaloedCoods.x = x
skaloedCoods.y = y
А Охотник – верхнюю левую. Причём эта клетка должна быть проходимой:
# координаты Охотника:
x = 0
y = 0
ohotnikCoords.x = x
ohotnikCoords.y = y
self.labyrinth[x][y] = HOD_SYM
Для большего удобства и взаимопонимания мы обозначили Охотника и
Скалоеда переменными:
# игроки:
OHOTNIK = 0
SKALOED = 1
То же самое относится и к символам, которые показывают положение
Охотника и Скалоеда на игровом поле:
OHOTNIK_SYM = 'O'
SKALOED_SYM = 'C'
Распечатать текущую позицию нам не составит труда – достаточно пройтись по всем клеткам Лабиринта и напечатать соответствующий символ –
332
свободного прохода или скальной породы. Также мы должны помнить и о
наших героях:
# ПЕЧАТАЕМ ТЕКУЩУЮ ПОЗИЦИЮ
def printPosition(self):
# лабиринт:
for y in range(0, self.size):
for x in range(0, self.size):
if (x == skaloedCoods.x and y == skaloedCoods.y):
print(SKALOED_SYM, end='')
elif (x == ohotnikCoords.x and y == ohotnikCoords.y):
print(OHOTNIK_SYM, end='')
else:
print(self.labyrinth[x][y], end='')
print()
print()
На Рис. 8.3, слева хорошо видно, что скальная порода образует непрерывные полосы, которые иногда могут полностью блокировать Охотника в
углу Лабиринта (Рис. 8.3, справа).
Рис. 8.3. Скальная порода – непроходимые клетки
Поэтому в методе prepare лучше оставить верхнюю горизонталь свободной
для прохода:
# скальная порода:
for j in range(1, self.size):
Теперь в игровом цикле пришла очередь Охотнику сделать свой первый
ход.
333
Он, конечно, волен выбрать любое направление, которое согласуется с правилами игры. Однако в спешке он может встать на неверный путь или
нажать неправильную клавишу, поэтому метод ввода хода игрока не столь
прост, как это может показаться с первого взгляда.
В первую очередь, мы обозначим направление ходов буквами:
L(eft) - влево
R(ight) - вправо
U(p) - верх
D(own) – вниз
Возможен ли тот или иной ход, мы проверяем в методе testOhotnik.
Здесь мы вычисляем координаты той клетки, в которую Охотник желает
перейти:
# ПРОВЕРЯЕМ ХОД ОХОТНИКА
def testOhotnik(self, dir):
if (dir == 'L'):
# новые координаты Охотника:
x = ohotnikCoords.x - 1
y = ohotnikCoords.y
if (dir == 'R'):
x = ohotnikCoords.x + 1
y = ohotnikCoords.y
if (dir == 'U'):
x = ohotnikCoords.x
y = ohotnikCoords.y - 1
if (dir == 'D'):
x = ohotnikCoords.x
y = ohotnikCoords.y + 1
Если она выходит за рамки дозволенного, то гипотетический ход невозможен, и метод возвращает Falsе:
# выход за граница лабиринта:
if (x < 0 or x >= self.size or y < 0 or y >= self.size):
return False
Ход допускается только в клетку со Скалоедом и в проходимую клетку:
334
# клетка со Скалоедом:
if (x == skaloedCoods.x and y == skaloedCoods.y):
return True
# свободный ход:
if (self.labyrinth[x][y] == HOD_SYM):
return True
# хода нет:
return False
Метод ohotnikMove получает от метода testOhotnik заключение по каждому
из четырёх возможных направлений и формирует строку smoves, которую
мы используем и для подсказки, и для проверки действий игрока:
# ХОД ОХОТНИКА
def ohotnikMove(self):
# допустимые ходы:
smoves = ''
if (self.testOhotnik('L')):
smoves += 'L '
if (self.testOhotnik('R')):
smoves += 'R '
if (self.testOhotnik('U')):
smoves += 'U '
if (self.testOhotnik('D')):
smoves += 'D '
smoves = smoves.strip()
Но если строка smoves так и осталась пустой, значит, Охотник временно замурован и вынужден ждать раскупорки со стороны Скалоеда:
# некуда ходить:
if (smoves == ''):
return
Если у Охотника имеются ходы, то программа подсказывает ему допустимые направления (Рис. 8.4):
# есть ходы:
move = ''
while True:
print('Ваш ход [%s]> ' %smoves, end='')
335
Рис. 8.4. Подсказка для Охотника
По крайней мере, на первом ходу у него всегда есть ход вправо и, возможно,
вниз.
Игрок должен нажать одну из предложенных ему букв (в любом регистре).
Если же он попытается ввести компьютер в заблуждение, то метод ohotnikMove, не обнаружив его буквы в строке smoves, вежливо, но настойчиво попросит его скорректировать свой ход:
# ход игрока:
move = input().upper()
#print(move)
# неверный ход:
if (not move in smoves):
print('Повторите Ваш ход!\r\n')
else:
break
Практика охоты на Скалоедов показывает, что Охотник должен иметь возможность пропустить ход, просто нажав клавишу ВВОД:
# пропускаем ход:
if (move == ''):
return
С другой стороны, подумайте, как наказывать Охотника за такое бездействие
на протяжении нескольких ходов!
336
Так он может подкараулить Скалоеда, спрятавшись за соседним камешком.
Если же игрок нажмёт верную буквенную клавишу, а затем и клавишу ВВОД,
чтобы перейти в соседнюю клетку, то мы вычисляем его новые координаты:
# новые координаты Охотника:
dir = move[0]
if (dir == 'L'):
ohotnikCoords.x -= 1
if (dir == 'R'):
ohotnikCoords.x += 1
if (dir == 'U'):
ohotnikCoords.y -= 1
if (dir == 'D'):
ohotnikCoords.y += 1
Ход игрока может быть тактическим, при котором он просто перемещается
на новое место в Лабиринте, но может быть и победным – если Охотник попадает в клетку со Скалоедом, который тут же отбрасывает рога и копыта
от неожиданности:
# ПРОВЕРЯЕМ, НЕ ЗАКОНЧЕНА ЛИ ИГРА
def isGameOver(self, player):
# победил Охотник?
if (player == OHOTNIK):
# клетка со Скалоедом:
if (ohotnikCoords.x == skaloedCoods.x and
ohotnikCoords.y == skaloedCoods.y):
return self.gameOver(OHOTNIK)
Контора по приёму рогов и копыт Скалоеда и её дружный коллектив
337
Немного забегая вперёд по тропе войны, мы проверяем и ход Скалоеда, который также одерживает победу, если огорошивает игрока своим стремительным вторжением в его клетку:
# победил Скалоед?
else:
if (ohotnikCoords.x == skaloedCoods.x and
ohotnikCoords.y == skaloedCoods.y):
return self.gameOver(SKALOED)
Во всех остальных случаях игра продолжается дальше:
# игра продолжается:
return False
И тогда Скалоед наносит ответный удар, то есть делает ход конём. Поскольку ума у него нет, то ходит он наудачу. Это значит, что мы должны составить для него список возможных ходов, из которых он милостиво выберет один. Для этого мы пишем метод testSkaloed, аналогичный методу
testOhotnik, но учитываем, что Скалоед может перейти в любую соседнюю
клетку – в пределах Лабиринта:
def testSkaloed(self, dir):
if (dir == 'L'):
# новые координаты Скалоеда:
x = skaloedCoods.x - 1
y = skaloedCoods.y
if (dir == 'R'):
x = skaloedCoods.x + 1
y = skaloedCoods.y
if (dir == 'U'):
x = skaloedCoods.x
y = skaloedCoods.y - 1
if (dir == 'D'):
x = skaloedCoods.x
y = skaloedCoods.y + 1
# выход за граница лабиринта:
if (x < 0 or x >= self.size or y < 0 or y >= self.size):
return False
return True
# ХОД СКАЛОЕДА
def skaloedMove(self):
338
# допустимые ходы:
smoves = ''
if (self.testSkaloed('L')):
smoves += 'L'
if (self.testSkaloed('R')):
smoves += 'R'
if (self.testSkaloed('U')):
smoves += 'U'
if (self.testSkaloed('D')):
smoves += 'D'
В отличие от Охотника, который может передвигаться только по свободным ходам, Скалоед выгрызает ходы в скальной породе и, наоборот, засыпает свободные проходы, то есть состояние клетки, из которой он уходит,
изменяется на противоположное: непроходимая клетка становится проходимой, а проходимая - непроходимой:
if (self.labyrinth[skaloedCoods.x][skaloedCoods.y] == SKALA_SYM):
# делаем проход:
self.labyrinth[skaloedCoods.x][skaloedCoods.y] = HOD_SYM
else:
# засыпаем проход:
self.labyrinth[skaloedCoods.x][skaloedCoods.y] = SKALA_SYM
Направление хода Скалоед выбирает случайно, извлекая букву из строки
smoves:
# выбираем случайный ход:
n = random.randint(0, len(smoves)-1)
dir = smoves[n]
if (dir == 'L'):
skaloedCoods.x -= 1
if (dir == 'R'):
skaloedCoods.x += 1
if (dir == 'U'):
skaloedCoods.y -= 1
if (dir == 'D'):
skaloedCoods.y += 1
print('Ход Скалоеда:', dir)
print()
Если ход одного из участников обоюдоострой охоты окажется результативным, то метод gameOver заканчивает их мытарства и злоключения, печатает обычную в таких случаях надпись GAME OVER, публикует имя победителя и изменяет счёт игры:
339
# ЗАКАНЧИВАЕМ ОЧЕРЕДНУЮ ПАРТИЮ
def gameOver(self, winner):
print()
print('GAME OVER')
if (winner == OHOTNIK):
print('ПОБЕДИЛ ОХОТНИК')
skaloedCoods.x = -1
skaloedCoods.y = -1
else:
print('ПОБЕДИЛ СКАЛОЕД')
print()
self.printPosition()
# общий счёт:
result[winner] += 1
print('Счёт Охотник:%i Скалоед:%i' %(result[OHOTNIK],
result[SKALOED]))
print()
return True
Тут, конечно, труба зовёт нас сразиться с
монстром подземного Лабиринта, и мы
начинаем игру!
Компас не оставляет нам выбора, строго
указывая направо (Рис. 8.5).
340
Рис. 8.5. И вечный бой!
Нажимаем букву R (или r) и клавишу ВВОД – Охотник идёт вправо. Скалоед
действует случайно, но встречно (Рис. 8.6).
Рис. 8.6. Обоюдоострая охота
Мы идём на сближение со Скалоедом и через несколько ходов оказываемся
в непосредственной близости с ним (Рис. 8.7).
Пора задуматься о последствиях! Охотник может отступить, выполнив ход
вверх, или устремиться навстречу Скалоеду, рискуя быть съеденным. Любители шампанского и адреналина выбрали бы ход влево, но вы можете
отступить и подождать, пока на поле не возникнет более выгодная для вас
позиция…
Увы, на этот раз Скалоед оказался проворнее и удачливее Охотника (Рис.
8.8, слева), но у него есть ещё порох в пороховницах и бесконечная тяга к
приключениям (Рис. 8.8, справа)!
341
Рис. 8.7. Уже чувствуется тяжёлое дыхание зверя!
Рис. .8.8 Для кого-то охота всегда удачна
Несмотря на незамысловатый сюжет, игра получилась довольно увлекательной, но всё-таки надо, надо добавить зверушке интеллекта, чтобы
охотник не дремал!
342
Проект Игра Охота на Скалоедов
Исходный код программы находится в папке Охота на Скалоедов.
Списки
Списки списков
Условный оператор if-else
Условный оператор if
Оператор break
Бесконечный цикл while
Вложенные циклы for
Операторы and и or
Оператор return
Функция с параметрами
Функция без параметров
Оператор continue
Класс random
На ловца и зве(п)рь бежит!
Охотницкая поговорка
Игру Охота на Скалоеда вполне можно причислить к пошаговым стратегиям. Да вот только персонажей маловато. Давайте углубим и усугубим
нашу игру, добавив новых Скалоедов!
Например, можно увеличить число Скалоедов до 5:
#макс. число Скалоедов:
MAX_SKALOEDY = 5
К сожалению, объявление этой переменной никак не отразится на нашей
программе, которую нам придётся существенно и основательно передоработать!
Вместо одного Скалоеда на игровом поле будет пастись целый табун монстров:
# список Скалоедов:
skaloedy = []
343
Для удобства изменим идентификаторы переменных:
# игроки:
IOHOTNIK = 0
ISKALOED = 1
OHOTNIK = 'O'
SKALOED = 'C'
EMPTY = ' '
и можно конструировать новую игру:
# КЛАСС ИГРЫ
class Game():
# КОНСТРУКТОР
def __init__(self, size, nsk):
# контролируем ввод пользователя:
if (size >= MIN_SIZE and size <= MAX_SIZE):
self.size = size
else:
self.size = MIN_SIZE
# создаём лабиринт:
self.labyrinth = [[HOD + EMPTY] * self.size
for i in range(self.size)]
result[IOHOTNIK] = 0
result[ISKALOED] = 0
print('Счёт Охотник: %i Скалоед: %i' %(0,0))
# Скалоеды:
if (nsk >= 1 and nsk <= MAX_SKALOEDY):
self.nSkaloedy = nsk
else:
self.nSkaloedy = 1
В конструкторе появился ещё один параметр – число Скалоедов. В функции
main мы задаём максимальное число этих существ:
# создаём игру:
game = Game(9, 5)
344
Игровой цикл почти не изменился по сравнению с предыдущей версией,
но после Охотника свои ходы будут последовательно выполнять все Скалоеды:
# ИГРОВОЙ ЦИКЛ
def play(self):
while True:
self.newGame()
while True:
# ход Охотника:
self.printPosition()
self.ohotnikMove()
if (self.isGameOver(OHOTNIK)):
break
# ход Скалоедов:
self.printPosition()
self.skaloedyMove()
if (self.isGameOver(SKALOED)):
break
# игра окончена - следующая партия?
print()
print('Играем дальше? (1 - да) > ', end ='')
s = input()
if (s != '1'):
break
print()
Метод newGame можно оставить без изменений – он просто вызывает метод prepare для подготовки к новой игре:
# НОВАЯ ИГРА
def newGame(self):
# готовимся к новой игре:
self.prepare()
print()
print('Новая игра')
print()
В методе prepare мы по старому рецепту создаём Лабиринт и выставляем
Охотника в его верхний угол. Однако сам массив labyrinth должен стать более сложным. Действительно, координаты одного Охотника и одного Скалоеда легко проверить, но для 5 Скалоедов эта процедура окажется слишком
длинной, поэтому лучше данные для каждой клетки Лабиринта хранить в
345
строке из двух символов. Первый символ – это статус клетки, а второй –
персонаж игры, то есть Охотник или Скалоед. Обозначим их номера в
строке «говорящими» переменными:
STATUS = 0
WHO = 1
Символ STATUS может принимать значения:
# непроходимые и прoходимые клетки:
SKALA = ':'
HOD = '.'
Так мы сразу определим, сможет ли Охотник перейти на какую-либо клетку
поля.
Символ WHO принимает значения:
OHOTNIK = 'O'
SKALOED = 'C'
EMPTY = ' '
и показывает, находится ли в клетке Охотник, или Скалоед, или она свободна (EMPTY).
При создании Лабиринта мы записываем в каждую клетку Лабиринта символы, обозначающие, что она проходима и пуста:
#ГОТОВИМСЯ К ИГРЕ
def Prepare(self):
# создаём лабиринт:
self.Labyrinth = [[HOD+EMPTY]*self.Size for i in range(self.Size)]
Затем мы засыпаем часть свободных проходов скальной породой, присваивая символу STATUS значение SKALA:
# ГОТОВИМСЯ К ИГРЕ
def prepare(self):
# создаём лабиринт:
346
self.labyrinth = [[HOD + EMPTY] * self.size
for i in range(self.size)]
# скальная порода:
for j in range(1, self.size):
# начало:
begin = random.randint(0, self.size-1)
# конец:
end = random.randint(begin, self.size-1)
for i in range(begin, end+1):
self.labyrinth[i][j] = SKALA + EMPTY
Охотник занимает верхнюю левую клетку, которую мы объявляем свободной (она уже свободна, но дисциплина важнее!):
# координаты Охотника:
x = 0
y = 0
ohotnikCoords.x = x
ohotnikCoords.y = y
self.labyrinth[x][y] = HOD + OHOTNIK
Генерация Скалоедов требует осторожности и аккуратности! Мы не можем
весь скоп Скалоедов засадить в одну центральную клетку поля – их следует
беспорядочно разбросать по просторам Лабиринта. Это несложно сделать,
ведь Скалоед прекрасно себя чувствует и в проходимой, и в непроходимой
клетке. Но было бы неэтично подселить новорождённого Скалоеда в клетку
с Охотником или с другим Скалоедом. Проблема решается просто: если в
случайно выбранной клетке пусто, то в неё можно прописать Скалоеда.
Сущность этого действа заключается в запоминании координат Скалоеда в
списке skaloedy и в присваивании символу WHO соответствующей клетки
Лабиринта значения SKALOED:
# координаты Скалоедов:
nsk = 0
x = 0
y = 0
skaloedy.clear()
while (nsk < self.nSkaloedy):
while (self.labyrinth[x][y][WHO] != EMPTY):
x = random.randint(0, self.size-1)
y = random.randint(0, self.size-1)
skaloedy.append(Coords(x, y))
self.labyrinth[x][y] = self.labyrinth[x][y][STATUS] + SKALOED
nsk += 1
347
На этом ритуальные обряды по подготовке к игре обрываются, и мы можем
во всей красе напечатать текущую (а для нас она - исходная) позицию. Метод printPosition благодаря символам WHO и STATUS получился простым и
изящным:
# ПЕЧАТАЕМ ТЕКУЩУЮ ПОЗИЦИЮ
def printPosition(self):
# лабиринт:
for y in range(0, self.size):
for x in range(0, self.size):
if (self.labyrinth[x][y][WHO] != EMPTY):
print(self.labyrinth[x][y][WHO], end='')
else:
print(self.labyrinth[x][y][STATUS], end='')
print()
print()
При пяти Скалоедах ситуация в Лабиринте может возникнуть такая – Рис.
8.9.
Рис. 8.9. Массированная атака Скалоедов
К счастью, начинает игру Охотник, поэтому уже первым ходом вправо он
может снести голову Скалоеду…
Начало метода ohotnikMove дословно цитирует нашу предыдущую программу, а вот потом появляются новоделы. Поскольку Охотник отмечен в
символе WHO занимаемой им клетки, то, прежде чем двинуться в соседнюю
клетку, он должен выписаться из текущей:
# ХОД ОХОТНИКА
def ohotnikMove(self):
# допустимые ходы:
smoves = ''
348
if (self.testOhotnik('L')):
smoves += 'L '
if (self.testOhotnik('R')):
smoves += 'R '
if (self.testOhotnik('U')):
smoves += 'U '
if (self.testOhotnik('D')):
smoves += 'D '
smoves = smoves.strip()
# некуда ходить:
if (smoves == ''):
return
# есть ходы:
move = ''
while True:
print('Ваш ход [%s]> ' %smoves, end='')
# ход игрока:
move = input().upper()
#print(move)
# неверный ход:
if (not move in smoves):
print('Повторите Ваш ход!\r\n')
else:
break
# пропускаем ход:
if (move == ''):
return
# стираем Охотника в прежней позиции:
self.labyrinth[ohotnikCoords.x][ohotnikCoords.y] =
self.labyrinth[ohotnikCoords.x][ohotnikCoords.y][STATUS]
+ EMPTY
При переселении в новую клетку Охотника может ожидать приятный сюрприз в виде жертвенного Скалоеда, присутствие и наличие (или намордие)
которого легко определить по значению символа WHO новой клетки:
# новые координаты Охотника:
dir = move[0]
if (dir == 'L'):
ohotnikCoords.x -= 1
if (dir == 'R'):
ohotnikCoords.x += 1
if (dir == 'U'):
ohotnikCoords.y -= 1
if (dir == 'D'):
ohotnikCoords.y += 1
349
# клетка со Скалоедом:
if (self.labyrinth[ohotnikCoords.x][ohotnikCoords.y][WHO] == SKALOED):
По закону жизни и игры Охотник и Скалоед не могут мирно существовать
на одной клетке – побеждает тот, кто застиг соперника врасплох. В данной
ситуации подфартило Охотнику, а неудачнику-Скалоеду пришла фиаска, и
его следует вычеркнуть из списка жильцов Лабиринта. В этой версии игры
Скалоед представлен только координатами клетки, поэтому мы должны
найти по ним самого Скалоеда (его индекс в списке skaloedy) и задать ему
такие координаты, которых у здравствующих особей быть может:
# уничтожаем его:
for i in skaloedy:
if (i.x == ohotnikCoords.x and i.y == ohotnikCoords.y):
i.x = -100
i.y = -100
break
Переселив Скалоеда в мир иной – с отрицательными координатами, - мы
избавляемся от его присутствия в Лабиринте, поскольку при любой проверке он окажется далеко вне его.
Покончив с формальностями, мы переносим Охотника в новую клетку:
# риcуем в новой позиции:
self.labyrinth[ohotnikCoords.x][ohotnikCoords.y] =
self.labyrinth[ohotnikCoords.x][ohotnikCoords.y][STATUS] + OHOTNIK
После каждого хода Охотника следует проверить в методе isGameOver, не
извёл ли он уже всех своих врагов. Как вы помните, у живых Скалоедов координаты неотрицательные, а у истреблённых – отрицательные. По этому
признаку мы определяем, остались ли в списке skaloedy здравствующие
твари или вся популяция загублена отважным, но безрассудным Охотником:
# ПРОВЕРЯЕМ, НЕ ЗАКОНЧЕНА ЛИ ИГРА
def isGameOver(self, player):
# победил Охотник?
if (player == OHOTNIK):
# все Скалоеды уничтожены?
flg = True
for i in skaloedy:
350
if (i.x >= 0):
flg = False
break
Если Охотник добился абсолютной виктории, то игре приходит полный
геймовер:
if (flg):
return self.gameOver(IOHOTNIK)
И чтобы не возвращаться в этот метод, посмотрим, как распорядится злая
судьба в случае фатальной неудачи самого Охотника. Если Скалоед окажется шустрее и удачливее Охотника, то мы присвоим ему отрицательные
координаты, с которыми он и перейдёт благополучно в тот же метод
gameOver, но с другим значением параметра:
# победил Скалоед?
else:
if (ohotnikCoords.X < 0):
return self.gameOver(ISKALOED)
Ну а если на естественный вопрос: - Есть кто живой? - мы получим в ответ
мат и рык, значит, игра находится в самом разгаре:
# игра продолжается:
return False
В методе gameOver мы совершаем традиционные обрядовые действия, уже
хорошо вам известные по прошлогоднему сезону охоты:
# ЗАКАНЧИВАЕМ ОЧЕРЕДНУЮ ПАРТИЮ
def gameOver(self, winner):
print()
print('GAME OVER')
if (winner == IOHOTNIK):
print('ПОБЕДИЛ ОХОТНИК')
else:
print('ПОБЕДИЛИ СКАЛОЕДЫ')
print()
351
self.printPosition()
# общий счёт:
result[winner] += 1
print('Счёт Охотник:%i Скалоед:%i' % (result[IOHOTNIK],
result[ISKALOED]))
print()
return True
Но до торжеств и народных гуляний ещё далеко, и после хода игрока наступает черёд Скалоедов, которые пытаются пойти куда глаза глядят.
Чтобы очередного Скалоеда не вынесло прежде времени за границы среды
обитания, мы вызываем метод testSkaloed:
def testSkaloed(self, id, dir):
if (dir == 'L'):
# новые координаты Скалоеда:
x = skaloedy[id].x - 1
y = skaloedy[id].y
if (dir == 'R'):
x = skaloedy[id].x + 1
y = skaloedy[id].y
if (dir == 'U'):
x = skaloedy[id].x
y = skaloedy[id].y - 1
if (dir == 'D'):
x = skaloedy[id].x
y = skaloedy[id].y + 1
# выход за граница лабиринта:
if (x < 0 or x >= self.size or y < 0 or y >= self.size):
return False
# клетка занята Скалоедом:
if (self.labyrinth[x][y][WHO] == SKALOED):
return False
return True
Как видите, ему категорически запрещено покидать Лабиринт или ущемлять жилищные права своих мордастых собратьев.
Если Скалоеду верного пути нет, то он остаётся прозябать в старой клетке:
# ХОД СКАЛОЕДА
def skaloedyMove(self):
for i in range(0, self.nSkaloedy):
352
# допустимые ходы:
smoves = ''
if (self.testSkaloed(i,
smoves += 'L'
if (self.testSkaloed(i,
smoves += 'R'
if (self.testSkaloed(i,
smoves += 'U'
if (self.testSkaloed(i,
smoves += 'D'
'L')):
'R')):
'U')):
'D')):
# некуда ходить:
if (smoves == ''):
continue
Если же горизонт чист, то Скалоед изменяет состояние текущей клетки:
x = skaloedy[i].x
y = skaloedy[i].y
if (self.labyrinth[x][y][STATUS] == SKALA):
# делаем проход:
self.labyrinth[x][y] = HOD + EMPTY
else:
# засыпаем проход:
self.labyrinth[x][y] = SKALA + EMPTY
И переходит в новую, но совершенно случайную клетку:
# выбираем случайный ход:
n = random.randint(0, len(smoves)-1)
dir = smoves[n]
if (dir == 'L'):
skaloedy[i].x -= 1
if (dir == 'R'):
skaloedy[i].x += 1
if (dir == 'U'):
skaloedy[i].y -= 1
if (dir == 'D'):
skaloedy[i].y += 1
Если в новой клетке ему подвернётся под горячую лапу Охотник, то Скалоед
выказывает ему своё отрицательное отношение к охоте в виде новых координат:
x = skaloedy[i].x
353
y = skaloedy[i].y
# в этой клетке Охотник?
if (self.labyrinth[x][y][WHO] == OHOTNIK):
ohotnikCoords.x = -100;
ohotnikCoords.y = -100;
И тут же рисуется в новой клетке:
# рисуем:
self.labyrinth[x][y] = self.labyrinth[x][y][STATUS] + SKALOED
Охотник, буде он ещё жив, получает сообщение о направлении хода очередного Скалоеда:
print('Ход Скалоеда %i: %s' %((i+1), dir))
print()
Отходив, Скалоеды через метод isGameOver узнают, не слопал ли кто-нибудь
из них невезучего Охотника. А тут и сказочной игре
Game Over
Процесс стратегической игры стал ещё увлекательнее, особенно если Охотник не прячется в кустиках, а смело бросается навстречу судьбе (Рис. 8.10)!
354
Рис. 8.10. Особенности русской охоты на Скалоедов
355
Проект Игра Угадай число
Исходный код программы находится в папке Угадай число.
Класс: конструктор, методы, поля
Игровой цикл
Класс random
Условный оператор if-else
Условный оператор if
Оператор break
Оператор or
Вложенные операторы if-else
Оператор return
Бабка надвое сказала:
либо дождик,
либо снег,
либо будет,
либо нет.
Фольклорный пример
бинарного предсказания
Давайте перейдём к программированию менее кровожадных игр! В первой
игре нужно угадать задуманное компьютером число в заданном диапазоне,
пользуясь подсказками. Игра имеет долгую историю, но популярна до сегодняшних дней.
По-русски игра так и называется Угадай число, по-английски - Guess the
Number.
Правила игры
Компьютер загадывает число от 1 до 100, а игрок должен угадать его за
ми- нимальное число попыток. Если число угадано, то компьютер
поздравляет его и загадывает следующее. И так до тех пор, пока игроку не
надоест. Если названное игроком число меньше задуманного, то
компьютер сообщает ему о недоборе, если больше – о переборе.
356
Стратегия игры
Игра очень простая – и стратегия игры тоже не сложнее: нужно каждый раз
называть число, находящееся посередине интервала оставшихся чисел.
Например, на первом ходу игрок должен назвать число 100 : 2 = 50. Если
угадал – сразу молодец! Если перебор, то диапазон уменьшается вдвое – 1..
49. При недоборе – аналогично: 51.. 100. Далее действуем по заранее
утверждённому
плану, без особых затей. Легко подсчитать, что
потребуется не более 8 ходов, чтобы угадать любое число от 1 до 100. Если
вам этого мало, загадывайте числа побольше.
Уточним. Минимальное загаданное число – 1, максимальное – 100. Разность
составляет 100 – 1 = 99. Половина разности равна 99 : 2 = 49.5. Поскольку
загадано целое число, то дробное нужно округлить: до меньшего – 49 или до
большего – 50. Таким образом, на первом ходу можно называть любое из
чисел. Дальше действуйте аналогично.
Несмотря на кажущуюся простоту, наша стратегия является хорошим примером двоичного (бинарного) поиска. Действительно, с каждым ходом
число претендентов уменьшается вдвое: вначале их было 100 (Рис. 8.11),
после первого хода - 50, затем - 25 – 13 – 7 – 4 – 2 – 1. Обычно этот метод
используют для поиска нужного элемента в упорядоченном массиве. Наше
применение немного нетрадиционное, но зато очень эффектное (Рис. 8.12)!
Рис. 8.11. Первый ход
Поскольку все игры, как и телевизионные сериалы, пишутся по одному сценарию, то класс игры можно позаимствовать у Охоты на Скалоеда. Угадайка
гораздо проще охоты, поэтому некоторые методы можно упразднить.
Чтобы сделать игру более универсальной, мы позволим игроку самостоятельно выбирать верхнюю границу чисел (нижняя всегда равняется единице) из диапазона MIN_NUM.. MAX_NUM:
357
Рис. 8.12. Бинарная угадайка в действии
# КЛАСС ИГРЫ
class Game():
# мин. загаданное число:
MIN_NUM = 15
# макс. загаданное число:
MAX_NUM = 1000
В классическом варианте это всегда сотня, в нашей игре верхняя граница
хранится в переменной:
# макс. загаданное число:
maxNumber = ...
Из диапазона 1.. maxNumber компьютер и загадает число:
358
# загаданное число:
number = ...
А число, которое назовёт игрок, мы сохраним в переменной:
# названное число:
num = ...
Хотя число попыток в игре не ограничивается, но статистика неумолимо
ведётся:
# число попыток:
nAttempt = ...
Для выбора случайного числа мы, как водится, подключаем модуль random:
import random
Конструктор игры получает от пользователя верхнюю границу диапазона,
проверяет её и сохраняет в поле maxNumber:
# КОНСТРУКТОР
def __init__(self, num):
# контролируем ввод пользователя:
if (num >= self.MIN_NUM and num <= self.MAX_NUM):
self.maxNumber = num
else:
self.maxNumber = 100
В функции main мы создаём классический вариант игры:
# ГЛАВНАЯ ФУНКЦИЯ
def main():
print()
print('Угадай число')
print()
359
# создаём игру:
game = Game(100)
# начинаем игровой цикл:
game.play()
main()
После создания игры начинается игровой цикл:
# ИГРОВОЙ ЦИКЛ
def play(self):
while True:
self.newGame()
#return
while True:
self.attempt()
if (self.isGameOver()):
break
# игра окончена - следующая партия?
print()
print()
print('Играем дальше? (1 - да) > ', end ='')
s = input()
if (s != '1'):
break
print()
Он даже проще, чем в наших охотничьих играх! Сначала компьютер готовится к новой игре:
# НОВАЯ ИГРА
def newGame(self):
# готовимся к новой игре -->
# загадываем число:
self.number = random.randint(1, self.maxNumber)
# обнуляем число попыток:
self.nAttempt = 0
print()
print('Новая игра')
print()
print('Отгадайте число от 1 до %i!' % self.maxNumber)
print()
360
Он загадывает число, случайно выбирая его из заданного диапазона, обнуляет число попыток и сообщает игроку диапазон, в котором находится загаданное число.
Для классического варианта приглашение к игре выглядит так (Рис. 8.13).
Рис. 8.13. Добро пожаловать!
Отзывчивый игрок вводит число с клавиатуры и нажимает клавишу ВВОД
(Рис. 8.14):
# ПЫТАЕМСЯ УГАДАТЬ ЧИСЛО
def attempt(self):
# попытка:
self.nAttempt += 1
print()
print('Попытка #%i > ' % self.nAttempt, end='')
self.num = int(input())
Рис. 8.14. Первая попытка
По правилам игры, компьютер в ответ не ходит, а только комментирует попытку игрока и следит за окончанием игры:
# ПРОВЕРЯЕМ, НЕ ЗАКОНЧЕНА ЛИ ИГРА
def isGameOver(self):
if (self.num > self.MAX_NUM or self.num < 1):
print(' Совсем мимо!!!', end='')
361
return False
if (self.num == self.number):
print(' Поздравляю, угадал!', end='')
return True
else:
if (self.num > self.number):
print(' Перебор!')
else:
print(' Недобор!')
# игра продолжается:
return False
Если число угадано, то игрок принимает поздравление, во всех остальных
случаях игра продолжается, но игрок получает подсказку, которая облегчает ему бремя выбора следующего числа.
Если игрок никогда не участвовал в шоу Поле чудес, то очень скоро он число
угадает, а затем либо закончит игру, либо продолжит её.
Очень простая, но увлекательная игра (Рис. 8.15)!
Рис. 8.15. Компьютерная угадайка
362
Проект Игра Крестики-нолики
Исходный код программы находится в папке Tic-Tac-Toe..
Класс: конструктор, методы, поля
Игровой цикл
Список списков
Класс random
Список
Вложенные операторы if-else
Вложенные циклы for
Оператор or
Бесконечный цикл while
Оператор return
Мальчик с нашей улицы
С девочкой играл
На асфальте крестики
Мелом рисовал
Шла над южным городом
Летняя пора
Крестики - нолики
Детская игра.
Детская песня
Крестики- нолики – игра, знакомая каждому с детства! На поле 3 х 3
клетки 2 игрока поочерёдно выставляют свой значок: первый игрок –
крестик, второй – нолик. Выигрывает тот, кто первым составит из трёх
своих значков непрерывный ряд – по горизонтали, по вертикали или по
диагонали.
Давно известно, что при правильной игре каждая партия в Крестики-нолики заканчивается вничью, но игра до сих пор привлекает внимание детей, взрослых и программистов…
По-английски игра называется Cross and Naught, Cross & Zero, Tic-Tac-Toe.
Поля класса Game отражают правила игры в крестики и нолики.
Размеры доски постоянны, поэтому их следует сохранить в переменной:
363
# КЛАСС ИГРЫ
class Game():
# размеры доски:
SIZE = 3
Игровое поле (доска) квадратное, что диктует нам хранение текущей позиции в списке списков:
# игровое поле:
board = []
Сначала все клетки поля пустые – IEMPTY, затем на них появляются символы PLAYER игрока (IPLAYER) и компьютера – COMPUTER (ICOMPUTER),
соответственно:
# игроки:
IPLAYER = 1
ICOMPUTER = 10
IEMPTY = 0
PLAYER = 'X'
COMPUTER = 'O'
EMPTY = '.'
Здесь требует пояснения только выбор значения для кода компьютера в
списке board. Казалось бы, это могла быть двойка, но при подсчёте крестиков и ноликов на доске очень важно, чтобы значения кода игрока и кода
компьютера существенно отличались (см. дальше).
Назначение остальных полей понятны без лишних слов:
# номер текущего хода:
nMove = 0
# счёт игры:
result = [0] * 2
Поскольку выбор параметров игры отсутствует, то в конструкторе мы
только обнуляем счёт и показываем его на экране:
364
# КОНСТРУКТОР
def __init__(self):
# счёт:
self.result[0] = 0
self.result[1] = 0
print('Счёт Игрок: %i Компьютер: %i' %(0, 0))
print()
В функции main почти ничего менять не нужно по сравнению с нашими более ранними проектами:
# ГЛАВНАЯ ФУНКЦИЯ
def main():
print()
print('Крестики-нолики')
print()
# создаём игру:
game = Game()
# начинаем игровой цикл:
game.play()
main()
Как и велено правилами, соперники ходят по очереди. Сначала – Игрок потом – Компьютер. Игра заканчивается, если:
Один из игроков выстроил непрерывную цепочку из трёх символов
Все клетки заняты. В этом случае фиксируется ничья
Таким образом, после каждого хода мы печатаем на экране текущую позицию, после чего Игрок и Компьютер ставят свой значок в свободную клетку,
а мы проверяем, не закончилась ли игра:
# ИГРОВОЙ ЦИКЛ
def play(self):
while True:
self.newGame()
while True:
self.printPosition()
self.playerMove()
if (self.isGameOver(IPLAYER)):
365
break
self.printPosition()
self.computerMove()
if (self.isGameOver(ICOMPUTER)):
break
# игра окончена - следующая партия?
print()
print()
print('Играем дальше? (1 - да) > ', end ='')
s = input()
if (s != '1'):
break
print()
Прежде всего, нужно научиться печатать текущую позицию на экране. Как
всегда, за это отвечает метод printPosition.
В нём мы просматриваем все элементы списка board и по их значениям печатаем соответствующий символ в консольном окне:
# ПЕЧАТАЕМ ТЕКУЩУЮ ПОЗИЦИЮ
def printPosition(self):
# оцифровка:
print(' ABC')
# доска:
for y in range(0, self.SIZE):
print(str(y + 1) + ' ', end='')
for x in range(0, self.SIZE):
if (self.board[x][y] == IEMPTY):
print(EMPTY, end='')
elif (self.board[x][y] == IPLAYER):
print(PLAYER, end='')
else:
print(COMPUTER, end='')
print()
print()
Здесь же мы должны подумать и о том, как игрок будет вводить свой ход,
ведь теперь мы не можем ограничиться одной буквой, как в Скалоедах.
Наверно, удобнее всего обозначить горизонтали цифрами, а вертикали – латинскими буквами, как это принято в шахматах или шашках (Рис. 8.16).
366
Рис. 8.16. Шахматная оцифровка
Правда, нам удобнее считать горизонтали сверху вниз, а не снизу вверх, но
это непринципиально.
Тогда начальная позиция, с пустыми клетками будет выглядеть так, как показано на Рис. 8.17.
Рис. 8.17. Начальная позиция
По-моему, получилось внятно…
Теперь Игрок должен выполнить свой ход, для чего мы отправляемся в метод playerMove:
# ХОД ИГРОКА
def playerMove(self):
LETTERS = ['A','B','C']
NUMBERS = ['1','2','3']
#ход:
self.nMove += 1
367
Игрок получает подсказку: сначала нужно напечатать букву (в любом регистре), обозначающую вертикаль, затем цифру – обозначающую горизонталь (Рис. 8.18, слева):
# вводим кординаты клетки:
while True:
print()
print('Ход #%i [БукваЦифра] > ' % self.nMove, end='')
move = input().upper()
Рис. 8.18. Ходьба начинается
Если Игрок хорошо владеет собой, пальцами и клавиатурой, то он напечатает строку из двух символов (Рис. 8.18, справа).
Эти символы мы легко извлекаем из строки пользователя:
# координаты клетки игрока:
x = move[0]
y = move[1]
После чего проверяем его:
Если символы из строки Игрока не входят в списки LETTERS и
NUMBERS, то Игрок должен повторить ход
Если Игрок пытается поставить свой значок в уже занятую клетку, то
мы сообщаем ему, что эта клетка уже занята
368
# проверяем ход:
if (not(x in LETTERS) and (y in NUMBERS)):
print('Повторите ввод!')
continue
x = LETTERS.index(x)
y = NUMBERS.index(y)
if (self.board[x][y] != IEMPTY):
print('Эта клетка уже занята!')
else:
break
В этих неблагоприятных случаях Игрок должен ввести новые координаты
клетки.
Если же верная рука Игрока не дрогнула, то мы заносим в список board его
код, который при печати позиции будет заменён символом Игрока, то есть
крестиком:
# ставим крестик:
self.board[x][y] = IPLAYER
print()
Поскольку элементы в списке board имеют числовые индексы, которые
начинаются с нуля, то необходимо по символам Игрока вычислить координаты клетки в массиве.
Мы знаем, что первый символ строки – это буква (латинская!): A, B или C.
Им соответствуют индексы 0, 1, 2. Их легко получить с помощью метода index:
x = LETTERS.index(x)
y = NUMBERS.index(y)
Как известно, лучший первый ход – в самую сердцевину игрового поля, в
клетку В2 (Рис. 8.19).
Наш Игрок не устоял перед искушением и сделал именно такой ход. К сожалению, ход слишком предсказуем и делает игру однообразной, поэтому,
возможно, первый ход нужно выбирать случайно…
369
Рис. 8.19. Первый ход – не в бровь, а в глаз
Однако пора и Компьютеру поставить свой нолик! Увы, без нашей помощи
ему не обойтись, поэтому бросаем все силы на разработку метода computerMove. Начало – очевидно:
def computerMove(self):
# ход:
self.nMove += 1
Поскольку Игрок уже сделал первый ход, то Компьютеру предстоит достойно ответить на него. Скорее всего, Игрок выбрал центральную клетку.
А может быть, и нет – люди коварнее компьютеров! В последнем случае
Компьютеру следует немедля занять центральную клетку, и вот почему. Через неё проходит 4 (Рис. 8.20, слева) из 8 трёхклеточных рядов (Рис. 8.20,
справа), это значит, что нолик в этой клетке позволит продолжить ряд в
наибольшем числе направлений по сравнению с периферийными клетками.
Рис. 8.20. Жадный выбор хода
370
Если же Игрок оккупировал центр поля, то Компьютеру следует занять
одну из угловых клеток (из соображения симметрии - любую). Опять смотрим на Рис. 8.21, слева – через угловую клетку проходит 3 ряда, а через
остальные периферийные клетки – только два (Рис. 8.21, справа).
Рис. 8.21. Жадность – не порок!
Координаты угловых клеток состоят только из нулей и двоек (Рис. 8.22),
что очень удобно при выборе случайного угла:
Рис. 8.22. Координаты угловых клеток
def computerMove(self):
# ход:
self.nMove += 1
# если это первый ход компьютера:
if (self.nMove == 2):
# если Игрок пошёл в центр:
if (self.board[1][1] == IPLAYER):
371
# выбираем один из четырёх углов:
x = random.randint(0,1) * 2
y = random.randint(0,1) * 2
return self.doMove(x, y)
# если Игрок не пошёл в центр,
# ставим нолик в центр:
else:
x = 1
y = 1
return self.doMove(x, y)
# ХОД КОМПЬЮТЕРА
def doMove(self, x, y):
LETTERS = ['A','B','C']
NUMBERS = ['1','2','3']
print('Ход #%i Компьютера: %s ' % (self.nMove, LETTERS[x] +
NUMBERS[y]))
# ставим нолик:
self.board[x][y] = ICOMPUTER
print()
Проверяем теорию на практике. Когда Игрок занимает центр, Компьютер
отвечает ходом в угол (Рис. 8.23).
Если Игрок хитрит, то Компьютер ставит нолик в центр поля (Рис. 8.24).
Итак, мы научили Компьютер правильно выбирать свой первый ход.
Рис. 8.23. Угловой удар головой
372
Рис. 8.24. Занимаем центр поля
Забудем на время о втором ходе Компьютера и перейдём в эндшпиль. Если
на поле возникнет одна из ситуаций, показанных на Рис. 8.25, слева, то Компьютер должен поставить нолик в свободную клетку, чтобы он дополнил
ряд до трёх ноликов. Этот ход принесёт Компьютеру победу!
Рис. 8.25. Победные ходы
Если же следующий ход может принести победу игроку (Рис. 8.25, справа),
то Компьютер должен упредить соперника и разорвать цепочку крестиков
(Рис. 8.26).
Для поиска таких позиций мы напишем специальный метод test00XX. Он
проверяет все 8 рядов игрового поля и ищет 2 символа игрока (нолики
или крестики) в ряду, в котором также имеется пустая клетка для
совершения хода. Так как в списке board крестики Игрока имеют значение
IPLAYER, а нолики Компьютера – ICOMPUTER, то сумму этих значков легко
найти:
373
def test00XX(self, player):
# сумма двух значков заданного игрока:
summa = player * 2
Рис. 8.26. Не забывайте об обороне!
Поскольку пустая клетка имеет в массиве нулевое значение IEMPTY, то,
найдя заданную сумму summa в массиве, мы смело можем утверждать, что
в проверяемом ряду ровно 2 символа заданного игрока и 1 пустая клетка.
В эту клетку и следует выполнять ход, поэтому её координаты нужно запомнить по ходу поиска. Как только искомая комбинация будет найдена,
дальнейшие поиски прекращаются, и метод возвращает координаты пустой клетки:
# ищем ряд из двух значков игрока -->
# проверяем горизонтали:
for y in range(0, self.SIZE):
sum = 0
for x in range(0, self.SIZE):
if (self.board[x][y] == IEMPTY):
emptycell = x
sum += self.board[x][y]
if (sum == summa):
return emptycell, y
# проверяем вертикали:
for x in range(0, self.SIZE):
sum = 0
for y in range(0, self.SIZE):
if (self.board[x][y] == IEMPTY):
emptycell = y
sum += self.board[x][y]
374
if (sum == summa):
return x, emptycell
# проверяем диагонали:
sum = 0
for y in range(0, self.SIZE):
if (self.board[y][y] == IEMPTY):
emptycell = y
sum += self.board[y][y]
if (sum == summa):
return emptycell, emptycell
sum = 0
for y in range(self.SIZE-1, 0-1, -1):
if (self.board[y][y] == IEMPTY):
emptycell = y
sum += self.board[self.SIZE - y - 1][y]
if (sum == summa):
return (self.SIZE - emptycell-1), emptycell
Если ни в одном ряду не окажется пары символов с пустой клеткой, то метод вернёт отрицательные координаты:
# двух символов нет:
return -1, -1
Все ходы, кроме первого, Компьютер выбирает с помощью следующего
кода:
# последующие ходы:
else:
# если есть 2 нолика и пустая клетка
# в каком-либо ряду,
# то ставим нолик:
x,y = self.test00XX(ICOMPUTER)
if (x != -1):
return self.doMove(x, y)
# если есть 2 крестика и пустая клетка
# в каком-либо ряду,
# то ставим нолик:
x,y = self.test00XX(IPLAYER)
if (x != -1):
return self.doMove(x, y)
375
На Рис. 8.27, слева вы видите, что Игрок выстроил в верхней горизонтали 2
крестика, но Компьютер умело защищается, пресекая это ряд ноликом.
Рис. 8.27. Хорошая обороноспособность!
Легкомысленный Игрок не заметил угрозы со стороны Компьютера и сделал опрометчивый ход, на что Компьютер безжалостно достраивает восходящую диагональ очередным ноликом и уверенно побеждает (Рис. 8.27,
справа)!
Дебют и эндшпиль этой незамысловатой игры мы разобрали по косточкам.
Осталось решить вопрос с миттельшпилем, когда очевидных ходов нет и
нужно выбирать одну из пустых клеток.
Как мы выяснили, вполне разумно занять пустующую угловую клетку, а
ежели это нам не удастся, то любую из свободных:
# если нет двух символов,
# то ходим в пустой угол:
for i in range(0, 100):
x = random.randint(0,1) * 2
y = random.randint(0,1) * 2
if (self.board[x][y] == IEMPTY):
return self.doMove(x, y)
# если не удалось поставить нолик в угол,
# ходим в случайную свободную клетку:
while True:
x = random.randint(0,2)
y = random.randint(0,2)
if (self.board[x][y] == IEMPTY):
376
return self.doMove(x, y)
Здесь нам приходится полагаться на случайность, но в целом Компьютер
играет довольно разумно.
Обычно в подобных играх для выбора лучшего (или хотя бы хорошего) хода
используют весьма непростой алгоритм минимакс, но для такой примитивной
игры это было бы неоправданной роскошью.
После каждого хода игроков нужно проверить, не закончилась ли игра. А
это событие может произойти в трёх случаях (Рис. 8.28):
Игрок выстроил ряд из трёх крестиков – победа Игрока
Компьютер выстроил ряд из трёх ноликов – победа Компьютера
На доске не осталось пустых клеток – ничья
Рис. 8.28. Исход игры
В методе isGameOver мы проверяем, остались ли у игроков ходы, и ищем
ряды из трёх одинаковых символов:
# ПРОВЕРЯЕМ, НЕ ЗАКОНЧЕНА ЛИ ИГРА
def isGameOver(self, player):
# ничья?
if (self.nMove == self.SIZE*self.SIZE):
return self.gameOver(0)
# сумма трёх значков заданного игрока:
summa = player * self.SIZE
# ищем ряд из трёх значков игрока -->
# проверяем горизонтали:
for y in range(0, self.SIZE):
sum = 0
for x in range(0, self.SIZE):
sum += self.board[x][y]
377
if (sum == summa):
return self.gameOver(player)
# проверяем вертикали:
for x in range(0, self.SIZE):
sum = 0
for y in range(0, self.SIZE):
sum += self.board[x][y]
if (sum == summa):
return self.gameOver(player)
# проверяем диагонали:
sum = 0
for y in range(0, self.SIZE):
sum += self.board[y][y]
if (sum == summa):
return self.gameOver(player)
sum = 0
for y in range(self.SIZE-1, 0-1, -1):
sum += self.board[self.SIZE - y - 1][y]
if (sum == summa):
return self.gameOver(player)
# игра продолжается:
return False
По результатам проверки игра либо продолжается, либо заканчивается в
методе gameOver:
# ЗАКАНЧИВАЕМ ОЧЕРЕДНУЮ ПАРТИЮ
def gameOver(self, winner):
print()
print('GAME OVER')
print()
if (winner == IPLAYER):
print('ПОБЕДИЛ ИГРОК')
self.result[0] += 1
elif (winner == ICOMPUTER):
print('ПОБЕДИЛ КОМПЬЮТЕР')
self.result[1] += 1
else:
print('НИЧЬЯ')
print()
self.printPosition()
# общий счёт:
print('Счёт Игрок:%i Компьютер:%i' % (self.result[0],
378
self.result[1]))
return True
И тут можно поставить на игре жирный
Х
379
Задания для самостоятельного решения
Волк и овцы
Волк и овцы - это старинная игра с шашками. В классическом варианте
шашки расставляют на чёрных полях доски 8 х 8 клеток так, как на Рис.
8.29.
Рис. 8.29. Начальная позиция
4 шашки – это овцы. Они могут ходить только вперёд на соседнюю свободную чёрную клетку (то есть по диагонали).
1 шашка другого цвета (обычно чёрная) – волк. Он выполняет ходы на
соседние чёрные клетки – как вперёд, так и назад. Начальная позиция
волка чаще находится у края доски, но это не обязательно.
Волк ходит первым, затем – одна из овец, затем опять волк, и так далее
по очереди.
380
Цель волка – дойти до противоположного края доски (или оказаться позади всех овец, что практически то же самое).
Цель овец – оттеснить волка к краю доски, чтобы он не смог выполнить
очередной ход.
Строгой теории игры нет, но практика свидетельствует, что при правильной игре овцы всегда побеждают.
Существуют варианты игры, в которых размеры доски 9 х 9 клеток, а
число овец – 4 или 3. В последнем случае легко побеждает волк.
Напишите игру Волк и овцы, взяв за основу программу Охота на Скалоедов.
Роль Охотника здесь переходит к волку, а Скалоедов – к овцам. Ходы выполняются не по горизонталям и вертикалям, а по диагоналям, но в целом игра Волк и овцы не очень сильно отличается от Охоты на Скалоедов.
В журнале Техника – молодёжи, №8 за 1986 год, на страницах 44-46 рассказывается о программировании этой игры на микрокалькуляторах.
В №11 за тот же год была напечатана статья Требуется выигрышная
стратегия, которая получила продолжение уже в следующем году. В номерах 6 и 7 обсуждаются приёмы – как программирования, так и собственно процесса игры.
Угадай число
Напишите программу, в которой вы загадываете число, а компьютер отгадывает его.
Крестики-нолики
Напишите программу для игры в крестики и нолики вдвоём, то есть
один игрок против другого. Задание очень простое.
381
В книге Марка Михаэлиса (Mark Michaelis) Essential C# 4.0 программа для
игры в Крестики-нолики используется как пример использования различных конструкций языка Си-шарп. На страницах 869-874 приведён полный листинг программы.
В другой книге – Брэдли Джонс (Bradley L. Jones) Sams Teach Yourself the C#
Language in 21 Days – на страницах 617-628 также напечатан листинг про
- граммы для игры в Крестики-нолики для двух игроков.
Компьютерные игры для двух игроков экономят бумагу, но расходуют
электроэнергию. Ещё это неплохое упражнение в программировании.
Других преимуществ у игр на компьютере без компьютера я не вижу.
382
Глава 9. Комбинаторика и теория
вероятностей
Комбинаторика - это раздел математики, который изучает множества (совокупности, наборы) каких-либо элементов. Первая книга по комбинаторике вышла в 1666 году под названием Рассуждения о комбинаторном искусстве. Её написал известный немецкий математик Готфрид Вильгельм
фон Лейбниц, который и придумал название комбинаторика для этого разделу математики.
Как в жизни, так и в программировании комбинаторные задачи встречаются очень часто. Например, сколько различных слов можно составить из
букв русского алфавита? Сколько существует различных комбинаций при
игре в кости двумя или тремя кубиками? Сколько разных нарядов можно
составить из трех юбок и четырёх блузок и так далее.
Все комбинаторные задачи решаются с помощью комбинаторных конфигураций: размещений, перестановок, сочетаний, композиций и разбиений.
Перестановки
А начнём мы наши комбинаторные упражнения с перестановок элементов.
Возьмём множество, состоящее из трёх разных элементов, например, русских букв - {К, О, Т}. Всякое «слово», составленное из всех этих букв без повторений, и называется перестановкой элементов множества.
Поскольку в множестве элементы не упорядочены, то мы выпишем их сначала в произвольном порядке. Например, так:
1. КОТ
Вот мы и получили первую перестановку, а значит, и первое слово – КОТ.
Оно «случайно» совпало с настоящим русским словом. Чтобы найти вторую перестановку, поменяем местами вторую и третью буквы:
2. КТО
Тоже получилось неплохо, ведь кто - это русское местоимение.
Давайте поменяем теперь первую и вторую буквы:
3. ТКО
383
Такого слова нет (в старой речи была частица –тко, имеющая тот же
смысл, что и современная -ка: бери-тко, читай-тко), а вот четвёртая перестановка снова удачная. Чтобы её получить, поменяем местами вторую
и третью буквы:
4. ТОК
Снова меняем первую и вторую буквы - и получаем пятую перестановку:
5. ОТК
Не ахти какое слово, но Отдел технического контроля тоже сгодится.
И последнюю перестановку мы найдём, переставив две последние буквы:
6. ОКТ
ОКТ – тоже сокращение от Оптическая когерентная томография, так что
все наши перестановки из трёх букв К, О, Т оказались не совсем бессмысленными. Конечно, с другими буквами результат был бы другим.
Но почему мы можем утверждать, что нашли все перестановки множества
из трёх элементов? – Давайте рассуждать логически. На первом месте в
слове может стоять любая из трёх букв. На второе место можно поставить
любую из двух оставшихся, а для последнего места останется только одна
буква. Таким образом, всего можно составить 3 х 2 х 1 = 6 разных слов. Но
мы ровно столько и составили, значит, других слов из этих букв составить
нельзя (естественно, мы используем каждую букву только один раз!).
Если взять множество из четырёх разных элементов, то, рассуждая аналогично, мы придём к выводу, что из них можно составить 4 х 3 х 2 х 1 = 24
разных слова. Этот ряд легко продолжить сколь угодно далеко, а произведение чисел от единицы до заданного называется факториалом.
384
Проект Генерируем перестановки
Исходный код программы находится в папке Перестановки
Рекурс.
Бесконечный цикл while
Метод int
Условный оператор if
Оператор return
Списки
Цикл for
Функция с параметрами
Условный оператор if-else
Рекурсивная функция
Мы научились подсчитывать общее число перестановок. Ну пусть мы знаем,
что из трёх разных букв можно составить 3! разных слов, но вопрос в том,
как получить эти перестановки? С множеством из трёх элементов мы легко
справились, а если взять больше – четыре, пять, а то и восемь?
Оказывается, мы не первые, кто задался этим вопросом. Ещё в семнадцатом
веке английские звонари научились выбивать на нескольких разных колоколах «мелодии», состоящие из всех перестановок этих колоколов. Например, для трёх колоколов нужно было сыграть такую мелодию:
Первый колокол – второй – третий.
Второй колокол – первый – третий.
Дальше вы и сами продолжите эту музыку.
Колоколов, конечно, было больше, а последовательность колокольных ударов нужно было держать в голове. Например, в Книге рекордов Гиннеса рассказывается о том, что в 1963 году за 17 с лишним часов удалось выбить
на восьми колоколах все 8! = 40320 перестановок. В семнадцатом веке, конечно, эти музыкально-комбинаторные экзерсисы были короче, но, тем не
менее, запомнить многие сотни перестановок было совсем непросто, поэтому звонари придумывали свои способы для «генерирования» всех коло
- кольных перестановок. Один из таких способов мы и положим в основу
ком- пьютерной программы, которая быстро и правильно выпишет все
переста- новки элементов заданного множества.
385
Нам вполне достаточно одной переменной MAX_ELEM для ограничения
числа элементов в множестве (иначе их печать на экране займёт очень
много времени) и одной целой переменной n_perm – для хранения числа
перестановок:
# -*- coding: Windows-1251 -*# Рекурсивная генерация перестановок
# макс. число элементов в
# множестве:
MAX_ELEM = 8
# число перестановок:
n_perm = 0
В функции main пользователь задаёт число элементов множества в диапазоне 1.. MAX_ELEM, после чего мы создаём список а и заполняем его числами
1.. MAX_ELEM. Так мы получаем первую перестановку:
1 2 3 4 … MAX_ELEM
# ГЛАВНАЯ ФУНКЦИЯ
def main():
print('Генерируем перестановки')
print()
global n_perm
# бесконечный цикл ввода данных #^пока пользователь не закроет программу
# или не введт 0:
while True:
s = "Число элементов (1.." + str(MAX_ELEM) +") >
print(s, end = '')
# число элементов:
n_elem = int(input())
# если пользователь ввёл 0,
# то программу закрываем:
if (n_elem == 0): return
"
# число перестановок:
n_perm = 0
# список для хранения очередной перестановки -->
# начальная перестановка:
a = list(range(1, n_elem+1))
386
Генерирование всех остальных перестановок, а также их печать происходит
в рекурсивной функции permutation_rec, которой мы передаём три аргумента:
1. всегда 0
2. число элементов в списке n_elem
3. список a
n_perm = 0
# генерируем перестановки:
permutation_rec(0, n_elem, a)
print("Число перестановок =", n_perm)
print()
В функции permutation_rec мы переставляем элементы списка до тех пор,
пока не получим новую перестановку при k == n, после чего печатаем её на
экране и продолжаем генерировать новые перестановки:
# ГЕНЕРИРУЕМ ПЕРЕСТАНОВКИ
def permutation_rec(k, n, a):
global n_perm
#н ашли очередную перестановку:
if (k == n):
n_perm += 1
# печатаем её:
print(*a, sep='', end='')
print()
else:
for i in range(k, len(a)):
a[k], a[i] = a[i], a[k]
permutation_rec(k+1, n, a)
a[k], a[i] = a[i], a[k]
# ГЛАВНАЯ ПРОГРАММА
main()
Запускаем программу и проверяем её работу при различных значениях
числа элементов в множестве. Рис. 9.1 убеждает нас, что программа верно
генерирует перестановки.
387
Рис. 9.1. Перестановочная программа в работе!
388
Проект «Правильные» перестановки
Исходный код программы находится в папке Перестановки.
Бесконечный цикл while
Метод int
Функция с параметрами
Список
Цикл for
Цикл while
Оператор return
Глядя на Рис. 9.1, вы легко заметите, что некоторые перестановки занимают не «свои» места. Например, перестановка 2 4 3 1 оказалась выше перестановки 2 4 1 3. Иногда нужно получить перестановки в лексикографическом порядке, то есть в таком, в каком стоят слова в алфавитном списке.
Тогда перестановка 2 4 1 3 должна предшествовать перестановке 2 4 3 1,
поскольку единица меньше тройки. Точно так же перестановка 4 1 2 3
должна занимать место выше перестановки 4 1 3 2.
Перестановки очень часто используются в различных комбинаторных задачах, поэтому нам нужно только подобрать соответствующий алгоритм и перевести его на язык Питон. В отличие от разработки собственно алгоритма,
перевод его на любой язык – дело техники.
Функцию main мы опять используем для ввода числа элементов в списке и
вызова функции permutation:
# -*- coding: Windows-1251 -*# Генерация перестановок в лексикографическом порядке
# макс. число элементов в
# множестве:
MAX_ELEM = 8
# ГЛАВНАЯ ФУНКЦИЯ
def main():
print('Генерируем перестановки')
print()
# бесконечный цикл ввода данных 389
# пока пользователь не закроет программу
# или не введёт 0:
while True:
s = "Число элементов (1.." + str(MAX_ELEM) +") >
print(s, end = '')
# число элементов:
n_elem = int(input())
# если пользователь ввёл 0,
# то программу закрываем:
if (n_elem == 0): return
"
# генерируем перестановки:
n = permutation(n_elem)
print("Число перестановок =", n)
print()
Эта функция нерекурсивная, поэтому создание и инициализацию списка
можно перенести непосредственно в неё. Как обычно, первую перестановку
образуют последовательные числа 1..n, которые затем переставляются
иначе, чем в предыдущей программе, чтобы сохранялся лексикографический порядок перестановок:
# ГЕНЕРИРУЕМ ПЕРЕСТАНОВКИ
def permutation(n):
# число перестановок:
n_perm = 0
# список чисел -->
# начальная перестановка:
a = list(range(0, n+1))
a.append(0)
j = ...
while (j != 0):
n_perm += 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):
390
h = a[i]
a[i] = a[k]
a[k] = h
i += 1
k -= 1
return n_perm
На Рис. 9.2 вы видите, что все перестановки заняли свои места.
Рис. 9.2. Перестановки по порядку
Обратите внимание, что в списке а первый и последний элементы всегда
равны нулю, поэтому при печати не используются.
391
Проект Функция permutations
Исходный код программы находится в папке Permutations.
Список
Цикл for
Функция permutations
В поставку Питона входит модуль itertools, в котором имеются полезные комбинаторные функции.
Например, функция permutations генерирует все
перестановки заданного множества.
Но, прежде всего, необходимо импортировать эту функцию в проект:
from itertools import permutations
Если нас интересуют перестановки чисел 0, 1, 2, 3, то мы перебираем их в
цикле for:
# перестановки 4 чисел:
for p in permutations(range(4)):
#print(list(p))
print(p)
Каждая перестановка – это кортеж из 4 чисел (Рис. 9.3).
Рис. 9.3. Перестановки чисел
392
Легко заметить, что функция permutations генерирует перестановки в лексикографическом порядке.
Точно так же можно получить перестановки элементов кортежа (Рис. 9.4):
# перестановки кортежа:
t = (0,2,4)
for p in permutations(t):
print(p)
t = ('К','О', 'Т')
for p in permutations(t):
print(p)
Рис. 9.4. Перестановки элементов кортежа
Буквы в слове (Рис. 9.5):
# перестановки букв в слове:
for p in permutations("КРОТ"):
# буквы:
#print(p)
# слово:
print("".join(p))
Рис. 9.5. Перестановки букв в слове
393
Так можно найти анаграммы к заданному слову. Например, в этом списке
имеется парная анаграмма КРОТ – КОРТ.
И даже список слов можно переставлять на все лады (Рис. 9.6):
# список слов:
lst = ["РАЗ", "ДВА", "ТРИ"]
for p in permutations(lst):
print(p)
Рис. 9.6. Перестановки списка слов
Но и это ещё не всё! Второй параметр функции permutations задаёт число
элементов в перестановке. По умолчанию это все элементы заданной последовательности.
Если второй параметр равен 1, то все перестановки будут состоять из един
- ственного элемента (Рис. 9.7):
# перестановки одного элемента последовательности (каждого):
for p in permutations(lst, 1):
print(list(p))
Рис. 9.7. Одинокие перестановки
Если значения параметра увеличить до двух, то функция permutations сгенерирует все перестановки из двух элементов последовательности (Рис.
9.8):
394
# перестановки из двух элементов последовательности:
for p in permutations(lst, 2):
print(list(p))
Рис. 9.8. Двухэлементные перестановки
И так далее. Но если второй аргумент больше длины последовательности,
то перестановки не генерируются.
Очень полезная функция, которую просто необходимо использовать в проектах!
395
Проект Премия за изобретение
Исходный код программы находится в папке Кордемский 067
15.
Список
Функция без параметров
Функция с параметрами
Оператор return
Цикл for
Генерирование перестановок – процесс занимательный и познавательный уже сам по себе, но нам
перестановки нужны для решения задач.
Задача 15 (16) из книги Удивительный мир чисел [КА86], страница 67:
Четыре изобретателя независимо друг от друга придумали 10 различных
приспособлений: Андрей - 1, Борис - 2, Виктор - 3 и Григорий - 4. Когда
внедрили их в производство, то выяснилось, что каждое приспособление,
разработанное одним и тем же изобретателем, даёт одну и ту же годовую
экономию:
чьё-то по 25 тыс. р.,
чьё-то по 125 тыс. р.,
чьё-то по 625 тыс. р.,
а чьё-то даже по 3125 тыс. р.
В результате годовая экономия составила 8350 тыс. р.
10% этой суммы выделили на премию, которую распределили между
изобретателями пропорционально вкладу каждого в общую годовую экономию.
Сколько рублей получил каждый из них в качестве премии?
Создадим список для хранения годовой экономии по приспособлениям
каждого изобретателя:
# список годовой экономии приспособлений:
eco = [0, 25, 125, 625, 3125]
396
Чтобы используемые значения имели индексы 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
Итого: 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, и так далее.
Оказалось, что это не так. Теперь мы можем переставить красные числа в
таблице иначе и снова посчитать общую годовую экономию:
397
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
# годовая экономия:
summa = 8350
Функция main вызывает функцию solve для решения поставленной задачи,
получает от него число найденных решений и печатает его на экране:
# ГЛАВНАЯ ФУНКЦИЯ
def main():
print()
print('Премия за изобретение')
print()
nVar = solve()
print('Найдены все варианты решения', nVar)
print()
main()
398
Функция solve создаёт список eco, о котором мы говорили выше, и передаёт
его функции permutation:
# -*- coding: Windows-1251 -*# Кордемский, с.67, Задача 15
# годовая экономия:
summa = 8350
# РЕШАЕМ ЗАДАЧУ
def solve():
# список годовой экономии приспособлений:
eco = [0, 25, 125, 625, 3125]
return permutation(4, eco)
На этот раз функция permutation получает не только число элементов в
списке, но и список годовой экономии для приспособлений каждого изобретателя. Так мы учитываем специфику конкретной задачи.
Мы не знаем наверняка, что задача имеет единственное решение, поэтому
будем посчитывать варианты:
# ГЕНЕРИРУЕМ ПЕРЕСТАНОВКИ
def permutation(n, eco):
# число вариантов:
nVar = 0
Для каждой перестановки а мы находим общую годовую экономию и сравниваем её с заданной. Для этого пишем функцию getSumma, которой передаём очередную перестановку чисел 1..4 и список годовых экономий eco:
def getSumma(a, eco):
n = len(a) - 2
sum = 0
Найти общую годовую экономию очень просто: перемножаем элементы из
списков a и eco с одинаковыми индексами и находим сумму этих произведений:
for i in range(1, n+1):
sum += a[i] * eco[i]
399
Если полученная сумма совпадёт с указанной в задаче, то мы печатаем решение:
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
И последняя функция проекта - 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()
400
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
тыс. р.
Также вы видите на Рис. 9.9, что каждый изобретатель получил 10% от
суммы головой экономии:
Григорий - 10 тыс. р.
Андрей – 12,5 тыс. р.
Виктор – 187,5 тыс. р.
Борис – 625 тыс. р.
Рис. 9.9. Изобретатели выявлены
А задача имеет единственное решение, напрасно мы сомневались…
401
Проект Сумма пятизначных чисел
Исходный код программы находится в папке Кордемский 065
11.
Функция без параметров
Список списков
Вложенные циклы for
Применим новый способ генерирования перестановок в следующем проекте.
Задача 11 (4-5) из книги Удивительный мир чисел [КА86], страница 65:
Предположим, что из цифр 1, 2, 3, 4, 5, 6 составлены всевозможные пятизначные числа, причём все цифры в записи каждого числа различны.
Чему равна сумма всех таких пятизначных чисел?
Легко посчитать, что пятизначных чисел из цифр 1..6 можно
составить столько же, сколько и перестановок из этих цифр.
Первое место в пятизначном числе может занимать одна из 6 цифр, второе
– одна из 5 оставшихся, и так далее. Всего:
6 * 5 * 4 * 3 * 2 = 6!
Следовательно, мы должны сначала получить все перестановки из цифр 1..
6, затем составить из них пятизначные числа, и наконец, найти их сумму.
Все перестановки генерирует функция permutation, которая и возвращает
их в список perms (Рис. 9.10):
# ГЛАВНАЯ ФУНКЦИЯ
def main():
print()
print('Сумма пятизначных чисел')
print()
solve()
print()
402
main()
# -*- coding: Windows-1251 -*# Кордемский, с.65, Задача 11
# РЕШАЕМ ЗАДАЧУ
def solve():
# генерируем перестановки:
perms = permutation(6)
Рис. 9.10. Пятизначные числа
Поскольку перестановки состоят из 6 цифр, а нам нужны только 5, то мы
будем составлять 5-значные числа из первых пяти цифр (или из последних
пяти цифр – кто как пожелает).
Зная цифры, само число легко вычислить, последовательно умножая число,
составленное из 1..n-1 первых цифр на 10 и прибавляя к произведению сле
- дующую цифру:
# находим сумму всех чисел:
sum = 0
for p in range(0, Factorial(6)):
num = 0
for n in range(0, 5):
num = num*10 + perms[p][n]
Полученное 5-значное число добавляем к промежуточной сумме
:
sum += num
print('Сумма пятизначных чисел равна', sum)
403
Запускаем программу и получаем ответ (Рис. 9.11).
Рис. 9.11. Сумма найдена
404
Проект Как собрать Дрим тим?
Исходный код программы находится в папке Сочетания.
Список
Бесконечный цикл while
Метод int
Условный оператор if
Оператор return
Оператор continue
Функцияс параметрами
Цикл while
Оператор break
Условный оператор if-else
Функция без параметров
Давайте решим ещё одну жизненно важную задачу. Представим себе, что
из 16 футболистов нам нужно выпустить на поле 11.
Общее число футболистов для комбинаторики - это число элементов в множестве, а число игроков в команде – число элементов в подмножестве. Таким образом, нам нужно найти все подмножества заданного множества.
Каждое подмножество какого-либо множества иначе называют набором из
заданного числа элементов, или сочетанием. В сочетании порядок элементов не играет роли, поэтому мы отберём только 11 игроков в команду, а
как между ними поделить номера на футболках (а, значит, и их амплуа на
поле), - это уже задача тренера.
Объявим новые глобальные переменные программы Сочетания:
# -*- coding: Windows-1251 -*# Сочетания
# ПРОГРАММА ДЛЯ ГЕНЕРИРОВАНИЯ
# ВСЕХ ПОДМНОЖЕСТВ k МНОЖЕСТВА ЧИСЕЛ n=1..nElem
# макс. число элементов в
# множестве:
MAX_ELEM = 30
# число элементов в
405
# подмножестве:
k = 0
# список, содержащий
# очередное подмножество:
a = []
# номер подмножества:
nSubset = 0
В функции main пользователь должен ввести два числа. Важно учесть, что
в подмножестве элементов не больше, чем в множестве:
# ГЛАВНАЯ ФУНКЦИЯ
def main():
print()
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()
main()
406
Затем функция 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
Функция печати очередного подмножества:
# ПЕЧАТАЕМ ОЧЕРЕДНУЮ ПЕРЕСТАНОВКУ
# ЭЛЕМЕНТОВ МНОЖЕСТВА
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)
407
Запускаем программу и вводим наши данные: 16 элементов в множестве и
11 – в подмножестве. Программа выдаёт огромный список из 4368 разных
команд (Рис. 9.12)! Комбинаторную задачу мы решили, а вот какая из этих
команд действительно станет командой мечты, тут комбинаторика бессильна!
Рис. 9.12. Футбольный комбинатор
Иногда полезно заранее знать, сколько же получится сочетаний при тех или
иных исходных данных - не печатать же весь список подмножеств, если нам
интересно узнать только их число! На этот случай в комбинаторике припасена несложная формула:
В нашей программе n = nElem.
Как видите, в комбинаторике без факториала не обойтись!
408
Проект Дартс
Исходный код программы находится в папке Дартс.
Списки
Множества
Цикл for
Функция combinations_with_replacement
Дартс – отличная игра для настоящих мужчин!
Ни беготни, ни шума, ни пыли. Знай себе мечи
дротики прямо в цель.
А цель – это круглая мишень, разделённая на 20 равных секторов.
В каждой игре нужно набрать быстрее соперника 501 очко. За 1 подход игрок может бросить 3 дротика. Желательно попасть ими в мишень. При попадании непосредственно в сектор (сектора окрашены в чёрный и белый
цвет попеременно) игроку начисляется столько очков, сколько написано
рядом с сектором. Если присмотреться к Рис. 9.8, то можно разглядеть, что
по периферии круга расставлены числа от 1 до 20 (Рис. 9.13).
Рис. 9.13. Мишень
409
Таким образом, одним броском можно выбить любое число от 1 до 20. Но
это ещё не всё! При попадании во внешнее зелёно- красное кольцо очки
соответствующего сектора удваиваются, а при попадании во внутреннее
зелёно-красное кольцо очки утраиваются. Но и это ещё не всё! Центральный красный глазок даёт 50 очков, а зелёное кольцо вокруг него – 25.
Легко подсчитать, что тремя дротиками можно выбить минимум 0 очков, а
максимум – 180.
В начале игры важно и выгодно набирать по 180 очков за раз, что
особенно ценится игроками и зрителями. Это магическое число можно
встретить на многих картинках, посвящённых этой замечательной и
броской игре (Рис. 9.14).
Рис. 9.14. Идеал!
В конце игры ситуация другая. Так как игрок должен набрать точно 501
очко, ни больше, ни меньше, то нужно чётко распределить дротики по секторам так, чтобы получить нужную сумму.
Забавно, но в конце игры запрещено набирать 180 очков, хотя это и можно
сделать…
Может показаться, что тремя дротиками можно выбить любую сумму от 1
до 180, но это не так. Некоторые суммы тремя дротиками выбить невозможно. Какие? – Вот в чём вопрос этого проекта.
В методе solve мы сначала записываем в множество score все очки, которые
можно выбить одним дротиком:
410
# -*- 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))
411
Теперь в множестве 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()
На Рис. 9.15 вы видите, что 9 сумм тремя дротиками выбить нельзя!
Рис. 9.15. Вот такие пироги!
Задачу можно решить и другим способом – со списком:
# -*- 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)
412
# все суммы -->
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()
413
Проект Разменный пункт
Исходный код программы находится в папке Разменный
пункт.
Список
Бесконечный цикл while
Метод int
Функция с параметрами
Цикл while
Условный оператор if
Вложенные циклы for
Пусть у нас имеется сколько угодно монет любого достоинства и нам нужно
составить из них сумму в n копеек. В комбинаторике подобная задача формулируется так.
Сколькими способами можно записать натуральное число n в виде
суммы:
n = n1 + n 2 + … + nk ?
(1)
Порядок слагаемых при этом не учитывается, но принято записывать их в
порядке убывания, то есть от больших слагаемых к меньшим. Такая запись
называется стандартной формой разбиения числа n на k слагаемых.
В англоязычной литературе разбиения чисел называют Integer Partitions.
Если речь идёт обо всех вариантах разбиения, то их число обозначают P(n).
Мы легко найдем, что:
P(1) = 1> 1, потому что единицу невозможно представить иначе.
P(2) = 2 > 2 11
P(3) = 3 > 3 21 111
P(4) = 5 > 4 31 22 211 1111
P(5) = 7 > 5 41 32 311 221 2111 11111
414
Эту процедуру можно продолжить, но уже сейчас явно прослеживается её
рекурсивная сущность. Однако мы воспользуемся итерационным алгоритмом, описанным Витольдом Липским в книге [ЛВ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('Разменный пункт')
print()
# бесконечный цикл ввода данных # пока пользователь не закроет программу
# или не введёт 0:
while True:
s = 'Введите число 2..20 > '
print(s, end='')
num = int(input())
# если пользователь ввёл 0,
# то программу закрываем:
if (num == 0):
return
415
# генерируем все разбиения:
generate(num)
print('Число разбиений =', nVar)
print()
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_p(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
416
adds[nAdds] = l
r[nAdds] = 1
print_p(nAdds)
Всякий раз, когда функция part сгенерирует новое разбиение, мы печатаем
его на экране:
# ПЕЧАТАЕМ РАЗБИЕНИЕ
def print_p (d):
global nVar, r, adds, nAdds
nVar += 1
s = ''
for i in range(1, d+1):
for j in range(1, r[i] + 1):
s += (str(adds[i]) + ' ')
print(s)
И вот уже без особого напряжения мы получаем полный список разбиений
числа (Рис. 9.16).
Рис. 9.16. Все разбиения числа 12
417
Проект Спортлото
Исходный код программы находится в папке Lotto.
Бесконечный цикл while
Метод int
Условный оператор if
Функция с параметрами
Оператор break
Условный оператор if-else
Цикл for
Оператор return
В книге Эрхарда Берендса
(Ehrhard Behrends) Fünf Minuten Mathematik: 100 Beiträge der Mathematik-Kolumne der Zeitung DIE WELT [BE13], на страницах 1-3 предлагаются вероятностные задачи, которые, как мы сейчас увидим, очень близки к задачам
комбинаторным.
В некоем большом городе, например, в Берлине или в Гамбурге кто-то забыл в автобусе зонтик. Предположим, что все телефонные номера в этих городах состоят из 7 цифр. Какова вероятность того, что вы (а ведь это вы
нашли тот забытый зонтик!), случайно набирая номер, попадёте на того са
- мого бюргера, который потерял зонтик?
Скорее всего, не все возможные комбинации из 7 цифр могут быть
телефон- ными номерами, но мы предположим, что это так и есть. Тогда
общее число номеров равно 107 = 10 000 000. Если вы позвоните 1 раз, то
вероятность составит 1 / 10 000 000. В общем случае вероятность можно
вычислить как отношение числа благоприятных случаев (исходов) к их
общему числу.
По адресу
http://www.youtube.com/watch?v=KA-gN1h15Ko
вы можете посмотреть ролик о поиске владельца потерянного зонтика
(Рис. 9.17).
418
Рис. 9.17. Учебно-познавательный ролик
В Германии, точно так же как и в России, многие годы играют в лото 6 из
49 (Рис. 9.18).
Рис. 9.18. Карточка немецкого «Спортлото»
В книге утверждается, что при покупке 1 билета вы имеете всего 1 шанс из
13 983 816 угадать все 6 номеров. Это даже меньше, чем найти незадачливого владельца зонтика в большом городе. Однако вряд ли кто-нибудь станет звонить по телефону в надежде попасть на него, а вот в лото играют
многие и охотно. И это легко понять: выиграть чужое гораздо приятнее,
чем его вернуть.
Но давайте проверим, правильно ли подсчитал вероятность автор книги.
419
«Клонируйте» проект Сочетания и сохраните его в новой папке под названием Lotto.
Поскольку 6 номеров из 49 можно выбрать многими способами, то распеча
тывать их нет никакого смысла.
А вот сохранить все сочетания в списке смысл есть. Действительно, шарики
выпадают из барабана совершенно случайно, поэтому все комбинации
совершенно равноправны, и это значит, что вам не нужно ломать голову над
составлением числовых комбинаций – выбирайте случайно любую из списка
и смело зачёркивайте числа! Если учесть, что в каждом билете может быть до
12 комбинаций (см. Рис.9.19), то таким нехитрым способом вы быстро
избавитесь от головной боли (и от денег).
Рис. 9.19. 12-кратная кароточка лото
Хотя, надо признать, этот способ был бы хорош, если бы только вы играли в
лото. Но кроме вас, ещё несколько миллионов человек надеются на удачу,
поэтому даже в случае выигрыша вам придётся делиться с такими же
везунчиками, как и вы. Чтобы избежать дележа, нужно выбирать «редкие»,
«маловероятные» комбинации чисел, чтобы никто другой их не нашёл. Как вы
помните, героиня комедии Спортлото-82 Таня поступила именно так,
зачеркнув 6 первых чисел (Рис. 9.20).
420
Рис. 9.20. Удачный выбор чисел
И выиграла (Рис. 9.21)!
Рис. 9.21. Увы, жизнь – это не кино!
Таким образом, наша задача упрощается – мы уже не будем распечатывать
сочетания, а ограничимся только их подсчётом:
# -*- coding: Windows-1251 -*# Lotto
# ПРОГРАММА ДЛЯ ПОДСЧЁТА
# ВСЕХ ПОДМНОЖЕСТВ k МНОЖЕСТВА ЧИСЕЛ n=1..nElem
# макс. число элементов в
# множестве:
MAX_ELEM = 90
#
#
k
#
#
число элементов в
подмножестве:
= 0
список, содержащий
очередное подмножество:
421
a = []
# номер подмножества:
nSubset = 0
# ГЛАВНАЯ ФУНКЦИЯ
def main():
print()
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)
n = getNumSubSets(nElem, k)
print('Число сочетаний =', n)
print()
main()
Для этого вполне годится готовая функция subSet из нашего предыдущего
проекта, в которой достаточно закомментировать всего одну строку:
# ГЕНЕРИРУЕМ ВСЕ СОЧЕТАНИЯ МНОЖЕСТВА 1..nElem
422
# ИЗ 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
if (k == nElem):
break
if (a[k] == nElem):
p -= 1
else:
p = k
if (p >= 1):
for i in range(k, p-1, -1):
a[i] = a[p] + i - p + 1
return nSubset
Запускаем программу – и убеждаемся в правоте автора: 6 чисел из 49
можно выбрать 13 983 816 способами (Рис. 9.22).
Рис. 9.22. Маловероятно!
Если не нужно сохранять комбинации чисел в списке, то можно найти число
сочетаний по формуле, которая вам известна из проекта Как нам собрать
Дрим тим?
423
К сожалению, для подсчётов она малопригодна, так как в числителе стоит
огромный факториал. Но нетрудно догадаться, что первые (n-k) чисел этого
факториала сократятся с этими же числами в знаменателе, то есть числитель равен произведению чисел от (n-k+1) до n, а в знаменателе останется
только факториал числа k. Изловчившись, мы легко напишем новую функцию для подсчёта сочетаний:
def getNumSubSets(n, k):
chisl = 1
for i in range(n, n - k, -1):
chisl *= i
znam = factorial(k)
return chisl // znam
# ВЫЧИСЛЯЕМ ФАКТОРИАЛ ЗАДАННОГО ЧИСЛА
def factorial(num):
if (num == 0):
return 1
fact = num
for i in range(2, num):
fact *= i
return fact
Вызываем функцию getNumSubSets в функции main и получаем тот же результат, что и раньше:
# считаем все подмножества:
#n = subSet(nElem)
n = getNumSubSets(nElem, k)
print('Число сочетаний =', n)
print()
Аналогично мы можем узнать число раскладов в игре скат (Рис. 9.23).
Рис. 9.23. Карточные расклады
424
Или число рукопожатий, которыми обменяются 14 человек, расходясь после праздника (примеры из книги) (Рис. 9.24).
Рис. 9.24. Число рукопожатий
В Чемпионате России по футболу участвуют 16 команд (Рис. 9.25).
Сколько всего матчей они проведут между собой?
Задача решается аналогично предыдущей, но следует учесть, что каждая
пара команд сыграет дважды – дома и на выезде.
Рис. 9.25. Таблица розыгрыша Чемпионата России по футболу 2015-2016
Поэтому команды проведут не 120 (Рис. 9.26), а 120 х 2 = 240
матчей.
425
Рис. 9.26. Число матчей
В современной России играют также в Гослото 6 из 45 и 5 из 36 (Рис. 9.27).
Рис. 9.27. Новое российское лото
Подсчитаем число вариантов и для них (Рис. 9.28).
По сравнению с иноземными лотереями, российские более обнадёживающие!
В Спортлото играют и в Италии – но по другим правилам. В одном из вариантов нужно угадать 5 чисел из 90. Легко подсчитать, что вероятность такого радостного события равна 1 / 43 949 268 (Рис. 9.29).
426
Рис. 9.28. Небольшая надежда всё-таки есть!
Рис. 9.29. Итальянское лото
В варианте SuperEnaLotto (Рис. 9.30) нужно угадать уже 6 чисел из 90, что
уменьшает шансы игроков до 1 / 622 614 630 (Рис. 9.31). «Нет, я так не играю!» - сказал бы Карлсон.
В итальянском лото все неразыгранные деньги переходят в следующий тираж. А поскольку вероятность выигрыша главного приза невелика, то постепенно джек-пот может достигать огромных значений – до 100 миллионов евро (Рис. 9.32)!
Рис. 9.30. Попробуй угадай!
427
Рис. 9.31. Везёт же итальянцам!
Рис. 9.32. Вот это джекпот!
И тогда из соседних стран в Италию отправляются караваны автобусов с
жаждущими наживы джентльменами удачи. Вот так, сравнительно дёшево
можно развивать туризм и познавательный интерес к своей стране…
Чтобы показать, насколько мала вероятность выигрыша в немецкое Спортлото, Эрхард Берендс прибегает к такому наглядному примеру.
Вы, конечно, понимаете, что в «цивилизованных» странах всё построено на
личной наживе, а не на заботе о развитиии спорта, поэтому я употребляю
слово Спортлото только по аналогии с советской версией лото.
Расстояние между Берлином и Котбусом составляет около 140 км (Рис. 9.
33) или в сантиметрах – 14 000 000. Как вы помните, практически столько
же разных карточек можно заполнить в немецком лото.
428
Рис. 9.33. Дорога дальняя!
Теперь представьте, что вы едете на автомобиле по этой дороге с завязанными глазами (опасаясь побочных эффектов и последствий, автор книги
уточняет: вас везут по этой дороге). Где-то на обочине стоит столб диаметром в 1 см (Рис. 9.34). Вы в любой момент времени можете бросить
одноцен- товую монету в надежде попасть в этот столб (или шест).
Рис. 9.34. Иллюстрация из книги
429
Вероятность этого события приблизительно равна вероятности выигрыша
в немецкое лото.
Для итальянского лото SuperEnaLotto потребуется дорога в 6000 километров: из Рима через Берлин в Москву и обратно.
По адресу
http://www.youtube.com/watch?v=ODwm29lIt0E
вы можете посмотреть ролик с экспериментом по бросанию монеты из автомобиля в шест (Рис. 9.35).
Рис. 9.35. Видеоэксперимент
Играйте, дорогие люди, в Спортлото,
За это не осудит вас никто!
(душераздирающий призыв из комедии Спортлото-82)
430
Проект Жребий брошен!
Исходный код программы находится в папке Монета.
Цикл for
Функция с параметрами
Условный оператор if
Оператор return
Форматированный вывод
Кто отжеребился, могут пройти к своим машинам.
Кто отжеребился, не мешайте спортсменам,
участвующим в жеребьёвке.
Из спортивных репортажей
Жребием может быть любой предмет, который можно бросить в лицо
судьбе. У нас это будет монетка. Она очень удобна, когда, например, нужно
распределить ворота команд на футбольном поле. А на третьем Чемпионате
Европы по футболу в 1968 году именно монетка определила судьбу
сборной СССР в полуфинале. Матч со сборной Италии, которая принимала
чемпио- нат, закончился вничью, а победителя тогда определяли таким
немудрё- ным способом. Повезло итальянцам…
Монета хороша тем, что у неё 2 стороны, на которые она с одинаковой вероятностью может упасть.
Впрочем, студенты нашли у монеты и скрытые
возможности:
- Давайте монетку кинем. Если орлом упадёт, то
пивка выпьем, если решка - то водочки. Если
ребром упадёт - доспим, ну а если в воздухе
зависнет, тогда учиться пойдём.
Понятно, что если мы подбросим монетку 1 раз, то выпадет либо орёл,
либо
решка. Судить о вероятности их выпадения по одному броску невозможно.
Это значит, что число подбросов нужно значительно увеличить. А это уже
431
утомительно, поэтому пусть виртуальный эксперимент проводит компьютер – ему ничего не стоит подбросить монету и 1 миллион, и даже 10 миллионов раз кряду!
Естественно, компьютер и одного раза не сможет подбросить настоящую
монетку, но зато с помощью генератора псевдослучайных чисел он может
имитировать (или симулировать – в хорошем смысле этого слова!) выпадение орлов и решек.
В функции main мы задаём параметры эксперимента – число серий из 10
экспериментов и число подбрасываний монеты в каждой серии:
# ГЛАВНАЯ ФУНКЦИЯ
def main():
print()
print('Подбрасываем монетку')
print()
# число серий:
ns = 10
for i in range(0, ns):
# число бросаний:
n = 1000000
orlov = experiment(n)
print('Орлов : Решек %i : %i' %(orlov, n - orlov))
print('Вероятность %f : %f' %(orlov/n, 1.0 - orlov/n))
print()
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):
432
orlov += 1
return orlov
Я провёл тестирование
нашей программы
при 1 миллионе
подбрасываниях в каждой серии. Рис. 9.36 показывает неплохое качество
нашего экспери- ментального устройства!
Рис. 9.36. Подбрасываем виртуальные деньги
Конечно, было бы странно, если бы при случайных бросаниях число орлов
и решек в точности совпало, но эти числа должны быть довольно близки
друг к другу, что мы и видим на Рис. 9.28.
433
Задания для самостоятельного решения
Сочетания
Напишите программу, которая вычисляла бы и печатала число сочетаний.
Ладейное окончание
Научившись генерировать перестановки, мы, сами того не подозревая,
решили знаменитую комбинаторную задачу: найти все варианты расстановки восьми ладей на шахматной доске так, чтобы они не били
друг друга.
Когда речь идёт о поиске всех вариантов, то обычно для решения задачи
используют метод перебора с возвратами (по-английски backtracking).
Однако число всех расстановок ладей 8! = 40320 невелико, и мы можем
решить задачу полным перебором (brute force), учтя, конечно, тот факт,
что на одной горизонтали может находиться единственная ладья.
Тогда каждая перестановка чисел 1..8 служит решением задачи о ладьях.
Например, перестановке 1 2 3 4 5 6 7 8 соответствует решение, представленное на Рис. 9.37.
Действительно, если каждую ладью ставить на отдельную горизонталь Г,
а в ней – на ту вертикаль, номер которой совпадает с числом, стоящим в
Г-той позиции перестановки, то ни одна ладья не будет угрожать другим
ладьям - и задача решена.
Тысячная перестановка, которую выдаёт наша программа Генерируем перестановки, такая: 1 4 3 7 2 6 5 8. Если мы расставим ладьи согласно этой
перестановке, то также получим решение задачи (Рис. 9.38).
Таким образом, мы умеем находить все решения ладейной задачи, и вам
нужно только и всего, что красиво вывести их на экран. Можно ограничиться крестиками X в консольном окне, хотя в картинках было бы
лучше…
434
Рис. 9.37. Перестановка ладей
Рис. 9.38. Одно из решений ладейной задачи
Ферзевой гамбит
Если мы заменим ладей ферзями, то оба наших решения окажутся неверными. Ферзи – более серьёзные шахматные фигуры и могут больно
ударить и по диагонали! С другой стороны, совершенно очевидно, что
среди 40320 решений для ладей отыщутся и все 92 решения для ферзей.
Например, такое (Рис. 9.39).
435
Так что для решения проблемы с ферзями вам необходимо устроить проверку всех перестановок и отобрать только те, в которых ферзи не угрожают друг другу. Конечно, не лучший способ для решения этой задачи, но
вполне пригодный.
Рис. 9.39. Безобидные ферзи
436
Глава 10. Рекурсия
Рекурсией называют вызов функции из самой же функции. Рекурсивные
функции имеют немало преимуществ и недостатков по сравнению с обычными. Мы рассмотрим их на конкретных примерах.
Проект Рекурсивный факториал
Исходный код программы находится в папке Factorial Rec.
Цикл for
Функция с параметрами
Условный оператор if
Оператор return
Форматированный вывод
Цикл while
Самый классический пример рекурсии – вычисление факториала, то есть
произведения всех чисел от 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
437
if (n <= 0):
return f
for i in range(2, n+1):
f *= i
return f
# ГЛАВНАЯ ФУНКЦИЯ
def main():
print()
n = 5
print('Факториал {0}! = {1}'.format(n, fact2(n)))
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
438
Из этого, кстати, следует, что большие факториалы, например, числа 1000
вычислить рекурсивно не удастся по той же самой причине. И это один из
недостатков рекурсии.
Основной случай называют
прекращения рекурсии.
также
терминальным
или
условием
При вычислении факториала основной случай возникает, когда нужно вычислить факториал нуля. Его вычислять не нужно, поскольку он по определению равен 1. Итак, если функция fact вызывается с нулевым аргументом,
то она должна тут же вернуть 1:
# -*- 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.
439
Чтобы проследить за всеми вызовами рекурсивной функции, дополним её
сообщениями:
# РЕКУРСИВНАЯ ФУНКЦИЯ
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 мы получим такие сообщения:
Начало функции при 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
440
Начало функции при n = 0
Основной случай n = 0
Функция возвращает 1
Функция возвращает 2
Функция возвращает 6
Функция возвращает 24
Функция возвращает 120
Факториал 5! = 120
Хорошо видно, что функция fact вызывает сама себя 5 раз с аргументами 4,
3, 2, 1 и 0. Когда возникает основной случай, все функции начинают
возвра- щать свои значения в вызывающую функцию, где они умножаются
на уже полученные значения.
441
Проект Рекурсивное умножение
Исходный код программы находится в папке Mult Rec.
Функция с параметрами
Условный оператор 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()
442
По сути, здесь вместо умножения двух чисел используется сложение. Если
второе число большое, то стек переполнится, и никакого результата вы не
получите.
Если второе число отрицательное, то произведение будет равно нулю, так
что таким способом умножения лучше не пользоваться!
Интересный способ умножения (Рис. 10.1)!
Рис. 10.1. «Сложное» умножение
443
Проект Рекурсивный модуль
Исходный код программы находится в папке Mod Rec.
Функция с параметрами
Условный оператор if - else
Оператор return
Форматированный вывод
Другой рекурсивный курьёз – вычисление модуля, то есть остатка от деления первого числа на второе.
Здесь также следует быть осторожным, поскольку деление на нуль вызовет
ошибку при выполнении программы (Рис. 10.2):
# -*- coding: Windows-1251 -*""" Рекурсивный способ вычисления модуля """
# РЕКУРСИВНО ВЫЧИСЛЯЕМ МОДУЛЬ
def mod_rec(m, n):
# основной случай:
if (m < n):
return m
# рекурсия:
else:
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()
444
main()
Рис. 10.2. Рекурсивный модуль
445
Проект Рекурсивные перевороты
Исходный код программы находится в папке Reverse Rec.
Цикл for
Функция с параметрами
Условный оператор if - else
Оператор return
Форматированный вывод
Списки
Срезы
Более полезный случай применения рекурсии – переворачивание списков
и строк:
# -*- coding: Windows-1251 -*""" Рекурсивный способ переворачивания списков и строк """
# ПЕРЕВОРАЧИВАЕМ СПИСОК
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]
return res
446
# ГЛАВНАЯ ФУНКЦИЯ
def main():
print()
print('Рекурсивные перевороты')
print()
# список:
lst = range(1, 10 + 1)
print(reverse_lst(lst, 0))
lst = 'КАТОК'
print(reverse_lst(lst, 0))
# строка:
lst = 'ШАЛАШ'
rev = reverse_str(lst, 0)
print(rev)
if (lst == rev):
print('Палиндром')
lst = 'А РОЗА УПАЛА НА ЛАПУ АЗОРА'
print(reverse_str(lst, 0))
print()
main()
Функция reverse_lst возвращает новый список, в котором элементы заданного списка располагаются в обратном порядке. Метод reverse_str действует аналогично, но только для строк и возвращает строку, записанную
задом наперёд (Рис. 10.3).
Рис. 10.3. Перевёртыши
Легко заметить, что обе функции действуют одинаково. Они получают последовательность и номер (индекс) очередного элемента в последовательности. Сначала это первый элемент (с нулевым индексом). Так как этот элемент принадлежит последовательности, то основной случай не наступает,
а функция рекурсивно вызывает себя со следующим индексом. И так до тех
пор, пока индекс не выйдет за границы последовательности. Тогда наступит основной случай, и рекурсивные вызовы прекратятся.
447
Все функции начнут возвращать свои значения, то есть символы строки,
начиная с пустого (его вернёт вызов с основным случаем). К нему добавится
последний, затем предпоследний. И так далее до самого первого. Так как
символы добавляются к пустой строки от конечного к начальному, то в
строке res мы получим обращённую строку.
Если вы хотите понаблюдать за этим процессом, добавьте функцию print,
например, в функцию reverse_str (Рис. 10.4):
# ПЕРЕВОРАЧИВАЕМ СТРОКУ
def reverse_str(lst, id):
# основной случай:
if (id >= len(lst)):
return ''
# рекурсия:
else:
res = reverse_str(lst, id + 1)
res += lst[id]
print(res)
return res
Рис. 10.4. Обратимый процесс
448
Эти же операции можно выполнить и без рекурсивных премудростей:
# НЕРЕКУРСИВНАЯ ФУНКЦИЯ
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]
449
Проект Рекурсивные палиндромы
Исходный код программы находится в папке Палиндромы
Рек.
Цикл 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'
450
# ищем палиндромы:
palindrome(fileName)
print()
main()
В функции 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)
Так как список слов небольшой, то и скорости нашей функции для этого
вполне достаточно (Рис. 10.5).
451
Рис. 10.5. Все русские палиндромы
452
Проект Нерекурсивные палиндромы
Исходный код программы находится в папке Палиндромы.
Цикл 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)
# ГЛАВНАЯ ФУНКЦИЯ
453
def main():
print()
print('Палиндромы')
print()
# словарь:
fileName = 'makarov_frc.txt'
#fileName = 'EnDictionary_frc.txt'
# ищем палиндромы:
palindrome(fileName)
print()
main()
Все отечественные палиндромы мы уже нашли, поэтому давайте поищем
английские. Их оказалось больше (Рис. 10.6)…
Рис. 10.6. Все английские палиндромы
454
Проект Рекурсивная считалка
Исходный код программы находится в папке CountRec.
Цикл for
Функция с параметрами
Условный оператор if - else
Оператор return
Оператор not
Модуль random
Списки
Лямбда-функции
Рассмотрим занимательный, но не очень полезный пример рекурсии.
Пусть у нас имеется список из n чисел, которые мы в данном проекте выбираем случайно:
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_list(num)
# печатаем его:
print(lst)
print()
И мы хотим подсчитать, сколько нечётных чисел в этом списке.
455
Рекурсивная функция может быть такой:
# СЧИТАЕМ
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()
И получаем верный ответ (Рис. 10.7).
Рис. 10.7. Посчитали
Нам могут потребоваться и другие подсчёты, поэтому напишем вторую версию функции, которая будет получать не только список, но и лямбда-функцию для проверки его элементов:
# СЧИТАЕМ С ЛЯМБДОЙ
def count2(lst, l):
return l(lst[0]) + count2(lst[1:], l) if lst else 0
Проверяем нашу новую функцию:
456
# считаем нечётные числа с лямбдой:
l = lambda n: n % 2
num_odd = count2(lst, l)
print('Нечётных чисел:', num_odd)
print()
Она даёт такие же результаты, что и первая версия (Рис. 10.8).
Рис. 10.8. Версия с лямбда-функцией
Теперь давайте подсчитаем чётные числа:
# считаем нечётные числа с лямбдой:
l = lambda n: not (n % 2)
num_even = count2(lst, l)
print('Чётных чисел:',num_even)
print()
Всё сходится (Рис. 10.9)!
Рис. 10.9. Считаем чётные числа
И наконец, посчитаем, сколько в списке оказалось чисел, которые больше
50 (Рис. 10.10):
# считаем числа > 50:
l = lambda n: n > 50
num51 = count2(lst, l)
457
print('Чисел > 50:', num51)
print()
Рис. 10.10. Считаем «большие» числа
Задавая разные лямбда-функции, вы можете сосчитать, что угодно!
И ещё одна функция для создания случайного списка чисел:
# СОЗДАЁМ СЛУЧАЙНЫЙ СПИСОК
def make_list2(n):
lst = [random.randint(0,100) for r in range(n)]
return lst
458
Проект Рекурсивные разбиения
Исходный код программы находится в папке Integer
Partitions.
Цикл for
Функция с параметрами
Условный оператор if
Оператор return
Списки
Оператор yield
Мы уже разбивали числа в проекте Разменный пункт, но там мы написали
очень громоздкую функцию. Рекурсивный вариант гораздо компактнее:
# -*- coding: Windows-1251 -*# РЕКУРСИВНЫЙ ГЕНЕРАТОР РАЗБИЕНИЙ
# ЗАДАННОГО ЧИСЛА
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 = 12
# генерируем:
parts = part_gen(num)
# число разбиений:
np = 0
# печатаем разбиения:
459
for p in parts:
np += 1
print(p)
print('Всего:', np)
print()
main()
Обратите внимание, что функция part_gen представляет собой генератор
разбиений, то есть она не возвращает список разбиений заданного числа, а
ждёт, когда вы захотите получить их, например, в цикле for.
Для сравнения с нашей предыдущей функцией мы найдём все разбиения
числа 12 (Рис. 10.11).
Рис. 10.11. Разбили дюжину
Все разбиения сгенерированы быстро и правильно, и порядок разбиений
здесь лексикографический, то есть «по алфавиту».
460
Проект Двадцать пятёрок
Исходный код программы находится в папке Двадцать пятёрок.
Цикл for
Функция с параметрами
Условный оператор if
Оператор return
Списки
Оператор yield
В журнале Квантик, №3 за 2016 год, на странице 33
напечатана такая конкурсная задача (Рис. 10.12).
Рис. 10.12. Условие задачи
Давайте аккуратно выпишем все пятёрки в ряд:
55555555555555555555
Теперь их нужно разбить на группы и поставить между ними знак сложения.
Если поставить 0 знаков, то получится огромное число.
Если поставить 19 плюсов, то получится 100.
Следовательно, нужно разбивать ряд из 20 пятёрок на разные группы и
находить сумму получившихся чисел. Разбивать числа мы уже умеем и из
любопытства можем напечатать все разбиения числа 20 (Рис. 10.13).
Их совсем немного, но мы можем рассматривать и гораздо меньше разбиений, если отбросим заведомо неподходящие. Например, все разбиения, в
ко- торых имеется группа из четырёх пятёрок, дадут сумму больше 1000.
Разбиение с тремя пятёрками 555 вполне может быть хорошим.
461
Рис. 10.13. Все разбиения числа 20
Итак, мы будем проверять только разбиения с небольшими группами.
Подправляем главную функцию из предыдущего проекта:
# ГЛАВНАЯ ФУНКЦИЯ
def main():
print()
print('Двадцать пятёрок')
print()
# число:
num = 20
# генерируем:
parts = part_gen(num)
# число разбиений:
np = 0
# печатаем разбиения:
for p in parts:
if max(p) < 4:
np += 1
print(p)
print('Всего:', np)
462
print()
main()
И узнаём, что таких разбиений всего 44 (Рис. 10.14).
Рис. 10.14. Все нужные разбиения
463
Из этого рисунка видно, что сумма чисел постоянно увеличивается, поэтому
решение будет единственным. Его можно найти и вручную, но зачем тратить время на вычисления?
Вызываем в главной функции main функцию solve для решения задачи:
# ГЛАВНАЯ ФУНКЦИЯ
def main():
print()
print('Двадцать пятёрок')
print()
# решаем задачу:
solve()
print()
main()
Функция solve получает разбиения заданного числа и проверяет те из них,
которые не содержат групп более чем из трёх пятёрок:
# РЕШАЕМ ЗАДАЧУ
def solve():
# число:
num = 20
# генерируем:
parts = part_gen(num)
# число решений:
np = 0
# печатаем разбиения:
for p in parts:
if max(p) < 4:
Одна пятёрка – это число 5, две – 55 и три – 555. Эти числа мы добавляем
к сумме:
# находим сумму чисел:
summa = 0
for i in p:
if i == 1:
summa += 5
elif i == 2:
summa += 55
else:
summa += 555
464
Если полученная сумма равняется 1000, то мы печатаем результат на
экране:
if summa == 1000:
print(p)
np += 1
print('Сумма =', summa)
print('Всего:', np)
print()
Запускаем программу и получаем единственное решение (Рис. 10.15).
Рис. 10.15. Вот и вся задача
В более привычной записи ответ такой:
555 + 55 + 55 + 55 + 55 + 55 + 55 + 55 + 55 + 5 = 1000
Здесь мы, естественно, не учитываем решений, которые можно получить
перестановкой чисел.
Как говорится, доверяй, но проверяй. Заходим в интерактивное окно, от
которого и получаем подтверждение правильности нашего решения (Рис.
10.16).
Рис. 10.16. Всё верно
Когда ответ известен, то нетрудно найти и логическое решение этой задачи…
465
Проект Двадцать пятёрок и больше
Исходный код программы находится в папке Двадцать пятёрок 2.
Цикл for
Функция с параметрами
Условный оператор if – elif - else
Оператор return
Оператор break
Списки
Оператор yield
Зададимся интересным вопросом: если взять не 20 пятёрок, а больше или
меньше, то будет ли задача иметь решение?
Легко сообразить, что 200 пятёрок дадут тысячу, поэтому задача может
быть решена не только для 20 пятёрок. Так же очевидно, что больше 200
пятёрок брать бесполезно, так как их сумма всегда больше 1000.
Наша задача облегчается: найти число пятёрок в диапазоне 1..200, для которых существует решение «квантовой» задачи.
Программу легко написать, воспользовавшись нашими наработками:
# -*- 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)
466
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:
print('Всего:', np)
return np
# ГЛАВНАЯ ФУНКЦИЯ
def main():
print()
print('Двадцать пятёрок')
print()
# число пятёрок:
for n5 in range(1, 200+1):
# решаем задачу:
np = solve(n5)
if np:
print('Число пятёрок =', n5)
print()
467
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
решения
решения
решения
решения
решения
решения
решения
Закономерность очевидна. Первое решение – для 20 пятёрок, а затем
каждый раз добавлять по 9 пятёрок.
Задачу можно решить для четвёрок.
Для получения 1000 необходимо взять 16 + 9n четвёрок
, где n = 0...250
Двоек потребуется взять 23 + 9n.
Единиц потребуется взять 28 + 9n (с исключениями).
Восьмёрок – 8 + 9n.
А вот интересные примеры с текущим годом:
468
111 + 111 + 111 + 111 + 111 + 111 + 111 + 111 + 111 + 111 + 111 + 111 +
111 + 111 + 111 + 111 + 111 + 111 + 11 + 1 + 1 + 1 + 1 + 1 + 1 + 1 = 2016
222 + 222 + 222 + 222 + 222 + 222 + 222 + 222 + 222 + 2 + 2 + 2 + 2 + 2 + 2
+ 2 + 2 + 2 = 2016
333 + 333 + 333 + 333 + 333 + 333 + 3 + 3 + 3 + 3 + 3 + 3 = 2016 или
333*3 + 333*3 + 3*3 + 3*3 или
(333 + 333 + 3 + 3)*3
444 + 444 + 444 + 444 + 44 + 44 + 44 + 44 + 44 + 4 + 4 + 4 + 4 + 4 = 2016 или
444 * 4 + 44*4 + 44 + 4*4 + 4 = 2016
666 + 666 + 666 + 6 + 6 + 6 = 2016
777 + 777 + 77 + 77 + 77 + 77 + 77 + 77 = 2016
888 + 888 + 88 + 88 + 8 + 8 + 8 + 8 + 8 + 8 + 8 + 8 = 2016 или
888 + 888 + 88 + 88 + 8 * 8 = 2016
999 + 999 + 9 + 9 = 2016
469
Проект Треугольные числа
Исходный код программы находится в папке Triangular Numbers.
Цикл for
Функция с параметрами
Условный оператор if
Оператор return
Форматированный вывод
Треугольные числа – это частный вид фигурных чисел.
Если из определённого числа кружков можно построить правильный треугольник, то такое число и называют треугольным.
Самое первое треугольное число – это единица. Правда, один кружок не
очень-то похож на треугольник (Рис. 10.17), но у математиков свой взгляд
на вещи.
Рис. 10.17. Первое треугольное число
Следует добавить, что нулевое треугольное число представляет собой
треугольник, которого вообще не видно. Он существует только в мозгу
математиков.
Второе треугольное число – тройка. Из трёх кружочков уже можно построить вполне реалистичный треугольник (Рис. 10.18).
Рис. 10.18. Второе треугольное число
Третье треугольное число – шестёрка (Рис. 10.19).
470
Рис. 10.19. Третье треугольное число
Четвёртое треугольное число – десятка (Рис. 10.20).
Рис. 10.20. Четвёртое треугольное число
Дальше можно не продолжать – закономерность понятна:
Первое число равно 1.
Второе – 1 + 2.
Третье - 1 + 2 + 3.
Четвёртое - 1 + 2 + 3 + 4.
Пятое - 1 + 2 + 3 + 4 + 5.
n-ное треугольное число равно сумме первых n натуральных чисел.
Ряд треугольных чисел представляет собой простейшую арифметическую
прогрессию, поэтому любое треугольное число можно легко вычислить по
формуле:
Tn = ½ n(n + 1)
(1)
Однако из наших нарисованных треугольников видно, что второе треугольное число получается, если к первому прибавить 2. Третье – если ко
второму прибавить 3. Для любого треугольного числа верна такая
рекуррентная формула:
471
Tn = Tn-1 + n
Из неё хорошо видна прямая аналогия с рекурсивным вычислением факториалов. Нам нужно только заменить умножение сложением (Рис. 10.21).
# -*- coding: Windows-1251 -*""" Рекурсивный способ вычисления треугольных чисел """
# РЕКУРСИВНАЯ ФУНКЦИЯ
def tri(n):
if (n == 0):
return 0
return n + tri(n-1)
# ГЛАВНАЯ ФУНКЦИЯ
def main():
print()
for n in range(11):
print('Треугольное число {0} = {1}'.format(n, tri(n)))
print()
main()
Рис. 10.21. Считаем треугольные числа
Если вам вдруг понадобятся очень большие треугольные числа, то их
можно получить от нерекурсивной функции (Рис. 10.22).
# НЕРЕКУРСИВНАЯ ФУНКЦИЯ
def tri2(n):
if (n <= 1):
472
return n
t = n * (n + 1) // 2
return t
Рис. 10.22. Большие треугольные числа
Интересно, что сумма двух последовательных треугольных чисел это квадратное число:
Tn + Tn+1 = (n+1)2
На Рис. 10.23 это свойство хорошо видно.
Рис. 10.23. Квадратные треугольники
А точное доказательство этого утверждения легко получить из формулы
(1), если найти сумму чисел Tn и Tn+1.
Менее очевидно такое свойство треугольных чисел: любое целое неотрицательное число можно представить в виде суммы не более трёх треугольных
чисел. Это предположение высказал Пьер Ферма в1638 году, а доказал его
1796 году Карл Гаусс.
473
Проект Рекурсивные числа Фибоначчи
Исходный код программы находится в папке Fibonacci Rec и
Fibonacci.
Цикл for
Функция с параметрами
Условный оператор if - else
Оператор return
Форматированный вывод
Классический пример плохой рекурсии – нахождение чисел Фибоначчи по
рекуррентной формуле:
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)
# ГЛАВНАЯ ФУНКЦИЯ
474
def main():
print()
for n in range(11):
print('Число Фибоначчи #{0} = {1}'.format(n, fibo(n)))
print()
main()
В функции fibo мы учитываем основной случай для двух первых чисел
Фибоначчи, которые не вычисляются, а даются в готовом виде. Все остальные числа находим, складывая 2 предыдущих (Рис. 10.24).
Рис. 10.24. Рекурсивное вычисление чисел Фибоначчи
Недостаток этого способа в том, что мы не сохраняем уже найденные числа,
а каждый раз вычисляем их снова. В результате для больших чисел получается огромная цепочка вызовов функции fibo, и время вычисления быстро
растёт.
Кроме как для демонстрации рекурсии, это способ никуда не годится.
Вообще говоря, если нам нужно получить только заданное число Фибоначчи, то не обязательно хранить в памяти все найденные числа – достаточно
двух последних.
На каждом шаге предпоследнее число становится последним, а сумма двух
последних чисел – текущим числом Фибоначчи, которое становится предпоследним для следующего:
# НЕРЕКУРСИВНАЯ ФУНКЦИЯ
def fibo_iter(n):
f, b = 0, 1
475
for i in range(n):
f, b = b, f + b
return f
Так как в новой функции fibo_iter остались только простые арифметические операции, то даже огромные числа Фибоначчи находятся практически
мгновенно (Рис. 10.25).
Рис. 10.25. Находим огромные числа Фибоначи
476
Проект Рекурсивные анаграммы
Исходный код программы находится в папке Anagram Rec.
Цикл for
Функция с параметрами
Условный оператор if
Оператор return
Списки
Срезы
Глобальные переменные
С анаграммами вы уже знакомы. Это слова, которые состоят из одних и тех
же букв, но записанных в разной последовательности. С комбинаторной
точки зрения, анаграммы – это перестановки букв.
Перестановки удобно генерировать с помощью функция permutations из
модуля itertools. Но нас сейчас интересуют рекурсивные перестановки, и мы
напишем свою функцию, которая умеет переставлять буквы в заданном
слове.
Пусть, для примера, это будет слово КАРП:
# ОСНОВНАЯ ФУНКЦИЯ
def main():
print()
global s, n_var
s = 'КАРП'
n_var = 0
get_anagrams(len(s))
print()
main()
В рекурсивной функции get_anagrams мы переставляем буквы в слове s и
печатаем найденные анаграммы:
# -*- coding: Windows-1251 -*-
477
""" Рекурсивный способ генерирования перестановок """
# ГЕНЕРИРУЕМ АНАГРАММЫ
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
Итак, мы научились находить все анаграммы для однобуквенных слов!
А что, если слово состоит из двух букв. Возьмём слово РА. В нём как раз 2
буквы. Из комбинаторики следует, что двухбуквенные слова имеют 2! = 2
анаграммы. Первая анаграмма – исходное слово, второе – слово, в котором
первая буква переставлена в конец слова:
РА АР
Значит, мы должны научиться переставлять (или сдвигать) первую букву в
конец слова. Для этого мы напишем функцию rotate. Так как нам придётся
переставлять буквы в словах разной длины, то мы сообщаем этой функции
число конечных букв size в слове s, в которых мы хотим переставить первую
букву в конец:
# СДВИГАЕМ ЗАДАННЫЕ БУКВЫ
def rotate(size):
global s
# первые len - size символов:
first = s[: len(s) - size]
# сдвигаем последние size символов:
478
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. Тогда мы получим такую инфор
- мацию для размышления (Рис. 10.26).
Рис. 10.26. Двухбуквенные анаграммы
Так как наше слово состоит из 2 букв, то слово first будет состоять из 0
букв, так как переставлять буквы нужно во всём слове целиком, и не
останется ни одной буквы перед ними. А это буквы, которые мы должны
оградить от изменения. Остаток двухбуквенного слова – это само
двухбуквенное слово РА. Срез от первой буквы (считаем от нуля!) до конца
слова – Р, первая буква – А. Переносим её в конец и получаем слово АР.
Цикл for выполнится 2 раза, так как длина всего слова 2 буквы. В первой
итерации будет напечатано слово РА, во второй – АР (Рис. 10.27).
479
Рис. 10.27. Все анаграммы слова РА
Трёхбуквенные слова имеют 6 анаграмм. Возьмём наше любимое слово
КОТ. Здесь цикл for будет выполнен 3 раза для 3-буквенных слов, в результате на первом месте последовательно окажутся буквы К, О, Т. Для каждого
из этих циклов дважды выполнится цикл for для двухбуквенных слов, полу
- чаемых из двух последних букв 3- буквенного слова. Буквы в
двухбуквенных словах поменяются местами, а первая буква сохранится.
Как и предписано теорией, мы получили 3 * 2 = 6 анаграмм (Рис. 10.28).
Рис. 10.28. Все трёхбуквенные анаграммы
Для 4- буквенного слова КАРП цикл for выполнится 4 раза для 4буквенных слов, и в начале слов последовательно окажутся буквы К, А, Р, П.
Для 3-буквенных слов цикл for выполнится 3 раза, как мы рассмотрели
выше. В итоге наша программа напечатает все 24 перестановки
указанных букв (Рис. 10.29).
480
Рис. 10.29. Все анаграммы слова КАРП
Среди них окажутся и слова КРАП и ПАРК – осмысленные анаграммы исходного слова.
481
Проект Числовые рекурсии
Исходный код программы находится в папке Number Reverse.
Цикл for
Функция с параметрами
Условный оператор if
Оператор return
Списки
Срезы
Глобальные переменные
В задачах часто бывает нужно найти, сколько цифр в заданном числе.
Мы решим задачу только для натуральных чисел. Нуль и отрицательные
числа потребуют дополнительной обработки ввода.
Итак, основное условие:
# СЧИТАЕМ ЦИФРЫ
def get_num_digits(num):
if num <= 0:
return 0
То есть, когда функция get_num_digits получит нуль или отрицательное
число, она вернёт нуль и на этом закончит свою работу.
В противном случае число натуральное и имеет по крайней мере 1 цифру.
Если мы разделим нацело заданное число на 10, то оно укоротится на 1
цифру или обратится в нуль. При следующем рекурсивном вызове get_num
digits получит укороченное число либо нуль. Если это нуль, то все цифры
числа уже посчитаны, в противном случае к числу цифр добавится ещё 1:
return 1 + get_num_digits(num // 10)
По этому сценарию можно подсчитать цифры и в нерекурсивной функции:
482
# ИТЕРАЦИОННАЯ ВЕРСИЯ
def get_num_digits_iter(num):
num_digit = 0
while num:
num_digit += 1
num //= 10
return num_digit
В главной функции программы мы организуем бесконечный цикл ввода
чисел и печатаем длину чисел, полученную от обеих функций (Рис. 10.30):
# ГЛАВНАЯ ФУНКЦИЯ
def main():
num = 1
# бесконечный цикл ввода чисел,
# пока пользователь вводит
# натуральные числа:
while num:
print('Введите число: ', end='')
num = int(input())
print(get_num_digits(num))
print(get_num_digits_iter(num))
print(num)
print()
main()
Рис. 10.30. Считаем цифры
483
Другая часто встречающаяся задача состоит в перевёртывании заданного
числа. Например, число 123 должно превратиться в 321.
Последнюю цифру числа мы легко получим как остаток от деления заданного числа num на 10. Её нужно перенести в начало числа. В числе 123 последняя цифра 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 вызов этой функции и убеждаемся, чо она работает
верно (Рис.10.31):
def main():
num = 1
# бесконечный цикл ввода чисел,
# пока пользователь вводит
# натуральные числа:
while num:
484
print('Введите число: ', end='')
num = int(input())
print(get_num_digits(num))
print(get_num_digits_iter(num))
num = reverse_num(num)
print(num)
print()
Рис. 10.31. Числовые перевороты
Для переворачивания чисел также существует нерекурсивная версия:
# ИТЕРАЦИОННАЯ ВЕРСИЯ
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
485
И ещё 2 способа нерекурсивного переворачивания чисел с предварительным их преобразованием в строку:
# СТРОКОВАЯ ВЕРСИЯ
def reverse_num_str(num):
return int(str(num)[::-1])
# СТРОКОВАЯ ВЕРСИЯ 2
def reverse_num_str2(num):
return int(''.join(reversed(str(num))))
В первом случае мы используем срез с отрицательным шагом, который
возвращает перевёрнутую строку. Во втором случае мы используем функцию reversed, которая возвращает символы строки в обратном порядке.
Мы добавляем их к пустой строке и получаем строку с перевёрнутым числом.
Так как нам нужны не строки, а числа, то мы конвертируем строки в числа.
486
Проект Великолепная четвёрка
Исходный код программы находится в папке Четвёрка.
Бесконечный цикл while
Функция с параметрами
Условный оператор if – elif - else
Оператор return
В книге Algorithmen und Problemlösungen mit C++ (Рис. 10.32), на страницах
300-302 решается такая задача.
Рис. 10.32. Обложка книги
Их четвёрки с помощью трёх простых операций получить любое натуральное число.
Операции такие:
добавить 4 в конец
добавить 0 в конец
разделить число на 2, если оно чётное
Эту задачу проще решить с конца, то есть заданное число превратить в четвёрку. Тогда и операции станут противоположными:
487
удалить 4, если она стоит в конце числа
удалить 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:
488
solve(num * 2, '(делим на 2)')
print('->', num, message, end='')
# ГЛАВНАЯ ФУНКЦИЯ
def main():
print('Четвёрка')
print()
# бесконечный цикл ввода данных # пока пользователь не закроет программу
# или не введёт нуль или отрицательное число:
while True:
s = 'Введите число > '
print(s, end = '')
num = int(input())
# если пользователь ввёл 0 или отрицательное число,
# то программу закрываем:
if (num <= 0):
return
# решаем задачу:
solve(num, '')
print(); print()
main()
На Рис. 10.33 вы можете видеть, как работает наша программа.
Рис. 10.33. Оперативно!
489
Проект Рекурсивный тортик
Исходный код программы находится в папке Тортик.
Метод с параметрами
Оператор return
Оператор if
Оператор if-else
Цикл while
Бесконечный цикл while
Оператор input
В книге Математические изюминки [ХР92], на страницах 9-11 Росс Хонсбергер показывает, как решить такую задачу:
Расположим n точек на окружности и соединим их попарно хордами.
Предположим, что никакие три хорды не имеют общей точки внутри
круга.
На сколько областей разобьётся круг этими хордами?
Вывод формулы вы можете проследить по книге, нас же интересует только
формула как таковая:
Здесь:
NR – число областей, на которые хорды разбивают круг
n – число точек на окружности
и
- число сочетаний по 2 и по 4 из n
Подобную задачу решает и Мартин Гарднер в книге Математические досуги [ГМ72], на страницах 82-87.
Она облечена в более занимательную форму:
490
На какое максимальное число кусков можно разделить круглый пирог,
если сделать n разрезов, каждый из которых пересекает все остальные?
Муж спрашивает у жены:
- Милая, на сколько частей разрезать тортик – на шесть или на восемь?
Жена отвечает:
- Режь на шесть – восемь я не съем…
На Рис. 10.34 вы можете внимательно проследить судьбу тортика, подвергнутого означенным разрезам.
Рис. 10.34. Режем тортик
По этим данным с помощью метода конечных разностей легко выводится
формула для подсчёта кусков:
NR = ½ n2 + ½ n +1 = ½ n(n + 1) + 1
(1)
Существует и рекуррентная формула:
NR(0) = 1
NR(n) = n + NR(n-1) при n > 0
491
Давайте в помощь домохозяйкам и кондитерам напишем программу, умеющую подсчитывать куски торта, которые получаются при заданном числе
разрезов (Рис. 10.35).
Поскольку в реальной жизни вряд ли придётся разрезать даже самый большой торт на тысячи частей (то есть в пыль!), то для практических нужд
вполне достаточно рекуррентной формулы:
# -*- coding: Windows-1251 -*# ПРОГРАММА ДЛЯ РЕКУРСИВНОГО РАЗРЕЗАНИЯ ТОРТА
# РЕЖЕМ ТОРТИК РЕКУРСИВНО
def tortik_rec(num):
if(num < 1):
return 1
else:
return num + tortik_rec(num - 1)
# РЕЖЕМ ТОРТИК НЕРЕКУРСИВНО
def tortik(num):
res = num*(num + 1) // 2 + 1
return res
# ГЛАВНАЯ ФУНКЦИЯ
def main():
print('Тортик')
print()
# бесконечный цикл ввода данных # пока пользователь не закроет программу
# или не введёт отрицательное число:
while True:
s = 'Введите число разрезов > '
print(s, end = '')
num = int(input())
# если пользователь ввёл отрицательное число,
# то программу закрываем:
if (num < 0):
return
# находим число разрезов:
num = tortik_rec(num)
# печатаем число кусков:
print('Максимальное число кусков =', num)
print()
main()
492
Рис. 10.35. Полезная программа
Но если вас интересуют и теоретические изыскания в кондитерской науке,
то нужно прибегнуть к формуле (1) (Рис. 10.36):
# РЕЖЕМ ТОРТИК НЕРЕКУРСИВНО
def tortik(num):
res = num*(num + 1) // 2 + 1
return res
Рис. 10.36. Очень мелкие кусочки!
Впрочем, хозяйке гораздо интереснее и полезнее знать, сколько нужно
сделать разрезов, чтобы получить вполне определённое число кусков
торта. Опять бросаемся на помощь домохозяйке!
493
В функции main она задаёт нужное число кусков и получает от метода tortik
необходимое число разрезов:
# ГЛАВНАЯ ФУНКЦИЯ
def main():
print('Тортик')
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 -*# ПРОГРАММА ДЛЯ РЕКУРСИВНОГО РАЗРЕЗАНИЯ ТОРТА
# РЕЖЕМ ТОРТИК
def tortik(num):
res = 0
while(num > (res*(res + 1) // 2 + 1)):
res += 1
return res
494
На Рис. 10.37 вы можете видеть работу этой во всех отношениях полезной
для дома и для семьи программы.
Рис. 10.37. Тоже полезная программа
Как вы видите, в некоторых случаях остаются «лишние» куски. Вот почему
так важно знать математику и лично нарезать тортики кусками!
Исходный код программы находится в папке Тортик2.
495
Проект Red and Black
Исходный код программы находится в папке Red and Black.
Вложенные циклы for
Функция с параметрами
Условный оператор if - elif
Оператор return
Оператор and
Списки
Глобальные переменные
Функция copy
В книге Data Structure Practice for Collegiate Programming Contests and Education (Рис. 10.38), на страницах 48-51 разбирается интересная задача POJ
1979 - Red and Black.
Рис. 10.38. Обложка книги
Прямоугольное помещение разделено на клетки, окрашенные в красный и
чёрный цвет.
496
Человек стоит на чёрной клетке и может передвигаться на соседние
клетки по четырём направлениям, но заходить на красные клетки ему запрещено.
Нужно написать программу для подсчёта числа чёрных клеток, которые может обойти человек.
Обозначения на карте:
. – чёрная клетка
# - красная клетка
@ - человек (стоит на чёрной клетке)
Задания хранятся в текстовом файле в таком формате:
11 9
.#.........
.#.#######.
.#.#.....#.
#.#.###.#.
.#.#..@#.#.
.#.#####.#.
.#.......#.
.#########.
...........
На олимпиадах это общепринятый способ ввода данных, но мы просто скопируем задания в программу, чтобы не заниматься лишней работой.
Глобальные переменные и условие первой задачи можно записать так:
# -*- coding: Windows-1251 -*"""
Задача POJ 1979 - Red and Black
"""
from copy import *
from pprint import pprint
# поле:
field = []
# посещённые клетки:
visited = []
# число посещённых клеток:
497
count = 0
# размеры поля:
w = 6
h = 9
problem1 = [
'....#.',
'.....#',
'......',
'......',
'......',
'......',
'......',
'#@...#',
'.#..#.'
]
Прежде всего нужно скопировать условие задачи в список field (мы будем
называть его массивом – для удобства). Эта операция необязательная, поскольку условие задачи уже хранится в списке problem1. Но если бы мы загружали задачу с диска нам пришлось бы заполнять массив field данными,
так что мы просто имитируем этот процесс.
Одновременно метод prepare возвращает координаты клетки, на которой
стоит человек, чтобы мы смогли начать обход помещения.
# РЕШАЕМ ЗАДАЧУ
def solve(problem):
# печатаем задачу:
print('h =', h, 'w =', w)
pprint(problem)
print()
# готовимся к решению:
starty, startx = prepare(problem)
# решаем задачу:
dfs(starty, startx)
print ()
print ('Число клеток =', count)
print_position()
main()
В функции prepare мы копируем массив из условия задачи в массив поля
field. Эта операция простая и безболезненная. А дальше мы должны просмотреть все клетки поля и отметить красные клетки единицей в массиве
498
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
startx = x
# печатаем исходную позицию посещённых клеток:
print('starty =', starty, 'startx =', startx)
print_position()
return starty, startx
Теперь можно напечатать текущее состояние массива visited:
# ПЕЧАТАЕМ ТЕКУЩУЮ ПОЗИЦИЮ
def print_position():
pprint(visited)
print()
Для первой задачи будет напечатана такая информация (Рис. 10.39).
499
Рис. 10.39. Подготовительные операции
Проверяем и убеждаемся, что наши функции работают исправно!
Приступаем непосредственно к решению задачи в методе dfs, которому мы
передаём координаты начальной клетки (y, x):
# СЧИТАЕМ КЛЕТКИ МЕТОДОМ DFS
def dfs(y, x):
global count
count += 1
print(count, end=' ')
Сразу же считаем ту чёрную клетку, на которой стоит человечек.
Так как наше поле принципиально не отличается от лабиринта, то для его
обхода можно использовать обычные для лабиринтов методы DFS и BFS.
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)
500
# идём налево:
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 с координатами соседней верхней клетки.
И так мы продолжаем идти вверх, пока над нами есть свободные клетки. Но
рано или поздно над нами окажется:
посещённая (или красная) клетка
граница поля.
Тогда СТОП – дальше в этом направлении идти нельзя. Что делать? – Как и
в любом лабиринте, главное – не растеряться и не потерять голову. Осматриваемся по сторонам и идём налево, пока это возможно. Затем снова пытаем пойти вверх. Не получится – налево. Когда все пути в этих направлениях закончатся, мы идём направо, и наконец, вниз.
Когда мы попадём в тупик, то функция dfs вернётся в вызывающую функцию – в ту самую клетку, из которой мы начали движение в тупик. Если из
этой клетки остались пути в других направлениях, то мы идём по ним. В
501
случае нового тупика функция dfs снова возвращается назад, и так далее,
пока мы не обойдём все доступные нам клетки.
Поскольку каждую из посещённых клеток мы посчитали ровно 1 раз, то в
переменной count окажется ответ на задачу.
Запускаем программу и получаем полную информацию о странствиях в лабиринте:
1 2 3 4 5 6 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
Число клеток = 45
[[1, 1, 1, 1, 1, 0],
[1, 1, 1, 1, 1, 1],
[1, 1, 1, 1, 1, 1],
[1, 1, 1, 1, 1, 1],
[1, 1, 1, 1, 1, 1],
[1, 1, 1, 1, 1, 1],
[1, 1, 1, 1, 1, 1],
[1, 1, 1, 1, 1, 1],
[0, 1, 1, 1, 1, 0]]
Обратите внимание, что мы не посчитали 3 чёрные клетки, но не по лености, а только потому, что они окружены со всех сторон красными клетками
, и человечек в них попасть не может (Рис. 10.40).
Рис. 10.40. Хода нет!
Если бы человечек мог беспрепятственно посетить все чёрные клетки, мы
просто пересчитали бы их в обычном цикле…
Чтобы решить вторую олимпиадную задачу, раскомментируйте её:
502
"""
w = 11
h = 9
problem1 = [
'.#.........',
'.#.#######.',
'.#.#.....#.',
'.#.#.###.#.',
'.#.#..@#.#.',
'.#.#####.#.',
'.#.......#.',
'.#########.',
'...........'
]
"""
Вы можете и самостоятельно придумать подобные «лабиринты» - это несложно.
503
Литература
[Нагибин88]
Нагибин Ф.Ф., Канин Е.С.
Математическая шкатулка
М.:Просвещение, 1988. – 160 с.
[ОО80]
Оре О.
Приглашение в теорию чисел
М.: Наука, 1980 г. - 128 с.
Библиотечка Квант, Выпуск 3
504
[КА86] [КА96]
Б.А. Кордемский, А.А.Ахадов
Удивительный мир чисел
(МАТЕМАТИЧЕСКИЕ ГОЛОВОЛОМКИ
И ЗАДАЧИ ДЛЯ ЛЮБОЗНАТЕЛЬНЫХ)
М.:Просвещение, 1986. – 144 с.
М.:Просвещение, 1996. – 159 с
[100]
В. А. Дагене, Г. К. Григас, К. Ф. Аугутис
100 задач по программированию
М.:Просвещение, 1993. – 251 с.
ISBN: 5-09-003864-3
505
[ХР92]
Росс Хонсбергер
Математические изюминки
Наука, 1992. - 176 c.
Библиотечка «Квант». Вып. 83
ISBN 5-02-014406-1
[ЗП88]
Абрамов С.А. и др.
Задачи по программированию
Наука, 1988. – 224 с.
ISBN: 5-02-013774-Х
Серия: Библиотечка программиста
506
[ВНН88]
Воробьёв Н.Н.
Признаки делимости
Наука. - 1988, 96 с.
ISBN 5-02-013731-6
[ГМ72]
Гарднер Мартин
Математические досуги
М.:Мир, 1972. – 495 с.
507
[ЛВ88]
Липский В.
Комбинаторика для программистов
Москва: Мир, 1988. – 200 с.
[БК85]
Брудно А. Л. Каплан Л. И.
Олимпиады по программированию для
школьников
Наука. - 1985, 96 с.
508
[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
509
Книги из серии Программирование на языке C# 5.0: Начальный уровень
[CS10]
Рубанцев Валерий
Программирование на языке C#
5:
Начальный уровень
RVGames, 2014. – 620 с.
Основы программирования на языке
Си-шарп.
[CS11]
Рубанцев Валерий
Программирование на языке C#
5:
Практикум по решению задач
начального уровня
RVGames, 2014. – 420 с.
Многочисленные проекты для укрепления навыков программирования
на языке Си-шарп.
510
[CS12]
Рубанцев Валерий
Программирование на языке C#
5:
Компьютерная графика. Начальный уровень
RVGames, 2014. – 460 с.
Основы компьютерной графики.
[CS13]
Рубанцев Валерий
Программирование на языке C#
5:
Тотальный тренинг по Си-шарпу.
Начальный уровень
RVGames, 2014. – 400 с.
Разнообразные полезные и занимательные проекты на языке Си-шарп.
511