web-dev-qa-db-fra.com

Comment fonctionne foldr?

Quelqu'un peut-il expliquer comment fonctionne foldr ?

Prenez ces exemples:

Prelude> foldr (-) 54 [10, 11]
53
Prelude> foldr (\x y -> (x+y)/2) 54 [12, 4, 10, 6]
12.0

Je suis confus au sujet de ces exécutions. Aucune suggestion?

57
dinsim

foldr commence à l'extrémité droite de la liste et combine chaque entrée de liste avec la valeur de l'accumulateur en utilisant la fonction que vous lui donnez. Le résultat est la valeur finale de l'accumulateur après "pliage" dans tous les éléments de la liste. Son type est:

foldr :: (a -> b -> b) -> b -> [a] -> b

et à partir de cela, vous pouvez voir que l'élément list (de type a) est le premier argument de la fonction donnée, et l'accumulateur (de type b) est le second.

Pour votre premier exemple:

Starting accumulator = 54
11 -   54  = -43
10 - (-43) =  53

        ^  Result from the previous line

 ^ Next list item

Donc, la réponse que vous avez obtenue était 53.

Le deuxième exemple:

Starting accumulator = 54
(6  + 54) / 2 = 30
(10 + 30) / 2 = 20
(4  + 20) / 2 = 12
(12 + 12) / 2 = 12

Le résultat est donc 12.

Edit: je voulais ajouter, c'est pour les listes finies. foldr peut également fonctionner sur des listes infinies, mais je pense qu'il est préférable de se familiariser d'abord avec le cas fini.

76
Nefrubyr

La façon la plus simple de comprendre foldr est de réécrire la liste que vous pliez sans le sucre.

[1,2,3,4,5] => 1:(2:(3:(4:(5:[]))))

maintenant quoi foldr f x fait, c'est qu'il remplace chaque : avec f sous forme d'infixe et [] avec x et évalue le résultat.

Par exemple:

sum [1,2,3] = foldr (+) 0 [1,2,3]

[1,2,3] === 1:(2:(3:[]))

donc

sum [1,2,3] === 1+(2+(3+0)) = 6
117
Tirpen

Il aide à comprendre la distinction entre foldr et foldl. Pourquoi foldr est-il appelé "repli vers la droite"?

Au départ, je pensais que c'était parce qu'il consommait des éléments de droite à gauche. Pourtant, foldr et foldl consomment la liste de gauche à droite.

  • foldlévalue de gauche à droite (gauche-associatif)
  • foldrévalue de droite à gauche (associative droite)

Nous pouvons clarifier cette distinction avec un exemple qui utilise un opérateur pour lequel l'associativité est importante. Nous pourrions utiliser un exemple humain, tel que l'opérateur, "mange":

foodChain = (human : (shark : (fish : (algae : []))))

foldl step [] foodChain
  where step eater food = eater `eats` food  -- note that "eater" is the accumulator and "food" is the element

foldl `eats` [] (human : (shark : (fish : (algae : []))))
  == foldl eats (human `eats` shark)                              (fish : (algae : []))
  == foldl eats ((human `eats` shark) `eats` fish)                (algae : [])
  == foldl eats (((human `eats` shark) `eats` fish) `eats` algae) []
  ==            (((human `eats` shark) `eats` fish) `eats` algae)

La sémantique de ce foldl est: Un humain mange du requin, puis le même humain qui a mangé du requin mange ensuite du poisson, etc. Le mangeur est l'accumulateur.

Comparez cela avec:

foldr step [] foodChain
    where step food eater = eater `eats` food.   -- note that "eater" is the element and "food" is the accumulator

foldr `eats` [] (human : (shark : (fish : (algae : []))))
  == foldr eats (human `eats` shark)                              (fish : (algae : []))))
  == foldr eats (human `eats` (shark `eats` (fish))               (algae : [])
  == foldr eats (human `eats` (shark `eats` (fish `eats` algae))) []
  ==            (human `eats` (shark `eats` (fish `eats` algae) 

La sémantique de ce foldr est: Un humain mange un requin qui a déjà mangé un poisson, qui a déjà mangé des algues. La nourriture est l'accumulateur.

foldl et foldr mangent "décoller" de gauche à droite, ce n'est donc pas la raison pour laquelle nous appelons foldl "pli gauche". Au lieu de cela, l'ordre d'évaluation est important.

38
Rose Perrone

Pensez à foldr très définition :

 -- if the list is empty, the result is the initial value z
 foldr f z []     = z                  
 -- if not, apply f to the first element and the result of folding the rest 
 foldr f z (x:xs) = f x (foldr f z xs)

Ainsi, par exemple, foldr (-) 54 [10,11] doit être égal à (-) 10 (foldr (-) 54 [11]), C'est-à-dire à nouveau en expansion, égal à (-) 10 ((-) 11 54). Ainsi, l'opération interne est 11 - 54, C'est-à-dire -43; et l'opération externe est 10 - (-43), c'est-à-dire 10 + 43, donc 53 comme vous le constatez. Suivez les étapes similaires pour votre deuxième cas, et vous verrez à nouveau comment le résultat se forme!

20
Alex Martelli

foldr signifie repli à partir de la droite, donc foldr (-) 0 [1, 2, 3] produit (1 - (2 - (3 - 0))). En comparaison, foldl produit (((0 - 1) - 2) - 3).

Lorsque les opérateurs ne sont pas commutatifsfoldl et foldr obtiendront des résultats différents.

Dans votre cas, le premier exemple se développe en (10 - (11 - 54)) ce qui donne 53.

13
Jeff Foster

Un moyen simple de comprendre foldr est le suivant: il remplace chaque constructeur de liste par une application de la fonction fournie. Votre premier exemple se traduirait par:

10 - (11 - 54)

de:

10 : (11 : [])

Un bon conseil que j'ai obtenu du Wikibook Haskell pourrait être utile ici:

En règle générale, vous devez utiliser foldr sur des listes qui peuvent être infinies ou dans lesquelles le pli constitue une structure de données, et foldl' si la liste est connue pour être finie et se résume à une seule valeur. foldl (sans la coche) devrait rarement être utilisé du tout.

6
Rayne

J'ai toujours pensé http://foldr.com pour être une illustration amusante. Voir le Lambda the Ultimate post.

5
Steven Huwig

Je pense que la mise en œuvre simple de map, foldl et foldr aide à expliquer leur fonctionnement. Les exemples travaillés aident également à notre compréhension.

  myMap f [] = []
  myMap f (x:xs) = f x : myMap f xs

  myFoldL f i [] = i
  myFoldL f i (x:xs) = myFoldL f (f i x) xs

  > tail [1,2,3,4] ==> [2,3,4]
  > last [1,2,3,4] ==> 4
  > head [1,2,3,4] ==> 1
  > init [1,2,3,4] ==> [1,2,3]

  -- where f is a function,
  --  acc is an accumulator which is given initially
  --  l is a list.
  --
  myFoldR' f acc [] = acc
  myFoldR' f acc l = myFoldR' f (f acc (last l)) (init l)

  myFoldR f z []     = z
  myFoldR f z (x:xs) = f x (myFoldR f z xs)

  > map (\x -> x/2) [12,4,10,6] ==> [6.0,2.0,5.0,3.0]
  > myMap (\x -> x/2) [12,4,10,6] ==> [6.0,2.0,5.0,3.0]

  > foldl (\x y -> (x+y)/2) 54 [12, 4, 10, 6] ==> 10.125
  > myFoldL (\x y -> (x+y)/2) 54 [12, 4, 10, 6] ==> 10.125

    foldl from above: Starting accumulator = 54
      (12  + 54) / 2 = 33
      (4 + 33) / 2 = 18.5
      (10  + 18.5) / 2 = 14.25
      (6 + 14.25) / 2 = 10.125`

 > foldr (++) "5" ["1", "2", "3", "4"] ==> "12345"

 > foldl (++) "5" ["1", "2", "3", "4"] ==> “51234"

 > foldr (\x y -> (x+y)/2) 54 [12,4,10,6] ==> 12
 > myFoldR' (\x y -> (x+y)/2) 54 [12,4,10,6] ==> 12
 > myFoldR (\x y -> (x+y)/2) 54 [12,4,10,6] ==> 12

    foldr from above: Starting accumulator = 54
        (6  + 54) / 2 = 30
        (10 + 30) / 2 = 20
        (4  + 20) / 2 = 12
        (12 + 12) / 2 = 12
2
shawfire

Ok, regardons les arguments:

  • une fonction (qui prend un élément de liste et une valeur (un résultat partiel possible) du même type de la valeur qu'elle renvoie);
  • une spécification du résultat initial pour le cas spécial de liste vide
  • une liste;

valeur de retour:

  • un résultat final

Il applique d'abord la fonction au dernier élément de la liste et au résultat de la liste vide. Il réapplique ensuite la fonction avec ce résultat et l'élément précédent, et ainsi de suite jusqu'à ce qu'il prenne un résultat en cours et le premier élément de la liste pour renvoyer le résultat final.

Pliez "replie" une liste autour d'un résultat initial en utilisant une fonction qui prend un élément et un résultat de pliage précédent. Il répète cela pour chaque élément. Donc, foldr fait cela en commençant à la fin de la liste, ou du côté droit de celle-ci.

folr f emptyresult [1,2,3,4] Se transforme en f(1, f(2, f(3, f(4, emptyresult) ) ) ). Maintenant, suivez simplement les parenthèses dans l'évaluation et c'est tout.

Une chose importante à noter est que la fonction fournie f doit gérer sa propre valeur de retour comme deuxième argument, ce qui implique que les deux doivent avoir le même type.

Source: mon article où je le regarde d'un point de vue javascript impératif et incurvé si vous pensez que cela pourrait aider.

1
Francisco M.