web-dev-qa-db-ja.com

foldrはどのように機能しますか?

誰が foldr がどのように機能するかを説明できますか?

以下の例をご覧ください。

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

私はこれらの実行について混乱しています。助言がありますか?

57
dinsim

foldrはリストの右端から始まり、指定した関数を使用して各リストエントリをアキュムレータ値と結合します。結果は、すべてのリスト要素の「折りたたみ」後のアキュムレータの最終値です。タイプは次のとおりです。

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

これから、リスト要素(タイプa)が特定の関数の最初の引数であり、アキュムレータ(タイプb)が2番目であることがわかります。

最初の例:

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

        ^  Result from the previous line

 ^ Next list item

答えは53です。

2番目の例:

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

結果は12です。

編集:追加するつもりだった、それは有限リスト用です。 foldrは無限リストでも機能しますが、まず有限ケースを回避するのが最善だと思います。

76
Nefrubyr

Foldrを理解する最も簡単な方法は、砂糖なしで折りたたむリストを書き換えることです。

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

それで foldr f xは、それぞれの:インフィックス形式のf[] with xを使用して、結果を評価します。

例えば:

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

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

そう

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

foldrfoldlの違いを理解するのに役立ちます。 foldrが「右折」と呼ばれるのはなぜですか?

最初は、要素を右から左に消費するためだと思いました。ただし、foldrfoldlは両方ともリストを左から右に消費します。

  • foldl評価左から右へ(左結合)
  • foldr評価右から左へ(右結合)

結合性が重要な演算子を使用する例で、この区別を明確にすることができます。演算子「食べる」などの人間の例を使用できます。

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)

このfoldlのセマンティクスは次のとおりです。人間がサメを食べ、サメを食べた同じ人間が魚などを食べます。食べる人はアキュムレーターです。

これと比較してください:

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) 

このfoldrのセマンティクスは次のとおりです。人間はすでに魚を食べたサメを食べ、魚はすでに藻を食べています。食べ物はアキュムレーターです。

foldlfoldrの両方が左から右に「ピールオフ」するので、foldlを「左折り」と呼ぶわけではありません。代わりに、評価の順序が重要です。

38
Rose Perrone

foldrの非常に 定義 について考えてください:

_ -- 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)
_

したがって、たとえばfoldr (-) 54 [10,11]は_(-) 10 (foldr (-) 54 [11])_に等しくなければなりません。つまり、再度展開すると_(-) 10 ((-) 11 54)_に等しくなります。したがって、内部操作は_11 - 54_、つまり-43です。外側の操作は10 - (-43)、つまり_10 + 43_であるため、観察したとおり_53_です。 2番目のケースでも同様の手順を実行すると、結果がどのように形成されるかがわかります。

20
Alex Martelli

foldrは右から折り畳むことを意味するため、foldr (-) 0 [1, 2, 3](1 - (2 - (3 - 0)))を生成します。比較すると、foldl(((0 - 1) - 2) - 3)を生成します。

演算子が commutativefoldlでない場合、foldrと異なる結果が得られます。

あなたの場合、最初の例は(10 - (11 - 54))に展開され、53になります。

13
Jeff Foster

foldrを簡単に理解する方法は次のとおりです。すべてのリストコンストラクターを、提供された関数のアプリケーションに置き換えます。最初の例は次のように翻訳されます。

10 - (11 - 54)

から:

10 : (11 : [])

Haskell Wikibookから得た良いアドバイスは、ここで役に立つかもしれません:

原則として、無限のリストまたはフォールドがデータ構造を構築しているリストでfoldrを使用し、foldl'リストが有限であることがわかっており、単一の値になった場合。 foldl(目盛りなし)を使用することはほとんどありません。

6
Rayne

http://foldr.com は楽しいイラストだといつも思っていました。 Lambda the Ultimate の投稿を参照してください。

5
Steven Huwig

Map、foldl、foldrを簡単な方法で実装すると、それらがどのように機能するかを説明するのに役立つと思います。実施例も理解を助けます。

  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

では、引数を見てみましょう。

  • 関数(リスト要素と、返される同じ種類の値の値(可能な部分結果)を受け取ります);
  • 空のリストの特別な場合の初期結果の仕様
  • リスト;

戻り値:

  • 最終結果

リストの最後の要素と空のリスト結果に最初に関数を適用します。次に、現在の結果とリストの最初の要素を取得して最終結果を返すまで、この結果と前の要素を使用して関数を再適用します。

フォールドは、要素と以前のフォールディング結果を受け取る関数を使用して、初期結果の周りでリストを「フォールド」します。これを要素ごとに繰り返します。したがって、foldrはリストの最後または右側から開始します。

_folr f emptyresult [1,2,3,4]_はf(1, f(2, f(3, f(4, emptyresult) ) ) )に変わります。ここで、評価の括弧に従ってください。それで終わりです。

注目すべき重要な点の1つは、指定された関数fが2番目の引数として独自の戻り値を処理する必要があることです。

ソース: 私の投稿 あなたが助けになると思うなら、私はそれを命令的でカリー化されていないJavaScriptの観点から見ます。

1
Francisco M.