Quelle est la manière la plus idiomatique d’atteindre quelque chose comme ceci, en Haskell:
foldl (+) 0 [1,2,3,4,5]
--> 15
Ou son équivalent en rubis:
[1,2,3,4,5].inject(0) {|m,x| m + x}
#> 15
Évidemment, Python fournit la fonction reduce
, qui est une implémentation de fold, exactement comme ci-dessus. Cependant, on m'a dit que la méthode de programmation 'Pythonic' consistait à éviter lambda
termes et fonctions d'ordre supérieur, préférant si possible la compréhension de liste. Par conséquent, existe-t-il un moyen préféré de plier une liste ou une structure semblable à une liste dans Python qui isn ' Est-ce que la fonction reduce
ou reduce
est le moyen idiomatique d’y parvenir?
La manière pythonique de sommer un tableau est sum
. À d’autres fins, vous pouvez parfois utiliser une combinaison de reduce
et du module operator
, par exemple.
def product(xs):
return reduce(operator.mul, xs, 1)
Sachez que reduce
est en fait un foldl
, en termes de Haskell. Il n'y a pas de syntaxe spéciale pour effectuer des plis, il n'y a pas de construction intégrée foldr
, et utiliser reduce
avec des opérateurs non associatifs est considéré comme un mauvais style.
L'utilisation de fonctions d'ordre supérieur est assez pythonique; il fait bon usage du principe de Python selon lequel tout est un objet, y compris les fonctions et les classes. Vous avez raison de dire que certains Pythonistes désapprouvent les lambdas, mais surtout parce qu’ils ont tendance à ne pas être très lisibles quand ils deviennent complexes.
Haskell
foldl (+) 0 [1,2,3,4,5]
Python
reduce(lambda a,b: a+b, [1,2,3,4,5], 0)
De toute évidence, il s’agit d’un exemple trivial pour illustrer un point. Dans Python vous feriez simplement sum([1,2,3,4,5])
] et même les puristes de Haskell préféreraient généralement sum [1,2,3,4,5]
.
Pour les scénarios non triviaux où il n'y a pas de fonction de commodité évidente, l'approche idiomatique Pythonic consiste à écrire explicitement la boucle for et à utiliser l'affectation de variable mutable au lieu d'utiliser reduce
ou fold
.
Ce n'est pas du tout le style fonctionnel, mais c'est la méthode "Pythonic". Python n'est pas conçu pour les puristes fonctionnels. Découvrez comment Python favorise les exceptions pour le contrôle de flux afin de déterminer comment un idiomatique non fonctionnel python = est.
Dans Python 3, le reduce
a été supprimé: notes de version . Néanmoins, vous pouvez utiliser le module functools
import operator, functools
def product(xs):
return functools.reduce(operator.mul, xs, 1)
D'autre part, la documentation exprime une préférence pour la boucle for
- au lieu de reduce
, d'où:
def product(xs):
result = 1
for i in xs:
result *= i
return result
Pas vraiment répondre à la question, mais one-liners pour foldl et foldr:
a = [8,3,4]
## Foldl
reduce(lambda x,y: x**y, a)
#68719476736
## Foldr
reduce(lambda x,y: y**x, a[::-1])
#14134776518227074636666380005943348126619871175004951664972849610340958208L
Vous pouvez aussi réinventer la roue:
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)
La réponse réelle à ce problème (réduire) est la suivante: utilisez simplement une boucle!
initial_value = 0
for x in the_list:
initial_value += x #or any function.
Ce sera plus rapide qu'une réduction et des choses comme PyPy peuvent optimiser de telles boucles.
BTW, le cas de somme devrait être résolu avec la fonction sum
Commencer Python 3.8
, et l'introduction de expressions d'affectation (PEP 572) (:=
opérateur), qui donne la possibilité de nommer le résultat d’une expression, on peut utiliser une liste de compréhension pour reproduire ce que d’autres langues appellent les opérations fold/foldleft/reduction:
Étant donné une liste, une fonction réductrice et un accumulateur:
items = [1, 2, 3, 4, 5]
f = lambda acc, x: acc * x
accumulator = 1
nous pouvons plier items
avec f
afin d'obtenir le résultat accumulation
:
[accumulator := f(accumulator, x) for x in items]
# accumulator = 120
ou dans un condensé formé:
acc = 1; [acc := acc * x for x in [1, 2, 3, 4, 5]]
# acc = 120
Notez qu'il s'agit en réalité d'une opération "scanleft" car le résultat de la compréhension de la liste représente l'état de l'accumulation à chaque étape:
acc = 1
scanned = [acc := acc * x for x in [1, 2, 3, 4, 5]]
# scanned = [1, 2, 6, 24, 120]
# acc = 120
Il se peut que je sois assez en retard à la fête, mais nous pouvons créer une foldr
personnalisée en utilisant un calcul lambda simple et une fonction curry. Voici mon implémentation 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))
Même si l'implémentation est récursive (peut être lente), il affichera les valeurs 15
et 120
respectivement
Je crois que certains des répondants de cette question ont oublié l’implication plus large de la fonction fold
en tant qu’outil abstrait. Oui, sum
peut faire la même chose pour une liste d’entiers, mais c’est un cas trivial. fold
est plus générique. C'est utile lorsque vous avez une séquence de structures de données de forme variable et que vous souhaitez exprimer proprement une agrégation. Ainsi, au lieu de devoir créer une boucle for
avec une variable agrégée et de la recalculer manuellement à chaque fois, une fonction fold
(ou la Python version, qui reduce
semble correspondre à) permet au programmeur d'exprimer beaucoup plus clairement l'intention de l'agrégation en fournissant simplement deux choses: