Простейший синтаксис 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]
Еще раз, для понятности отобразим визуально:
В этом посте я рассмотрел только простые, «нежизненные» примеры работы со списками, но у меня в запасе есть пример пожизнеспособней, и я его выложу в ближайшее время в новом посте, вместе с некоторыми соображениями по поводу производительности, применимости, о том, какое это находит отражение в других областях компьютерной науки, и вообще. Надеюсь, это будет кому-то интересно. За сим хочу откланяться, спасибо за внимание.