Курс по Python: https://stepik.org/course/100707
Мы продолжаем тему
генераторов списков языка Python. На прошлом занятии мы увидели,
как можно формировать списки, используя конструкции:
[<способ
формирования значения> for <счетчик> in <итерируемый объект>]
и
[<способ
формирования значения>
for <счетчик> in <итерируемый объект>
if <условие>
]
Но, в
действительности, Python позволяет записывать любое число циклов for в генераторах
списков. Здесь операторы if после циклов
являются необязательными (мы их можем прописывать, а можем и пропускать).
Как всегда,
работу таких вложенных циклов лучше всего увидеть на примере. В самом простом
варианте, мы сформируем просто пары чисел в списке, следующим образом:
a = [(i, j) for i in range(3) for j in range(4)]
print(a)
Для большей
наглядности запишем генератор списка в несколько строк:
a = [(i, j)
for i in range(3)
for j in range(4)
]
Вот отсюда уже
гораздо лучше видно, что второй цикл вложен в первый. То есть, сначала при i = 0
отрабатывает внутренний цикл по j от 0 до 3. Затем, переходим к первому
циклу, i увеличивается
на 1 и внутренний цикл повторяется. В результате, получаем пары чисел:
[(0, 0), (0, 1),
(0, 2), (0, 3), (1, 0), (1, 1), (1, 2), (1, 3), (2, 0), (2, 1), (2, 2), (2, 3)]
То есть, здесь у
нас происходит работа двух вложенных циклов. Дополнительно, мы можем указывать
условия для каждого из них, например, так:
a = [(i, j)
for i in range(3) if i % 3 == 0
for j in range(4)
]
Или, у обоих
вместе:
a = [(i, j)
for i in range(3) if i % 3 == 0
for j in range(4) if j % 2 != 0
]
Используя этот
подход, мы можем, например, сформировать таблицу умножения:
a = [f"{i}*{j} = {i*j}"
for i in range(1, 4)
for j in range(1, 4)
]
Или, двумерный
список превратить в одномерный:
matrix = [[0, 1, 2, 3],
[10, 11, 12, 13],
[20, 21, 22, 23]]
a = [x
for row in matrix
for x in row
]
Обратите
внимание, мы во втором цикле используем переменную row из первого
цикла. Это вполне допустимая операция, т.к. второй цикл вложен в первый и в нем
доступны все переменные, объявленные ранее.
Вложенные генераторы списков
Давайте еще раз
посмотрим на исходное определение генератора списка:
[<оператор>
for <счетчик> in <итерируемый объект>]
Как я уже
отмечал, в качестве оператора можно записывать любую конструкцию языка Python. А раз так, то
кто нам мешает прописать здесь еще один генератор:
[[генератор списка]
for <переменная> in <итерируемый объект>
]
В результате,
получим вложенный генератор списка. Давайте посмотрим на примере, как это будет
работать:
M, N = 3, 4
matrix = [[a for a in range(M)] for b in range(N)]
print(matrix)
Получаем
двумерный список, вида:
[[0, 1, 2], [0, 1, 2], [0, 1, 2], [0, 1, 2]]
Результат вполне
очевиден. Смотрите, сначала отрабатывает первый (внешний) генератор списка и
переменная b = 0. Затем,
выполнение переходит к вложенному генератору, который выдает список [0, 1, 2].
Этот список помещается как первый элемент основного списка. Далее, снова
отрабатывает первый генератор и b принимает значение 1. После этого
переходим к вложенному генератору, который возвращает такой же список [0, 1, 2].
И так пока не закончится работа первого генератора. В итоге, видим список, в
который вложены четыре других списка.
Где может
пригодиться такой подход? Например, для изменения значений двумерного списка.
Давайте предположим, что у нас есть вот такой список:
A = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
И мы хотим
возвести все его значения в квадрат. Лучше всего это сделать именно через
генератор, следующим образом:
A = [[x ** 2 for x in row] for row in A]
В первом
генераторе происходит перебор строк (вложенных списков) матрицы A, а во вложенном
генераторе – обход элементов строк матрицы. Каждое значение возводится в
квадрат и на основе этого формируется текущая строка. Обратите внимание, что во
вложении мы можем использовать переменные из внешнего генератора списка, в
частности, переменную row, ссылающуюся на текущую строку матрицы A.
Другой пример –
это транспонирование матрицы A (то есть, замена строк на столбцы) с
использованием вложенных генераторов. Сделать это можно, следующим образом:
A = [[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12]]
A = [[row[i] for row in A] for i in range(len(A[0]))]
Поясню работу
этой конструкции. Сначала значение i = 0, а переменная row[i] пробегает
первые значения строк матрицы A. В результате формируется первая строка
транспонированной матрицы. Далее, i увеличивается на 1 и row[i] пробегает уже
вторые элементы строк матрицы A. Получаем вторую строку
транспонированной матрицы. И так делаем для всех столбцов. На выходе
формируется транспонированная матрица.
С таким типом
вложений, я думаю, в целом все должно быть понятно. Другой вариант, когда мы
список помещаем в качестве итерируемого объекта. Да, сам по себе генератор
списка поддерживает механизм итерирования – перебора элементов через итератор,
поэтому может быть использован в операторе for, например, так:
g = [u ** 2 for u in [x+1 for x in range(5)]]
Здесь сначала отрабатывает
вложенный генератор списка, получаем список [1, 2, 3, 4], а затем, эти значения
перебираются первым генератором и возводятся в квадрат, получаем результат:
[1, 4, 9, 16, 25]
Это очень похоже
на вычисление значений сложной (вложенной) функции:
g(u(x+1)) = (x+1) ^ 2
Вот такие
варианты вложений генераторов списков, а также их комбинации, можно
использовать в Python. Для закрепления этого материала
пройдите практические задания и жду всех вас на следующем уроке.
Курс по Python: https://stepik.org/course/100707