EduBrick
классная работа

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

Программирование на Python: от нуля до олимпиад

0/18решено 0 из 18до зачёта осталось 10

Чему научитесь

  • Строить таблицы и фигуры из символов
  • Перебирать пары и не считать одну и ту же дважды
  • Замечать, когда внутренний цикл можно заменить вычислением
  • Прикидывать количество операций до того, как решение отправлено

Как устроено занятие

Сначала разбор с примерами и короткими проверками понимания — они нужны, чтобы поймать непонятое сразу, а не через три темы.

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

После занятия — вторая часть, ещё 15 задач на те же приёмы в новых сюжетах.

Сколько это займёт

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

теория

Цикл внутри цикла

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

Так и пишут: цикл внутри цикла.

for i in range(1, 4):        # строки
    for j in range(1, 4):    # столбцы, внутри каждой строки
        print(i * j, end=" ")
    print()                  # перевод строки — после внутреннего цикла

Результат:

1 2 3
2 4 6
3 6 9

Внутренний цикл проходит полностью на каждом шаге внешнего. Внешний сделал три шага, внутренний на каждом — ещё три, итого девять печатей.

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

И переменные должны быть разными. Написать for i внутри for i — это испортить счётчик внешнего цикла, а ошибку такого рода потом ищут долго.

теория

Строки, столбцы, фигуры

Любая картинка из символов строится одинаково: внешний цикл идёт по строкам, внутренний решает, что стоит в каждой клетке.

n = int(input())

for i in range(n):
    row = ""                 # строка собирается в переменную
    for j in range(n):
        if i == j:
            row += "*"
        else:
            row += "."
    print(row)               # и печатается целиком

Собирать строку в переменную удобнее, чем печатать по символу: видно, что именно получилось, и не остаётся лишних пробелов в конце.

Вся разница между фигурами — в условии внутри:

фигура условие для звёздочки
диагональ i == j
шахматная доска (i + j) % 2 == 0
рамка i == 0 or j == 0 or i == n - 1 or j == m - 1
крест i == середина or j == середина

Там, где строки разной длины — треугольник, ромб, — внутренний цикл вообще не нужен: " " * k + "*" * m строит строку сразу. Главное посчитать, сколько чего в строке номер ii.

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

тест

Проверка: сколько раз

Сколько раз выполнится строка print?

for i in range(5):
    for j in range(3):
        print(i, j)
Войдите, чтобы ответить.
теория

Перебор пар

Второе применение вложенных циклов — задачи вида «сколько пар обладают свойством».

count = 0

for i in range(1, n + 1):
    for j in range(1, n + 1):
        if i + j == s:
            count += 1

Здесь важно понять, что считается парой. Если (1,2)(1, 2) и (2,1)(2, 1) — разные пары, перебираются все сочетания, как выше. Если это одна и та же пара, внутренний цикл начинают не с единицы:

for i in range(1, n + 1):
    for j in range(i + 1, n + 1):     # только j больше i

Такой перебор даёт ровно каждую пару по одному разу и заодно работает вдвое быстрее. Условие всегда говорит, какой вариант нужен, — читайте эту фразу внимательно, на ней теряют больше всего попыток.

Когда внутренний цикл не нужен

Часто второе число определяется первым однозначно. В задаче про сумму i+j=si + j = s достаточно перебрать ii и проверить, попадает ли j=sij = s - i в допустимые границы:

for i in range(1, n + 1):
    j = s - i
    if 1 <= j <= n:
        count += 1

Вместо n2n^2 шагов получается nn. Приём общий: если внутренний цикл ищет то, что можно вычислить, — вычисляйте.

теория

Сколько это операций

Двойной цикл — первое место в курсе, где верное решение может не пройти. Не из-за ошибки, а из-за объёма: при n=100000n = 100\,000 перебор всех пар — это десять миллиардов шагов.

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

вложенность шагов при n = 1000 при n = 100 000
один цикл 1 000 100 000
два цикла 1 000 000 10 000 000 000
три цикла 1 000 000 000 не считается

Ориентир для Python: до десяти миллионов простых операций укладываются в обычные пару секунд, сто миллионов — уже нет. Другие языки быстрее в десятки раз, но порядок величин тот же.

Отсюда практическое правило: увидев в условии nn до тысячи, двойной цикл писать можно. Увидев nn до ста тысяч — нельзя, нужно искать способ обойтись одним.

Это не строгая теория — до оценок сложности мы дойдём в шестом модуле. Пока достаточно счёта на пальцах: перемножьте длины циклов и сравните с десятью миллионами.

расчёт

Проверка: сколько пар

Сколько пар (i,j)(i, j) переберёт этот цикл при n=100n = 100?

for i in range(1, n + 1):
    for j in range(i + 1, n + 1):
        ...

Введите целое число.

Войдите, чтобы ответить.
теория

Три ошибки этого занятия

Одна переменная на два цикла. for i внутри for i ломает внешний счётчик. Признак: строк напечаталось меньше, чем должно, или цикл повёл себя необъяснимо.

Перевод строки не на месте. Пустой print() внутри внутреннего цикла — каждый символ на своей строке; отсутствие его вовсе — всё в одну строку. Само по себе это не заметно в коде, зато сразу видно в выводе.

Накопитель строки не обнуляется. row = "" должно стоять в начале каждой строки, то есть внутри внешнего цикла и до внутреннего. Вынесете выше — вся фигура склеится в одну длинную строку.

Как проверять себя

  • самый маленький размер: n=1n = 1, а для прямоугольников — 1×11 \times 1, 1×m1 \times m и n×1n \times 1. Узкие случаи ломают рамку и шахматную доску чаще всего;
  • несимметричный размер: 3×73 \times 7 и 7×37 \times 3 должны дать разные фигуры. Если одинаковые — где-то перепутаны nn и mm;
  • счёт шагов: перемножьте длины циклов до отправки.
теория

Практика: пятнадцать задач

Лестница прежняя: пять разминочных, семь основных, три со звёздочкой. Зачёт при десяти решённых.

Задачи трёх видов: фигуры из символов, таблицы чисел и подсчёт пар. В фигурах внимательно относитесь к пробелам — они проверяются наравне со звёздочками.

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

лестница задач
развёрнутый ответ

Таблица, которая печатается в столбик

Задача: «выведите таблицу умножения nn на nn — по строке на каждое ii».

Ученик написал:

n = int(input())

for i in range(1, n + 1):
    for j in range(1, n + 1):
        print(i * j, end=" ")
        print()

Вместо таблицы получается столбик из одного числа в строке. Объясните, почему так выходит, где должна стоять строка print() и сколько всего чисел напечатает программа при n=4n = 4.

Войдите, чтобы ответить.