Вложенные циклы. Примеры задач с вложенными циклами

Курс по Python: https://stepik.org/course/100707

Смотреть материал на YouTube | RuTube

На предыдущих занятиях мы с вами в деталях познакомились с работой операторов циклов for и while. На этом занятии сделаем следующий важный шаг и узнаем, как реализуются и работают вложенные циклы.

Само это название уже говорит, что один оператор цикла можно вложить в другой. Это могут быть два цикла for или два цикла while или смешанные варианты. Давайте вначале разберемся, как работают эти конструкции. Принцип у всех един, поэтому, для простоты, я возьму два цикла for (один вложен в другой). Эти циклы будут просто пробегать диапазоны чисел, первый от 1 до 3, а второй – от 1 до 5:

for i in range(1, 4):
    for j in range(1, 6):
        print(f"i = {i}, j = {j}", end=' ')
    print()

Во втором вложенном цикле мы будем выводить значения i и j в строку без перехода на новую строчку. А после завершения работы вложенного цикла вызовем функцию print(), как раз, для перевода курсора на новую строку. В результате выполнения этой программы, мы получим таблицу значений переменных i и j.

Почему получились именно такие значения? Вначале у нас счетчик i принимает значение 1, а счетчик j пробегает числа от 1 до 5, в итоге получаем первую строку. После завершения вложенного цикла, срабатывает функция print() и курсор переходит на новую строку. После этого переходим ко второй итерации первого цикла и i = 2. Счетчик j снова проходит значения от 1 до 5 и получаем вторую строку. На следующей итерации первого цикла i = 3, j проходит от 1 до 5 и получаем третью строку. То есть, у нас вложенный цикл for трижды запускался заново и каждый раз j изменялось от 1 до 5. Это и есть принцип работы вложенного цикла – на каждой итерации он отрабатывает снова и снова, пока не завершится первый цикл.

Теперь второй вопрос – зачем все это нужно? Давайте представим, что у нас есть вложенный (двумерный) список:

a = [[1, 2, 3, 4], [2, 3, 4, 5], [3, 4, 5, 6]]

(О таких списках мы с вами уже говорили и вам здесь все должно быть понятно). Так вот, если мы будем перебирать его элементы с помощью одного оператора цикла for:

for row in a:
    print(row, type(row))

То переменная row будет ссылаться сначала на первый вложенный список, затем, на второй и потом на третий. Но, так как row ссылается на список, то есть, на итерируемый объект, то нам ничто не мешает перебрать его элементы с помощью второго, вложенного цикла for:

for row in a:
    for x in row:
        print(x, type(x), end=' ')
    print()

Как видите, теперь в консоль выводятся числа типа int, то есть, мы обращаемся непосредственно к элементам этого двумерного списка.

Ну, хорошо, а все-таки, зачем это может быть нужно? Например, так можно выполнить сложение значений из двух одинаковых двумерных списков:

a = [[1, 2, 3, 4], [2, 3, 4, 5], [3, 4, 5, 6]]
b = [[1, 1, 1, 1], [2, 2, 2, 2], [3, 3, 3, 3]]

И сформировать на их основе третий список:

c = []

следующим образом:

for i, row in enumerate(a):
    r = []
    for j, x in enumerate(row):
        r.append(x + b[i][j])
 
    c.append(r)
 
print(c)

Я здесь воспользовался еще одной уже знакомой нам функцией enumerate(), которая возвращает индекс и значение текущего элемента. Это удобно для реализации данной программы. Внутри первого цикла мы каждый раз создаем новый пустой список и с помощью метода append() добавляем в его конец новый элемент как сумму значений из списков a и b. Полученную строку (список r) мы, затем, добавляем в основной список c. Так вычисляется сумма значений элементов двух одинаковых списков a и b.

Как видите, для реализации данной программы нам потребовался вложенный оператор цикла for. И это лишь один маленький пример. Другой пример, пусть у нас имеется текст, представленный в виде списка:

t = ["– Скажи-ка,  дядя, ведь не даром",
    "Я Python выучил с   каналом",
    "Балакирев что    раздавал?",
    "Ведь были  ж заданья боевые,",
    "Да, говорят,  еще какие!",
    "Недаром помнит    вся Россия",
    "Как мы рубили   их тогда!"
]

Здесь в строках присутствуют два и более пробелов. Наша задача удалить их и оставить только один. Сделаем это с помощью вложенных циклов. В первом цикле for будем перебирать строки – элементы списка, а во втором (вложенном) цикле while удалять лишние пробелы:

for i, line in enumerate(t):
    while line.count('  '):
        line = line.replace('  ', ' ')
 
    t[i] = line
 
print(t)

В качестве условия цикла мы здесь вызываем метод count(), который подсчитывает число фрагментов из двух пробелов подряд. Как только их станет 0 – это будет означать False и цикл завершится. Преобразованная строка становится новым i-м элементом списка и в конце результат выводим в консоль.

Следующий пример. Предположим, вначале мы формируем вложенный список размером M x N элементов. Причем, M, N вводим с клавиатуры. Вначале сформируем список, состоящий из всех нулей:

M, N = list(map(int, input("Введите M и N: ").split()))
 
zeros = []
for i in range(M):
    zeros.append([0]*N)
 
print(zeros)

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

for i in range(M):
    for j in range(N):
        zeros[i][j] = 1

Как видите, в целом, все достаточно просто.

И последний пример. Пусть у нас имеется квадратный список (размерности совпадают):

A = [[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12], [13, 14, 15, 16]]

Необходимо поменять строки на столбцы и получить новое представление этого же списка. Для этого достаточно поменять местами элементы, стоящие выше главной диагонали с элементами, стоящими ниже главной диагонали. То есть, у нас счетчик i будет меняться от 0 до 3, а счетчик j от i+1 до 3. Затем, соответствующие элементы будем менять между собой:

for i in range(len(A)):
    for j in range(i+1, len(A)):
        A[i][j], A[j][i] = A[j][i], A[i][j]
 
for r in A:
    for x in r:
        print(x, end='\t')
    print()

Как видите, у нас получилось нужно преобразование. В математике это называется транспонированием матрицы.

Конечно, уровень вложенности операторов циклов может быть еще больше и два и три и четыре. Однако, на практике слишком большого вложения следует избегать, так как программа становится менее читаемой и гибкой для дальнейшего изменения. Лучше ограничиваться тремя подряд идущими во вложении операторами циклов, не более.

Для закрепления, как всегда, пройдите практические задания и, затем, переходите к следующему уроку.

Курс по Python: https://stepik.org/course/100707

Видео по теме