web-dev-qa-db-ja.com

foldとreduceの違いは?

F#を学習しようとしましたが、 foldreduce を区別しようとすると混乱しました。 Foldは same thing を実行しているようですが、余分なパラメータが必要です。これらの2つの機能が存在する正当な理由があるのでしょうか、それとも異なるバックグラウンドを持つ人々に対応するためにあるのでしょうか? (例:文字列およびC#の文字列)

サンプルからコピーしたコードスニペットを次に示します。

let sumAList list =
    List.reduce (fun acc elem -> acc + elem) list

let sumAFoldingList list =
    List.fold (fun acc elem -> acc + elem) 0 list

printfn "Are these two the same? %A " 
             (sumAList [2; 4; 10] = sumAFoldingList [2; 4; 10])
115
Wallace

Foldはアキュムレータの明示的な初期値を取りますが、reduceは入力リストの最初の要素をアキュムレータの初期値として使用します。

つまり、アキュムレータと結果の型はリスト要素の型と一致する必要がありますが、アキュムレータは個別に提供されるため、foldが異なる場合があります。これはタイプに反映されます:

List.fold : ('State -> 'T -> 'State) -> 'State -> 'T list -> 'State
List.reduce : ('T -> 'T -> 'T) -> 'T list -> 'T

さらに、reduceは空の入力リストで例外をスローします。

161
Lee

Leeが言ったことに加えて、reducefoldを定義できますが、(簡単に)逆ではありません:

let reduce f list = 
  match list with
  | head::tail -> List.fold f head tail
  | [] -> failwith "The list was empty!"

foldはアキュムレータの明示的な初期値をとるという事実は、fold関数の結果がリスト内の値の型と異なる型を持つことができることも意味します。たとえば、タイプstringのアキュムレータを使用して、リスト内のすべての数値をテキスト表現に連結できます。

[1 .. 10] |> List.fold (fun str n -> str + "," + (string n)) ""

reduceを使用する場合、アキュムレーターのタイプはリスト内の値のタイプと同じです。つまり、数値のリストがある場合、結果は数値でなければなりません。前のサンプルを実装するには、最初に数値をstringに変換してから累積する必要があります。

[1 .. 10] |> List.map string
          |> List.reduce (fun s1 s2 -> s1 + "," + s2)
178
Tomas Petricek

それらの署名を見てみましょう:

_> List.reduce;;
val it : (('a -> 'a -> 'a) -> 'a list -> 'a) = <fun:clo@1>
> List.fold;;
val it : (('a -> 'b -> 'a) -> 'a -> 'b list -> 'a) = <fun:clo@2-1>
_

重要な違いがいくつかあります。

  • reduceは1つのタイプのエレメントでのみ機能しますが、foldのアキュムレーターとリストのエレメントは異なるタイプである可能性があります。
  • reduceを使用すると、最初の要素から始まるすべてのリスト要素に関数fを適用できます。

    f (... (f i0 i1) i2 ...) iN

    foldを使用して、アキュムレータfから開始してsを適用します。

    f (... (f s i0) i1 ...) iN

したがって、reduceは空のリストでArgumentExceptionになります。さらに、foldreduceよりも汎用的です。 foldを使用してreduceを簡単に実装できます。

場合によっては、reduceを使用する方が簡潔です。

_// Return the last element in the list
let last xs = List.reduce (fun _ x -> x) xs
_

または合理的なアキュムレータがない場合はより便利です:

_// Intersect a list of sets altogether
let intersectMany xss = List.reduce (fun acc xs -> Set.intersect acc xs) xss
_

一般に、foldは、任意のタイプのアキュムレーターを使用するとより強力です。

_// Reverse a list using an empty list as the accumulator
let rev xs = List.fold (fun acc x -> x::acc) [] xs
_
19
pad

foldは、reduceよりもはるかに価値のある関数です。 foldに関して多くの異なる関数を定義できます。

reducefoldのサブセットです。

折り畳みの定義:

let rec fold f v xs =
    match xs with 
    | [] -> v
    | (x::xs) -> f (x) (fold f v xs )

折り畳みに関して定義された関数の例:

let sum xs = fold (fun x y -> x + y) 0 xs

let product xs = fold (fun x y -> x * y) 1 xs

let length xs = fold (fun _ y -> 1 + y) 0 xs

let all p xs = fold (fun x y -> (p x) && y) true xs

let reverse xs = fold (fun x y -> y @ [x]) [] xs

let map f xs = fold (fun x y -> f x :: y) [] xs

let append xs ys = fold (fun x y -> x :: y) [] [xs;ys]

let any p xs = fold (fun x y -> (p x) || y) false xs 

let filter p xs = 
    let func x y =
        match (p x) with
        | true -> x::y
        | _ -> y
    fold func [] xs
16
Raz Megrelidze