Unterschied zwischen falten und reduzieren?


119

Der Versuch, F # zu lernen, war jedoch verwirrt, als er versuchte, zwischen Falten und Reduzieren zu unterscheiden . Fold scheint dasselbe zu tun , benötigt jedoch einen zusätzlichen Parameter. Gibt es einen legitimen Grund für die Existenz dieser beiden Funktionen oder sind sie dazu da, Menschen mit unterschiedlichem Hintergrund aufzunehmen? (ZB: String und String in C #)

Hier ist ein Code-Snippet, das aus dem Beispiel kopiert wurde:

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

1
Sie können verkleinern und falten in Bezug aufeinander schreiben, zB fold f a lkann als geschrieben werden reduce f a::l.
Neil

9
@Neil - Die Implementierung foldin Bezug auf reduceist komplizierter als das - die Art des Akkumulators von foldmuss nicht mit der Art der Dinge in der Liste übereinstimmen !
Tomas Petricek

@TomasPetricek Mein Fehler, ich wollte es ursprünglich anders herum schreiben.
Neil

Antworten:


169

FoldNimmt einen expliziten Anfangswert für den Akkumulator, während reducedas erste Element der Eingabeliste als anfänglicher Akkumulatorwert verwendet wird.

Dies bedeutet, dass der Akkumulator und damit der Ergebnistyp mit dem Listenelementtyp übereinstimmen müssen, während sie sich unterscheiden können, foldda der Akkumulator separat bereitgestellt wird. Dies spiegelt sich in den Typen wider:

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

Darüber hinaus reducelöst eine Ausnahme auf eine leere Eingabeliste.


Anstatt dies zu tun fold, können Sie diesen Anfangswert einfach am Anfang der Liste hinzufügen und tun reduce? Was ist der Sinn von folddann?
Pacerier

2
@Pacerier - Die Akkumulatorfunktion für Falz hat einen anderen Typ: 'state -> 'a -> 'stateFür Falz und 'a -> 'a -> 'afür Reduzieren. Reduziert also, dass der Ergebnistyp mit dem Elementtyp identisch ist. Siehe die Antwort von Tomas Petricek unten.
Lee

178

Zusätzlich zu dem, was Lee sagte, können Sie festlegen , reducein Bezug auf fold, aber nicht (leicht) umgekehrt:

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

Die Tatsache, dass foldein expliziter Anfangswert für den Akkumulator verwendet wird, bedeutet auch, dass das Ergebnis der foldFunktion einen anderen Typ haben kann als der Wertetyp in der Liste. Beispielsweise können Sie einen Akkumulator vom Typ verwenden, stringum alle Zahlen in einer Liste zu einer Textdarstellung zu verketten:

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

Bei Verwendung entspricht reduceder Akkumulatortyp dem Wertetyp in der Liste. Wenn Sie also eine Liste mit Zahlen haben, muss das Ergebnis eine Zahl sein. Um das vorherige Beispiel zu implementieren, müssen Sie stringzuerst die Zahlen in konvertieren und dann akkumulieren:

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

2
Warum Reduzieren so definieren, dass es zur Laufzeit zu Fehlern kommen kann?
Fresheyeball

+1 für den Hinweis zur Allgemeinheit der fold' & its ability to express Reduzierung '. Einige Sprachen haben ein Konzept der strukturellen Chiralität (Haskell, ich sehe dich an), das Sie in diesem Wiki ( en.wikipedia.org/wiki/Fold_%28higher-order_function ) visuell nach links oder rechts falten können . Mit einem Identitätskonstrukt können die beiden anderen "grundlegenden" FP-Operatoren (Filter und fmap) auch mit einem vorhandenen erstklassigen "Fold" -Sprachenkonstrukt implementiert werden (alle sind isomorphe Konstrukte). ( cs.nott.ac.uk/~pszgmh/fold.pdf ) Siehe: HoTT, Princeton (Dieser Kommentarbereich ist zu klein, um ihn zu enthalten ..)
Andrew

Aus Neugier ... würde dies dazu führen, dass die Leistung schneller reduziert als gefaltet wird, weil weniger Annahmen über Typen und Ausnahmen getroffen werden?
Sksallaj

19

Schauen wir uns ihre Unterschriften an:

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

Es gibt einige wichtige Unterschiede:

  • Während nur reducefür einen foldElementtyp gearbeitet wird , können sich der Akkumulator und die Listenelemente in unterschiedlichen Typen befinden.
  • Mit reducewenden Sie eine Funktion fauf jedes Listenelement ab dem ersten an:

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

    Mit foldbewerben Sie sich fab dem Akku s:

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

Daher wird reduceeine ArgumentExceptionleere Liste angezeigt. Darüber hinaus foldist generischer als reduce; Sie können verwenden fold, um reduceeinfach zu implementieren .

In einigen Fällen ist die Verwendung reduceprägnanter:

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

oder bequemer, wenn es keinen vernünftigen Akku gibt:

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

Im Allgemeinen foldist mit einem Akkumulator eines beliebigen Typs leistungsfähiger:

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

17

foldist eine viel wertvollere Funktion als reduce. Sie können viele verschiedene Funktionen in Bezug auf definieren fold.

reduceist nur eine Teilmenge von fold.

Definition der Falte:

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

Beispiele für Funktionen, die in Bezug auf Fold definiert sind:

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

1
Sie definieren Ihre foldanders List.foldals die Art von List.foldist ('a -> 'b -> 'a) -> 'a -> 'b list -> 'a, aber in Ihrem Fall ('a -> 'b -> 'b) -> 'b -> 'a list -> 'b. Nur um es explizit zu machen. Außerdem ist Ihre Implementierung von append falsch. Es würde funktionieren, wenn Sie eine Bindung hinzufügen, z. B. List.collect id (fold (fun x y -> x :: y) [] [xs;ys])oder Nachteile durch den Append-Operator ersetzen. Daher ist Anhängen nicht das beste Beispiel in dieser Liste.
Jpe
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.