¿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 reduce
función, que es una implementación de fold, exactamente como arriba, sin embargo, me dijeron que la forma 'pitónica' de programar era evitar lambda
términos y funciones de orden superior, prefiriendo listas por comprensión cuando fuera posible. Por lo tanto, ¿hay una forma preferida de plegar una lista o una estructura similar a una lista en Python que no sea la reduce
función, o es reduce
la forma idiomática de lograr esto?
sum
no es lo suficientemente bueno?sum
, es posible que desee proporcionar algunos tipos diferentes de ejemplos.sum()
realidad, proporciona una funcionalidad limitada con esto.sum([[a], [b, c, d], [e, f]], [])
devuelve,[a, b, c, d, e, f]
por ejemplo.+
en las listas es una operación de tiempo lineal tanto en tiempo como en memoria, lo que hace que toda la llamada sea cuadrática. El usolist(itertools.chain.from_iterable([a], [b,c,d],[e,f],[]])
es lineal en general, y si solo necesita iterar sobre él una vez, puede eliminar la llamada alist
para que sea constante en términos de memoria.Respuestas:
La forma Pythonic de sumar una matriz está usando
sum
. Para otros propósitos, a veces puede usar alguna combinación dereduce
(delfunctools
módulo) y eloperator
módulo, por ejemplo:Tenga en cuenta que en
reduce
realidad es unafoldl
, en términos de Haskell. No hay una sintaxis especial para realizar pliegues, no hay una función incorporadafoldr
y, en realidad, el usoreduce
con operadores no asociativos se considera de mal estilo.El uso de funciones de orden superior es bastante pitónico; hace un buen uso del principio de Python de que todo es un objeto, incluidas las funciones y clases. Tienes razón en que algunos Pythonistas desaprueban las lambdas, pero sobre todo porque tienden a no ser muy legibles cuando se vuelven complejas.
fuente
reduce()
está bastante limitada a los operadores asociativos, y en todos los demás casos es mejor escribir el ciclo de acumulación explícitamente". Entonces, su uso es limitado, pero incluso GvR aparentemente tuvo que admitir que es lo suficientemente útil como para mantenerlo en la biblioteca estándar.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 simplemente lo haría
sum([1,2,3,4,5])
e incluso los puristas de Haskell generalmente preferiríansum [1,2,3,4,5]
.Para escenarios no triviales cuando no hay una función de conveniencia obvia, el enfoque pitónico idiomático es escribir explícitamente el ciclo for y usar la asignación de variable mutable en lugar de usar
reduce
o afold
.Ese no es en absoluto el estilo funcional, pero es la forma "pitónica". Python no está diseñado para puristas funcionales. Vea cómo Python favorece las excepciones para el control de flujo para ver qué tan no funcional es Python idiomático.
fuente
En Python 3,
reduce
se ha eliminado: Notas de la versión . Sin embargo, puede utilizar el módulo de funcionesPor otro lado, la documentación expresa preferencia hacia
for
-loop en lugar dereduce
, por lo tanto:fuente
reduce
no se eliminó de la biblioteca estándar de Python 3.reduce
movido alfunctools
módulo como se muestra.A partir
Python 3.8
, y la introducción de expresiones de asignación (PEP 572) (:=
operador), que da la posibilidad de nombrar el resultado de una expresión, podemos usar una lista de comprensión para replicar lo que otros lenguajes llaman operaciones de plegar / plegar a la izquierda / reducir:Dada una lista, una función reductora y un acumulador:
podemos doblar
items
con elf
fin de obtener el resultadoaccumulation
:o en forma condensada:
Tenga en cuenta que esto también es una operación "scanleft" ya que el resultado de la comprensión de la lista representa el estado de la acumulación en cada paso:
fuente
También puedes reinventar la rueda:
fuente
f
en tu caso recursivo.reduce
ya ofrece (tenga en cuenta que la firma de la función reduce esreduce(function, sequence[, initial]) -> value
; también incluye la funcionalidad de dar un valor inicial para el acumulador).En realidad, no es la respuesta a la pregunta, pero sí frases para foldl y foldr:
fuente
reduce(lambda y, x: x**y, reversed(a))
. Ahora tiene un uso más natural, funciona con iteradores y consume menos memoria.La respuesta real a este problema (de reducción) es: ¡Simplemente use un bucle!
Esto será más rápido que una reducción y cosas como PyPy pueden optimizar bucles como ese.
Por cierto, el caso de la suma debe resolverse con la
sum
funciónfuente
reduce
es una forma común de optimizar un programa Python.product
contra uno en tu estilo, y es más rápido (aunque marginalmente).operator.add
) como argumento para reducir: esa llamada adicional es una llamada C (que es mucho más barata que una llamada Python), y ahorra el envío e interpretación de un par de instrucciones de código de bytes, que fácilmente pueden causar docenas de llamadas a funciones.Creo que algunos de los que respondieron a esta pregunta han pasado por alto la implicación más amplia de la
fold
función como herramienta abstracta. Sí,sum
puede hacer lo mismo con una lista de números 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 expresar una agregación de forma limpia. Entonces, en lugar de tener que construir unfor
bucle con una variable agregada y volver a calcularlo manualmente cada vez, unafold
función (o la versión de Python, quereduce
parece corresponder) permite al programador expresar la intención de la agregación de manera mucho más sencilla simplemente proporcionando dos cosas:fuente
fold
que es difícil de hacer limpiamente en Python, y luego "fold
" eso en Python :-)Puede que llegue bastante tarde a la fiesta, pero podemos crear personalización
foldr
utilizando un cálculo lambda simple y una función de curry. Aquí está mi implementación de foldr en python.Aunque la implementación es recursiva (puede ser lenta), imprimirá los valores
15
y120
respectivamentefuente