web-dev-qa-db-fra.com

Quel est l'équivalent 'Pythonic' de la programmation 'fold' de la programmation fonctionnelle?

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?

104
mistertim

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.

107
Fred Foo

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.

15
clay

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
12
Kyr

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
5
Mehdi Nellen

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)
4
Zenobe

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

2
JBernardo

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
1
Xavier Guihot

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

1
Pant

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:

  • Une valeur de départ ou "d'amorce" par défaut pour l'agrégation.
  • Fonction qui prend la valeur actuelle de l'agrégation (en commençant par le "germe") et l'élément suivant de la liste, puis renvoie la valeur d'agrégation suivante.
0
rq_