MyTetra Share
Делитесь знаниями!
List comprehensions (LC)
Время создания: 31.08.2017 21:00
Раздел: Python - Patterns
Запись: xintrea/mytetra_db_mcold/master/base/1503493888pjgtd26bvh/text.html на raw.githubusercontent.com


Простейший синтаксис LC таков:

[ expression(a) for a in x ]



где 
x — список, a — элемент списка, а expression(a) — некоторое выражение, в котором обычно участвует a. LC — это выражение, и его результатом является список. В смысловом плане вышеописанное LC соответствует функции map следующего вида:

map(lambda a: expression(a), x)



Далее, перед самой последней квадратной скобочкой может стоять еще ветка 
if:

[ expression(a) for a in x if condition(a) ]



Как вы догадались, это аналог 
filter. При помощи функций мы можем переписать это выражение следующим образом:

map(lambda a: expression(a),

filter(lambda a: condition(a), x))



Для 
reduce синтаксического аналога нет, т.к. основная, первичная цель LC — конструирование списков. Стоит отметить еще один интересный момент: функция map может принимать несколько списков. В этом случае каждый раз при вызове функции-преобразователя ей будет передаваться несколько аргументов: первый аргумент будет значением текущего элемента из первого списка, второй аргумент — значением текущего элемента второго списка и т.д.:

map(

lambda a1, a2, a3: a1 + a2 + a3,

[1, 2, 3],

[4, 5, 6],

[7, 8, 9])



В LC присутствует, казалось бы, похожая конструкция:

[ expression(a1, a2) for a1 in x1, for a2 in x2 ]



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

[ a1 + a2 for a1 in [ 'a', 'b', 'c'] for a2 in ['x', 'y'] ]



=> ['ax', 'ay', 'bx', 'by', 'cx', 'cy']



На практике, LC удобно применять для простых, невложенных операций, вроде получения квадратов чисел от 1 до 10. В иных случаях (см. сложный пример ниже) лучше использовать функции.

Простые примеры



Код ко всем примерам данного поста смотрите ниже. Давайте возьмем список следующего вида:

x = [ 2, 3, 4, 5, 7, 5 ]



Начнем с чего-нибудь простого; например, возведем все элементы списка в квадрат:

map(lambda a: a ** 2, x)


# то же самое, но при помощи LC

[ a ** 2 for a in x ]



=> [4, 9, 16, 25, 49, 25]



Теперь применим фильтрацию — отсеем все четные числа:

filter(lambda a: a % 2 == 1, x)


# то же самое, но при помощи LC

[ a for a in x if a % 2 == 1 ]



=> [3, 5, 7, 5]



Теперь скомбинируем — выведем нечетные квадраты чисел списка:

filter(lambda a: a % 2 == 1,

map(lambda a: a ** 2,

x))


# то же самое, но при помощи LC

[ a ** 2 for a in x if a ** 2 % 2 == 1 ]



=> [9, 25, 49, 25]



Как видите, в первом случае сначала производим отображение списка, а затем — фильтрацию результата. Теперь поиграем с 
reduce. Для начала выведем сумму всех чисел списка:

reduce(lambda a, b: a + b, x, 0)



=> 26



Первый параметр, как вы уже поняли, это функция-редуктор (в данном случае, сумматор). Второй параметр — это наш список, и третий — начальное значение, или инициализатор. Чтобы показать важность правильного выбора инициализатора, приведем тот же пример, но для умножения:

reduce(lambda a, b: a * b, x, 0)



=> 0



Здесь мы получили 0. И ведь правильно: получается, выполняется следующее выражение: ((((((0 * 2) * 3) * 4) * 5) * 7) * 5). Исправим этот пример, установив значение инициализатора в единицу:

reduce(lambda a, b: a * b, x, 1)



=> 4200



Теперь мы получим корректное значение. Теперь попробуем получить максимальное значение из списка:

reduce(lambda a, b: max(a, b), x)



=> 7



Здесь уже нет инициализатора. Когда инициализатор не указан, 
reduce подставляет на его место None. Работу этого кода легче всего пояснить визуально:



И наконец, возьмем задачу обращения списка посредством свертки. Напомню, у нас имеется список чисел 2, 3, 4, 5, 7, 5. Обращенный список будет таков: 5, 7, 5, 4, 3, 2. Давайте расставим скобочки, чтобы увидеть, какую операцию нам нужно будет применить в функции-редукторе: (5, (7, (5, (4, (3, (2)). Очевидно, что это операция добавления каждого нового элемента списка 
в начало результата с предыдущего шага свертки. Инициализатор же должен быть пустым списком: (5, (7, (5, (4, (3, (2, [])). Теперь запишем конкретный код:

reduce(lambda a, b: [ b ] + a, x, [])



=> [5, 7, 5, 4, 3, 2]



Еще раз, для понятности отобразим визуально:



В этом посте я рассмотрел только простые, «нежизненные» примеры работы со списками, но у меня в запасе есть пример пожизнеспособней, и я его выложу в ближайшее время в новом посте, вместе с некоторыми соображениями по поводу производительности, применимости, о том, какое это находит отражение в других областях компьютерной науки, и вообще. Надеюсь, это будет кому-то интересно. За сим хочу откланяться, спасибо за внимание.

 
MyTetra Share v.0.65
Яндекс индекс цитирования