Python 12 уроков для начинающих
Python 12 уроков для начинающих
Санкт-Петербург
«БХВ-Петербург»
2023
УДК 004.43
ББК 32.973.26-018.1
Д57
Добряк П. В.
Д57 Python. 12 уроков для начинающих. — СПб.: БХВ-Петербург, 2023. —
272 с.: ил. — (Для начинающих)
ISBN 978-5-9775-1799-7
В 12 уроках показаны основы программирования и базовые конструкции языка
Python. Изложены принципы различных стилей программирования. Даны понятия
ввода-вывода, переменных, условий, потока чисел, циклов и списков, массивов,
функций и рекурсий. Рассмотрены особенности структурного, объектно-ориенти-
рованного и функционального программирования. В каждой главе предложены
практические задачи и дано их пошаговое решение с подробным описанием алго-
ритма.
Для начинающих программистов
УДК 004.43
ББК 32.973.26-018.1
Введение ............................................................................................................................ 5
Как обучают языкам программирования? ..................................................................................... 5
И вот появился язык Python ............................................................................................................ 7
Структура книги............................................................................................................................... 7
Благодарности .................................................................................................................................. 8
Об авторе .......................................................................................................................................... 9
Структура книги
Книга разделена на 12 уроков, примерно соответствующих трехчасовым занятиям
в том виде, как я преподаю Python на курсах. Урок состоит из нескольких разде-
лов — отдельных задач и их модификаций. Вы можете заниматься медленнее и
вдумчивее, проходя один раздел за одно занятие продолжительностью примерно
1
Правильно его читать как «Пайтон» — с ударением на первом слоге.
8 Введение
Благодарности
Мне сложно вспомнить поименно всех моих учеников, студентов и слушателей
курсов, с которыми я решал задачи из этой книги, и перечислить их всех, чтобы
никого не забыть.
Отдельной благодарности заслуживают мои сыновья Андрей и Артем Добряки,
которые в достаточно юном возрасте выдержали занятия со мной. Нужно поблаго-
дарить Виктора Запорожца, который не только решал ряд задач из моей подборки
на разных языках программирования, но еще и читал мои объяснения и дал ряд
ценных советов по изложению материала. Другой мой ученик, Александр Ефимов,
начал изучать программирование, уже будучи взрослым человеком. Я благодарен
Введение 9
ему за то, что он заставил меня обстоятельно и не торопясь объяснять материал еще
доходчивее, чем я делал до этого.
Ошибки в моих программах, которые иногда случались, находил сам Python, а вот
для исправления ошибок в человеческой речи по-прежнему нужен человек. Я бла-
годарен моей жене Евгении Хрущевой, которая терпеливо редактировала мою кни-
гу, исправляя ошибки.
Об авторе
Павел Вадимович Добряк — кандидат технических наук, университетский препо-
даватель, ведущий занятия по различным языкам программирования, базам дан-
ных, искусственному интеллекту и проектированию информационных систем.
Репетитор математики и информатики. Область научных интересов: сложные мо-
дели данных и алгоритмы, мультипарадигменное программирование.
УРОК 1
Ход программирования
Традиционно первое, что пишут изучающие программирование, — это программа,
выводящая на экран сообщение «Привет, мир!».
Если вы полный новичок в программировании, вам нужно сначала познакомиться
со средой разработки, которой вы будете пользоваться, а именно — понять, где
находятся два окна, или поля ввода:
1. В первом окне вы, как программист, будете писать и редактировать программу.
2. Во втором окне программа будет выводить сообщения и запрашивать у вас, как
пользователя, данные для обработки.
Я рекомендую начать изучать программирование на Python со среды разработки
IDLE Python. Это свободно распространяемое программное обеспечение, которое
можно скачать с официального сайта: https://www.python.org/downloads/. Пройдя
по этой ссылке, нажмите в открывшемся окне на кнопку Download Python
(рис. 1.1).
После загрузки с сайта запустите скачанный файл, и среда разработки IDLE уста-
новится на ваш компьютер.
Когда вы запускаете IDLE, то открывается окно № 2, в котором вы будете вводить
данные для обработки (рис. 1.2).
Ввод/вывод, переменные, условия 11
Чтобы начать писать программу, вам нужно открыть первое окно, создав файл
с программой. Для этого выберите пункт меню File | New File, и откроется редактор
программы (рис. 1.3).
И именно здесь вы будете писать программу, а результат увидите в предыдущем
окне.
Особенность Python в том, что он очень лаконичен. Программа на Python «Привет,
мир!», в отличие от программ на других языках высокого уровня (Фортрана, Пас-
каля, С++, Java, С# и др.), занимает одну строчку. Ее основа — это функция print.
12 Урок 1
или апострофы:
print('')
Привыкайте писать сначала обе круглые скобки, потом обе пары кавычек внутри
круглых скобок и только затем — текст внутри. Так делают все программисты.
Разных скобок и кавычек придется писать очень много, и если вы какую-нибудь из
них забудете, то программа не станет работать. У меня с этим связана история. На
одной вечеринке возникла необходимость срочно набрать текст. Чем я и занялся.
Человек, который видел меня в первый раз, сказал: «Да ты программист!» — «Как
ты это понял?» — «Очень просто: ты открывающие и закрывающие скобки ста-
вишь одновременно!» Такая вот у программистов профдеформация.
Ввод/вывод, переменные, условия 13
Шаг 4. Теперь нужно нашу программу запустить. Разберитесь, где находится кноп-
ка запуска программы или соответствующий пункт меню. В IDLE Python это пункт
меню Run | Run Module.
Шаг 5. Если не вышло никаких сообщений об ошибках, то смотрим в окне вывода
нашу фразу:
Привет, мир!
Ход программирования
Шаг 1. Для начала выведем сообщение: «Как тебя зовут?» — так же, как это мы
делали в программе «Привет, мир!»:
print("Как тебя зовут?")
Шаг 2. Для ввода информации нам понадобится вызвать функцию ввода input:
input()
Обратите внимание на пустые круглые скобки. Поскольку input, как и print, являет-
ся функцией, то после названия функции круглые скобки обязательны — они сиг-
нализируют Python, что нужно вызвать функцию. Но этого мало, и поскольку
результат ввода (имя человека) нам понадобится дальше для вывода, сохраним его
в переменной name:
name=input()
Думаю, что у читателя есть интуитивное понимание того, что такое переменная,
которую можно воспринимать так же, как и в математике или физике, — т. е. как
некоторую величину, которая может принимать различные значения (например,
температура, обозначаемая обычно буквой T). Переменные могут использоваться
в математических формулах. Переменной соответствует некоторая область памяти
компьютера, где хранится ее значение. Переменным вы можете давать любые на-
звания, состоящие из строчных и заглавных латинских букв, арабских цифр и ниж-
него подчеркивания. Переменная должна начинаться на букву.
Шаг 3. Для вывода опять пользуемся функцией print:
print("Привет,")
14 Урок 1
Но нам нужно еще вывести имя. Мы можем вывести несколько текстов с помощью
одного вызова print. Для этого запишем нужные текстовые блоки через запятую:
print("Привет",name)
Обратите внимание, что name мы написали без кавычек. Если мы хотим, чтобы
вместо слова name (название переменной) подставилось его значение, то пишем пе-
ременную без кавычек. Мы получили готовую программу (листинг 1.2.1).
Паша
name
name Паша
Название переменной name
значение переменной Паша
Название переменной name значение переменной Паша
Название переменной - name, а ее значение – Паша
Шаг 6. Программу можно сделать короче. Функция input, как и print, тоже может
выводить сообщения на экран (листинг 1.2.3).
Шаг 7. Программу можно сделать еще короче. Поскольку переменная name исполь-
зуется только один раз, просто копируем ее код туда, где она сработает, — т. е.
input копируем внутрь print на место name и получаем еще одну версию программы
(листинг 1.2.4).
Ход программирования
Шаг 1. Спрашиваем у пользователя значения двух переменных с именами a и b:
a=input("a=")
b=input("b=")
Дело в том, что input возвращает строковый тип переменных. То есть переменные a
и b хранят строки. А оператор «плюс», когда видит, что надо складывать строки,
соединяет их вместе.
Здесь мы впервые столкнулись с тем, что у переменной есть не только имя и значе-
ние, но и тип — множество значений, которые она может принимать, и операции,
которые можно над ней совершать. В написанной нами программе тип данных —
это строки, и оператор сложения для них работает как соединение строк. А нам
нужен числовой тип данных, которых в Python три: целые числа, числа с дробной
частью (плавающей точкой) и комплексные числа.
Шаг 5. Нам необходимо преобразовать вводимые строки в числа, чтобы получить
результат математического сложения. Для этого преобразования воспользуемся
функцией int() (листинг 1.3.2)
Результат выполнения:
a=12.3
b=45.6
a+b= 57.9
print(a/(b+c)) 1.0
print(a/b*c) 17.5
print(a/(b*c)) 0.7
print(int(a/b)) 3
Обратите внимание на неочевидные операторы **, //, % и на то, как скобки влияют
на результат.
Ход программирования
Шаг 1. Разбираемся, какие переменные вводятся, а какие вычисляются. Вводятся k
и b, а x — вычисляется. Для ввода используем input. Обратите внимание: x с по-
мощью input вводить не нужно (это ошибка № 1 начинающих программистов):
print('Введите числа:')
k=int(input())
b=int(input())
Шаг 2. Сам Python решать уравнения не умеет (это могут делать только математи-
ческие программы — например, Mathcad, в которых есть элементы программиро-
вания), поэтому вводить формулу kx + b = 0 в программу не нужно (это ошибка № 2
начинающих программистов). Формулу для вычисления x выводим сами:
x=-b/k
Шаг 6. Еще раз посмотрите на отступы у некоторых строчек кода. Их принято де-
лать с помощью табуляции. Если вы еще не поняли, зачем они нужны, попробуйте
убрать некоторые отступы и обратите внимание, что в большинстве случаев про-
грамма не будет работать или будет выдавать неверный результат (табл. 1.2).
Шаг 7. Зададим себе вопрос: все ли случаи мы учли? При k = 0 и b = 0 мы имеем
уравнение
0x + 0 = 0.
Упрощаем:
0 = 0.
То есть от x ничего не зависит. Ответ: «x — любое».
20 Урок 1
Программа 1 Программа 2
print("Введите числа: ") print("Введите числа: ")
k=int(input()) k=int(input())
b=int(input()) b=int(input())
if k!=0: if k!=0:
x=-b/k x=-b/k
print(x) print(x)
else: else:
print("корней нет") print("корней нет")
Программа 3 Программа 4
print("Введите числа: ") print("Введите числа: ")
k=int(input()) k=int(input())
b=int(input()) b=int(input())
if k!=0: if k!=0:
x=-b/k x=-b/k
print(x) print(x)
else: else:
print("корней нет") print("корней нет")
Программа 5 Программа 6
print("Введите числа: ") print("Введите числа: ")
k=int(input()) k=int(input())
b=int(input()) b=int(input())
if k!=0: if k!=0:
x=-b/k x=-b/k
print(x) print(x)
else: else:
print("корней нет") print("корней нет")
Программа 7
print("Введите числа: ")
k=int(input())
b=int(input())
if k!=0:
x=-b/k
print(x)
else:
print("корней нет")
Значит, ветка алгоритма при k = 0 (у нас это блок else) распадается на две ветки
в зависимости от того, равно b нулю или нет. Вставим это условие внутрь блока
else и получим готовую программу (листинг 1.4.3).
Ввод/вывод, переменные, условия 21
вложен в условие:
if b!=0:
Хотелось бы иметь возможность написать программу в виде трех веток. Это можно
сделать с помощью альтернативных условий elif (их может быть сколько угодно).
Формат условия с альтернативными условиями:
Ввод/вывод, переменные, условия 23
if условие:
#код программы, если условие истинно
elif альтернативное условие 1:
#код программы, если альтернативное условие истинно
elif альтернативное условие 2:
#код программы, если альтернативное условие истинно
...
elif альтернативное условие n:
#код программы, если альтернативное условие истинно
else:
#код программы, если условие ложно
#Код программы, который выполняется после условия
Напишем заготовку кода под три ветки:
if условие:
#код программы, если условие истинно
elif альтернативное условие 1:
#код программы, если альтернативное условие истинно
else:
равенство == . Кажется, что это ошибка, и надо писать =, но ошибкой будет как раз
написать одинарное равно =.
Думаю, что все языки программирования различают два оператора равенства:
«равно как присваивание» (в Python это =) и «равно как сравнение» (в Python
это ==).
«Равно как присваивание» мы уже использовали, когда организовывали вычисле-
ниями по формулам, — например:
x=-b/k
Оно означает, что надо вычислить выражение в правой части равенства и результат
сохранить в переменной левой части.
Оператор сравнения на равенство:
k==0
Шаг 11. Для нас важно, чтобы условия k==0 и b==0 выполнялись одновременно. По-
этому их надо соединить оператором and (табл. 1.5).
В этой программе учесть все возможные комбинации входных данных было важно
для выдачи ответа. Но мы не проверили, например, что будет, если пользователь
введет строки вместо чисел. Такая проверка называется защитой от дурака, и ее
важно делать в промышленных программах. Здесь же мы решаем учебные задачи,
и в них мы не станем «защищаться от дурака», чтобы не отвлекать ваше внимание
от сути задач и освоения приемов программирования.
Идея алгоритма
В общем-то, все необходимые знания для решения этой задачи у вас есть (вы их
получили в ходе создания программы решения линейного уравнения). Поэтому
сначала попытайтесь написать программу сами, а потом сравните с программой,
приведенной в этом разделе. Задача предусматривает отработку вложенных блоков
условий, составных условий и альтернативных условий. Вам понадобятся также
знания из математики — теорема о существовании треугольника по трем сторонам.
Наглядно покажем эту теорему с помощью рис. 1.6 и 1.7.
Ход программирования
Шаг 1. Определимся со структурой программы. Прежде всего нужно проверить,
существует треугольник или нет. И если он существует, то только тогда мы и ста-
нем определять его разновидности. Заготовка программы:
#Ввод данных
if условие существования треугольника:
#определение разновидностей треугольника
else:
print('Треугольник не существует')
Ввод/вывод, переменные, условия 27
Наша программа работает правильно, но что будет, если мы внесем в нее измене-
ния? Поэкспериментируйте, меняя местами условия и заменяя and на or.
В Python можно использовать двойные равенства (неравенства), поэтому мы
можем сделать замену:
a==b and b==c
Ввод/вывод, переменные, условия 29
на
a==b==c
Ход программирования
Для решения этой задачи нет надобности изучать какие-то дополнительные языко-
вые конструкции — нужно лишь применить смекалку. Мы напишем несколько вер-
сий программы, но для одной из них понадобится новая языковая конструкция.
Шаг 1. Напишем заготовку программы (ввод/вывод):
x=int(input("Введите x "))
y=int(input("Введите y "))
print(x,y)
#здесь будет обмен значениями.
print(x,y)
Результат этот неправильный. Это связано с тем, что алгоритм выполняется по ша-
гам. Посмотрим значения переменных в пошаговой отладке (табл. 1.6).
(2)
Чай Кофе
(1) (3)
Пустой
стакан
Шаг 5. Для обмена значениями переменных в Python есть особая языковая конст-
рукция — кортежи (упорядоченные последовательности переменных). Приведем
программу с кортежами (листинг 1.6.5).
Ход программирования
Шаг 1. Для начала нужно научиться вводить числа. Мы вводим числа, пока не
встретится ноль. Здесь нам понадобится новая языковая конструкция, но, допустим,
что мы используем if. Мы вводим числа при выполнении условия, поэтому:
if a>0:
a=int(input())
Шаг 2. Запустив программу, мы видим, что условие срабатывает только один раз.
То есть мы вводим только одно число a. А нам нужно вводить много чисел. Опера-
тор if здесь не подойдет. Нам нужен новый оператор — оператор цикла while («по-
ка»). Он будет многократно выполнять действия, пока истинно содержащееся в нем
условие:
a=1
while a>0:
a=int(input())
Шаг 3. Каждый раз, принимая новое значение a, мы забываем его старое значение.
Значит, всякий раз, принимая a, нам нужно его обрабатывать.
Помните принцип структурного программирования — что мы можем вводить но-
вые переменные по мере их необходимости? Так как среднее арифметическое рав-
но сумме чисел, деленной на их количество, то в этой задаче нам нужны две пере-
менные: сумма s и счетчик c.
Разберемся сначала с суммой. Пусть принимаются следующие числа:
a= 10 20 30 40 50 0
А что такое 100? Из приведенной таблички значений видно, что 100 — это то же
значение s, только старое. Значит, получаем формулу:
s=s+a
Шаг 4. Теперь нужно подсчитать количество введенных чисел. Подобно тому как
мы сконструировали формулу для s, внесем в нашу табличку значений счетчик c
для суммы:
a= 10 20 30 40 50 0
s= 0 10 30 60 100 150
с= 1 2 3 4 5
Задача 2
Дополнительно к условию из предыдущей задачи надо найти максимум из введен-
ных чисел.
Поток чисел, циклы и списки 37
Отсюда видно, что переменная максимума m обновляется только тогда, когда теку-
щее введенное a больше, чем значение переменной m. Поэтому в тело цикла надо
вставить блок обновления m с условием:
if m<a:
m=a
Обратите внимание, что мы вложили условие if внутрь цикла while с помощью от-
ступа. Помните принцип структурного программирования, что мы можем блоки
вкладывать друг в друга на неограниченную глубину?
Задача 3
Подсчитать сумму вводимых чисел. Числа вводятся до тех пор, пока не будет вве-
ден третий по счету ноль. Пример вводимых чисел:
a= 10 20 0 30 40 0 50 0
38 Урок 2
Шаг 8. Но можно обойтись без двух вложенных циклов. Мы ведь можем подсчи-
тывать количество нулей. Введем новую переменную: z — счетчик нулей. Запол-
ним табличку значений с его работой:
Поток чисел, циклы и списки 39
a= 10 20 0 30 40 0 50 0
z= 0 0 0 1 1 1 2 2 3
Отсюда видно, что счетчик увеличивается при a=0. Введем условие внутрь цикла
while:
a=1
s=0
z=0
while a!=0:
a=int(input())
s=s+a
if a==0:
z=z+1
print(s)
Задача 4
Подсчитать сумму вводимых чисел. На этот раз числа вводятся до тех пор, пока не
будут введены три нуля подряд. Пример вводимых чисел:
a= 10 20 0 30 40 0 0 50 0 0 0
Ход программирования
Шаг 1. Для начала узнаем, что такое «список», и какие есть функции для работы
с ним. Запустим программу из листинга 2.2.1 и посмотрим на ее вывод на экран:
Листинг 2.2.2. Пока Листинг 2.2.3. Три нуля Листинг 2.2.4. Три нуля
не встретится 0 подряд
a=1 a=1 a=1
s=0 z=0 L=[]
L=[] L=[] z=0
while a>0: while z<3: while z<3:
a=int(input()) a=int(input()) a=int(input())
L.append(a) L.append(a) L.append(a)
print(L) if a==0: if a==0:
print(sum(L)) z=z+1 z=z+1
print(sum(L)) else:
z=0
print(sum(L))
Шаг 4. Заменить счетчик нулей на count в программе «Три нуля подряд» из листин-
га 2.2.4 не получится, т. к. мы считаем три нуля, идущих подряд. Это значит, что
нам нужно научиться обращаться к элементам списка по отдельности. Это можно
сделать по номеру элемента, заключенному в квадратные скобки. Посмотрите сле-
дующий пример (листинг 2.2.7):
Листинг 2.2.7. Обращение к элементу списка по его номеру Результат
L=[10,20,30,40,50]
print(L) [10, 20, 30, 40, 50]
print(L[0]) 10
print(L[1]) 20
print(L[2]) 30
print(L[3]) 40
print(L[4]) 50
Обратите внимание, что нумерация элементов идет с нуля: в примере — пять эле-
ментов списка, а последний (пятый) элемент имеет номер 4.
Поток чисел, циклы и списки 43
Шаг 5. Для того чтобы обратиться к последнему элементу списка, нужно знать его
размер. Это может быть не очень удобно, но в Python предусмотрена обратная ин-
дексация списка через отрицательные номера. То есть последний элемент списка
имеет номер –1, предпоследний: –2 и т. д. Приведу пример этой нумерации в лис-
тинге 2.2.8:
Прямой индекс 0 1 2 3 4
Список 10 20 30 40 50
Обратный индекс -5 -4 -3 -2 -1
Обратная индексация может быть удобна для подсчета количества нулей подряд.
После добавления a в список нам нужно проверить три последних элемента. Хотя
бы один из них должен быть неравным нулю:
L[-1]!=0 or L[-2]!=0 or L[-3]!=0
Шаг 7. Однако это условие должно проверяться тогда, когда в списке количество
элементов не меньше трех. Добавим:
if len(L)>=3 and L[-1]==L[-2]==L[-3]==0:
break
Шаг 8. Теперь нам нужно подумать, каким будет условие нахождения в цикле.
И обратите внимание: никакое условие здесь вообще не нужно, т. к. выход из цикла
выполняется с помощью break.
Так что нам нужно не условие, а бесконечный цикл. Такой конструкции в Python
нет, но мы легко можем превратить цикл while в бесконечный цикл следующим
образом:
while True:
True — это обозначение истины. С ним, если нет break, цикл будет работать беско-
нечно. Есть и обозначение для лжи — False. Мы познакомимся с ними на следую-
щем уроке.
А пока мы получили готовую программу (листинг 2.2.9).
Здесь мы видим, что программа стала еще сложнее: в while вложен if, в который
вложен for.
Шаг 10. Но есть способ упростить с помощью списков и эту программу! Для этого
надо использовать новую языковую конструкцию — срез (фрагмент списка). Чтобы
получить срез, нужно в квадратных скобках задать его левую и правую границы.
При отсутствии одной из них считается, что срез идет с самого начала или до само-
го конца соответственно. Причем нумерация в срезе может быть как прямой, так и
обратной. Посмотрите на следующий пример (листинг 2.2.11).
Чтобы взять текущие три последние элемента списка, используем следующий срез:
L[-3:]
46 Урок 2
Ход программирования
Задачи двух предыдущих разделов можно было решить как с помощью списков,
так и без них. Но есть программы, где без списков не обойтись. Решим задачи
с векторами.
Шаг 1. Прежде всего, научимся вводить векторы.
Сначала введем размерность пространства векторов (количество элементов спи-
сков — т. к. векторы будут храниться как списки):
n=int(input())
Строчку надо разделить на числа пробелами. Для этого предназначен метод split:
s=input()
p=s.split()
Задача 1
Найти длину вектора.
Шаг 4. Перед тем как мы начнем писать программу по вычислению длины вектора,
напомню, что длина вектора — это корень из суммы квадратов его координат
(обобщение теоремы Пифагора). Школьники привыкли к векторам на плоскости
или в пространстве, но мы можем распространить формулу длины вектора и на
многомерное пространство:
n −1
v = 2 v02 + v12 + ... + vn2−1 = 2 ∑ vi2 .
i =0
Шаг 5. После цикла нам нужно извлечь корень. Вспомним, что корни — это дроб-
ные степени:
1
n
a = an ,
и напишем программу (листинг 2.3.2).
Задача 2
Найти сумму векторов.
Шаг 7. Сумма векторов — это вектор, у которого координаты — это суммы соот-
ветствующих координат. Например:
[1, 2, 3] + [4, 5, 6] = [1 + 4, 2 + 5, 3 + 6] = [5, 7, 9].
Выполнив следующую программу, мы увидим результат:
Программа Результат
print([1,2,3]+[4,5,6]) [1, 2, 3, 4, 5, 6]
Задача 3
Найти скалярное произведение векторов.
Шаг 8. Скалярное произведение векторов — это сумма произведений соответст-
вующих координат. Например:
[1, 2, 3]*[4, 5, 6] = 1*4 + 2*5 + 3*6 = 4 + 10 + 18 = 32
Обратите внимание, что когда мы складываем векторы, то результат сложения —
это вектор. А когда мы умножаем векторы, то результатом умножения будет чис-
ло. Поэтому, если мы возьмем за основу программу сложения векторов, то нам
придется внести два изменения:
сложение координат заменим на умножение:
Было Стало
u[i]+v[i] u[i]*v[i]
Третий урок будет посвящен, пожалуй, самому часто используемому приему про-
граммирования — флагам. На флаги мы решим четыре задачи, при этом напишем
альтернативные программы с помощью встроенных в Python функций и коллекций.
Ход программирования
Шаг 1. Напишем заготовку программы: ввод количества чисел и ввод самих чисел.
Используем цикл for (листинг 3.1.1).
if какое-то условие:
print("Есть больные")
else:
print("Все здоровы")
if какое-то условие:
print("Есть больные")
else:
print("Все здоровы")
Флаги. Структурное программирование и стиль Python 53
Шаг 5. Можно догадаться, что нам нужна новая переменная. Ведь вывод на экран
мы делаем, проверяя какое-то условие (т. е. значение переменной). Ну а при t>37 мы
как раз будем устанавливать значение этой переменной, сигнализирующее, что на
борту есть больной.
Из известных нам приемов мы можем использовать счетчик, чтобы подсчитать ко-
личество больных. Но здесь не нужен даже счетчик — ведь нам всего лишь надо
сообщить только «да» или «нет»: есть больные или их нет.
Такая переменная называется флаг. Она примет одно из двух значений: истину,
когда на борту есть больные, и ложь, когда все здоровы. Воспользуемся для этого
логическими значениями: истиной (True) и ложью (False). Перед циклом установим
флаг False (исходно предполагаем, что на борту нет больных). А в самом цикле,
если обнаружим повышенную температуру, установим флаг True. Получится сле-
дующая программа (листинг 3.1.5).
Ведь «равенство как сравнение» как раз возвращает True или False, и нет необходи-
мости истину сравнивать с истиной. Получаем готовую версию программы (лис-
тинг 3.1.6).
else:
print("все здоровы")
Шаг 7. Заметим, что нам нет необходимости проверять всех людей. Мы можем
прервать проверку с помощью оператора break, когда обнаружим первого больного
(листинг 3.1.7).
Сравним программы из листингов 3.1.3 и 3.1.4. Мы как будто имеем дело с разны-
ми языками программирования. На самом деле это разные стили мышления или,
как мы уже отмечали во введении и на предыдущих уроках, — разные парадигмы
программирования. Язык Python поддерживает несколько парадигм программиро-
вания. Мы познакомимся с несколькими из них в этой книге. И на протяжении это-
го урока, как и обещали ранее, будем писать программы в двух парадигмах:
1. В стиле структурного программирования.
2. В стиле Python.
Ход программирования
Шаг 1. Алгоритм состоит в том, что мы просматриваем строку с двух концов,
сравнивая буквы, как показано на рис. 3.1.
Для обращения к буквам левого края будем использовать прямую индексацию,
а к буквам правого края — обратную (работа со строками похожа на работу со спи-
сками):
56 Урок 3
Прямой индекс 0 1 2 3 4 5 6
Слово a a b C b a a
Обратный индекс -7 -6 -5 -4 -3 -2 -1
Шаг 3. Теперь возникает вопрос: «Какой знак поставить между s[i] и s[-1-i]»?
Второй распространенной ошибкой является постановка равенства:
s[i] == s[-1-i]
Если мы обнаружим пару разных букв, как показано на рис. 3.2 (поз. 2), то сразу
сможем сделать вывод о том, что строка не является палиндромом, — продолжать
сравнение больше смысла нет, поэтому делаем прерывание (листинг 3.2.1).
Шаг 4. А вот вывод о том, что строка является палиндромом, мы можем сделать,
только когда сравним все буквы. Но условие сравнения букв находится в цикле.
И это верный признак того, что нужно использовать флаг.
Будем оптимистичны: предположим, что строка — палиндром, и перед циклом
установим значение флага в состояние «истина». А в случае неравенства букв из-
меним значение флага на «ложь» (листинг 3.2.2).
if f:
print("Палиндром")
else:
print("Не палиндром")
Шаг 6. Заметим, что нам можно менять i только до половины строки, поскольку
далее левый индекс сравнения i и правый индекс -1-i просто поменяются местами.
А эти сравнения мы уже делали, значит, напрашивается оптимизация нашей рабо-
тающей программы (листинг 3.2.4).
Эта задача — пример того, что мы можем написать алгоритм, пользуясь минималь-
ным количеством языковых средств, а можем воспользоваться специальными сред-
ствами Python, и программа будет очень простой.
Задачи на палиндром очень плодотворны, и на следующих уроках мы решим еще
несколько задач на палиндром.
Ход программирования
Шаг 1. Зададим исходную строку и фрагмент для поиска. Переберем все возмож-
ные начальные положения фрагмента (листинг 3.3.1).
Шаг 3. Чтобы понять, что фрагмент найден, создадим флаг (листинг 3.3.3).
if f:
print(i)
else:
print(-1)
Шаг 5. Теперь заменим найденный в строке фрагмент на новый. Для этого, исполь-
зуя срезы, разделим строку на три части и заменим среднюю часть (листинг 3.3.5).
Заметим, что нам нет нужды проверять, есть ли фрагмент в списке. Если его нет, то
замены не произойдет.
Шаг 8. Что же означает 1 в списке аргументов replace?
s=s.replace(p,r,1)
62 Урок 3
Это количество замен, которые нужно сделать. Если мы уберем этот аргумент, все
фрагменты будут заменены (листинг 3.3.7).
Ход программирования
Шаг 1. Прежде всего, определимся, нужны здесь списки или нет. Поскольку мы
сравниваем текущее число с соседним (предыдущим), то можно обойтись без спи-
сков, хотя программа получится несколько сложнее, чем со списками.
Напишем обе версии программы, начав с программы без списков. Этот прием про-
граммирования называется: «запоминание предыдущего числа в потоке».
Флаги. Структурное программирование и стиль Python 63
При этом вся обработка будет вестись при условии i>0 (естественно, что ap при
первом введенном a просто отсутствует). А приравнивание:
ap=a
Шаг 3. Подумаем о том, как нам применить флаг. Допустим, все числа равны. Как
нам это отследить? Равенство пары соседних чисел еще ни о чем не говорит. А вот
неравенство означает, что мы не можем сделать вывод о том, что все числа равны.
Введем флаг f, равный изначально True, который будет срабатывать в случае нера-
венства соседних чисел (листинг 3.3.2).
Шаг 4. Чтобы различить случаи «Есть соседние равные и неравные» и «Нет сосед-
них равных», нам нужно срабатывание флага при выполнении условия:
if a==ap:
for i in range(n):
a=int(input())
if i>0:
if a!=ap:
f=False
else:
g=False
ap=a
elif g:
print("нет соседних равных")
else:
print("есть соседние равные и неравные")
Шаг 7. В задаче «Эпидемия на корабле» нам было достаточно найти одного боль-
ного, после чего прервать ввод. Здесь тоже можно сделать прерывание, но тогда,
когда мы уже нашли пару равных чисел и пару неравных (листинг 3.4.6).
Шаг 8. Заметим, что уже после первого сравнения флаги f и g не могут быть равны
True одновременно, а значит, мы можем упростить условие прерывания — вместо:
if f==g==False:
написать:
f==g:
if i>0:
if a!=ap:
f=False
else:
g=False
if f==g:
break
ap=a
if f:
print("все числа равны")
elif g:
print("нет соседних равных")
else:
print("есть соседние равные и неравные")
взять:
len(L)-1
Вообще, приучите себя к тому, что когда вы меняете индекс списка на некоторую
формулу, сразу же меняйте границы выполнения цикла.
Мы получили готовую версию программы (листинг 3.4.9).
for i in range(len(L)-1):
if L[i]!=L[i+1]:
f=False
elif L[i]==L[i+1]:
g=False
if f==g:
break
if f:
print("все числа равны!")
elif g:
print("нет соседних равных")
else:
print("есть соседние равные и неравные")
Задача 2
Вводится список чисел. Вывести одно из трех сообщений:
1. Все числа равны.
2. Есть равные и неравные.
3. Нет равных.
Сравнив эту задачу с предыдущей, мы увидим, что слово «соседние» из условий 2
и 3 исчезло. А значит, нам нужно сравнивать все числа со всеми. Примеры входных
данных и вывода для этой задачи приведены в табл. 3.4. Обратите внимание, что
третий пример стал соответствовать второму выводу.
Ход программирования
Шаг 1. Поскольку мы сравниваем уже все числа со всеми, то нам не годится срав-
нение:
if L[i]!=L[i+1]:
Флаги. Структурное программирование и стиль Python 69
Но откуда возьмется j? Нам нужен еще один цикл. В программе один цикл будет
расположен внутри другого цикла (листинг 3.4.10).
Шаг 2. Оставив циклы так, как мы написали, мы можем получить ошибочную про-
грамму — ведь i и j меняются независимо друг от друга, а значит, могут совпасть.
Тогда сработает условие равенства, и программа сделает вывод, что есть равные
числа, даже тогда, когда равных чисел нет. Выпишем числа, которые мы сравнива-
ем (рис. 3.3).
a1 a2
a1 a3 a2 a3
a1 a4 a2 a4 a3 a4
a1 an a2 an a3 an an−1 an
Все прочее мы оставляем, как было сделано в предыдущей версии программы (лис-
тинг 3.4.11).
Шаг 3. Оператор break прерывает только цикл, в котором он находится. При суще-
ствующем условии нам нужно прервать оба цикла. Повторим прерывание и в цикле i
(листинг 3.4.12).
Шаг 4. Мы можем сделать еще одну оптимизацию. Заметим, что если все числа
равны друг другу, то после окончания первого цикла j нам можно уже не продол-
жать следующие, — т. е. при:
L[0]==L[1]
L[0]==L[2]
L[0]==L[3]
...
L[0]==L[len(L)-1]
мы можем не сравнивать:
L[1]==L[2]
L[1]==L[3]
...
L[1]==L[len(L)-1]
L[2]==L[1]
L[2]==L[2]
L[2]==L[3]
...
L[2]==L[len(L)-1]
...
if f:
print("все числа равны!")
elif g:
print("нет равных")
else:
print("есть равные и неравные")
Такая форма цикла нам еще не встречалась. Здесь el — это элемент списка L.
Если все числа равны между собой, то count для каждого элемента списка вернет
ответ, равный длине строки (листинг 3.4.15).
Если встречаются равные числа, то для них count вернет значения больше 1 (лис-
тинг 3.4.16).
Если элемент списка встречается один раз, то для него count вернет 1. Но при этом
условии нам ничего делать не нужно — ведь чтобы сделать вывод о том, что все
числа различны, нам нужно, чтобы для всех элементов списка count вернул 1.
Обнаружив, что все числа равны или есть равные, нам нужно сделать прерывания
(листинг 3.4.17).
С методом count мы уже имели дело, но, может быть, есть еще какая-нибудь языко-
вая конструкция, которая сделает нашу задачу еще проще? Есть. Это множества.
Вспомним, что список — это упорядоченный набор элементов, которые могут по-
вторяться. Множество — это неупорядоченный набор элементов без повторений.
Существуют функции преобразования списка (или строки) во множество и обрат-
но — далее показаны примеры конвертации строки во множество, в список и об-
ратно в строку (листинг 3.4.19).
74 Урок 3
Если все числа разные, то длины списка и множества совпадают (листинг 3.4.21).
Ход программирования
Шаг 1. Приведем примеры, когда мы можем составить палиндром путем переста-
новки букв, а когда — нет (табл. 4.1).
Из примеров видно, что палиндром можно составить, если каждая буква встречает-
ся четное количество раз, или есть только одна буква, которая встречается нечетное
количество раз.
Итак, нам нужно перебирать все буквы строки и считать, сколько раз они встреча-
ются в строке — воспользуемся для этого конструкцией count (листинг 4.1.1).
Шаг 4. При отладке программы на наших примерах мы увидим, что для строки:
aab
— неверный.
78 Урок 4
Дело в том, что буква a в строке aaabb встречается три раза, но и проверяться она
будет тоже три раза, т. к. мы перебираем строку поэлементно, а значит, счетчик c
для букв а будет равняться 3.
Как же нам сделать, чтобы буква а проверялась ровно 1 раз? Нужно преобразовать
строку во множество и уже для каждого элемента множества искать, сколько раз он
встречается в исходной строке. То есть цикл:
for el in s:
Задача 2
Из введенного слова составить палиндром путем перестановки букв, если это воз-
можно.
Языковые конструкции: строка, count, словарь.
Приемы программирования: флаг, счетчик.
Ход программирования
Шаг 1. Естественным развитием предыдущей задачи является то, что если мы
определили, что из слова можно составить палиндром, то этот палиндром нужно
составить. Используем предыдущую программу как заготовку (листинг 4.1.8).
print(v['a']) 4
print(list(v.keys())) ['a', 'b', 'c', 'cde']
print(list(v.values())) [4, 1, 2, 5]
Листинг 4.1.10
s=input()
if sum([s.count(el)%2 for el in set(s)])<=1:
v={}
for el in set(s):
v[el]=s.count(el)
print(v)
# здесь по словарю будем составлять палиндром
else:
print('нельзя составить палиндром')
Можно сделать более короткую запись в словарь, используя вторую форму записи
цикла так же, как мы это делали со списками (листинг 4.1.11).
Листинг 4.1.11
s=input()
if sum([s.count(el)%2 for el in set(s)])<=1:
v={el:s.count(el) for el in set(s)}
# здесь по словарю будем составлять палиндром
else:
print('нельзя составить палиндром')
if v[el]%2==1:
c=el
p=p+c+p[::-1]
print(p)
else:
print('нельзя составить палиндром')
то получим список строк. Мы можем соединить в одну строку все элементы списка
с помощью конструкции join — как показано в листинге 4.1.16.
Листинг 4.1.16. Конструкция join Результат
L=["a","bc","def"]
print(L) ['a', 'bc', 'def']
print("".join(L)) abcdef
Если мы еще раз окинем взглядом написанную программу, то мы увидим, что в ней
используются коллекции: строка, множество, список и словарь — т. е. все основ-
ные коллекции Python. Этим решенная нами задача и ценна.
Словари, рекуррентный индекс в списке 83
4.2. Подстановки
Задача
Для числовых подстановок найти степень подстановки и ее разложение на циклы.
Здесь мы имеем дело с математическим объектом — подстановкой. Что это такое,
я объясню по ходу программирования.
Надеюсь, что читатель уже четко отличает в списке значение элемента от его ин-
декса. Потому что если нет, то я его запутаю окончательно...
Языковая конструкция: список.
Приемы программирования: рекуррентный индекс списка, список флагов.
Ход программирования
Шаг 1. Пусть нам дан ряд чисел от 0 до n-1. Перемешаем эти числа и запишем не-
упорядоченный ряд под упорядоченным:
0 1 2 3 4 5
5 4 1 3 2 0
Как это запрограммировать? Дело в том, что в квадратных скобках (в индексе эле-
мента списка) мы можем писать сложные выражения — например, математические
формулы. Причем в этих формулах можно использовать значения ячеек других
списков или даже того же самого списка (этот прием называется рекуррентный
индекс). Посмотрите, как работает программа из листинга 4.2.2.
84 Урок 4
Шаг 4. В этой программе мы видим много повторяющихся кусков кода. Это вер-
ный признак того, что нам нужно использовать еще один цикл, куда мы и поместим
повторяющийся код (листинг 4.2.4).
Научимся получать этот цикл программно, для чего станем хранить получающиеся
элементы цикла в списке L. Если при очередном применении перестановки полу-
ченный элемент совпадет с начальным, то делаем прерывание (листинг 4.2.5).
Шаг 6. Получим циклы для каждого числа, поместив написанную программу в цикл
(листинг 4.2.6).
Как же удалить лишние циклы? Для этого создадим список флагов, в котором бу-
дем запоминать, было ли уже число в предыдущих найденных циклах или нет. При
инициализации заполним список флагов истинными значениями (листинг 4.2.7).
Листинг 4.2.8
p=[5,4,1,3,2,0]
f=[True]*len(p)
for j in range(len(p)):
if f[j]==True:
# здесь будет поиск нового цикла
Листинг 4.2.9
p=[5,4,1,3,2,0]
f=[True]*len(p)
for j in range(len(p)):
if f[j]==True:
L=[j]
for i in range(len(p)):
L.append(p[L[-1]])
if L[-1]==L[0]:
break
Листинг 4.2.11
p=[5,4,1,3,2,0]
f=[True]*len(p)
for j in range(len(p)):
if f[j]==True:
L=[j]
for i in range(len(p)):
L.append(p[L[-1]])
if L[-1]==L[0]:
break
print("j=",j,"L=",L)
for el in L:
f[el]=False
print("f=",f)
Результат
j= 0 L= [0, 5, 0]
f= [False, True, True, True, True, False]
j= 1 L= [1, 4, 2, 1]
f= [False, False, False, True, False, False]
j= 3 L= [3, 3]
f= [False, False, False, False, False, False]
В этой задаче мы увидели, что в качестве индексов могут выступать элементы спи-
сков, в том числе того же самого списка. Кроме того, если на предыдущих уроках
мы научились применять такие приемы, как буфер обмена, счетчик, накапливаю-
щаяся сумма и флаг, то по этой задаче видно, что иногда нужны списки флагов,
а значит, могут потребоваться списки счетчиков, накапливающихся сумм и т. п.
УРОК 5
Двумерные списки
Задача 1
Организовать ввод/вывод матрицы (списка списков).
Языковая конструкция: список списков.
Ход программирования
Шаг 1. В начале мы разберемся, как задавать и выводить матрицу. Посмотрите на
листинг 5.1.1.
Листинг 5.1.1 Результат
M=[[1,2,3], [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
[4,5,6], [1, 2, 3]
[7,8,9]] [4, 5, 6]
print(M) [7, 8, 9]
Двумерные списки 89
for el in M:
print(el) [1, 2, 3]
print() [4, 5, 6]
for i in range(len(M)): [7, 8, 9]
print(M[i]) 6
print(M[1][2])
Листинг 5.1.4
n=3
M=[]
for i in range(n):
row = [int(el) for el in input().split()]
M.append(L)
Листинг 5.1.5
n=3
M=[]
Двумерные списки 91
for i in range(n):
M.append([int(el) for el in input().split()])
Задача 2
В матрице найти максимальный элемент.
Ход программирования
Шаг 1. Если написать так:
M=[[int(el) for el in input().split()] for i in range(n)]
print (max(M))
Задача 3
Транспонировать матрицу. Транспонированная матрица — это перевернутая мат-
рица, у которой строки становятся столбцами соответствующего номера (табл. 5.1).
Ход программирования
Напишем две версии программы.
Шаг 1. В первой версии создадим вторую матрицу, которая и будет транспониро-
ванной. Исходную матрицу введем, а заготовку для второй матрицы заполним
одними нулями — иначе мы не сможем записывать в нее элементы по индексам
ячеек (листинг 5.1.9).
Листинг 5.1.9
n=int(input())
M=[[int(el) for el in input().split()] for i in range(n)]
T=[[0]*n for i in range(len(M))]
#Здесь будет транспонирование
for i in range(len(T)):
print(T[i])
а становится он:
Т[0][1]
превращается в:
T[j][i]
Здесь мы также можем применить любой из этих трех способов. Выберем кортежи
и посмотрим на результат (листинг 5.1.12).
94 Урок 5
Листинг 5.1.12
n=int(input())
M=[[int(el) for el in input().split()] for i in range(n)]
for i in range(n):
for j in range(n):
M[j][i],M[i][j]=M[i][j],M[j][i]
for el in M:
print(el)
Результат
3
1 2 3
4 5 6
7 8 9
1 2 7
4 5 6
3 8 9
Задача 4
Написать программу, складывающую две матрицы.
Языковая конструкция: список списков.
Прием программирования: цикл внутри цикла.
Ход программирования
Сложение матриц выполняется просто — как и сумма векторов, оно делается по-
элементно:
⎛ 1 2 3 ⎞ ⎛ 10 20 30 ⎞ ⎛ 1 + 10 2 + 20 3 + 30 ⎞ ⎛ 11 22 33 ⎞
⎜ ⎟ ⎜ ⎟ ⎜ ⎟ ⎜ ⎟
⎜ 4 5 6 ⎟ + ⎜ 40 50 60 ⎟ = ⎜ 4 + 40 5 + 50 6 + 60 ⎟ = ⎜ 44 55 66 ⎟ .
⎜ 7 8 9 ⎟ ⎜ 70 80 90 ⎟ ⎜ 7 + 70 8 + 80 9 + 90 ⎟ ⎜ 77 88 99 ⎟
⎝ ⎠ ⎝ ⎠ ⎝ ⎠ ⎝ ⎠
Организуем цикл внутри цикла, сложим элементы матриц:
C[i][j]=A[i][j]+B[i][j]
Задача 5
Написать программу, перемножающую две матрицы.
Языковая конструкция: список списков.
Прием программирования: цикл внутри цикла внутри цикла.
Ход программирования
Умножение матриц сложнее, чем сложение, и делается по следующему правилу:
элемент результирующей матрицы с координатами [i][j] получается в результате
скалярного произведения i-й строки левой матрицы на j-й столбец правой мат-
рицы:
⎛ 1 2 3 ⎞ ⎛ 10 20 30 ⎞
⎜ 4 5 6 ⎟ ⋅ ⎜ 40 50 60 ⎟ =
⎜ ⎟ ⎜ ⎟
⎜ 7 8 9 ⎟ ⎜ 70 80 90 ⎟
⎝ ⎠ ⎝ ⎠
⎛ ⎛ 10 ⎞ ⎛ 20 ⎞ ⎛ 30 ⎞ ⎞
⎜ (1 2 3) ⋅ ⎜ 40 ⎟ (1 2 3) ⋅ ⎜ 50 ⎟ (1 2 3) ⋅ ⎜ 60 ⎟ ⎟
⎜ ⎜ ⎟ ⎜ ⎟ ⎜ ⎟⎟
⎜ ⎜ 70 ⎟ ⎜ 60 ⎟ ⎜ 90 ⎟ ⎟
⎝ ⎠ ⎝ ⎠ ⎝ ⎠
⎜ ⎟
⎜ ⎛ 10 ⎞ ⎛ 20 ⎞ ⎛ 30 ⎞ ⎟
= ⎜ ( 4 5 6 ) ⋅ ⎜ 40 ⎟ ( 4 5 6 ) ⋅ ⎜ 50 ⎟ ( 4 5 6 ) ⋅ ⎜ 60 ⎟ ⎟ .
⎜ ⎜ ⎟ ⎜ ⎟ ⎜ ⎟⎟
⎜ 70 ⎟ ⎜ 60 ⎟ ⎜ 90 ⎟
⎜ ⎝ ⎠ ⎝ ⎠ ⎝ ⎠⎟
⎜ 10 20 ⎟
⎜ ⎛ ⎞ ⎛ ⎞ ⎛ 30 ⎞ ⎟
⎜ ( 7 8 9 ) ⋅ ⎜⎜ 40 ⎟⎟ ( 7 8 9 ) ⋅ ⎜⎜ 50 ⎟⎟ ( 7 8 9 ) ⋅ ⎜⎜ 60 ⎟⎟ ⎟
⎜ ⎜ 70 ⎟ ⎜ 60 ⎟ ⎜ 90 ⎟ ⎟
⎝ ⎝ ⎠ ⎝ ⎠ ⎝ ⎠⎠
Двумерные списки 97
Но, чтобы организовать умножение векторов, нам нужен цикл (см. разд. 2.3), т. к.:
C[i][j]=A[i][0]*B[0][j]+ A[i][1]*B[1][j]+ A[i][2]*B[2][j]+...
это накапливающаяся сумма, и нам нужен еще один индекс — k — для изменения
которого придется организовать третий вложенный цикл:
for i in range(n):
for j in range(n):
for k in range(n):
Результат
[300, 360, 420]
[660, 810, 960]
[1020, 1260, 1500]
for i in range(n):
for j in range(n):
C[i][j]=sum([A[i][k]*B[k][j] for k in range(n)])
Суммы
16 3 2 13 34
5 10 11 8 34
9 6 7 12 34
4 15 14 1 34
34 34 34 34 34 34
Ход программирования
Решим задачу в четыре этапа:
1. Найдем сумму главной диагонали.
2. Найдем сумму второй диагонали.
3. Найдем суммы по строкам.
4. Найдем суммы по столбцам.
Шаг 1. Найдем сумму главной диагонали. Для нашего примера она:
d1=16+10+7+1=34
Устраивать для подсчета суммы элементов диагонали цикл внутри цикла будет
ошибкой. Здесь видно, что номер строки равен номеру столбца, — т. е. мы склады-
ваем M[i][i]. Поэтому достаточно цикла по индексу i (листинг 5.2.1).
Видно, что, хотя индексы строки и столбца не равны, но между ними явно имеется
зависимость, — т. е. мы складываем M[i][-1-i]. Поэтому для d2 тоже нужен только
один цикл:
for i in range(len(M)):
d2=d2+M[i][-i-1]
Шаг 3. Для подсчета суммы по строкам нам достаточно организовать один цикл.
Строка из матрицы выбирается как M[i], а ее сумма вычисляется как sum(M[i]).
Считать суммы по строкам нужно только в том случае, если две диагонали равны,
и прекращать счет, если встретилась хотя бы одна сумма, не равная прежним. Вве-
дем флаг f=True (мы оптимистичны и предполагаем в начале программы, что мат-
рица — это магический квадрат) и получим программу, приведенную в листин-
ге 5.2.2.
100 Урок 5
Шаг 4. Теперь нужно найти суммы по столбцам. Так просто, как строку, выделить
столбец не получится. Организуем два цикла: цикл по i будет выбирать столбцы,
а цикл по j — ячейки в столбцах, т. е. строки. С помощью обнуляющейся для каж-
дого столбца накапливающейся суммы s мы найдем суммы по столбцам и решим
задачу (листинг 5.2.3).
if s!=d1:
f=False
break
else:
f=False
if f:
print("магический")
else:
print("обычный")
Листинг 5.2.4
M=[[16,3,2,13],
[5,10,11,8],
[9,6,7,12],
[4,15,14,1]]
f=True
D1=[]
for i in range(len(M)):
D1.append(M[i][i])
d1=sum(D1)
D2=[]
for i in range(len(M)):
D2.append(M[i][-i-1])
d2=sum(D2)
if d1==d2:
for i in range(len(M)):
if sum(M[i])!=d1:
f=False
break
if f:
for i in range(len(M)):
C=[]
for j in range(len(M)):
C.append(M[j][i])
s=sum(C)
if s!=d1:
f=False
break
102 Урок 5
else:
f=False
if f:
print("магический")
else:
print("обычный")
Листинг 5.2.5
M=[[16,3,2,13],
[5,10,11,8],
[9,6,7,12],
[4,15,14,1]]
f=True
d1=sum([M[i][i] for i in range(len(M))])
d2=sum([M[i][-i-1] for i in range(len(M))])
if d1==d2:
for i in range(len(M)):
if sum(M[i])!=d1:
f=False
break
if f:
for i in range(len(M)):
s=sum([M[j][i] for j in range(len(M))])
if s!=d1:
f=False
break
else:
f=False
if f:
print("магический")
else:
print("обычный")
Листинг 5.2.6
M=[[16,3,2,13],
[5,10,11,8],
[9,6,7,12],
[4,15,14,1]]
f=True
d1=sum([M[i][i] for i in range(len(M))])
d2=sum([M[i][-i-1] for i in range(len(M))])
R=[sum(M[i]) for i in range(len(M))]
C=[sum([M[j][i] for j in range(len(M))]) for i in range(len(M))]
S=[d1,d2]+R+C
print(S)
if len(set(S))==1:
print("магический")
else:
print("обычный")
Листинг 5.2.7
M=[[16,3,2,13],
[5,10,11,8],
[9,6,7,12],
[4,15,14,1]]
S=([sum([M[i][i] for i in range(len(M))]),
sum([M[i][-i-1] for i in range(len(M))])]
+[sum(M[i]) for i in range(len(M))]
+[sum([M[j][i] for j in range(len(M))]) for i in range(len(M))])
print(S)
if len(set(S))==1:
print("магический")
else:
print("обычный")
Шаг 9. Заметим, что список сумм S после своего создания задействуется только
один раз. А это значит, что мы можем избавиться от него, поместив его вычисления
прямо туда, где он используется, — в set(S) (листинг 5.2.8).
104 Урок 5
Листинг 5.2.8
M=[[16,3,2,13],
[5,10,11,8],
[9,6,7,12],
[4,15,14,1]]
if len(set([sum([M[i][i] for i in range(len(M))]),
sum([M[i][-i-1] for i in range(len(M))])]
+[sum(M[i]) for i in range(len(M))]
+[sum([M[j][i] for j in range(len(M))]) for i in range(len(M))]))==1:
print("магический")
else:
print("обычный")
Шаг 10. Ну и, наконец, чтобы программа была «совсем в стиле Python», воспользу-
емся второй записью условия, т. е. заменим:
if ... :
print("магический")
else:
print("обычный")
на
print("магический" if ... else "обычный")
В нем вводятся две пары переменных: x и y — и для каждой пары делаются вычис-
ления по одной и той же формуле (с точностью до замены переменных). Если по
этой формуле нужны еще вычисления, то количество переменных и записей фор-
мулы будут возрастать. Из предыдущих занятий мы знаем, что если в программе
есть похожие блоки кода, то их нужно каким-либо образом объединять. В рассмат-
риваемом случае для этого предназначена новая языковая конструкция — функция
(листинг 6.1.2).
x=int(input())
y=int(input())
print(f(x,y))
a=int(input())
b=int(input())
print(f(a,b))
Декомпозиция программы в функции 107
x=int(input()) 2
y=int(input()) 3
print(f(x,y)) 13
print(f(y,x)) 17
При вызове функции мы можем вместо имен переменных и чисел писать формулы,
в том числе с вызовами других функций и этой же самой функции! Посмотрите на
листинг 6.1.4.
x=int(input()) 2
y=int(input()) 3
print(f(x,y)) 13
print(f(y,x)) 17
print(f(x+y,x*y)) 61
print(f(f(x,y),f(y,x))) 407
x=int(input()) 2
y=int(input()) 3
print(f(x,y)) 13
print(f(x)) 4
print(f()) 0
Ход программирования
Шаг 1. Поскольку нам надо перемножать числа от 1 до n, воспользуемся циклом:
for i in range(1,n+1):
Обратите внимание, что в range началом указана 1 (по умолчанию 0), а верхняя гра-
ница: n+1, т. к. цикл не доходит до верхней границы, а нам нужно, чтобы он
дошел до n.
Шаг 2. Мы научились генерировать i — теперь надо вычислить факториал i. Со-
ставим расчетную табличку:
i= 1 2 3 4 5
p= 1 2 6 24 120
n=int(input())
for i in range(1,n+1):
p=p*i
Шаг 4. Изменим задачу: пусть надо ввести два числа: n и m и вычислить n!+m!.
Скопируем код программы два раза, изменив названия переменных (листинг 6.2.2).
Так же как и на предыдущем уроке, наш код неизящен — в нем есть похожие бло-
ки. Тогда для повторяющихся формул мы применили функции — аналог формул,
как их понимают в математике. Но в программировании функции могут быть не
просто формулами — они могут содержать в себе алгоритмы. По сути, функции —
это маленькие отлаженные кусочки кода, имеющие свой законченный смысл, по-
этому они могут использоваться (вызываться) в разных местах программы.
Шаг 5. Перенесем вычисление факториала в функцию и получим следующую про-
грамму (листинг 6.2.3).
110 Урок 6
n=int(input())
m=int(input())
print(fact(n)+fact(m))
n=int(input()) 3
print(fact(n)) 6
print(fact(fact(n))) 720
Ход программирования
Шаг 1. Формулы для количества размещений, перестановок и сочетаний можно
найти в математических справочниках. Во всех них используется факториал. По-
Декомпозиция программы в функции 111
def arrange(m,n):
return fact(m)//fact(m-n)
m=int(input())
n=int(input())
print(arrange(m,n))
def arrange(m,n):
return fact(m)//fact(m-n)
def permut(n):
return arrange(n,n)
n=int(input())
print(permut(n))
Шаг 4. В магазине продаются семь видов овощей. Мы хотим сделать салат из трех
видов овощей. Сколько вариантов салата у нас есть перед покупкой?
Рассуждать начнем так же, как и в задаче с флагами. Когда мы кладем в корзину
первый овощ, у нас есть выбор из семи овощей, когда второй — из 6, третий —
из 5. Получается, что нам нужно подсчитать размещение:
A73 = 7 ⋅ 6 ⋅ 5 = 210.
Но не все так просто. Мы ограничились бы формулой размещения, если бы порядок
овощей имел значение (мы бы делали многослойный салат). В задаче с флагами
порядок вывешивания флагов имел значение. Но мы делаем обычный салат, пере-
мешивая овощи, т. е. салаты:
1. Капуста, морковка, горох.
2. Капуста, горох, морковка.
3. Морковка, капуста, горох.
4. Морковка, горох, капуста.
5. Горох, капуста, морковка.
6. Горох, морковка, капуста.
Но это один и тот же салат!
Декомпозиция программы в функции 113
И так с любой другой тройкой овощей. Получается, что результат размещения 210
нужно поделить на 6:
210
= 35 .
6
Но что такое 6? Это же количество перестановок 6 предметов!
Приведенная задача — это задача на сочетания. Математическое обозначение со-
четания — буква C — от английского слова Combination:
Amn
Сmn = .
Pn
Напишем функцию comb, вычисляющую сочетания через перестановки и размеще-
ния, и получим полную программу (листинг 6.3.3).
def arrange(m,n):
return fact(m)//fact(m-n)
def permut(n):
return arrange(n,n)
def comb(m,n):
return arrange(m,n)//permut(n)
m=int(input())
n=int(input())
print(comb(m,n))
m=int(input()) m=int(input())
n=int(input()) n=int(input())
print(comb(m,n)) print(combinatorics.comb(m,n))
Ход программирования
Шаг 1. На уроке 5 мы написали программу, определяющую, является ли квадрат-
ная таблица магическим квадратом (см. разд. 5.2). А в предыдущем разделе нам не
было необходимости выделять функции — они возникли естественным путем из
математических формул. В этой же задаче необходимо продумать, какие функции
нам нужны.
Предлагаю выделить три функции:
1. Выборку главной диагонали.
2. Выборку второй диагонали.
3. Выборку столбца.
Делать функцию выборки строки нет смысла, т. к. строку мы и так получаем по ин-
дексу: M[i].
Будущие функции будут возвращать именно списки, а не вычисленные суммы,
поскольку суммы легко подсчитать с помощью встроенной функции sum.
Функции диагоналей будут иметь один аргумент — матрицу. А функция выборки
колонки — два аргумента: матрицу и номер колонки.
Мы получим программу, приведенную в листинге 6.4.1.
Листинг 6.4.1
def diag1(M):
return [M[i][i] for i in range(len(M))]
def diag2(M):
return [M[i][-i-1] for i in range(len(M))]
def col(M,n):
return [M[i][n] for i in range(len(M))]
Декомпозиция программы в функции 115
M=[[16,3,2,13],
[5,10,11,8],
[9,6,7,12],
[4,15,14,1]]
f=True
d1=sum(diag1(M))
d2=sum(diag2(M))
if d1==d2:
for i in range(len(M)):
if sum(M[i])!=d1:
f=False
break
if f:
for i in range(len(M)):
if sum(col(M,i))!=d1:
f=False
break
else:
f=False
if f:
print("магический")
else:
print("обычный")
Листинг 6.4.2
def diag1(M):
return [M[i][i] for i in range(len(M))]
def diag2(M):
return [M[i][-i-1] for i in range(len(M))]
def col(M,n):
return [M[i][n] for i in range(len(M))]
def magic(M):
f=True
d1=sum(diag1(M))
d2=sum(diag2(M))
if d1==d2:
for i in range(len(M)):
if sum(M[i])!=d1 or sum(col(M,i))!=d1:
f=False
break
116 Урок 6
else:
f=False
return f
M=[[16,3,2,13],
[5,10,11,8],
[9,6,7,12],
[4,15,14,1]]
if magic(M):
print("магический")
else:
print("обычный")
def diag2(M):
return [M[i][-i-1] for i in range(len(M))]
def col(M,n):
return [M[i][n] for i in range(len(M))]
def magic(M):
return len(set([sum(diag1(M)),
sum(diag2(M))]
+[sum(M[i]) for i in range(len(M))]
+[sum(col(M,i)) for i in range(len(M))]))==1
M=[[16,3,2,13],
[5,10,11,8],
[9,6,7,12],
[4,15,14,1]]
if magic(M):
print("магический")
else:
print("обычный")
Рекурсии
Ход программирования
Шаг 1. Напишем вторую версию вычисления факториала с применением первого
приема программирования, связанного с функциями, — рекурсией. Посмотрим на
математическое определение факториала:
n ! = 1 ⋅ 2 ⋅ 3 ⋅ … ⋅ (n − 1) ⋅ n.
Если убрать последний множитель, то что мы получим?
1 ⋅ 2 ⋅ 3 ⋅ … ⋅ ( n − 1) = ( n − 1)!.
То есть:
n ! = (n − 1)!⋅ n.
Действительно, если мы посмотрим на следующую табличку, то убедимся в этом:
n 1 2 3 4 5
n! 1 2 6 24 120
Шаг 3. Напишем ветку для n=1 — она очень проста: функция должна вернуть в ка-
честве ответа 1 (листинг 7.1.2).
Шаг 4. Теперь напишем вторую ветку факториала. Поскольку там нужно вычис-
лить (n–1)!, то некоторые начинающие программисты для вычисления этого факто-
риала пишут цикл. И совершают ошибку — ведь тогда эта версия будет просто
усложненной версией факториала с циклом из разд. 6.2.
Поступим формально. Что соответствует (n–1)! в программе? Вызов функции фак-
ториала — т. е. fact(n-1). Так и запишем и получим новую версию факториала
(листинг 7.1.3).
Листинг 7.1.3. Рекурсивный факториал Листинг 6.2.3. Факториал с циклом Z
(новая версия) (из разд. 6.2)
def fact(n): def fact(n):
if n==1: p=1
return 1 for i in range(1,n+1):
else: p=p*i
return fact(n-1)*n return p
n=int(input()) n=int(input())
print(fact(n)) print(fact(n))
В разд. 4.2, где речь велась про подстановки, у нас был рекуррентный индекс
списка:
L[L[L[a]]]
Так почему бы функции не вызывать саму себя? То есть мы получили прием про-
граммирования, который называется рекурсией.
Посмотрим, как происходит передача вызовов функции при подсчете 5! (рис. 7.1).
Нет ничего страшного в том, что функция вызывает саму себя. Главное, что у нее
изменяется аргумент вызова, который уменьшается до 1. Это приводит в конце
концов к ветке функции без рекурсии, которая называется терминальным случаем.
Сравнив программу факториала с циклом (см. листинг 6.2.3) с программой рекур-
сивного факториала (см. листинг 7.1.3), мы заметим, что сложность у них примерно
одинакова (только рекурсия пока непривычна), скорость выполнения тоже пример-
но одинакова. Но есть программы, в которых рекурсия — это наиболее естествен-
ное решение, и без ее использования написать алгоритм весьма затруднительно.
Мы познакомимся с такими задачами в этой книге (например, при вычислении
определителя матрицы — см. разд. 11.3).
Номер n 1 2 3 4 5 6 7 8 9 10
Число Фибоначчи 1 1 2 3 5 8 13 21 34 55
написать, — так что прежде, чем читать их реализации в этой книге, попробуйте
написать их самостоятельно.
Задача 1
Написать рекурсивную версию алгоритма вычисления чисел Фибоначчи.
Языковые конструкции: функции
Прием программирования: рекурсия.
Ход программирования
Запишем математическое определение чисел Фибоначчи точно так же, как мы это
делали с рекурсивным факториалом.
Начнем с примера:
55 = 34 + 21.
По расчетной табличке видно, что:
Номер n 1 2 3 4 5 6 7 8 9 10
Число Фибоначчи 1 1 2 3 5 8 13 21 34 55
Его формула fib(8) fib(9) fib(10)
То есть
fib(10) = fib(9) + fib(8)
или в общем виде
fib(n) = fib(n − 1) + fib(n − 2) .
Но вычисления так идут не всегда — иначе мы получили бы бесконечный вызов
функций со все более уменьшающимися аргументами. Посмотрим на определение
чисел Фибоначчи: два первых числа равны 1. Это терминальные случаи. Запишем
с их учетом полное математическое определение:
⎧ 1, n ≤ 2
fib( n) = ⎨ .
⎩ fib( n − 1) + fib( n − 2), n > 2
И формально напишем программу по этому определению (листинг 7.2.1).
n=int(input())
print(fib(n))
Рекурсии 121
Задача 2
Написать функцию, вычисляющую число Фибоначчи с помощью списка.
Языковые конструкции: функция, список, цикл.
Прием программирования: обращение к предыдущим элементам списка.
Ход программирования
Шаг 1. Табличка с числами Фибоначчи начинается с первого номера. Но мы соби-
раемся использовать список для хранения подсчитанных чисел Фибоначчи, а в спи-
ске нумерация начинается с нуля. Дополним числа Фибоначчи числом с нулевым
номером, равным нулю:
Номер n 0 1 2 3 4 5 6 7 8 9 10
Число Фибоначчи 0 1 1 2 3 5 8 13 21 34 55
И в общем виде:
L[i]=L[i-1]+L[i-2]
n=int(input())
print(fib(n))
Задача 3
Написать функцию, вычисляющую число Фибоначчи без списка.
Языковые конструкции: функция, список, цикл.
Прием программирования: запоминание предыдущего числа в потоке.
Ход программирования
Пусть n — это вводимый номер числа Фибоначчи, которое нужно вычислить.
Вспомним, что для вычисления числа Фибоначчи нам нужно знать два предыдущих
числа, и задействуем три переменные: a, b и f, где f — это вновь вычисляемое чис-
Рекурсии 123
Шаг 2. Каждое следующее число Фибоначчи вычисляется как сумма двух преды-
дущих. Используем математическую формулу:
f=a+b
Номер числа 1 2 3 4 5 6 7 8 9 10 11 12
Число Фибоначчи 1 1 2 3 5 8 13 21 34 55 89 144
i=3 a b f
i=4 a b f
i=5 a b f
i=6 a b f
i=7 a b f
i=8 a b f
i=9 a b f
i=10 a b f
i=11 a b f
i=12 a b f
Листинг 7.2.5. Вычисление числа Фибоначчи с помощью двух предыдущих чисел в потоке
def fib(n):
a=1
b=1
f=1
for i in range(3,n+1):
f=a+b
a=b
b=f
return f
n=int(input())
print(fib(n))
Задача
Написать быструю версию возведения числа в степень.
Конечно, в Python есть встроенное возведение в степень:
a**n
Идея алгоритма
Разберемся с идеей алгоритма быстрого возведения в степень.
Предположим, нужно подсчитать 38. Как вы это сделаете? Есть два способа:
126 Урок 7
⎧
⎪ a, n = 1
⎪
a = ⎨ a n −1 ⋅ a, n — нечетное .
n
⎪ n n
⎪
⎩ a 2 ⋅ a 2 , n — четное
Ход программирования
Шаг 1. Напишем программу, строго следуя математическому определению (лис-
тинг 7.3.1).
Листинг 7.3.1
def fastpow(a,n):
if n==1:
return a
elif n%2==0:
return fastpow(a,n//2)*fastpow(a,n//2)
else:
return fastpow(a,n-1)*a
a = float(input())
n = int(input())
print(fastpow(a,n))
Программа два раза вычисляет одно и то же, и вся быстрота теряется! Нужно вве-
сти еще одну переменную и осуществлять вычисление так:
r=fastpow(a,n//2)
return r*r
else:
return fastpow(a,n-1)*a
a = float(input())
n = int(input())
print(fastpow(a,n))
⎪ n
⎪
⎩(a ⋅ a ) 2 , n — четное
Попробуйте сами написать программу этой версии быстрого возведения в сте-
пень — она получается путем небольших изменений в предыдущей программе.
Готовая программа приведена в листинге 7.3.3.
a = float(input())
n = int(input())
print(fastpow(a,n))
Ход программирования
Шаг 1. Разберемся, почему рекурсивная функция работает медленно.
Это связано с тем, что функция с одним и тем же аргументом вызывается много
раз. Например, для fib(5) происходит вызов fib(3) два раза (рис. 7.4). Неудиви-
тельно, что при вызове fib(35) на большинстве компьютеров будет заметно замед-
ление.
1 2 3 4
fib(5) fib(4) fib(3) fib(2) mem[2]
5
fib(1) mem[1]
6
fib(2) mem[2]
7
fib(3) mem[3]
Шаг 6. В рекурсии, прежде чем делать return, нам нужно сохранить подсчитанные
значения в списке. А return мы сделаем общий в конце всей функции — будем воз-
вращать число из списка. Так мы получим готовую программу (листинг 7.4.1).
n = int(input())
print(fib(n))
if F[n]==-1:
F[n]=fib(n-1)+fib(n-2)
return F[n]
n = int(input())
print(fib(n))
Листинг 7.4.3
F=[]
def fib(n):
if n>=len(F):
F=F+[-1]*(n+1)
if F[n]==-1:
if n<=2:
F[n]=1
else:
F[n]=fib(n-1)+fib(n-2)
return F[n]
n = int(input())
print(fib(n))
Шаг 9. Запустив полученный код, мы получим ошибку. Python почему-то счел спи-
сок F внутри функции локальным и потребовал начальной инициализации. Чтобы
решить проблему, надо добавить в функцию следующую строку:
global F
При ней Python будет понимать, что имеет дело с глобальным списком (лис-
тинг 7.4.4). Так мы получим готовую безопасную версию рекурсивной функции
с мемоизацией (кешированием).
else:
F[n]=fib(n-1)+fib(n-2)
return F[n]
n = int(input())
print(fib(n))
Шаг 10. Если приведенная функция кажется вам сложной, то есть более простой
и наглядный способ мемоизации: можно использовать не список, а словарь. В этом
случае не надо заботиться о переполнении памяти (листинг 7.4.5).
n = int(input())
print(fib(n))
Мы еще раз переделаем эту функцию на уроке 9, когда будем изучать приемы
функционального программирования (см. разд. 9.6. Универсальный мемоизатор).
Задача 1
Задан алфавит и длина слова. Нужно создать все возможные слова заданной длины
из букв этого алфавита. В конкретном слове некоторые буквы могут повторяться,
а некоторые — отсутствовать. Слова не обязательно должны быть осмысленными.
Например, пусть алфавит состоит из букв а и b, а длина слова — 2. Тогда все воз-
можные слова будут: aa, ab, ba, bb.
Ход программирования
Шаг 1. Научимся сначала создавать слова единичной длины и зададим алфавит
строкой:
alph="abc"
Рекурсии 133
Шаг 2. Научимся составлять слова длины 2. Если бы мы решали задачу без ком-
пьютера, было бы важно не пропустить ни одного возможного слова. Так что возь-
мем первую букву алфавита и допишем ее перед составленными на предыдущем
этапе словами:
aa
ab
ac
Шаг 3. Для составления трехбуквенных слов нам нужно каждую букву алфавита
дописать ко всем возможным двухбуквенным словам:
aaa baa caa
aab bab cab
aac bac cac
aba bba cba
abb bbb cbb
abc bbc cbc
aca bca cca
acb bcb ccb
acc bcc ccc
Для этого понадобится третий цикл, в который будут вложены циклы предыдущего
шага (листинг 7.5.3).
for j in alph:
for k in alph:
L.append(i+j+k)
return L
for p in prod("abc",3):
print(p)
for p in prod("abc",4):
print(p)
for p in prod("abc",4):
print(p)
for p in prod("abc",4):
print(p)
Задача 2
Задан алфавит и длина слова. Нужно создать все возможные слова заданной длины
из букв этого алфавита. Буквы в слове не должны повторяться. Слова не обязатель-
но должны быть осмысленными.
Отличие этой задачи от предыдущей в том, что в этой задаче буквы в словах не
должны повторяться. При этом если длина слова равна количеству букв в алфавите,
то количество возможных слов равно количеству перестановок множества всех
букв алфавита (см. разд. 6.3. Библиотека формул комбинаторики).
Например, для алфавита a, b, c все перестановки: abc, acb, bac, bca, cab, cba.
Ход программирования
Шаг 8. Изменим программу, генерирующую слова (см. листинг 7.5.4). Чтобы не
было повторения букв, рекурсивно будем вызывать функцию не только с умень-
шенной длиной слова, но и с урезанным алфавитом. Для этого букву, которую мы
намерены приписать будущим сгенерированным словам уменьшенной длины, уда-
лим из алфавита с помощью функции replace. Мы получим готовую программу
(листинг 7.5.8).
for p in perm("abc",3):
print(p)
Рекурсии 137
Шаг 11. Для получения всех перестановок букв слова в библиотеке itertools тоже
есть специальная функция — permutations (листинг 7.5.11):
Динамика по подотрезкам
Идея алгоритма
Рассмотрим пример: пусть дана строка abxyvxbca. Видно, что палиндром наиболь-
шей длины получится, если вычеркнуть буквы v и c:
abxyvxbca → abxyxba
Но как мы об этом догадались? Думаю, что любой человек, который будет долго
рассматривать исходную строку такой длины, найдет палиндром правильно. Но вот
объяснить способ поиска тяжело.
Алгоритм заключается в следующем. Посмотрев на крайние буквы, мы увидим, что
они совпадают. Значит, они входят в палиндром. Отделим их от строки:
a+bxyvxbc+a
Здесь крайние буквы не равны. Значит, нам нужно осуществить поиск по двум
строчкам, которые получаются путем урезания исходной строки справа и слева:
bxyvxb и xyvxbc
Ход программирования
Шаг 1. Пишем заготовку программы: функцию, ввод данных и ее вызов (лис-
тинг 8.1.1).
S=input()
print(pal(S))
S=input()
print(pal(S))
Шаг 3. Разберемся со случаем пустой строки. В нем, очевидно, надо вернуть пус-
тую строку:
return ""
Шаг 4. Разберемся со случаем строки из одного символа. В нем надо вернуть этот
символ, т. е. саму строку:
return S
else:
return B
Листинг 8.1.3
def pal(S):
if len(S)==0:
return ""
if len(S)==1:
return S
elif (S[0]==S[-1]):
return S[0]+pal(S[1:-1])+S[-1]
else:
A=pal(S[:-1])
B=pal(S[1:])
if len(A)>=len(B):
return A
else:
return B
Было Стало
if len(S)==0: if len(S)==0:
return "" return S
А это значит, что исполняемая часть у двух условий одинакова, объединим их:
Было Стало
if len(S)==0: if len(S)<=1:
return S return S
if len(S)==1:
return S
Шаг 8. Можно применить еще одну оптимизацию. Что, если введенная строка уже
является палиндромом? В этом случае мы ответ получим путем постепенного уре-
зания строки и новой ее компоновки с помощью нашей рекурсивной функции. Но
зачем это, если у нас есть очень лаконичная проверка того, является ли строка
палиндромом, — ее сравнение с ее же перевернутой копией (см. разд. 3.2):
S==S[::-1]
Это мне кажется остроумным, поэтому произведем замену и получим первую вер-
сию программы (листинг 8.1.4).
S=input()
print(pal(S))
Шаг 9. Оптимизация.
При построении алгоритма для abxyvxbca мы не стали рассматривать этот пример до
конца — т. е. не стали урезать строчку до тех пор, пока не дойдем до одиночных
символов. Мой опыт преподавания показывает, что дальше ученики начинают идти
по ложному пути, рисуя дерево и погружаясь в думы о том, как они на основе этого
дерева будут компоновать результат. Поэтому очень важно заметить, что алгоритм
рекурсивен, подумать о терминальных случаях и начать программировать.
А вот получив работающую версию программы, далее надо подумать об оптимиза-
ции. И для этого как раз и нужно расписать решение полностью! Сделаем это для
предельно неэффективного случая, когда все буквы в строке разные: abcde. Нарису-
ем дерево вызовов функции для урезанных строк (рис. 8.1).
Подобно рекурсивному алгоритму для чисел Фибоначчи, у нас есть целые повто-
ряющиеся ветки, от которых неплохо бы избавиться (рис. 8.2).
В идеале обработка подстрок должна проходить так, как показано на рис. 8.3.
Как же этого добиться? Так же как и при рекурсивной версии чисел Фибоначчи, —
мемоизацией. Самый простой способ: осуществлять мемоизацию в словаре.
144 Урок 8
abcde
abcd bcde
ab bc bc cd bc cd cd de
a b b c b c c d b c c d c d d e
Рис. 8.1. Дерево вызовов функции палиндрома
abcde
abcd bcde
ab bc bc cd bc cd cd de
a b b c b c c d b c c d c d d e
abcde
abcd bcde
ab bc cd de
a b c d e
Динамика по подотрезкам 145
Листинг 8.1.5
D={}
def pal(S):
...
Листинг 8.1.6
D={}
def pal(S):
if S not in D:
#здесь вычислительная часть
return D[S]
S=input()
print(pal(S))
print(D)
Листинг 8.1.7
D={}
def pal(S):
if S not in D:
if S==S[::-1]:
return S
elif (S[0]==S[-1]):
return S[0]+pal(S[1:-1])+S[-1]
else:
A=pal(S[:-1])
B=pal(S[1:])
if len(A)>=len(B):
return A
146 Урок 8
else:
return B
return D[S]
S=input()
print(pal(S))
print(D)
S=input()
print(pal(S))
языках. Признаюсь вам, что ранее я считал Python «игрушечным языком» и обучал
программированию на С++ и С#. Но именно после того, как я написал эту про-
грамму, я перешел на преподавание Python для начинающих программистов.
Есть еще один способ мемоизации (именно так ее делают на других языках высоко-
го уровня) — он работает быстрее, чем написанная нами версия мемоизации со
словарем, но он более сложен и требует переработки программы. Напишем и вто-
рую версию мемоизации.
Шаг 11. Вернемся к первой программе и избавимся от срезов. Срез каждый раз
строит нам новую строку. Мы же при повторных вызовах функции будем переда-
вать в нее не новую строку, а старую, но с указанием границ поиска (левой границы
left и правой — right):
def pal(S,left,right):
Листинг 8.1.9
def pal(S,left,right):
if left==right:
#обработка первого терминального случая
elif left==right-1:
#обработка второго терминального случая
Листинг 8.1.10
def pal(S,left,right):
if left==right:
return S[left]
elif left==right-1:
if S[left]==S[right]:
return S[left]+S[right]
else:
return S[left]
В общих случаях нам нужно произвести замену краев и добавить аргументы left
и right в вызов функции со сдвинутыми границами:
Было Стало
return S[0]+pal(S[1:-1])+S[-1] return S[left]+pal(S,left+1,right-1)+S[right]
A=pal(S[:-1]) A=pal(S,left,right-1)
B=pal(S[1:]) B=pal(S,left+1,right)
148 Урок 8
Листинг 8.1.11
def pal(S,left,right):
if left==right:
return S[left]
elif left==right-1:
if S[left]==S[right]:
return S[left]+S[right]
else:
return S[left]
elif (S[left]==S[right]):
return S[left]+ pal(S,left+1,right-1)+S[right]
else:
A=pal(S,left,right-1)
B=pal(S,left+1,right)
if len(A)>=len(B):
return A
else:
return B
S=input()
print(pal(S,0,len(S)-1))
S=input()
print(pal(S))
Листинг 8.1.13
M=[]
def pal(S,left=0,right=-1):
global M
if len(M)==0:
M=[["" for j in range(len(S))] for i in range(len(S))]
(Если вы забыли, как сделать двумерный список, заполненный одним и тем же,
перечитайте начало разд. 5.1.)
Далее продолжаем формальную процедуру изменения кода:
1. Проверяем список с индексами left и right на равенство пустой строке.
2. Сдвигаем «табом» всю вычислительную часть.
3. Заменяем return на помещение результатов в список.
4. В конце —– общий return для всех веток из значения в списке.
Мы получили работающий код (листинг 8.1.14).
Листинг 8.1.14
M=[]
def pal(S,left=0,right=-1):
global M
if len(M)==0:
M=[["" for j in range(len(S))]
for i in range(len(S))]
if right==-1:
right=len(S)-1
if M[left][right]=="":
if left==right:
M[left][right]=S[left]
elif left==right-1:
if S[left]==S[right]:
M[left][right]=S[left]+S[right]
else:
M[left][right]=S[left]
elif (S[left]==S[right]):
M[left][right]=S[left]+pal(S,left+1,right-1)+S[right]
Динамика по подотрезкам 151
else:
A=pal(S,left,right-1)
B=pal(S,left+1,right)
if len(A)>=len(B):
M[left][right]=A
else:
M[left][right]=B
return M[left][right]
S=input()
print(pal(S))
Шаг 14. Если мы теперь выведем кеш на экран, чтобы посмотреть, что там хра-
нится:
S=input()
print(pal(S))
for i in M:
print(i)
то увидим:
x
x x
x x x
x x x x
Мы можем создавать неровные списки списков — для каждой строки списки раз-
ного размера:
152 Урок 8
вместо:
M=[["" for j in range(len(S))] for i in range(len(S))]
напишем:
M=[["" for j in range(len(S)-i)] for i in range(len(S))]
напишем:
M[left][right-left]
Листинг 8.1.15
M=[]
def pal(S,left=0,right=-1):
global M
if len(M)==0:
M=[[""]*(len(S)-i) for i in range(len(S))]
if right==-1:
right=len(S)-1
if M[left][right-left]=="":
if left==right:
M[left][right-left]=S[left]
elif left==right-1:
if S[left]==S[right]:
M[left][right-left]=S[left]+S[right]
else:
M[left][right-left]=S[left]
elif S[left]==S[right]:
M[left][right-left]=S[left]+pal(S,left+1,right-1)+S[right]
else:
a=pal(S,left,right-1)
b=pal(S,left+1,right)
if len(a)>=len(b):
M[left][right-left]=a
Динамика по подотрезкам 153
else:
M[left][right-left]=b
return M[left][right-left]
S=input()
print(pal(S))
for i in M:
print(i)
Получается, что список М уникален для каждой строки, а значит, он не должен быть
глобальным.
Если мы его спрячем внутрь функции:
def pal(S,left=0,right=-1):
M=[]
то это тоже плохо, т. к. он будет создаваться заново при каждом вызове функции,
в том числе при каждом рекурсивном вызове обработки одной и той же строки.
154 Урок 8
Но этого мало: нам нужно еще его передавать в рекурсивные вызовы функций по
каждой ветке — т. е. надо произвести замены, например:
pal(S,left+1,right-1)
на:
pal(S,left+1,right-1,M)
Таким образом, при первом запуске функции для конкретной строки мы создаем
список, заполненный пустыми строками. А дальше он уже передается по цепочке
рекурсивных вызовов, постепенно заполняясь.
Мы получили финальную версию программы (листинг 8.1.16).
S=input()
print(pal(S))
сок. Палиндромы для разных подстрок разные, но то, что мы используем один и тот
же мемоизирующий словарь, даже дает нам преимущество! Если мы ищем палин-
дром в разных строках, имеющих общие фрагменты, то для этих фрагментов соз-
даются общие записи в одном словаре.
Есть и формальный признак — нужно делать мемоизирующий объект (кеш) гло-
бальным или поместить его в список аргументов. У чисел Фибоначчи и у первой
версии палиндрома при обращении к кешу мы использовали полный перечень
аргументов (номер числа Фибоначчи или строку), а в последней программе —
только часть аргументов (левую и правую границы, но не строку). Получается, что
если при обращении к кешу используется только часть аргументов функции, то для
корректной работы надо кеш помещать в аргументы функции, а иначе делать его
глобальным.
Мемоизация с помощью словаря, хотя и медленнее мемоизации с помощью дву-
мерного списка, но все же имеет преимущество. Почему же я не ограничился
мемоизацией словарем, а еще дополнительно объяснил мемоизацию двумерным
списком? Потому что в этом примере впервые мы разобрались с важным вопросом,
касающимся использования функций, а именно: какими должны быть переменные
в списке аргументов — локальными или глобальными? Кроме того, в следующем
разделе мы будем мемоизировать уже с помощью трехмерного списка...
Задача
Дана матрица (таблица, двумерный список). Найти в ней длину стороны самого
большого квадрата, заполненного одними нулями.
Языковые конструкции: функции, двумерные списки.
Прием программирования: динамика по подотрезкам (рекурсия, мемоизация
в трехмерном списке).
Идея алгоритма
Есть много способов решить эту задачу. Мы решим ее динамикой по подотрезкам.
Разберемся в идее метода на примере. Пусть задана матрица:
0 0 1 0 1
1 0 0 0 0
1 1 0 0 0
0 0 0 0 0
0 0 1 1 0
156 Урок 8
Сначала будем оптимистичны и проверим — может, она уже вся заполнена одними
нулями? Если да, то просто вернем ее размер. Если нет, то будем изучать урезан-
ные матрицы.
Матрицу можно урезать с четырех углов, удаляя боковые столбцы и строки, приле-
гающие к углам. То есть нам надо далее изучить четыре матрицы (рис. 8.4).
0 0 1 0 1 0 0 1 0 1
1 0 0 0 0 1 0 0 0 0
1 1 0 0 0 1 1 0 0 0
0 0 0 0 0 0 0 0 0 0
0 0 1 1 0 0 0 1 1 0
1 2
0 0 1 0 1 0 0 1 0 1
1 0 0 0 0 1 0 0 0 0
1 1 0 0 0 1 1 0 0 0
0 0 0 0 0 0 0 0 0 0
0 0 1 1 0 0 0 1 1 0
3 4
Рис. 8.4. Урезанные матрицы
Ход программирования
Шаг 1. Надо определиться, что еще, кроме матрицы, будет списком аргументов
функции? Так как мы используем прием программирования «динамика по подот-
резкам», то нам надо научиться описывать квадрат в матрице. Он однозначно зада-
ется тремя величинами: двумя координатами одного из углов и длиной стороны
квадрата. Вот эти три переменные: x, y и l — мы их поместим в список аргументов:
def square(M,y,x,l):
Листинг 8.2.1
def square(M,y,x,l):
#здесь будет поиск максимального квадрата
Поэтому нам надо задать значения по умолчанию. При первом вызове пусть угол
будет левый верхний, а длина равна всей матрице (листинг 8.2.2).
Динамика по подотрезкам 157
Листинг 8.2.2
def square(M,y=0,x=0,l=-1):
if l==-1:
l=len(M)
#здесь будет поиск максимального квадрата
Листинг 8.2.3
def square(M,y=0,x=0,l=-1):
if l==-1:
l=len(M)
f=True
for i in range (y,y+l):
for j in range (x,x+l):
if M[i][j]!=0:
f=False
break
if f==False:
break
if f==True:
return l
else:
#здесь будет обрезание квадрата
Листинг 8.2.4
def square (M,y=0,x=0,l=-1):
if l==-1:
l=len(M)
f=True
for i in range (y,y+l):
for j in range (x,x+l):
158 Урок 8
if M[i][j]!=0:
f=False
break
if f==False:
break
if f==True:
return l
else:
a=square(M,y+1,x+1,l-1)
b=square(M,y,x+1,l-1)
c=square(M,y+1,x,l-1)
d=square(M,y,x,l-1)
Шаг 4. Далее нам нужно найти самую большую величину. Код для этого может
быть различным. Начинающие программисты часто при этом теряются — в лис-
тинге 8.2.5 приведен код, который написал один из моих учеников.
Листинг 8.2.5
if a>b and a>c and a>d:
return a
elif b>c and b>d:
return b
elif c>d:
return c
else:
return d
Он вполне рабочий, но у нас же есть функция поиска максимума max. Поместим все
вычисления в список и подсчитаем максимум, получив первую версию программы
(листинг 8.2.6).
else:
return max([square(M,y+1,x+1,l-1),
square(M,y,x+1,l-1),
square(M,y+1,x,l-1),
square(M,y,x,l-1)])
M=[[0,0,1,0,1],
[1,0,0,0,0],
[1,1,0,0,0],
[0,0,0,0,0],
[0,0,1,1,0]]
print(square(M))
0 0 1 0 1 0 0 1 0 1
1 0 0 0 0 1 0 1 1 1
1 1 0 0 0 1 1 1 1 1
0 0 0 0 0 0 0 1 1 1
0 0 1 1 0 0 0 1 1 0
Матрица 1, ответ: 3 Матрица 2, ответ: 2
0 0 1 0 1 1 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 0 1 1 1 1 1
Матрица 3, ответ: 1 Матрица 4, ответ: 0
Рис. 8.5. Матрицы для тестирования программы
Здесь сразу видно, что недостаток метода — повторение проверки одних и тех же
квадратов по разным веткам. Устраним из рисунка повторные ветки и получим
картинку рекурсивных вызовов, которую хочется иметь в идеале (рис. 8.7).
Чтобы достичь этого, используем мемоизацию в списке. Пойдем тем же путем, что
и в последней версии мемоизации поиска палиндрома.
1. Понимаем, что при повторных вызовах функции для разных строк мемоизи-
рующий список (кеш) должен быть своим для каждой исходной матрицы. Зна-
Динамика по подотрезкам 161
Листинг 8.2.7
def square(M,y=0,x=0,l=-1,L=[]):
if l==-1:
l=len(M)
162 Урок 8
L=[]
for i in range(l+1):
L.append([])
for j in range(l+1):
L[-1].append([-1]*(l+1))
M=[[0,0,1,0,1],
[1,0,0,0,0],
[1,1,0,0,0],
[0,0,0,0,0],
[0,0,1,1,0]]
print(square(M))
Функциональное программирование
Ход программирования
Мы уже обладаем достаточными знаниями, чтобы решить эту задачу множеством
способов: со списками и без списков, с использованием функций (рекурсивных или
обычных) и без функций.
Шаг 1. Не будем думать об эффективности будущей программы и выберем вариант
программы с декомпозицией алгоритма в функции. Факториал мы уже писали, так
что возьмем готовую функцию «факториал», причем самую первую — с циклом.
Напишем теперь функцию суммы факториалов, используя накапливающуюся сум-
му (листинг 9.1.1).
def sigmafact(n):
s=0
for i in range(1,n+1):
s=s+fact(i)
return s
n=int(input())
print (sigmafact(n))
∑ f (i) .
i =1
Листинг 9.1.2
def fact(n):
p=1
for i in range(1,n+1):
p=p*i
return p
Функциональное программирование 165
def sigma(n,f):
s=0
for i in range(1,n+1):
s=s+f(i)
return s
n=int(input())
print (sigma(n,fact))
Обратите внимание, что функция f приведена в списке аргументов безо всяких ско-
бок:
def sigma(n,f):
∑ f (i) ,
i =1
∏ f (i) .
i =1
Листинг 9.1.3
def pi(n,f):
p=1
for i in range (1,n+1):
p=p*f(i)
return p
Для того чтобы приспособить функцию pi для факториала, нам понадобится новая
очень простая функция:
simple(n) = n .
Напишем эту функцию. Факториал превратим в обертку — вызов функции pi
с аргументом simple (листинг 9.1.4).
Листинг 9.1.4
def pi(n,f):
p=1
for i in range (1,n+1):
p=p*f(i)
return p
def sigma(n,f):
s=0
for i in range(1,n+1):
s=s+f(i)
return s
def simple(n):
return n
def fact(n):
return pi(n,simple)
n=int(input())
print (sigma(n,fact))
Шаг 4. Сравним функции pi и sigma. Видим, что они очень похожи. Для еще боль-
шей похожести в факториале переименуем p в s:
def pi(n,f): def sigma(n,f):
s=1 s=0
for i in range (1,n+1): for i in range(1,n+1):
s=s*f(i) s=s+f(i)
return s return s
Листинг 9.1.5
def row(n,f,s,bin):
for i in range (1,n+1):
s=bin(s,f(i))
return s
заменились на:
s=bin(s,f(i))
def summ(a,b):
return a+b
def mult(a,b):
return a*b
def simple(n):
return n
def fact(n):
return row(n,simple,1,mult)
n=int(input())
print (row(n,fact,0,summ))
168 Урок 9
print(f(2,3))
def fact(n):
return row(n,lambda n: n,1,lambda a,b:a*b)
n=int(input())
print (row(n,fact,0,lambda a,b:a+b))
Функциональное программирование 169
n=int(input())
print (row(n,lambda n:row(n,lambda n: n,1,lambda a,b:a*b),0,lambda a,b:a+b))
не было понятно, для чего в языке Python есть стиль Python. Теперь стало ясно —
для совместимости с функциональным программированием.
Далее мы познакомимся с другими приемами функционального программирования.
Задача 1
Для заданного списка осуществить фильтрацию — например, выбрать элементы
больше 0.
Языковые конструкции: filter, анонимные функции.
Ход программирования
Шаг 1. Думаю, что вы легко можете написать эту программу, перебирая в цикле
элементы списка и добавляя их в новый список при соответствии их условию (лис-
тинг 9.2.1).
Листинг 9.2.1. Фильтрация в структурном стиле Результат
L=[-2,3,0,-5,7]
print(L) [-2, 3, 0, -5, 7]
M=[]
for el in L:
if el>0:
M.append(el)
print(M) [3, 7]
Листинг 9.2.3
def positive(n):
if n>0:
return True
else:
return False
L=[-2,3,0,-5,7]
print(L)
M=list(filter(positive,L))
print(M)
Шаг 4. Пока получилось не очень лаконично, не правда ли? Вспомним, что опера-
торы сравнения сами по себе возвращают истину или ложь. Это позволит сильно
сократить функцию (листинг 9.2.4).
Листинг 9.2.4
def positive(n):
return n>0
L=[-2,3,0,-5,7]
print(L)
M=list(filter(positive,L))
print(M)
Задача 2
Для заданного списка построить новый, содержащий квадраты чисел исходного
списка.
Языковые конструкции: map, анонимные функции.
Ход программирования
Шаг 1. Как и в предыдущем случае, решим задачу сначала в структурном стиле
(листинг 9.2.6).
Листинг 9.2.6. Отображение в структурном стиле Результат
L=[-2,3,0,-5,7]
print(L) [-2, 3, 0, -5, 7]
M=[]
for el in L:
M.append(el*el)
print(M) [4, 9, 0, 25, 49]
Задача 3
Подсчитать произведение всех элементов списка.
Языковые конструкции: reduce, анонимные функции.
Функциональное программирование 173
Ход программирования
Мы писали ту программу с использованием флагов и ее Python-версию со счетчи-
ком (там мы ее довели до «предела питонизации» — здесь ее до такого предела мы
доводить не станем). Остановимся на идее алгоритма со счетчиком. Итак, вводятся
температуры людей. Будем подчитывать количество людей с повышенной темпера-
174 Урок 9
турой. Если найдется хотя бы один такой человек, то сообщаем, что есть больные.
Иначе выводим сообщение, что все здоровы.
Шаг 1. Напишем программу в структурном стиле (листинг 9.3.1).
Листинг 9.3.2
T=[int(el) for el in input().split()]
c=sum([t>37 for t in T])
if c>0:
print("есть больные")
else:
print("все здоровы")
Листинг 9.3.4
T=[int(el) for el in input().split()]
c=sum(list(map(lambda t:t>37,T)))
if c>0:
print("есть больные")
else:
print("все здоровы")
В разд. 9.1 мы написали две функции: первая вычисляла факториал, вторая — сум-
му факториалов, а потом постепенно изменили эту программу до неузнаваемости,
преобразовав ее в функциональный стиль. Здесь поступим так же, но используя
стандартные функционалы Python map и reduce.
Языковые конструкции: map, reduce.
Ход программирования
Шаг 1. Посмотрим, что нам нужно вычислить:
1! + 2! + 3! + ... + n ! ,
где
n ! = 1 ⋅ 2 ⋅ 3 ⋅ ... ⋅ n .
Стандартные функционалы Python преобразовывают список. Здесь видно, что мы
в обеих формулах преобразуем список [1,2,3,...,n]. Построим его с помощью range
(листинг 9.4.1).
Листинг 9.4.3
from functools import reduce
def fact(n):
L=list(range(1,n+1))
f=reduce(lambda a,b:a*b,L)
return f
n=int(input())
print(fact(n))
Функциональное программирование 177
def fact(n):
L=list(range(1,n+1))
f=reduce(lambda a,b:a*b,L)
return f
n=int(input()) 5
L=list(range(1,n+1))
print(L) [1,2,3,4,5]
M=list(map(fact,L))
print(M) [1,2,6,24,120]
def fact(n):
L=list(range(1,n+1))
f=reduce(lambda a,b:a*b,L)
return f
n=int(input()) 5
L=list(range(1,n+1))
print(L) [1,2,3,4,5]
M=list(map(fact,L))
print(M) [1,2,6,24,120]
print(sum(M)) 153
ные объявления, подставив код вычисления этих переменных туда, где они исполь-
зуются (листинг 9.4.6).
Листинг 9.4.6
from functools import reduce
def fact(n):
return reduce(lambda a,b:a*b,range(1,n+1))
n=int(input())
print(sum(list(map(fact,range(1,n+1)))))
Листинг 9.4.7
from functools import reduce
n=int(input())
print(sum(list(map(lambda n:reduce(lambda a,b:a*b, range(1,n+1)), range(1,n+1)))))
Шаг 8. Ввод n тоже поместим в вычислительную часть. Заметим, что ввод нужно
поместить только в самый конец и один раз. В часть, посвященную факториалу, его
помещать не нужно — там так и останется n. Мы получили финальную версию
программы в функциональном стиле (листинг 9.4.8).
print(sum(list(map(lambda n: reduce(lambda
a,b:a*b,range(1,n+1)),range(1,int(input())+1)))))
Задача
Вводится n. Написать функцию, создающую функцию возведения в заданную сте-
пень n.
Со школы мы знаем, что такое возведение в степень. Мы говорим, например:
«a в пятой степени». Но для двух показателей степеней — второй и третьей, есть
Функциональное программирование 179
Ход программирования
Шаг 1. Стандартное решение задачи в стиле урока 6, на котором мы изучали де-
композицию функций, — это написать функции квадрат (square) и куб (cube) как
обертки операторов возведения в степень (листинг 9.5.1).
Листинг 9.5.1 Результат
def square(a):
return a**2
def cube(a):
return a**3
a=float(input()) 5
print(square(a)) 25.0
print(cube(a)) 125.0
Шаг 2. Определим square и cube вторым способом задания функции — через lambda
(листинг 9.5.2).
Листинг 9.5.2
square = lambda a: a**2
cube = lambda a: a**3
a=float(input())
print(square(a))
print(cube(a))
Шаг 3. Что мы видим? Коды, задающие square и cube, очень похожи. А мы привык-
ли похожие коды объединять. Напишем общую функцию, которая объединит эти
фрагменты. Чем различаются эти куски кода? Двойкой и тройкой. Вот они и станут
аргументами новой функции genpow (листинг 9.5.3).
Листинг 9.5.3
def genpow(n):
return lambda a: a**n
square=genpow(2)
cube=genpow(3)
180 Урок 9
a=float(input())
print(square(a))
print(cube(a))
Заметим, что функция genpow закончила свою работу, но вновь созданные функции:
square и cube — помнят о значении переменной n. Это называется замыканием (за-
хватом переменной).
Шаг 4. Получается, что теперь пользователь может ввести n, а мы с помощью
genpow создадим новую функцию (листинг 9.5.4).
n=int(input())
userpow=genpow(n) 4
a=float(input()) 5
print(userpow(a)) 625.0
Шаг 5. Мы можем обойтись без введения userpow — ведь этот вызов используется
в программе только один раз (листинг 9.5.5).
Листинг 9.5.5
def genpow(n):
return lambda a: a**n
n=int(input())
a=float(input())
print(genpow(n)(a))
square=genpow(2)
cube=genpow(3)
a=float(input()) 5
print(square(a)) 25.0
print(cube(a)) 125.0
n=int(input()) 4
userpow=genpow(n)
print(userpow(a)) 625.0
print(genpow(n)(a)) 625.0
Функциональное программирование 181
def genpow(n):
return lambda a: fastpow(a,n)
square=genpow(2)
cube=genpow(3)
a=float(input())
print(square(a))
print(cube(a))
n=int(input())
userpow=genpow(n)
print(userpow(a))
print(genpow(n)(a))
Листинг 9.5.8
def fastpow(a,n):
if n==0:
return 1
elif n%2==0:
return fastpow(a*a,n//2)
else:
return fastpow(a,n-1)*a
def partapply(n,f):
return lambda a: f(a,n)
square=partapply(2,fastpow)
cube=partapply(3,fastpow)
a=float(input())
print(square(a))
print(cube(a))
n=int(input())
userpow=partapply(n,fastpow)
print(userpow(a))
print(partapply(n,fastpow)(a))
square=partapply(2,lambda a,n:a**n)
cube=partapply(3,lambda a,n:a**n)
a=float(input())
print(square(a))
print(cube(a))
print(f(100,10,1)) 321
g=partial(f,0)
print(g(100,10)) 210
h=partial(f,0,0)
print(h(100)) 100
print(f(100,10,1)) 321
k=partial(f,y=0,z=0)
print(k(100)) 300
print(f(100,10,1)) 321
k=partial(f,y=0,z=0)
print(k(100)) 300
print(k.keywords) {'y': 0, 'z': 0}
k.keywords["y"]=2
k.keywords["z"]=3
print(k.keywords) {'y': 2, 'z': 3}
print(k(100)) 307
184 Урок 9
def fastpow(a,n):
if n==0:
return 1
elif n%2==0:
return fastpow(a*a,n//2)
else:
return fastpow(a,n-1)*a
square=partial(fastpow,n=2)
cube=partial(fastpow,n=3)
a=float(input())
print(square(a))
print(cube(a))
Задача
На примере функции возведения в квадрат написать универсальный мемоизатор.
Языковые конструкции: функции, анонимные функции, вложенные функции.
Приемы программирования: функции высших порядков, замыкание, мемоизация,
универсальный мемоизатор.
Ход программирования
Шаг 1. Напишем обычную функцию возведения в квадрат (листинг 9.6.1).
n=int(input())
print(square(n))
n=int(input())
print(square(n))
Шаг 3. Оставим функцию square, какой она была, — пусть ее вызывает memsquare
(листинг 9.6.3).
n=int(input())
print(square(n))
L=[]
def memsquare(n):
global L
if len(L)<=n:
L=L+[-1]*(n+1)
if L[n]==-1:
L[n]=square(n)
return L[n]
n=int(input())
print(memsquare(n))
Листинг 9.6.4
def square(n):
return n*n
n=int(input())
print(square(n))
L=[]
def memo(n,f):
global L
if len(L)<=n:
L=L+[-1]*(n+1)
if L[n]==-1:
L[n]=f(n)
return L[n]
n=int(input())
print(memo(n,square))
Шаг 5. Но полученное решение очень плохое. Если мы запустим memo для другой
функции, то рискуем получить ошибку, т. к. мемоизирующий список (кеш) будет
общим для всех функций!
Мемоизатор должен быть универсальным. Он должен принимать только один
аргумент — функцию — и изменять ее:
def memo(f):
Кроме того, кеш L следует спрятать внутри функции memo, т. к. этот кеш должен соз-
даваться для каждой мемоизированной функции:
def memo(f):
L=[]
Что же делать с остальным кодом? Это будет функция внутри функции (вложенная
функция), которая станет принимать второй аргумент n (листинг 9.6.5).
Листинг 9.6.5
def memo(f):
L=[]
def res(n):
nonlocal L
if n>=len(L):
L=L+[-1]*(n+1)
if L[n]==-1:
L[n]=f(n)
return L[n]
Функциональное программирование 187
Обратите внимание: для того, чтобы список L перестал быть глобальным, мы заме-
нили зарезервированное слово global на nonlocal.
Что же будет возвращать memo? Очевидно, переделанную функцию f, т. е. res. Мы
получили работающую программу (листинг 9.6.6).
def square(n):
return n*n
memsquare=memo(square)
n=int(input())
print(memsquare(n))
Шаг 6. Если мы не хотим плодить имена функций и нам обычная функция square
уже не нужна, то вспомним, как мы поступали с переменными в рекуррентных
формулах:
s=s+a
Да, можем. Этот прием называется подмена функций. Мы получим вполне рабо-
тающую программу (листинг 9.6.7).
188 Урок 9
Листинг 9.6.7
def memo(f):
L=[]
def res(n):
nonlocal L
if n>=len(L):
L=L+[-1]*(n+1)
if L[n]==-1:
L[n]=f(n)
return L[n]
return res
def square(n):
return n*n
square=memo(square)
n=int(input())
print(square(n))
@memo
def square(n):
return n*n
Функциональное программирование 189
n=int(input())
print(square(n))
Листинг 9.6.9
def memo(f):
L=[]
def res(n):
nonlocal L
if n>=len(L):
L=L+[-1]*(n+1)
if L[n]==-1:
L[n]=f(n)
return L[n]
return res
def fib(n):
if n<=2:
return 1
else:
return fib(n-1)+fib(n-2)
memfib=memo(fib)
n=int(input())
print(memfib(n))
if L[n]==-1:
L[n]=f(n)
return L[n]
return res
@memo
def fib(n):
if n<=2:
return 1
else:
return fib(n-1)+fib(n-2)
n=int(input())
print(fib(n))
Листинг 9.6.11
from functools import lru_cache
@lru_cache(maxsize=128)
def fib(n):
if n==0:
return 0
elif n==1:
return 1
else:
return fib(n-1)+fib(n-2)
n=int(input())
@cache
def fib(n):
if n==0:
return 0
elif n==1:
return 1
Функциональное программирование 191
else:
return fib(n-1)+fib(n-2)
n=int(input())
9.7. Декораторы
Вернемся к декораторам, которые мы начали использовать в предыдущем разделе,
и разберемся, что же это такое?
Задача
Переделать программу «Как тебя зовут?» с помощью декораторов.
Пусть имеется функция, и нужно сделать некоторые дополнительные действия до
ее запуска и после. Такое преобразование функции и называется декоратором.
Языковые конструкции: декораторы.
Ход программирования
Шаг 1. Вернемся к истокам — задаче «Как тебя зовут?» из разд. 1.2 (листинг 9.7.1).
Листинг 9.7.1
name=input("Как тебя зовут?")
print("Привет,",name, "!")
Результат
Как тебя зовут? Паша
Привет, Паша!
Листинг 9.7.2
def dear(s):
print("уважаемый",s)
Результат
Как тебя зовут? Паша
Привет!
уважаемый Паша
!
Листинг 9.7.3
def dear(s):
print("уважаемый",s)
def sayhello(s):
print("Привет!")
dear(s)
print("!")
Листинг 9.7.4
def dear(s):
print("уважаемый",s)
def muchesteemed(s):
print("глубокоуважаемый",s)
def sayhello(s,f):
print("Привет!")
f(s)
print("!")
Результат
Как тебя зовут? Паша
Привет!
глубокоуважаемый Паша
!
Функциональное программирование 193
Листинг 9.7.5
def dear(s):
print("уважаемый",s)
def muchesteemed(s):
print("глубокоуважаемый",s)
def decohello(f):
def sayhello(s):
print("Привет!")
f(s)
print("!")
return sayhello
hello= decohello(muchesteemed)
name=input("Как тебя зовут?")
hello(name)
Листинг 9.7.6
def dear(s):
print("уважаемый",s)
def muchesteemed(s):
print("глубокоуважаемый",s)
def decohello(f):
def sayhello(s):
print("Привет!")
f(s)
print("!")
return sayhello
hellodear= decohello(dear)
hellomuchesteemed= decohello(muchesteemed)
name=input("Как тебя зовут?")
hellomuchesteemed(name)
194 Урок 9
Шаг 7. Если вы думаете, что вежливость требует таких жертв и нам придется сми-
риться с кучей функций, то вы ошибаетесь. Давайте сделаем подмену исходных
функций dear и muchesteemed (листинг 9.7.7).
Листинг 9.7.7
def dear(s):
print("уважаемый",s)
def muchesteemed(s):
print("глубокоуважаемый",s)
def decohello(f):
def sayhello(s):
print("Привет!")
f(s)
print("!")
return sayhello
dear=decohello(dear)
muchesteemed=decohello(muchesteemed)
name=input("Как тебя зовут?")
dear(name)
Результат
Как тебя зовут? Паша
Привет!
уважаемый Паша
!
Листинг 9.7.8
def decohello(f):
def sayhello(s):
print("Привет!")
Функциональное программирование 195
f(s)
print("!")
return sayhello
@decohello
def dear(s):
print("уважаемый",s)
@decohello
def muchesteemed(s):
print("глубокоуважаемый",s)
Листинг 9.7.9
def decohello(f):
def sayhello(s):
print("Привет!")
f(s)
print("!")
return sayhello
def decogoodbye(f):
def saygoodbye(s):
print("Пока!")
f(s)
print("!")
return saygoodbye
@decohello
def dear(s):
print("уважаемый",s)
@decogoodbye
def muchesteemed(s):
print("глубокоуважаемый",s)
Результат
Как тебя зовут? Паша
Привет!
уважаемый Паша
!
Я - Python!
Пока!
глубокоуважаемый Паша
Шаг 10. Декораторы decohello и decogoodbye очень похожи. Хочется сделать из них
один декоратор deco c аргументом в виде строки Привет или Пока. Для этого возьмем
какой-нибудь из декораторов, сделаем в нем нейтральные названия функций, под-
ходящие и для приветствия, и для прощания (листинг 9.7.10)...
Листинг 9.7.10
def deconew(f):
def say(s):
print("...")
f(name)
print("!")
return say
...и поместим их внутрь новой функции deco, которая будет получать строку в каче-
стве аргумента (листинг 9.7.11).
Листинг 9.7.11
def deco(t):
def deconew(f):
def say(s):
print(t)
f(s)
print("!")
return say
return deconew
Листинг 9.7.12
def deco(t):
def deconew(f):
Функциональное программирование 197
def say(s):
print(t)
f(s)
print("!")
return say
return deconew
@deco("Привет!")
def dear(s):
print("уважаемый",s)
@deco("Пока!")
def muchesteemed(s):
print("глубокоуважаемый",s)
Результат
Как тебя зовут? Паша
Привет!
уважаемый Паша
!
Я - Python!
Пока!
глубокоуважаемый Паша
!
Шаг 11. К функции мы можем применить сразу два декоратора (листинг 9.7.13).
Листинг 9.7.13
def deco(t):
def deconew(f):
def say(s):
print(t)
f(s)
print("!")
return say
return deconew
@deco("Привет!")
@deco("Пока")
def dear(s):
print("уважаемый",s)
198 Урок 9
Результат
Как тебя зовут? Паша
Привет!
Пока!
уважаемый Паша
!
!
Шаг 12. Если вы думаете, что ваши мучения на этом закончились, то вы ошибае-
тесь. Мы можем декорировать сам декоратор (листинг 9.7.14).
Листинг 9.7.14
def piton(decorator):
def res(f):
print("Я - Python!")
return decorator(f)
return res
@piton
def deco(t):
def deconew(f):
def say(s):
print(t)
f(s)
print("!")
return say
return deconew
@deco("Привет!")
@deco("Пока")
def dear(s):
print("уважаемый",s)
Результат
Я - Python!
Я - Python!
Как тебя зовут? Паша
Привет!
Пока
уважаемый Паша
!
!
Функциональное программирование 199
9.8. Генераторы
Задача
Создать арифметическую и геометрическую прогрессию и подсчитать суммы их
членов.
Со школы мы знаем, что арифметическая прогрессия — это последовательность
элементов, где каждый следующий больше предыдущего на фиксированное значе-
ние. Например:
[5,7,9,11,13]
Ход программирования
Шаг 1. Начнем с арифметической прогрессии в ее простейшем варианте:
1, 2, 3, ... , n.
Чтобы найти ее сумму, список не нужен, но по условию задачи мы должны сохра-
нить ее в списке (листинг 9.8.1).
Листинг 9.8.3
n=int(input())
L=list(range(1,n+1))
print(L)
print(sum(L))
Шаг 4. Если список нам не нужен, то запишем программу так, как представлено
в листинге 9.8.4.
Листинг 9.8.4
n=int(input())
print(sum(list(range(1,n+1))))
Результат
6
[1, 2, 4, 8, 16, 32]
63
Функциональное программирование 201
Листинг 9.8.8
def geomprogr(n):
for i in range(n):
yield 2**i
n=int(input())
L=list(geomprogr(n))
print(L)
print(sum(L))
Листинг 9.8.9
def geomprogr(n):
for i in range(n):
yield 2**i
n=int(input())
print(sum(geomprogr(n)))
n=int(input())
L=[i for i in geomprogr(n,1,2)]
print(L)
print(list(progress(6)))
print(list(progress(6,1)))
print(list(progress(6,1,2)))
print(list(progress(6,1,2,lambda a,b:a*b)))
print(list(progress(6,3,2,lambda a,b:a**b)))
Результат
[0, 1, 2, 3, 4, 5]
[1, 2, 3, 4, 5, 6]
[1, 3, 5, 7, 9, 11]
[1, 2, 4, 8, 16, 32]
[3, 9, 81, 6561, 43046721, 1853020188851841]
Объектно-ориентированное программирование
предметной области «Геометрия»
Ход программирования
Будем решать поставленную задачу постепенно, преобразуя код, созданный в раз-
ных стилях программирования, в объектно-ориентированный и показывая его пре-
имущества.
Объектно-ориентированное программирование предметной области «Геометрия» 205
Шаг 1. Кажется, что это элементарная задача — вводятся четыре числа: координа-
ты x и y двух точек, после чего вычисляется расстояние по формуле, сводящейся
к теореме Пифагора (рис. 10.1).
y2‐y1
Шаг 2. Предположим, что нам нужно вычислить много расстояний между разными
точками. Тогда логично вынести формулу подсчета расстояния в отдельную функ-
цию — dist (листинг 10.1.2).
x1=float(input())
y1=float(input())
x2=float(input())
y2=float(input())
print (dist(x1,y1,x2,y2))
соблены друг от друга — они идут одним списком и вводятся независимо друг от
друга. Мы можем обособить их, используя новую языковую конструкцию — класс.
Введем класс «точка»:
class point:
pass
#здесь будет код, относящийся к точке.
У нас будут и другие точки. Если point — это класс, то переменная A — это экземп-
ляр класса.
Такое объявление похоже на то, как мы создавали список или словарь:
L=[]
Точки будут хранить две координаты: x и y. Обращаться к ним мы будем через точ-
ку после имени экземпляра класса:
A.x
A.y
A=point()
A.x=float(input())
A.y=float(input())
B=point()
B.x=float(input())
B.y=float(input())
print(dist(A,B))
Шаг 4. Код слегка портит то, что ввод точки в основной части программы осуще-
ствляется в два вызова input:
A.x=float(input())
A.y=float(input())
A=point()
A.input()
Обратите внимание, как вызывается функция input, — через точку после имени
переменной. Мы уже сталкивались с похожим вызовом функций — например, при
подсчете вхождений элемента в списке:
L.count(3)
Листинг 10.1.5
class point:
def input():
s=[]
s=input().split()
x=float(s[0])
y=float(s[1])
Листинг 10.1.6
class point:
def input(self):
s=[]
s=input().split()
self.x=float(s[0])
self.y=float(s[1])
A=point()
B=point()
A.input()
B.input()
print(dist(A,B))
Когда Python видит имя класса в таком контексте, он его подменяет на вызов функ-
ции.
Но теперь у нас не получится объявить точку таким образом:
B=point()
A=point()
B=point()
B.input()
print(dist(A,B))
A=point().input()
B=point().input())
print(dist(A,B))
Не правда ли, хороший код для подсчета расстояний? А все подробности подсчета
могут быть скрыты в библиотеке. В них разбираться не обязательно.
В этом разделе мы узнали много терминов объектно-ориентированного програм-
мирования. Вам нужно хорошо уяснить, что они представляют собой и чем разли-
чаются:
1. Объект, класс, экземпляр класса.
2. Функция и метод.
3. Свойство, метод, конструктор.
4. Абстракция, инкапсуляция.
Ход программирования
Шаг 1. Напишем класс «отрезок». Он будет задаваться двумя точками. Возьмем за
основу класс «точка», скопируем его и переименуем, внеся следующие изменения:
конструктор будет принимать две точки как аргументы. Прямо в списке аргу-
ментов создадим точки — значения по умолчанию:
class sect:
def __init__(self,A=point(),B=point(1,0)):
self.A=A
self.B=B
исправим функцию ввода — она должна вызывать методы input у класса «отре-
зок»:
class sect:
...
def input (self):
self.A.input()
self.B.input()
напишем функцию длины — length. Эта функция будет просто вызывать функ-
цию подсчета расстояния между точками:
class sect:
...
def length(self):
return(dist(self.A,self.B))
212 Урок 10
Обратите внимание, что во всех методах первый аргумент — self. И через него
происходит обращение к свойствам и методам внутри класса.
Мы получили программу (листинг 10.2.1).
class sect:
def __init__(self,A=point(),B=point(1,0)):
self.A=A
self.B=B
def input (self):
self.A.input()
self.B.input()
return self
def length(self):
return(dist(self.A,self.B))
AB=sect().input()
print(AB.length())
ABC=triangle().input()
print (ABC.per()," ",ABC.square())
Шаг 3. Как уже было отмечено ранее, вычисление площади треугольника по фор-
муле Герона осуществляется через периметр. Поэтому метод вычисления площади
вызывает внутри себя метод вычисления периметра. Но длины сторон по-прежнему
вычисляются одинаково.
Напишем вторую версию класса треугольника. Пусть он задается тремя вершинами
в конструкторе и методе ввода. Но пусть также на основе трех вершин создаются
и три отрезка — стороны треугольника, которые потом будут использоваться в ме-
тодах подсчета периметра и площади.
Поскольку три стороны создаются и в конструкторе, и при вводе, выделим созда-
ние сторон в отдельный метод onChange, который будет вызываться всегда, когда
создается или вводится треугольник (листинг 10.2.3).
ABC=triangle()
ABC.input()
print (ABC.per()," ",ABC.square())
Объектно-ориентированное программирование предметной области «Геометрия» 215
Шаг 4. Создадим класс «прямоугольник». Пусть для простоты его стороны будут
параллельны осям координат. В этом случае он однозначно определяется двумя
противоположными по диагонали точками (рис. 10.3) — эти точки мы и будем хра-
нить в прямоугольнике. У такого прямоугольника легко вычислить периметр
и площадь, обращаясь к координатам этих точек (листинг 10.2.4).
ABCD=rectangle()
ABCD.input()
print (ABCD.per()," ",ABCD.square())
216 Урок 10
Листинг 10.2.5
...
s=input()
if s=="triangle":
f=triangle()
elif s=="rectangle":
f=rectangle()
elif s=="sect":
f=sect()
elif s=="point":
f=point()
f.input()
print (f.per()," ",f.square())
Но если мы запустим эту программу для отрезка и точки, то она выдаст ошибку,
потому что для них нет методов вычисления периметра и площади. Зададим пери-
метр отрезка как двойную длину его стороны (ведь периметр у других фигур полу-
чается обходом сторон с возвращением в исходную точку). Этим функция пери-
метра отрезка отличается от длины отрезка (листинг 10.2.6).
Листинг 10.2.6
class sect(figure):
...
def length(self):
return(dist(self.A,self.B))
def per(self):
return 2*self.length()
Площади точки и отрезка равны нулю, периметр точки тоже равен нулю. Получает-
ся, что придется писать еще три метода? Есть способ лучше. Он связан с примене-
нием четвертого принципа объектно-ориентированного программирования — на-
следования.
Точка, отрезок, треугольник и прямоугольник — это фигуры. Сделаем класс «фи-
гура» с двумя методами: периметром и площадью, возвращающими ноль. А ос-
тальные классы объявим классами-наследниками фигуры. Родительские классы
указываются в скобках после имени класса — например:
class point(figure):
class point(figure):
...
class sect(figure):
...
class triangle(figure):
...
class rectangle(figure):
...
s=input()
if s=="triangle":
f=triangle()
elif s=="rectangle":
f=rectangle()
elif s=="sect":
f=sect()
elif s=="point":
f=point()
else:
f=figure()
f.input()
print (f.per()," ",f.square())
218 Урок 10
Результат
{'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': <class
'_frozen_importlib.BuiltinImporter'>, '__spec__': None, '__annotations__': {},
'__builtins__': <module 'builtins' (built-in)>, '__file__':
'E:/Users/Paul/IT/Teaching/Python/Уроки Python/Программы/интроспекция.py', 'V': {...}}
Результат
{'__name__': '__main__', '__doc__': '\nV=globals()\nprint(V)\n', '__package__': None,
'__loader__': <class '_frozen_importlib.BuiltinImporter'>, '__spec__': None,
'__annotations__': {}, '__builtins__': <module 'builtins' (built-in)>, '__file__':
'E:/Users/Paul/IT/Teaching/Python/Уроки Python/Программы/интроспекция.py', 'a': 3,
'b': 4, 'V': {...}}
Здесь видно, что в конце словаря появились две записи, в которых ключи — наши
переменные, а значения — это значения этих переменных:
...'a': 3, 'b': 4, ...
Объектно-ориентированное программирование предметной области «Геометрия» 219
Функции тоже хранятся в этом глобальном словаре, и мы можем через него к ним
обращаться (листинг 10.2.11).
def mult(x,y):
return x*y
a=3
b=4
V=globals()
print("a=",a,"b=",b)
s=input("имя переменной:")
n=int(input("новое значение:"))
V[s]=n
print("a=",a,"b=",b)
sf=input("имя функции:")
print("результат:",V[sf](a,b))
Результат
a= 3 b= 4
имя переменной:a
новое значение:5
a= 5 b= 4
имя функции:mult
результат: 20
else:
f=figure()
f.input()
print (f.per(),f.square())
class point(figure):
def __init__(self,x=0,y=0):
self.x=x
self.y=y
def input(self):
s=[]
s=input().split(' ')
self.x=float(s[0])
self.y=float(s[1])
return self
class sect(figure):
def __init__(self,A=point(),B=point(1,0)):
self.A=A
self.B=B
def input (self):
self.A.input()
self.B.input()
return self
def length(self):
return(dist(self.A,self.B))
def per(self):
return 2*self.length
class triangle(figure):
def __init__(self,A=point(),B=point(),C=point()):
self.A=A
self.B=B
self.C=C
self.onChange()
Объектно-ориентированное программирование предметной области «Геометрия» 221
def input(self):
self.A.input()
self.B.input()
self.C.input()
self.onChange()
return self
def onChange(self):
self.AB=sect(self.A,self.B)
self.BC=sect(self.B,self.C)
self.AC=sect(self.A,self.C)
def per(self):
return (self.AB.length() + self.BC.length()
+ self.AC.length())
def square(self):
p=self.per()/2
return ((p*(p-self.AB.length())
* (p-self.BC.length())
* (p-self.AC.length()))**(0.5))
class rectangle(figure):
def __init__(self,A=point(),B=point()):
self.A=A
self.B=B
def input(self):
self.A.input()
self.B.input()
return self
def per(self):
return (2*(abs(self.B.x-self.A.x)
+ abs(self.B.y-self.A.y)))
def square(self):
return (abs(self.B.x-self.A.x)
* abs(self.B.y-self.A.y))
s=input()
if s in globals():
f=globals()[s]()
else:
f=figure()
f.input()
print (f.per(),f.square())
Ход программирования
Шаг 1. Временно отвлечемся от классов и вернемся к функциям. Изучим еще одну
возможность, которая нам пригодится в многоугольнике, — функцию с переменным
числом параметров.
Пусть мы хотим написать функцию func, которая будет искать сумму всех чисел,
указанных у нее как аргументы, — например, сделаем возможным такой вызов:
func(10)
func(10,20)
func(10,20,30)
func(10,20,30,40)
func(10,20,30,40,50)
...
Как нам ее объявить? Пока мы знаем только одно средство — функцию со значе-
ниями аргументов по умолчанию:
def func(a=0,b=0,c=0,d=0,e=0):
L=[10,20,30,40,50]
print(func(L))
print(func(10))
print(func(10,20))
print(func(10,20,30))
print(func(10,20,30,40))
print(func(10,20,30,40,50))
class point(figure):
def __init__(self,x=0,y=0):
self.x=x
self.y=y
self.p=[self]
A=point(1,2)
B=figure(point(0,0),point(3,0),point(0,4))
class point(figure):
...
def print(self):
print("point: x=",self.x,"y=",self.y)
A=point(1,2)
A.print()
Print()
B=figure(point(0,0),point(3,0),point(0,4))
B.print()
Результат
point: x= 1 y= 2
figure:
point: x= 0 y= 0
point: x= 3 y= 0
point: x= 0 y= 4
Листинг 10.3.4
...
A=point(1,2)
A.print()
print(A.perimetr())
print()
B=figure(point(0,0),point(3,0),point(0,4))
B.print()
print(B.perimetr())
Результат
point: x= 1 y= 2
0.0
figure
point: x= 0 y= 0
point: x= 3 y= 0
point: x= 0 y= 4
12.0
class point(figure):
def __init__(self,x=0,y=0):
self.x=x
self.y=y
self.p=[self]
def print(self):
print("point: x=",self.x,"y=",self.y)
class sector(figure):
def __init__(self,A=point(0,0),B=point(0,0)):
self.p=[A,B]
def length(self):
return dist(self.p[0],self.p[1])
def dist(A,B):
return ((A.x-B.x)**2+(A.y-B.y)**2)**(1/2)
A=point(1,2)
A.print()
print(A.perimetr())
print()
B=figure(point(0,0),point(3,0),point(0,4))
B.print()
print(B.perimetr())
print()
B=sector(point(0,0),point(3,4))
B.print()
print(B.perimetr())
print(B.length())
#print(A.x,A.y)
#print(A.p[0].x,A.p[0].y)
figure
point: x= 0 y= 0
point: x= 3 y= 0
point: x= 0 y= 4
12.0
figure
point: x= 0 y= 0
point: x= 3 y= 4
10.0
5.0
Объектно-ориентированное программирование предметной области «Геометрия» 227
Заметим, что для отрезка она написала при выводе слово figure, т. к. специального
метода для вывода отрезка у нас нет.
Ход программирования
Шаг 1. Возьмем предыдущую программу без изменений и попробуем создать
составную фигуру из трех треугольников (листинг 10.4.1).
Листинг 10.4.1
...
A=figure(point(0,0),point(3,0),point(0,4))
B=figure(point(10,10),point(13,10),point(10,14))
C=figure(point(100,100),point(103,100),point(100,104))
F=figure(A,B,C)
F.print()
Результат
figure
figure
point: x= 0 y= 0
point: x= 3 y= 0
point: x= 0 y= 4
figure
point: x= 10 y= 10
point: x= 13 y= 10
point: x= 10 y= 14
figure
point: x= 100 y= 100
point: x= 103 y= 100
point: x= 100 y= 104
Значит, нам надо узнать, что является элементами списка. Если там все точки, то
вычислять сумму расстояний между ними, а если есть хотя бы один многоуголь-
ник, то вычислять периметры составных частей.
Как определить название класса по его экземпляру? Для этого есть функция type.
Вот пример ее использования:
if type(el)!=point:
else:
return sum([el.perimetr() for el in self.p])
class point(figure):
def __init__(self,x=0,y=0):
self.x=x
self.y=y
self.p=[self]
def print(self):
print("point: x=",self.x,"y=",self.y)
class sector(figure):
def __init__(self,A=point(0,0),B=point(0,0)):
self.p=[A,B]
def length(self):
return dist(self.p[0],self.p[1])
def dist(A,B):
return ((A.x-B.x)**2+(A.y-B.y)**2)**(1/2)
A=point(1,2)
A.print()
print(A.perimetr())
print()
B=figure(point(0,0),point(3,0),point(0,4))
B.print()
print(B.perimetr())
print()
B=sector(point(0,0),point(3,4))
B.print()
print(B.perimetr())
print(B.length())
print()
C=figure(figure(point(0,0),point(3,0),point(0,4)),figure(point(10,10),point(13,10),
point(10,14)),figure(point(100,100),point(103,100),point(100,104)))
C.print()
print(C.perimetr())
print()
Результат
point: x= 1 y= 2
0.0
figure
point: x= 0 y= 0
point: x= 3 y= 0
230 Урок 10
point: x= 0 y= 4
12.0
figure
point: x= 0 y= 0
point: x= 3 y= 4
10.0
5.0
figure
figure
point: x= 0 y= 0
point: x= 3 y= 0
point: x= 0 y= 4
figure
point: x= 10 y= 10
point: x= 13 y= 10
point: x= 10 y= 14
figure
point: x= 100 y= 100
point: x= 103 y= 100
point: x= 100 y= 104
36.0
Я начинал преподавать объектно-ориентированное программирование с класса
«геометрия» в его первом варианте: с точкой, отрезком и треугольником, т. к. это
наиболее простой пример для понимания того, зачем вообще нужно объектно-
ориентированное программирование.
Но даже в такой простой, на первый взгляд, области можно увидеть много интерес-
ного. Так, постепенно появлялись:
1. Альтернативное определение треугольника.
2. Наследование от класса «фигура».
3. Интроспекция (пользователь вводил названия фигур).
4. Альтернативное представление фигуры в виде многоугольника.
5. Составные фигуры.
Оказалось, что эта простая область потребовала знаний значительной части объ-
ектно-ориентированного программирования и в итоге породила рекурсивные
составные структуры, терминальным уровнем которых являются точки. Кстати, вы
заметили, что функция вычисления периметра — рекурсивна?
УРОК 11
Ход программирования
Шаг 1. В конструкторе (помните, что для них есть зарезервированное слово
__init__) начнем создавать матрицу размера n (передается как аргумент). Это будет
список списков, заполненный одними нулями (листинг 11.1.1).
M=matrix(2)
232 Урок 11
Когда Python видит название класса с круглыми скобками, он вызывает метод с за-
резервированным названием __init__ .
Шаг 2. При работе с матрицей в основной части программы нам нужно уметь об-
ращаться к ее отдельным элементам. Пока это можно сделать очень громоздко:
M.array[0][1]=2
print(M.array[0][1])
Для этого нужно написать метод, который вернет строку матрицы. Точнее, это
будут два метода: для чтения и для записи. Эти методы называются индексато-
рами. Для них тоже есть зарезервированные слова: __getitem__ и __setitem__ (лис-
тинг 11.1.2).
def __setitem__(self,i,v):
self.array[i]=v
def input(self):
self.array=[[float(el) for el in input().split()]
for i in range(self.n)]
return self
def print(self):
for row in self:
print(row)
return self
M=matrix(2)
M.input()
M.print()
Ход программирования
Шаг 1. Начнем с транспонирования. Создадим метод transpone, перенесем туда код
из разд. 5.1 и получим программу, приведенную в листинге 11.2.1.
matrix(2).input().print().transpone().print()
234 Урок 11
A=matrix(2).input()
B=matrix(2).input()
C=A+B
C.print()
def transpone(self):
for i in range(self.n):
for j in range(i,self.n):
self[j][i],self[i][j]=self[i][j],self[j][i]
return self
def __add__ (self,other):
R=matrix(self.n)
for i in range(self.n):
for j in range(self.n):
R[i][j]=self[i][j]+other[i][j]
return R
def __mul__ (self,other):
R=matrix(self.n)
for i in range(self.n):
for j in range(self.n):
for k in range (self.n):
R[i][j]=R[i][j]+self[i][k]*other[k][j]
return R
A=matrix(2).input()
B=matrix(2).input()
C=A*B
C.print()
Ход программирования
Шаг 1. Начнем с возведения в степень. Напишем метод __pow__, чтобы можно было
пользоваться оператором возведения в степень **.
236 Урок 11
Для возведения в степень можно просто организовать цикл и написать в нем рекур-
рентную формулу (листинг 11.3.1).
class matrix:
...
def __pow__(self,n):
return fastpow(self,n)
A=matrix(2).input()
n=int(input())
B=A**n
B.print()
Все работает! То, что функция возведения в степень, которую мы написали для
чисел, прекрасно работает и для матриц безо всякой адаптации, это большое дос-
Матрица в объектно-ориентированном стиле 237
def copy(self):
R=matrix(self.n)
for i in range (self.n):
for j in range (self.n):
R[i][j]=self[i][j]
return R
A=matrix(3).input()
print(A.det())
elif n%2==0:
return fastpow(a*a,n//2)
else:
return fastpow(a,n-1)*a
class matrix:
def __init__(self,n=1):
self.n=n
self.array=[]
for i in range(n):
self.array.append([0]*n)
def __getitem__ (self,i):
return self.array[i]
def __setitem__(self,i,v):
self.array[i]=v
def input(self):
self.array=[[float(el) for el in input().split()]
for i in range(self.n)]
return self
def print(self):
for row in self:
print(row)
return self
def transpone(self):
for i in range(self.n):
for j in range(i,self.n):
self[j][i],self[i][j]=self[i][j],self[j][i]
return self
def __add__ (self,other):
R=matrix(self.n)
for i in range(self.n):
for j in range(self.n):
R[i][j]=self[i][j]+other[i][j]
return R
244 Урок 11
else:
return fastpow(self.inverse(),-n)
A=matrix(2).input()
n=int(input())
B=A**n
B.print()
12.1. Функторы
В этом разделе мы научим функцию вести себя как список, т. е. функцию f(n) мож-
но будет запускать двумя способами:
с круглыми скобками — f(n);
и с квадратными — f[n],
и посмотрим, какую практическую пользу можно извлечь из подобной мимикрии.
Задача
Изменить функцию вычисления квадрата числа так, чтобы к ней можно было
обращаться не только как к функции, но и как к списку.
Языковые конструкции: класс, индексатор, итератор, перегрузка операторов
квадратных и круглых скобок, декоратор.
Прием программирования: функтор.
Ход программирования
Чтобы решить задачу, нам понадобится соединить вместе функциональное и объ-
ектно-ориентированное программирование.
Шаг 1. Напишем функцию возведения в квадрат:
def f(n):
return n*n
Листинг 12.1.1
class square:
def f(n):
return n*n
def __getitem__(self, n):
return square.f(n)
s = square()
n = int(input())
print(s[n])
s = square()
n = int(input())
print(s(n))
print(s[n])
class functor:
def __init__(self,f):
self.f=f
def __call__(self,n):
return self.f(n)
def __getitem__(self, n):
return self.f(n)
s=functor(square)
n = int(input())
print(s(n))
print(s[n])
Листинг 12.1.4
class functor:
def __init__(self,f):
self.f=f
def __call__(self,n):
return self.f(n)
def __getitem__(self, n):
return self.f(n)
def square(n):
return n*n
square=functor(square)
n = int(input())
print(square(n))
print(square[n])
@functor
def square(n):
return n*n
n = int(input())
print(square(n))
print(square[n])
@functor
def square(n):
return n*n
functor.size=int(input())
for i in range(len(square)):
print(square[i])
250 Урок 12
Здесь сам объект, т. е. self, вызывается с круглыми скобками, а это значит, что вы-
полнение передается методу __call__, который, в свою очередь, вызывает функцию
f, в нашем случае — возведение в квадрат уменьшенного индекса.
Полученная программа приведена в листинге 12.1.7.
def __iter__(self):
self.i = 0
return self
def __next__(self):
self.i = self.i + 1
if self.i>self.size:
raise StopIteration
return self(self.i - 1)
@functor
def square(n):
return n*n
functor.size=int(input())
for el in square:
print(el)
Шаг 9. У списка есть метод index, который возвращает индекс первого вхождения
элемента. Для этого просто напишем метод index, который будет перебирать все
элементы и возвращать номер первого найденного элемента (значения функции)
или -1, если такового не нашлось (листинг 12.1.8).
@functor
def square(n):
return n*n
functor.size=int(input())
v=int(input())
print(square.index(v))
@functor
def square(n):
return n*n
Программирование сложных коллекций 253
functor.size=int(input())
v=int(input())
print(square.index(v))
Задача 1
Сделать коллекцию «кольцо».
Языковые конструкции: класс, индексатор, итератор, декоратор.
Прием программирования: коллекция — кольцо.
Ход программирования
Шаг 1. Прежде всего, нужно решить, как мы будем хранить данные внутри кольца.
Естественное решение — хранить их в списке. Спрячем список внутри кольца и
перепрограммируем его поведение. Напишем конструктор и методы ввода/вывода
(листинг 12.2.1).
Листинг 12.2.1
class ring:
def __init__(self, r = []):
self.r = r
def input(self):
self.r = list(input().split())
def print(self):
print(self.r)
r = ring(["a","b","c","d","e"])
r.print()
254 Урок 12
r = ring(["a","b","c","d","e"])
print(r)
Шаг 3. Особенность кольца в том, что оно замкнуто, а это значит, что в нем не
может быть ошибки выхода за пределы списка. Когда мы обращаемся по номеру,
который превышает реальный размер списка, спрятанного внутри кольца, то воз-
вращаемся к его началу:
r = [ a b c d e ]
i=0 i=1 i=2 i=3 i=4
i=5 i=6 i=7 i=8 i=9
i=10 i=11 i=12 i=13 ...
Индекс элемента здесь равен остатку от деления введенного номера на длину списка.
Напишем методы индексатора (листинг 12.2.3) — если вы забыли, что такое индек-
сатор, вернитесь к предыдущему разделу.
def __setitem__(self,i,val):
self.r[i % len(self.r)] = val
Шаг 4. Чтобы была возможность выполнять цикл for для перебора кольца, напи-
шем методы итератора (листинг 12.2.4). Об итераторах тоже шла речь в предыду-
щем разделе.
Шаг 5. Заметим, что методы итератора совершенно типовые (убрана только гене-
рация исключения StopIteration), но при попытке организовать цикл:
for el in r:
Листинг 12.2.5
N=15
i=0
for el in r:
print(el)
i=i+1
if i==N:
break
Шаг 6. Нам могут понадобиться метод вычисления размера кольца __len__ и метод
удаления элемента кольца по его номеру: __delitem__ (листинг 12.2.6).
Шаг 7. Напишем метод поиска номера (индекса) элемента по его значению (лис-
тинг 12.2.7).
r = ring(["a","b","c"])
print(r)
N=15
i=0
for el in r:
print(el)
Программирование сложных коллекций 257
i=i+1
if i==N:
break
Для разных коллекций существуют задачи, которые наиболее удобно решить через
эту коллекцию. Рассмотрим задачу, которую хорошо решать с помощью кольца.
Задача 2
Эта задача имеет древнюю историю. Во время Иудейской войны еврейские солдаты
оказались заперты римлянами в пещере и решили покончить с собой, чтобы не сда-
ваться в плен. Они встали в круг и каждый второй убил следующего за ним —
и т. д. по кругу, пока не остался один живой. Среди солдат был историк Иосиф
Флавий. Он хорошо знал математику и не хотел умирать. Флавий успел сделать
расчет, чтобы оказаться последним выжившим. Пример такого круга с семью
людьми показан в табл. 12.1.
Прием программирования: коллекция «кольцо».
A
G B
Вот пример с семью людьми:
F C
E D
A
A убивает B, G B
C убивает D,
F C
Е убивает G
E D
A
G B
G убивает A,
C убивает E F C
E D
A
G B
G убивает C
F C
E D
258 Урок 12
Ход программирования
Шаг 1. Создадим кольцо людей (используем буквы вместо их имен):
...
r = ring(["a","b","c","d","e","f","g"])
print(r)
Шаг 2. Перебор людей по кругу будет идти до тех пор, пока в кольце людей боль-
ше одного, поэтому используем цикл while:
...
r = ring(["a","b","c","d","e","f","g"])
print(r)
while len(r)>1:
Листинг 12.2.9
...
r = ring(["a","b","c","d","e","f","g"])
print(r)
s=1
while len(r)>1:
if s==1:
print("жив",r[i])
else:
print("убит",r[i])
s=-s
Шаг 4. Введем индекс текущего человека i (листинг 12.2.10). В случае, если чело-
век остается жив, то индекс увеличивается на 1 (переход к следующему человеку).
Листинг 12.2.10
r = ring(["a","b","c","d","e","f","g"])
r.print()
s=1
i=0
while len(r)>1:
if s==1:
print("жив",r[i])
i=i+1
else:
print("убит",r[i])
s=-s
Программирование сложных коллекций 259
Листинг 12.2.11
r = ring(["a","b","c","d","e","f","g"])
print(r)
s=1
i=0
while len(r)>1:
if s==1:
print("жив",r[i])
i=i+1
else:
print("убит",r[i])
i=r.index(r[i])
del r[i]
s=-s
def index(self,v):
r=-1
for i in range(len(self)):
if self[i]==v:
r=i
break
return r
Результат
{'a': [1, 0, 0], 'bc': [1, 0, 1], 'def': [0, 1, 1]}
[1, 0, 1]
А так — нет:
V={[1,0,0]:"a", [1,0,1]:"bc", [0,1,1]:"def"}
Задача 1
Сделать хешируемый список.
Языковые конструкции: класс, хеш-функция.
Ход программирования
Шаг 1. Создадим класс hlist — обертку над списком (листинг 12.3.2).
Листинг 12.3.2
class hlist:
def __init__(self, a = []):
self.a = a
def __str__(self):
return str(self.a)
262 Урок 12
Шаг 2. Для того чтобы класс был хешируемым (его экземпляры можно было
использовать как ключи в словаре), нужно создать в нем два метода: __eq__ и
__hash__.
Метод __eq__ сравнивает два объекта и возвращает True, если они равны, и False,
если нет. Поскольку Python умеет сравнивать списки сам, код этого метода очень
простой:
class hlist:
...
def __eq__(self, other):
return (self.a == other.a)
Шаг 3. Разберемся с хеш-функцией __hash__. Это функция, которая для всех воз-
можных объектов возвращает разные числовые значения. Как же нам ее построить?
Рассмотрим это на примере хеш-функции:
Пусть наш список хранит только 0 и 1. Тогда мы можем построить хеш-функцию
так:
n −1
H ( L) = L0 ⋅ 20 + L1 ⋅ 21 + L2 ⋅ 22 + ... = ∑ Lii .
i =0
Приведем все списки длиной 3, вычислим для них хеш-функции и убедимся, что
она выдает ответ, различный для каждой (табл. 12.2).
Списки Хеш-функции
L=[0,0,0] H(L)=0*1+0*2+0*4=0
L=[0,0,1] H(L)=0*1+0*2+1*4=4
L=[0,1,0] H(L)=0*1+1*2+0*4=2
L=[0,1,1] H(L)=0*1+1*2+1*4=6
L=[1,0,0] H(L)=1*1+0*2+0*4=1
L=[1,0,1] H(L)=1*1+0*2+1*4=5
L=[1,1,0] H(L)=1*1+1*2+0*4=3
L=[1,1,1] H(L)=1*1+1*2+1*4=7
Это сработает только в случае, если список хранит 0 и 1. А что делать, если список
может хранить значения, отличные от 0 и 1? Вместо значения 2 нужно считать
основание степени равным количеству вариантов значений элемента списка. Вве-
дем в hlist переменную maxh, которая будет этим основанием.
Запрограммируем хеш-функцию и получим работающую программу (лис-
тинг 12.3.3).
Программирование сложных коллекций 263
v={hlist([1,0,1]):"a",hlist([1,1,1]):"b"}
print(v)
print(v[hlist([1,1,1])])
Результат
{<__main__.hlist object at 0x02B1E058>: 'a', <__main__.hlist object at 0x02B1E088>: 'b'}
b
Несмотря на то что при выводе всего словаря Python выдал что-то нечитаемое, все
же на поиск по ключу:
print(v[hlist([1,1,1])])
Задача 2
Написать программу, которая осуществит поиск в матрице квадрата наибольшего
размера, заполненного одинаковыми элементами.
Языковые конструкции: класс, хеш-функция, словарь, двумерный список, мно-
жество.
Приемы программирования: динамика по подотрезкам, мемоизация в словаре,
полиморфизм.
Ход программирования
Шаг 1. Заметим, что условие программы слегка отличается от родительской про-
граммы из разд. 8.2 (прочитайте его вновь, чтобы освежить в памяти алгоритм).
264 Урок 12
Листинг 12.3.4
f=True
for i in range (y,y+l):
for j in range (x,x+l):
if M[i][j]!=0:
f=False
break
if f==False:
break
if f==True:
return l
Листинг 12.3.6
M=[[0,0,1,0,1],
[1,0,0,0,0],
[1,1,0,0,0],
[0,0,0,0,0],
[0,0,1,1,0]]
def check(M,y,x,l):
L=[]
for row in M[y:y+l]:
L=L+list(set(row[x:x+l]))
return len(set(L))==1
Программирование сложных коллекций 265
print(square(M))
def check(M):
L=[]
for row in M:
L=L+row
return len(set(L))==1
def minor(M,y,x,l):
return [row[x:x+l] for row in M[y:y+l]]
print(square(M))
Шаг 3. Напишем класс «хешируемая матрица». Метод __eq__ прост (Python умеет
сравнивать списки списков). Метод __hash__ будет похож на то, что мы делали
в классе «хешируемый список». Отличие состоит в следующем: показатель степени
теперь зависит от индексов строки и столбца. Можно было бы соединить все стро-
ки в один список и от него вычислить хеш-функцию, но в нашей задаче в словарь
будут помещаться матрицы разного размера, и это может привести к потенциально
одинаковым ответам хеш-функции для разных матриц. Выход состоит в том, чтобы
ввести еще одну переменную внутри класса hmatrix — maxs, которую нужно устано-
вить равной значению размера самой большой матрицы, и вычислить показатель
степени так:
i*self.maxs+j
def check(M):
L=[]
for row in M:
L=L+row
return len(set(L))==1
def minor(M,y,x,l):
return [row[x:x+l] for row in M[y:y+l]]
V={}
def square (M):
HM=hmatrix(M)
if HM not in V:
if check(M):
V[HM]=len(M)
else:
V[HM]=max([square(minor(M,0,0,len(M)-1)),
square(minor(M,0,1,len(M)-1)),
square(minor(M,1,0,len(M)-1)),
square(minor(M,1,1,len(M)-1))])
return V[HM]
M=[[0,0,1,0,1],
[1,0,0,0,0],
[1,1,0,0,0],
[0,0,0,0,0],
[0,0,1,1,0]]
print(square(M))
Должен вам признаться, что именно после написания этой программы я решил
перейти на преподавание Python новичкам, которые хотят научиться программиро-
вать, — настолько просто и лаконично она делается на Python по сравнению с дру-
гими языками программирования. Да и дошли мы до нее всего за 12 уроков.
Мы использовали в ней: класс, адаптированный для словарей, динамическое про-
граммирование, словарь, множество — вполне достаточно для последней програм-
мы этой книги.
А И
Абстракция 206 Индексатор 232, 246, 254
Агрегация 212 Инкапсуляция 208
Алгоритм 14 Инкремент 32
Алгоритмическое мышление 31 Интроспекция 218
Альтернативные условия elif 22 Итератор 250, 255
Анонимная функция 168
Арифметическая прогрессия 199 К
Кеширование 129
Б Класс 206
Бесконечный цикл 44 Ключевое слово yield 201
Библиотека itertools 137 Комбинаторика 110
Божественная функция 169 Комментарий 27
Буфер обмена 31 Конструктор 209, 231
Конструкция join 82
Кортеж 33
В
Векторы 46 М
Ветвления 14
Магический квадрат 98
Внутренние переменные функции 110
Математические операторы 17
Матрица 88
Г Меандр 258
Генератор 199, 201 Мемоизация 129
Метод 207
Геометрическая прогрессия 199
Миноры 238
Множество 73
Д
Двойные равенства (неравенства) 28 Н
Двумерные списки 88
Накапливающаяся сумма 35
Декоратор 188, 191, 194, 248 Наследование 216
Дерево условий 22
Динамика по подотрезкам 139, 148
Динамическое программирование 139 О
Длина вектора 48 Обертка 166, 179
Обратная индексация списка 43
З Объект 206
Объектно-ориентированное
Замыкание 180 программирование 204, 231
Захват переменной 180 Оператор
Защита от дурака 26 ◊ «не равно» != 23
◊ break 70
272 Предметный указатель
С Х
Свойство 206 Хеш-функция 262
Синтаксический сахар 33
Скалярное произведение векторов 50
Словарь 79
Ц
Сложение матриц 95 Цикл 14, 35, 38
Сочетания 113
Список 41, 43
Среда разработки 10
Ч
Среднее арифметическое 34 Частичное применение функции 181
Срез 45, 58, 72 Числа Фибоначчи 119
Степень 126 Числовой тип данных 16
Степень подстановки 84
Стиль Python 48, 54
Строковый тип переменных 16
Ш
Структурное программирование 21 Шаг (step) 58
Счетчик 36
◊ со сбросом 40 Э
Т Экземпляр класса 206