【问题标题】:Difference between fold and reduce?折叠和减少的区别?
【发布时间】:2012-02-21 18:32:57
【问题描述】:

尝试学习 F#,但在尝试区分 foldreduce 时感到困惑。 Fold 似乎做了same thing 但需要一个额外的参数。这两个功能的存在是否有正当理由,或者它们的存在是为了容纳不同背景的人? (例如:C#中的字符串和字符串)

这是从示例中复制的代码 sn-p:

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

【问题讨论】:

  • 你可以写reduce和fold,例如fold f a l可以写成reduce f a::l
  • @Neil - 用reduce 实现fold 比这更复杂 - fold 的累加器类型不必与列表中事物的类型相同!
  • @TomasPetricek 我错了,我本来打算反过来写的。

标签: f# functional-programming reduce fold


【解决方案1】:

Fold 为累加器采用显式初始值,而reduce 使用输入列表的第一个元素作为初始累加器值。

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

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

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

【讨论】:

  • 所以基本上不用fold,您可以简单地将初始值添加到列表的开头并执行reduce?那么fold 有什么意义呢?
  • @Pacerier - fold 的累加器函数有不同的类型:'state -> 'a -> 'state for fold vs 'a -> 'a -> 'a for reduce,因此 reduce 将结果类型限制为与元素类型相同。请参阅下面的Tomas Petricek's answer
【解决方案2】:

除了 Lee 所说的,您可以根据 fold 定义 reduce,但不能(轻松)反过来:

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)

【讨论】:

  • 为什么要定义 reduce 让它在运行时出错?
  • +1 表示fold' & its ability to express reduce' 的一般性说明。有些语言有结构手性的概念(Haskell,我在看你)你可以在这个 wiki 中视觉上向左或向右折叠(en.wikipedia.org/wiki/Fold_%28higher-order_function)。使用恒等构造,其他两个“基本”FP 运算符(过滤器和 fmap)也可以使用现有的“折叠”一流语言构造(它们都是同构构造)来实现。 (cs.nott.ac.uk/~pszgmh/fold.pdf) 见:HoTT,普林斯顿(此评论部分太小,无法包含..)
  • 出于好奇......这是否会使 reduce 的性能比 fold 更快,因为它对类型和异常的假设较少?
【解决方案3】:

让我们看看他们的签名:

> 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,您从累加器s开始应用f

    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

【讨论】:

    【解决方案4】:

    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
    

    【讨论】:

    • 您将foldList.fold 定义不同,因为List.fold 的类型为('a -&gt; 'b -&gt; 'a) -&gt; 'a -&gt; 'b list -&gt; 'a,但在您的情况下为('a -&gt; 'b -&gt; 'b) -&gt; 'b -&gt; 'a list -&gt; 'b。只是为了让它明确。此外,您对 append 的实现是错误的。如果您为其添加绑定,它将起作用,例如List.collect id (fold (fun x y -&gt; x :: y) [] [xs;ys]),或将 cons 替换为 append 运算符。因此 append 不是这个列表中最好的例子。
    猜你喜欢
    • 1970-01-01
    • 2017-11-09
    • 1970-01-01
    • 2013-12-27
    • 1970-01-01
    • 2011-12-05
    • 1970-01-01
    • 1970-01-01
    • 2019-09-10
    相关资源
    最近更新 更多