¿Cuál es el equivalente ‘pythonic’ a la función ‘fold’ de la progtwigción funcional?

¿Cuál es la forma más idiomática de lograr algo como lo siguiente, en Haskell:

foldl (+) 0 [1,2,3,4,5] --> 15 

O su equivalente en Ruby:

 [1,2,3,4,5].inject(0) {|m,x| m + x} #> 15 

Obviamente, Python proporciona la función de reduce , que es una implementación del pliegue, exactamente igual a la anterior, sin embargo, se me dijo que la forma “pythonica” de la progtwigción era evitar los términos lambda y las funciones de orden superior, prefiriendo listas de comprensión siempre que fuera posible. Por lo tanto, ¿existe una forma preferida de plegar una lista, o una estructura similar a una lista en Python que no sea la función de reduce , o es reduce la forma idiomática de lograr esto?

La forma pythonica de sumr una matriz es la sum . Para otros fines, a veces puede usar una combinación de reduce y el módulo del operator , por ejemplo,

 def product(xs): return reduce(operator.mul, xs, 1) 

Tenga en cuenta que reduce es realmente un foldl , en términos de Haskell. No hay una syntax especial para realizar los pliegues, no hay un foldr incorporado, y el uso de reduce con operadores no asociativos se considera un estilo malo.

Usar funciones de orden superior es bastante pythonic; hace un buen uso del principio de Python de que todo es un objeto, incluidas las funciones y las clases. Tienes razón en que los lambdas están mal vistos por algunos pitonistas, pero sobre todo porque tienden a no ser muy legibles cuando se vuelven complejos.

Haskell

foldl (+) 0 [1,2,3,4,5]

Pitón

reduce(lambda a,b: a+b, [1,2,3,4,5], 0)

Obviamente, ese es un ejemplo trivial para ilustrar un punto. En Python solo harías la sum([1,2,3,4,5]) e incluso los puristas de Haskell generalmente preferirían la sum [1,2,3,4,5] .

Para escenarios no triviales cuando no hay una función de conveniencia obvia, el enfoque idiomático idiomático es escribir explícitamente el bucle for y usar la asignación de variables mutables en lugar de usar reduce o un fold .

Ese no es en absoluto el estilo funcional, pero esa es la manera “pythonica”. Python no está diseñado para puristas funcionales. Vea cómo Python favorece las excepciones para el control de flujo para ver cómo es python idiomático no funcional.

En Python 3, la reduce se ha eliminado: Notas de la versión . Sin embargo puedes usar el modulo functools.

 import operator, functools def product(xs): return functools.reduce(operator.mul, xs, 1) 

Por otro lado, la documentación expresa preferencia hacia for loop en lugar de reduce , por lo tanto:

 def product(xs): result = 1 for i in xs: result *= i return result 

También puedes reinventar la rueda:

 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's elements print "Count(x==True):", print fold(lambda x, y : x+1 if(y) else x, [False, True, True], 0) 

En realidad no responden a la pregunta, sino en una sola línea para foldl y foldr:

 a = [8,3,4] ## Foldl reduce(lambda x,y: x**y, a) #68719476736 ## Foldr reduce(lambda x,y: y**x, a[::-1]) #14134776518227074636666380005943348126619871175004951664972849610340958208L 

La respuesta real a este problema (reducir) es: ¡Solo use un bucle!

 initial_value = 0 for x in the_list: initial_value += x #or any function. 

Esto será más rápido que una reducción y cosas como PyPy pueden optimizar bucles de esa manera.

Por cierto, el caso de sum debe resolverse con la función de sum

Puede que sea bastante tarde para la fiesta, pero podemos crear un foldr personalizado utilizando el simple cálculo lambda y la función de curry. Aquí está mi implementación de foldr en 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)) 

Aunque la implementación es recursiva (podría ser lenta), imprimirá los valores 15 y 120 respectivamente

Creo que algunos de los que respondieron a esta pregunta se han perdido la implicación más amplia de la función fold como una herramienta abstracta. Sí, la sum puede hacer lo mismo para una lista de enteros, pero este es un caso trivial. fold es más genérico. Es útil cuando tiene una secuencia de estructuras de datos de forma variable y desea express de manera limpia una agregación. Entonces, en lugar de tener que construir un bucle for con una variable agregada y recompensarlo manualmente cada vez, una función de fold (o la versión de Python, que reduce parece corresponder) permite al progtwigdor express la intención de la agregación mucho más claramente simplemente proporcionando dos cosas:

  • Un valor de inicio o “semilla” predeterminado para la agregación.
  • Una función que toma el valor actual de la agregación (comenzando con la “semilla”) y el siguiente elemento de la lista, y devuelve el siguiente valor de agregación.