¿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: