折叠和缩小之间的区别?


121

尝试学习F#,但在区分折叠缩小时感到困惑。折叠似乎做同样的事情,但是需要一个额外的参数。是否存在这两个功能存在的正当理由,或者它们存在以容纳不同背景的人?(例如:字符串和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])

1
您可以相互书写reduce和fold,例如fold f a l可以写成reduce f a::l
尼尔

9
@Neil-就实现fold而言reduce比这复杂得多-的累加器类型fold不必与列表中的事物类型相同!
Tomas Petricek,2012年

@TomasPetricek我的错误,我原本打算以其他方式编写。
尼尔

Answers:


171

Foldreduce使用累加器的显式初始值,同时使用输入列表的第一个元素作为初始累加器值。

这意味着累加器及其结果类型必须与列表元素类型匹配,而fold累加器是单独提供的,因此它们可能有所不同。这反映在以下类型中:

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

此外reduce,在空的输入列表上引发异常。


因此,从根本上不做fold,您可以简单地将该初始值添加到列表的开头,然后执行reduce?那有什么意义fold呢?
Pacerier '17

2
@Pacerier-fold的累加器函数具有不同的类型:'state -> 'a -> 'statefold相'a -> 'a -> 'a对于reduce,因此reduce约束结果类型与元素类型相同。请参阅下面的Tomas Petricek的答案
李李

178

除了什么李先生说,你可以定义reduce来讲fold,而不是(容易)倒过来:

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)

2
为什么定义reduce使其在运行时可能出错?
Fresheyeball 2016年

+1表示关于fold' & its ability to express reduce 通用性的注释。某些语言具有结构手性的概念(Haskell,我在看着您),您可以在此Wiki(en.wikipedia.org/wiki/Fold_%28higher-order_function)中直观地向左或向右折叠。使用身份构造,其他两个“基本” FP运算符(filter和fmap)也可以使用现有的“折叠”一级语言构造(它们都是同构构造)实现。(cs.nott.ac.uk/~pszgmh/fold.pdf)参见:普林斯顿大学霍特HoTT)(此评论部分太小,无法包含..)
安德鲁(Andrew

出于好奇..这是因为对类型和异常的假设较少,所以降低性能的速​​度快于折叠速度吗?
sksallaj19年

19

让我们看看他们的签名:

> 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仅对一种类型的元素起作用,但其中的累加器和列表元素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

18

fold是比reduce。更为重要的函数。您可以根据定义许多不同的功能fold

reduce只是的子集fold

折叠的定义:

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

1
您可以在定义fold从不同List.fold的类型List.foldIS ('a -> 'b -> 'a) -> 'a -> 'b list -> 'a,但在你的情况('a -> 'b -> 'b) -> 'b -> 'a list -> 'b。只是为了使其明确。另外,您对append的实现是错误的。如果您向其添加绑定(例如)List.collect id (fold (fun x y -> x :: y) [] [xs;ys]),或者用append运算符替换cons,它将起作用。因此,append不是此列表中的最佳示例。
jpe 2015年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.