誰が foldr
がどのように機能するかを説明できますか?
以下の例をご覧ください。
Prelude> foldr (-) 54 [10, 11]
53
Prelude> foldr (\x y -> (x+y)/2) 54 [12, 4, 10, 6]
12.0
私はこれらの実行について混乱しています。助言がありますか?
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
は無限リストでも機能しますが、まず有限ケースを回避するのが最善だと思います。
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
foldr
とfoldl
の違いを理解するのに役立ちます。 foldr
が「右折」と呼ばれるのはなぜですか?
最初は、要素を右から左に消費するためだと思いました。ただし、foldr
とfoldl
は両方ともリストを左から右に消費します。
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
のセマンティクスは次のとおりです。人間はすでに魚を食べたサメを食べ、魚はすでに藻を食べています。食べ物はアキュムレーターです。
foldl
とfoldr
の両方が左から右に「ピールオフ」するので、foldlを「左折り」と呼ぶわけではありません。代わりに、評価の順序が重要です。
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番目のケースでも同様の手順を実行すると、結果がどのように形成されるかがわかります。
foldr
は右から折り畳むことを意味するため、foldr (-) 0 [1, 2, 3]
は(1 - (2 - (3 - 0)))
を生成します。比較すると、foldl
は(((0 - 1) - 2) - 3)
を生成します。
演算子が commutativefoldl
でない場合、foldr
と異なる結果が得られます。
あなたの場合、最初の例は(10 - (11 - 54))
に展開され、53になります。
foldr
を簡単に理解する方法は次のとおりです。すべてのリストコンストラクターを、提供された関数のアプリケーションに置き換えます。最初の例は次のように翻訳されます。
10 - (11 - 54)
から:
10 : (11 : [])
Haskell Wikibookから得た良いアドバイスは、ここで役に立つかもしれません:
原則として、無限のリストまたはフォールドがデータ構造を構築しているリストで
foldr
を使用し、foldl'
リストが有限であることがわかっており、単一の値になった場合。foldl
(目盛りなし)を使用することはほとんどありません。
http://foldr.com は楽しいイラストだといつも思っていました。 Lambda the Ultimate の投稿を参照してください。
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
では、引数を見てみましょう。
戻り値:
リストの最後の要素と空のリスト結果に最初に関数を適用します。次に、現在の結果とリストの最初の要素を取得して最終結果を返すまで、この結果と前の要素を使用して関数を再適用します。
フォールドは、要素と以前のフォールディング結果を受け取る関数を使用して、初期結果の周りでリストを「フォールド」します。これを要素ごとに繰り返します。したがって、foldrはリストの最後または右側から開始します。
_folr f emptyresult [1,2,3,4]
_はf(1, f(2, f(3, f(4, emptyresult) ) ) )
に変わります。ここで、評価の括弧に従ってください。それで終わりです。
注目すべき重要な点の1つは、指定された関数f
が2番目の引数として独自の戻り値を処理する必要があることです。
ソース: 私の投稿 あなたが助けになると思うなら、私はそれを命令的でカリー化されていないJavaScriptの観点から見ます。