Что такое "pythonic", эквивалентный функции "fold" от функционального программирования?
Каков самый идиоматический способ добиться в Haskell чего-то вроде следующего:
foldl (+) 0 [1,2,3,4,5]
--> 15
Или его эквивалент в Ruby:
[1,2,3,4,5].inject(0) {|m,x| m + x}
#> 15
Очевидно, что Python предоставляет функцию reduce
, которая представляет собой реализацию fold, точно так же, как указано выше, однако мне сказали, что "питонический" способ программирования состоит в том, чтобы избежать терминов lambda
и функций более высокого порядка, предпочитая, если это возможно, понимание списков. Поэтому существует ли предпочтительный способ сгибания списка или структуры списка в Python, которая не является функцией reduce
, или является reduce
идиоматическим способом достижения этого?
Ответы
Ответ 1
Pythonic способ суммирования массива использует sum
. Для других целей иногда можно использовать некоторую комбинацию из reduce
(из модуля functools
) и модуля operator
, например:
def product(xs):
return reduce(operator.mul, xs, 1)
Имейте в виду, что reduce
на самом деле является foldl
, с точки зрения Хаскелла. Нет специального синтаксиса для выполнения свертывания, нет встроенного foldr
, и фактически использование reduce
с неассоциативными операторами считается плохим стилем.
Использование функций высшего порядка довольно питонно; в нем хорошо используется принцип Python, согласно которому все является объектом, включая функции и классы. Вы правы в том, что некоторые питонисты не одобряют лямбды, но в основном потому, что они, как правило, не очень читабельны, когда становятся сложными.
Ответ 2
Хаскел
foldl (+) 0 [1,2,3,4,5]
Python
reduce(lambda a,b: a+b, [1,2,3,4,5], 0)
Очевидно, что это тривиальный пример, иллюстрирующий точку. В Python вы просто делаете sum([1,2,3,4,5])
, и даже пуристы Haskell обычно предпочитают sum [1,2,3,4,5]
.
Для нетривиальных сценариев, когда нет очевидной удобной функции, идиоматический пифонический подход заключается в том, чтобы явно выписать цикл for и использовать переменное переменное переменное вместо использования reduce
или fold
.
Это вовсе не функциональный стиль, но это "питонический" способ. Python не предназначен для функциональных пуристов. Посмотрите, как Python поддерживает исключения для управления потоком, чтобы увидеть, как нефункциональный идиоматический питон.
Ответ 3
В Python 3 reduce
было удалено: Примечания к выпуску. Тем не менее вы можете использовать модуль functools
import operator, functools
def product(xs):
return functools.reduce(operator.mul, xs, 1)
С другой стороны, в документации выражает предпочтение в сторону for
-loop вместо того, чтобы reduce
, следовательно:
def product(xs):
result = 1
for i in xs:
result *= i
return result
Ответ 4
Вы также можете изобрести колесо:
def fold(f, l, a):
"""
f: the function to apply
l: the list to fold
a: the accumulator, who is also the 'zero' on the first call
"""
return a if(len(l) == 0) else fold(f, l[1:], f(a, l[0]))
print "Sum:", fold(lambda x, y : x+y, [1,2,3,4,5], 0)
print "Any:", fold(lambda x, y : x or y, [False, True, False], False)
print "All:", fold(lambda x, y : x and y, [False, True, False], True)
# Prove that result can be of a different type of the list elements
print "Count(x==True):",
print fold(lambda x, y : x+1 if(y) else x, [False, True, True], 0)
Ответ 5
Не отвечайте на вопрос, но однострочные для foldl и foldr:
a = [8,3,4]
## Foldl
reduce(lambda x,y: x**y, a)
#68719476736
## Foldr
reduce(lambda x,y: y**x, a[::-1])
#14134776518227074636666380005943348126619871175004951664972849610340958208L
Ответ 6
Фактический ответ на эту проблему (уменьшение): просто используйте цикл!
initial_value = 0
for x in the_list:
initial_value += x #or any function.
Это будет быстрее, чем сокращение, и такие вещи, как PyPy, могут оптимизировать такие циклы.
BTW, сумма должна быть решена с помощью функции sum
Ответ 7
Начиная с Python 3.8
и введением выражений присваивания (PEP 572) (:=
оператор), который дает возможность назвать результат выражения, мы можем использовать понимание списка для репликации того, что другие языки называют операциями сворачивания/складывания/сокращения:
Приведены список, редукционная функция и аккумулятор:
items = [1, 2, 3, 4, 5]
f = lambda acc, x: acc * x
accumulator = 1
мы можем сложить items
с f
для получения результирующего accumulation
:
[accumulator := f(accumulator, x) for x in items]
# accumulator = 120
или в сгущенном виде:
acc = 1; [acc := acc * x for x in [1, 2, 3, 4, 5]]
# acc = 120
Обратите внимание, что на самом деле это также операция "scanleft", так как результат понимания списка представляет состояние накопления на каждом шаге:
acc = 1
scanned = [acc := acc * x for x in [1, 2, 3, 4, 5]]
# scanned = [1, 2, 6, 24, 120]
# acc = 120
Ответ 8
Возможно, я уже опаздываю на вечеринку, но мы можем создать пользовательский foldr
используя простое лямбда-исчисление и функцию карри. Вот моя реализация foldr в python.
def foldr(func):
def accumulator(acc):
def listFunc(l):
if l:
x = l[0]
xs = l[1:]
return func(x)(foldr(func)(acc)(xs))
else:
return acc
return listFunc
return accumulator
def curried_add(x):
def inner(y):
return x + y
return inner
def curried_mult(x):
def inner(y):
return x * y
return inner
print foldr(curried_add)(0)(range(1, 6))
print foldr(curried_mult)(1)(range(1, 6))
Хотя реализация является рекурсивной (может быть медленной), она выведет значения 15
и 120
соответственно.
Ответ 9
Я полагаю, что некоторые из респондентов этого вопроса упустили более широкое значение функции fold
как абстрактного инструмента. Да, sum
может сделать то же самое для списка целых чисел, но это тривиальный случай. fold
более общий. Это полезно, когда у вас есть последовательность структур данных различной формы и вы хотите четко выразить агрегацию. Таким образом, вместо того, чтобы создавать цикл for
с агрегатной переменной и вручную каждый раз пересчитывать его, функция fold
(или версия Python, reduce
которой, по-видимому, соответствует) позволяет программисту выражать намерение агрегации гораздо более просто. просто предоставив две вещи:
- Начальное или начальное значение по умолчанию для агрегации.
- Функция, которая принимает текущее значение агрегации (начиная с "семени") и следующего элемента в списке и возвращает следующее значение агрегации.