Differenza tra piegare e ridurre?


121

Cercando di imparare F # ma mi sono confuso quando ho provato a distinguere tra piega e riduzione . Fold sembra fare la stessa cosa ma richiede un parametro in più. C'è una ragione legittima per l'esistenza di queste due funzioni o sono lì per accogliere persone con background diversi? (Ad esempio: stringa e stringa in C #)

Ecco uno snippet di codice copiato dall'esempio:

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
È possibile scrivere ridurre e piegare l'uno rispetto all'altro, ad esempio fold f a lpuò essere scritto come reduce f a::l.
Neil

9
@ Neil - L'implementazione foldin termini di reduceè più complicata di così - il tipo di accumulatore di foldnon deve essere lo stesso del tipo di cose nell'elenco!
Tomas Petricek

@TomasPetricek Errore mio, originariamente avevo intenzione di scriverlo al contrario.
Neil

Risposte:


171

Foldaccetta un valore iniziale esplicito per l'accumulatore mentre reduceutilizza il primo elemento della lista di input come valore dell'accumulatore iniziale.

Ciò significa che l'accumulatore e quindi il tipo di risultato devono corrispondere al tipo di elemento della lista, mentre possono differire in foldquanto l'accumulatore è fornito separatamente. Ciò si riflette nei tipi:

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

Inoltre reducegenera un'eccezione su un elenco di input vuoto.


Quindi in pratica invece di fare fold, puoi semplicemente aggiungere quel valore iniziale all'inizio dell'elenco e fare reduce? Qual è il punto di foldallora?
Pacerier

2
@Pacerier - La funzione accumulator per fold ha un tipo diverso: 'state -> 'a -> 'statefor fold vs 'a -> 'a -> 'afor reduce, quindi reduce vincola il tipo di risultato a essere lo stesso del tipo di elemento. Vedi la risposta di Tomas Petricek di seguito.
Lee

178

Oltre a ciò che ha detto Lee, puoi definire reducein termini fold, ma non (facilmente) il contrario:

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

Il fatto che foldassuma un valore iniziale esplicito per l'accumulatore significa anche che il risultato della foldfunzione può avere un tipo diverso dal tipo di valori nell'elenco. Ad esempio, puoi utilizzare l'accumulatore di tipo stringper concatenare tutti i numeri in un elenco in una rappresentazione testuale:

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

Quando si utilizza reduce, il tipo di accumulatore è lo stesso del tipo di valori nell'elenco: ciò significa che se si dispone di un elenco di numeri, il risultato dovrà essere un numero. Per implementare l'esempio precedente, dovresti prima convertire i numeri in stringe poi accumulare:

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

2
Perché definire ridurre in modo tale che possa generare errori in fase di esecuzione?
Fresheyeball

+1 per la nota sulla generalità della fold' & its ability to express riduzione '. Alcune lingue hanno un concetto di chiralità strutturale (Haskell ti sto guardando) che puoi piegare a sinistra oa destra rappresentata visivamente in questo wiki ( en.wikipedia.org/wiki/Fold_%28higher-order_function ). Con un costrutto di identità, anche gli altri due operatori FP "fondamentali" (filtro e fmap) sono implementabili con un costrutto di linguaggio di prima classe "fold" esistente (sono tutti costrutti isomorfi). ( cs.nott.ac.uk/~pszgmh/fold.pdf ) Vedi: HoTT, Princeton (Questa sezione di commenti è troppo piccola per contenere ..)
Andrew

Per curiosità .. questo renderebbe le prestazioni di ridurre più velocemente di volte perché è sotto meno ipotesi su tipi ed eccezioni?
sksallaj

19

Diamo un'occhiata alle loro firme:

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

Ci sono alcune differenze importanti:

  • Sebbene reducefunzioni solo su un tipo di elementi, gli elementi dell'accumulatore e dell'elenco in foldpotrebbero essere di tipi diversi.
  • Con reducesi applica una funzione fa ogni elemento della lista a partire dal primo:

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

    Con foldsi applica a fpartire dall'accumulatore s:

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

Pertanto, reducerisulta in un ArgumentExceptionelenco vuoto. Inoltre, foldè più generico di reduce; puoi usare foldper implementare reducefacilmente.

In alcuni casi, l'utilizzo reduceè più succinto:

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

o più conveniente se non c'è un accumulatore ragionevole:

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

In generale, foldè più potente con un accumulatore di tipo arbitrario:

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

18

foldè una funzione molto più preziosa di reduce. È possibile definire molte funzioni diverse in termini di fold.

reduceè solo un sottoinsieme di fold.

Definizione di piega:

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

Esempi di funzioni definite in termini di piega:

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
Definisci il tuo in modo folddiverso da List.foldcome il tipo di List.foldè ('a -> 'b -> 'a) -> 'a -> 'b list -> 'a, ma nel tuo caso ('a -> 'b -> 'b) -> 'b -> 'a list -> 'b. Solo per renderlo esplicito. Inoltre, la tua implementazione di append è sbagliata. Funzionerebbe se si aggiunge un bind, ad esempio List.collect id (fold (fun x y -> x :: y) [] [xs;ys]), o si sostituisce cons con l'operatore append. Quindi append non è il miglior esempio in questo elenco.
jpe
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.