Каппес Даниил Олегович

ПРАКТИКУМ PYTHON · ЗАНЯТИЕ 5

break и continue, чтение чужого кода, вложенные циклы

Цикл учится останавливаться и пропускать шаги; заодно разбираем чужие ошибки и ставим цикл внутрь цикла.

Чему научимся

break: выйти из цикла досрочно

Иногда продолжать цикл уже незачем: ответ найден. Оператор break прерывает ближайший цикл — for или while — и передаёт управление первой команде после него.

Вот проверка числа на простоту из прошлого занятия, но с break:

num = int(input())
flag = True

for i in range(2, num):
    if num % i == 0:
        flag = False
        break        # делитель нашёлся — дальше искать нечего

if flag == True:
    print('Число простое')
else:
    print('Число составное')

Как только встретился делитель, ответ уже известен: число составное. Без break цикл честно доработал бы до конца — для числа в миллион это миллион лишних проверок.

break прерывает цикл, а не программу: следующей выполнится команда, стоящая сразу за циклом.

У break есть и обратная сторона — с ним становится осмысленным бесконечный цикл. Программа, которая работает и принимает запросы, так и устроена:

while True:
    query = get_new_query()   # ждём очередной запрос
    query.process()          # обрабатываем его

И когда причин остановиться много, их проще выписать внутри тела, чем собирать все в заголовок цикла:

while True:
    if условие_1:
        break
    ...
    if условие_2:
        break

Если цикл бесконечный, убедитесь, что break в нём достижим. Иначе программа не остановится никогда — и это уже не приём, а ошибка.

continue: пропустить шаг

continue не выходит из цикла, а переходит к следующему шагу, не выполняя оставшуюся часть тела.

for i in range(1, 101):
    if i == 7 or i == 17 or i == 29:
        continue      # эти числа пропускаем
    print(i)

То же самое можно написать и через if с обратным условием — печатать, когда число не из списка. Разница в читаемости: когда исключений много, continue в начале тела избавляет от длинного отрицания и лишнего отступа.

5.1

Наименьший делитель

На вход подаётся число n > 1. Напишите программу, которая выводит его наименьший делитель, отличный от 1.

Используйте break при обнаружении делителя.

Формат входных данных
Одно натуральное число n > 1.
Формат выходных данных
Наименьший делитель, отличный от единицы.
Ввод 1
12
Вывод 1
2
Ввод 2
35
Вывод 2
5
Ввод 3
13
Вывод 3
13
Подсказка 1

Перебирать делители надо по возрастанию — тогда первый же найденный и будет наименьшим.

Подсказка 2

Само число тоже делитель: у простого числа других и нет, поэтому перебор должен доходить до n включительно.

Разбор
  1. Идём по числам от 2 до n включительно.
  2. Первое же, на которое n делится нацело, печатаем.
  3. И сразу выходим из цикла: остальные делители нас не интересуют.
Решение
n = int(input())

for d in range(2, n + 1):
    if n % d == 0:
        print(d)
        break        # первый найденный делитель и есть наименьший

Без break программа напечатала бы все делители подряд. Именно порядок перебора — от меньшего к большему — делает первый найденный ответом; будь перебор обратным, break дал бы наибольший делитель.

5.2

Числа с пропусками

На вход подаётся натуральное число n. Напишите программу, которая выводит числа от 1 до n включительно, за исключением:

  • чисел от 5 до 9 включительно;
  • чисел от 17 до 37 включительно;
  • чисел от 78 до 87 включительно.

Используйте continue.

Формат входных данных
Натуральное число n.
Формат выходных данных
Числа от 1 до n, кроме перечисленных, каждое на отдельной строке.
Ввод
10
Вывод
1
2
3
4
10
Подсказка 1

Каждый запрещённый участок — это цепочка сравнений вроде 5 <= i <= 9.

Подсказка 2

Число пропускается, если попало хотя бы в один участок, — значит, связка or.

Разбор
  1. Перебираем все числа от 1 до n.
  2. Если число попало в любой из трёх промежутков — continue, и печать не выполняется.
  3. Иначе печатаем.
Решение
n = int(input())

for i in range(1, n + 1):
    if 5 <= i <= 9 or 17 <= i <= 37 or 78 <= i <= 87:
        continue      # запрещённое число — сразу к следующему шагу
    print(i)

Без continue пришлось бы писать условие наоборот, целиком под not, и печатать внутри него. Так короче, а главное — видно, что именно исключается.

Блок else у цикла

У циклов while и for может быть необязательный блок else. Это редкая особенность Python — в большинстве языков её нет.

n = 5
while n > 0:
    n -= 1
    print(n)
else:
    print('Цикл завершён.')

Результат

4
3
2
1
0
Цикл завершён.

Зачем он нужен, если код можно просто написать после цикла? Разница ровно одна: блок else не выполняется, если цикл был прерван оператором break.

n = 5
while n > 0:
    n -= 1
    print(n)
    if n == 2:
        break
else:
    print('Цикл завершён.')

Результат

4
3
2

Гвидо ван Россум, создатель Python, признавался: проектируй он язык заново, от else у циклов он бы отказался. Знать про него стоит — чтобы понимать чужой код, — а писать самому лучше понятнее.

Читабельность кода

Код читают чаще, чем пишут, — и читают его люди. Чтобы он читался, придерживаются стандарта PEP 8:

  • отступы и пробелы — четыре пробела на уровень; никогда не смешивать пробелы и табуляцию;
  • названия переменных — говорящие (total, counter, product) и в стиле lower_case_with_underscores;
  • пустые строки — ими отделяют логически связанные куски: подготовку переменных, основной алгоритм, вывод результата;
  • комментарии — законченными предложениями и по делу.

Комментарий, который противоречит коду, хуже, чем его отсутствие. Меняете код — исправляйте и комментарий.

Подробнее о PEP 8 на русском: pythonworld.ru.

Следующие четыре задачи — ревью кода. Программист торопился и написал программу неправильно; нужно найти все ошибки. Это тот же навык, что и на экзамене: прочитать чужой код и понять, что он делает на самом деле, а не что задумывалось.

5.3

Ревью: сумма чётных

На обработку поступает последовательность из 7 целых чисел. Нужно написать программу, которая подсчитывает и выводит сумму всех чётных чисел последовательности или 0, если чётных чисел нет. Программист торопился и написал программу неправильно.

Найдите все ошибки в этой программе (их ровно 4). Каждая ошибка затрагивает только одну строку и может быть исправлена без изменения других строк.

Код с ошибками

s = 1
for i in range(1, 7):
    n = input()
    if i % 2 == 0:
        s = s + n
print(s)
Формат входных данных
Семь целых чисел, каждое на отдельной строке.
Формат выходных данных
Сумма чётных чисел или 0.
Ввод 1
1
2
3
4
5
6
7
Вывод 1
12
Ввод 2
1
3
5
7
9
11
13
Вывод 2
0
Подсказка 1

Проверьте по очереди: с чего начинается сумма, сколько чисел прочитается, какого типа прочитанное и что именно проверяется в условии.

Подсказка 2

Самая коварная ошибка — в условии: i — это номер шага, а чётность нужна у самого числа.

Разбор
  1. s = 1 — сумма обязана начинаться с нуля, иначе ответ всегда на единицу больше.
  2. range(1, 7) даёт шесть шагов, а чисел семь: нужен range(7).
  3. n = input() возвращает строку — сложение с ней даст ошибку; нужен int().
  4. if i % 2 == 0 проверяет чётность номера шага, а не числа: нужно n % 2 == 0.
Решение
s = 0                    # было s = 1: сумма начинается с нуля
for i in range(7):       # было range(1, 7): чисел семь, а не шесть
    n = int(input())     # было input(): нужно число, а не строка
    if n % 2 == 0:       # было i % 2: чётность проверяем у числа, а не у номера шага
        s = s + n
print(s)

Три ошибки из четырёх программа выдала бы сама — упала бы или выдала явную чушь. А вот i % 2 вместо n % 2 не ломает ничего: программа считает и печатает правдоподобное число. Такие ошибки находятся только чтением кода или проверкой на примере, где ответ известен заранее.

5.4

Ревью: произведение цифр

На обработку поступает натуральное число. Нужно написать программу, которая выводит на экран произведение цифр введённого числа. Программист торопился и написал программу неправильно.

Найдите все ошибки в этой программе (их ровно 2). Каждая ошибка затрагивает только одну строку.

Код с ошибками

n = input()
product = 1
while n >= 10:
    digit = n % 10
    product = product * digit
    n //= 10
print(product)
Формат входных данных
Одно натуральное число.
Формат выходных данных
Произведение его цифр.
Ввод 1
234
Вывод 1
24
Ввод 2
7
Вывод 2
7
Ввод 3
105
Вывод 3
0
Подсказка 1

Первая строка ломает программу сразу: сравнить строку с числом Python не сможет.

Подсказка 2

Возьмите число 234 и пройдите цикл по шагам. Все ли цифры попали в произведение?

Разбор
  1. n = input() даёт строку — нужен int().
  2. Условие n >= 10 обрывает цикл, когда осталась одна цифра, — и старшая цифра в произведение не попадает. У числа 234 получилось бы 12 вместо 24.
  3. Правильное условие — while n > 0: цикл доходит до последней цифры.
  4. Однозначное число при этом тоже обрабатывается верно — со старым условием цикл для него не выполнялся ни разу, и программа печатала единицу.
Решение
n = int(input())     # было input(): нужно число
product = 1
while n > 0:         # было n >= 10: так терялась старшая цифра
    digit = n % 10   # последняя цифра
    product = product * digit
    n //= 10         # отбрасываем её и идём дальше
print(product)

Проверять такие программы удобно на числе, у которого цифры разные и ответ считается в уме: 234 → 24. На числе 123 обе версии дают 6, и ошибка не видна — старшая цифра там единица, а умножение на единицу ничего не меняет.

5.5

Ревью: старшая цифра

На обработку поступает натуральное число. Нужно написать программу, которая выводит его первую слева (старшую) цифру. Программист торопился и написал программу неправильно.

Найдите все ошибки в этой программе (их ровно 2).

Код с ошибками

n = int(input())
while n > 0:
    n %= 10
print(n)
Формат входных данных
Одно натуральное число.
Формат выходных данных
Старшая цифра числа.
Ввод 1
5384
Вывод 1
5
Ввод 2
7
Вывод 2
7
Подсказка 1

Чтобы добраться до старшей цифры, число надо укорачивать справа. Какая операция это делает — остаток или деление нацело?

Подсказка 2

Останавливаться надо, пока цифра ещё осталась. При условии n > 0 цикл доработает до нуля — и напечатает ноль.

Разбор
  1. n %= 10 оставляет последнюю цифру, а нужно наоборот — отбросить её: n //= 10.
  2. С условием n > 0 цикл дойдёт до нуля, и вся первая цифра потеряется. Останавливаться надо, когда число стало однозначным: while n >= 10.
  3. После цикла в n остаётся ровно старшая цифра.
Решение
n = int(input())
while n >= 10:       # было n > 0: цикл доходил до нуля
    n //= 10         # было n %= 10: отбрасываем последнюю цифру
print(n)

Обратите внимание, что первая версия ещё и зацикливалась бы: n %= 10 у однозначного числа не меняет его вовсе, и условие n > 0 оставалось бы истинным вечно. Две ошибки здесь чинят друг друга по-разному, поэтому искать их надо обе.

5.6

Ревью: неотрицательные числа

На обработку поступает последовательность из 10 целых чисел. Нужно написать программу, которая выводит количество неотрицательных чисел последовательности и их произведение. Если неотрицательных чисел нет, требуется вывести «NO». Программист торопился и написал программу неправильно.

Найдите все ошибки в этой программе (их ровно 4).

Код с ошибками

count = 0
p = 0
for i in range(1, 10):
    x = int(input())
    if x > 0:
        p = p * x
        count = count + 1
if count > 0:
    print(x)
    print(p)
else:
    print('NO')
Формат входных данных
Десять целых чисел, каждое на отдельной строке.
Формат выходных данных
Количество неотрицательных чисел и их произведение — каждое на отдельной строке, либо «NO».
Ввод 1
1
2
3
4
5
6
7
8
9
10
Вывод 1
10
3628800
Ввод 2
-1
-2
-3
-4
-5
-6
-7
-8
-9
-10
Вывод 2
NO
Ввод 3
0
-1
-2
-3
-4
-5
-6
-7
-8
-9
Вывод 3
1
0
Подсказка 1

Две ошибки — арифметические (с чего начинается произведение и сколько чисел читается), две — смысловые (что считать неотрицательным и что печатать).

Подсказка 2

«Неотрицательное» — это не то же самое, что «положительное»: ноль тоже неотрицателен.

Разбор
  1. p = 0 — произведение с нуля так и останется нулём; начальное значение должно быть 1.
  2. range(1, 10) даёт девять шагов, а чисел десять: нужен range(10).
  3. if x > 0 пропускает ноль, а он неотрицателен: нужно x >= 0.
  4. print(x) печатает последнее прочитанное число вместо количества: нужно print(count).
Решение
count = 0
p = 1                    # было p = 0: произведение начинается с единицы
for i in range(10):      # было range(1, 10): чисел десять
    x = int(input())
    if x >= 0:           # было x > 0: ноль тоже неотрицателен
        p = p * x
        count = count + 1

if count > 0:
    print(count)         # было print(x): печатать надо количество
    print(p)
else:
    print('NO')

Третий пример проверяет как раз спорное место: единственный ноль в наборе — это одно неотрицательное число и произведение, равное нулю. Программа с x > 0 ответила бы «NO» и была бы неправа.

Вложенные циклы

Вложенный цикл — это цикл внутри цикла. Понятнее всего он на часах: секундная стрелка делает 60 шагов на один шаг минутной, а минутная — 60 шагов на один шаг часовой.

for hours in range(24):
    for minutes in range(60):
        for seconds in range(60):
            print(hours, ':', minutes, ':', seconds)

Отсюда три правила, которые стоит помнить:

  • внутренний цикл выполняет все свои шаги на каждом шаге внешнего;
  • внутренние циклы заканчиваются раньше внешних;
  • общее число шагов — это произведение: 24 × 60 × 60.

break и continue действуют на ближайший цикл, а не на все сразу:

for i in range(3):
    for j in range(3):
        if i == j:
            break
        print(i, j)

Результат

1 0
2 0
2 1

Тот же код с continue вместо break даёт уже шесть строк — пропускается только один шаг, а не весь внутренний цикл.

Классический пример вложенных циклов — фигуры из символов. Внешний цикл отвечает за строки, внутренний — за то, что печатается в строке:

for i in range(8):
    for j in range(i + 1):
        print('*', end='')
    print()

Результат

*
**
***
****
*****
******
*******
********

print() с пустыми скобками в конце внешнего цикла — это перевод строки. Без него все звёздочки напечатались бы в одну строку: end='' во внутреннем цикле отменил перевод.

5.7

Таблица из одного числа

Дано натуральное число n (n ≤ 9). Напишите программу, которая печатает таблицу размером n × 3, состоящую из этого числа. Числа отделяются одним пробелом.

Воспользуйтесь вложенным циклом.

Формат входных данных
Одно натуральное число n.
Формат выходных данных
n строк по три числа.
Ввод
5
Вывод
5 5 5
5 5 5
5 5 5
5 5 5
5 5 5
Подсказка 1

Внешний цикл считает строки, внутренний — числа в строке.

Подсказка 2

Чтобы числа печатались в одну строку, нужен end=' ', а перевод строки — отдельной командой print() после внутреннего цикла.

Разбор
  1. Внешний цикл повторяется n раз — по числу строк.
  2. Внутренний три раза печатает n с пробелом вместо перевода строки.
  3. После внутреннего цикла — пустой print(), чтобы перейти на новую строку.
Решение
n = int(input())

for i in range(n):          # строки
    for j in range(3):      # числа в строке
        print(n, end=' ')
    print()                 # перевод строки после каждой строки таблицы

Отступ у последнего print() — ровно то, что отличает таблицу от одной длинной строки. Он стоит на уровне внутреннего цикла, то есть выполняется один раз за строку, а не после каждого числа.

5.8

Таблица номеров строк

Дано натуральное число n (n ≤ 9). Напишите программу, которая печатает таблицу размером n × 5, где в i-й строке напечатано число i. Числа отделяются одним пробелом.

Формат входных данных
Одно натуральное число n.
Формат выходных данных
n строк по пять чисел.
Ввод
3
Вывод
1 1 1 1 1
2 2 2 2 2
3 3 3 3 3
Подсказка 1

Отличие от предыдущей задачи только в том, что печатается: не n, а номер строки.

Подсказка 2

Чтобы номера шли с единицы, задайте range(1, n + 1).

Разбор
  1. Внешний цикл идёт по номерам строк от 1 до n.
  2. Внутренний пять раз печатает этот номер.
  3. Перевод строки — после внутреннего цикла.
Решение
n = int(input())

for i in range(1, n + 1):   # номер строки — он же печатаемое число
    for j in range(5):
        print(i, end=' ')
    print()

Здесь впервые пригодилась переменная внешнего цикла: в прошлой задаче i был просто счётчиком повторений, а тут он попал в вывод.

5.9

Звёздный треугольник

Дано нечётное натуральное число n. Напишите программу, которая печатает равнобедренный звёздный треугольник с основанием, равным n: строки сначала растут, потом убывают.

Формат входных данных
Нечётное натуральное число n.
Формат выходных данных
Треугольник из звёздочек.
Ввод 1
5
Вывод 1
*
**
***
**
*
Ввод 2
7
Вывод 2
*
**
***
****
***
**
*
Подсказка 1

Всего строк n, и самая длинная — посередине. Сколько в ней звёздочек?

Подсказка 2

У основания 5 середина равна 3, у основания 7 — 4. Это (n + 1) // 2.

Разбор
  1. Считаем высоту пика: (n + 1) // 2.
  2. Первый цикл печатает строки от одной звёздочки до пика.
  3. Второй — от «пик минус один» обратно до одной.
Решение
n = int(input())
top = (n + 1) // 2          # самая длинная строка — посередине

for i in range(1, top + 1):     # растём
    print('*' * i)

for i in range(top - 1, 0, -1): # и убываем, не повторяя пик
    print('*' * i)

Вложенный цикл здесь не нужен вовсе: умножение строки на число заменяет внутренний цикл целиком. Два цикла подряд — это не «два раза одно и то же», а две разные части фигуры.

5.10

Таблица сложения для числа

Дано натуральное число n (n ≤ 9). Напишите программу, которая печатает таблицу сложения этого числа со всеми числами от 1 до 9 — по образцу.

Формат входных данных
Одно натуральное число n.
Формат выходных данных
Девять строк вида 1 + 1 = 2.
Ввод
1
Вывод
1 + 1 = 2
1 + 2 = 3
1 + 3 = 4
1 + 4 = 5
1 + 5 = 6
1 + 6 = 7
1 + 7 = 8
1 + 8 = 9
1 + 9 = 10
Подсказка 1

Строк ровно девять — по числу вторых слагаемых.

Подсказка 2

Знаки + и = — это текст в кавычках; пробелы вокруг них print() поставит сам.

Разбор
  1. Цикл идёт по второму слагаемому: от 1 до 9.
  2. На каждом шаге печатаем пять значений: n, знак, слагаемое, знак равенства и сумму.
Решение
n = int(input())

for j in range(1, 10):
    print(n, '+', j, '=', n + j)

Сумму считаем прямо в print() — заводить для неё переменную незачем. А вот пробелы вокруг знаков лучше не дописывать руками: print() уже разделяет значения пробелом.

5.11

Таблица сложения целиком

Дано натуральное число n (n ≤ 9). Напишите программу, которая печатает таблицу сложения для всех чисел от 1 до n. Блоки отделяются друг от друга пустой строкой.

Формат входных данных
Одно натуральное число n.
Формат выходных данных
n блоков по девять строк, между блоками — пустая строка.
Ввод
2
Вывод
1 + 1 = 2
1 + 2 = 3
1 + 3 = 4
1 + 4 = 5
1 + 5 = 6
1 + 6 = 7
1 + 7 = 8
1 + 8 = 9
1 + 9 = 10

2 + 1 = 3
2 + 2 = 4
2 + 3 = 5
2 + 4 = 6
2 + 5 = 7
2 + 6 = 8
2 + 7 = 9
2 + 8 = 10
2 + 9 = 11
Подсказка 1

Это предыдущая задача, обёрнутая ещё одним циклом.

Подсказка 2

Пустая строка между блоками — это print() без аргументов после внутреннего цикла.

Разбор
  1. Внешний цикл идёт по первому слагаемому: от 1 до n.
  2. Внутренний — по второму: от 1 до 9.
  3. После внутреннего цикла печатаем пустую строку, отделяя блок от следующего.
Решение
n = int(input())

for i in range(1, n + 1):       # первое слагаемое
    for j in range(1, 10):      # второе
        print(i, '+', j, '=', i + j)
    print()                     # пустая строка между блоками

Всего строк получится 9n плюс разделители — вот и вся арифметика вложенных циклов: шаги перемножаются. При n = 9 таблица займёт 81 строку.

5.12

Численный треугольник

Дано натуральное число n. Напишите программу, которая печатает численный треугольник: в первой строке одна единица, во второй две двойки, в третьей три тройки и так далее.

Формат входных данных
Одно натуральное число n.
Формат выходных данных
n строк: в i-й строке число i, повторённое i раз.
Ввод
5
Вывод
1
22
333
4444
55555
Подсказка 1

Повторить символ несколько раз умеет умножение строки на число.

Подсказка 2

Но умножать надо строку, а не число: 5 * 5 даст 25, а не «55555».

Разбор
  1. Цикл идёт по номерам строк от 1 до n.
  2. Превращаем номер в строку функцией str().
  3. Умножаем эту строку на номер и печатаем.
Решение
n = int(input())

for i in range(1, n + 1):
    print(str(i) * i)       # str(i) — строка, поэтому умножение повторяет её

Тут видна разница между числом и строкой из тех же цифр: str(3) * 3 — это «333», а 3 * 3 — это 9. Одна и та же запись * делает два совершенно разных дела в зависимости от типа.

Итог занятия

Главное с пятого занятия. break прерывает ближайший цикл, continue пропускает шаг — оба действуют только на свой цикл, а не на все вложенные. Во вложенных циклах внутренний проходит полностью на каждом шаге внешнего, а общее число шагов перемножается. И главное умение занятия — читать код: три ошибки из четырёх программа покажет сама, а четвёртую находят только чтением и проверкой на примере, где ответ известен заранее.