F#,实现fold3,fold4,fold_n

时间:2017-03-30 02:34:46

标签: f#

我有兴趣实现fold3,fold4等,类似于List.fold和List.fold2。 e.g。

// TESTCASE
let polynomial (x:double) a b c = a*x + b*x*x + c*x*x*x
let A = [2.0; 3.0; 4.0; 5.0]
let B = [1.5; 1.0; 0.5; 0.2]
let C = [0.8; 0.01; 0.001; 0.0001]

let result = fold3 polynomial 0.7 A B C
// 2.0 * (0.7   ) + 1.5 * (0.7   )^2 + 0.8    * (0.7   )^3 -> 2.4094
// 3.0 * (2.4094) + 1.0 * (2.4094)^2 + 0.01   * (2.4094)^3 -> 13.173
// 4.0 * (13.173) + 0.5 * (13.173)^2 + 0.001  * (13.173)^3 -> 141.75
// 5.0 * (141.75) + 0.2 * (141.75)^2 + 0.0001 * (141.75)^3 -> 5011.964
//
// Output: result = 5011.964

我的第一种方法是将3个列表A,B,C分组到元组列表中,然后应用list.fold

let fold3 f x A B C =
    List.map3 (fun a b c -> (a,b,c)) A B C
       |> List.fold (fun acc (a,b,c) -> f acc a b c) x

// e.g. creates [(2.0,1.5,0.8);  (3.0,1.0,0.01); ......]

我的第二种方法是声明一个可变数据,并使用List.map3

let mutable result = 0.7
List.map3 (fun a b c ->
               result <- polynomial result a b c  // Change mutable data
               // Output intermediate data
               result) A B C
// Output from List.map3: [2.4094; 13.17327905; 141.7467853; 5011.963942]
// result mutable: 5011.963942

我想知道是否有其他方法可以解决这个问题。谢谢。

4 个答案:

答案 0 :(得分:6)

对于fold3,您可以zip3然后fold

let polynomial (x:double) (a, b, c) = a*x + b*x*x + c*x*x*x
List.zip3 A B C |> List.fold polynomial 0.7

但是如果你想要一般的情况,那么你需要我们称之为&#34; applicative functors&#34;

首先,假设您有一个函数列表和一个值列表。我们暂时假设它们具有相同的大小:

let fs = [ (fun x -> x+1); (fun x -> x+2); (fun x -> x+3) ]
let xs = [3;5;7]

你想做的事情(只是自然的)是将每个函数应用于每个值。使用List.map2

可以轻松完成此操作
let apply fs xs = List.map2 (fun f x -> f x) fs xs

apply fs xs  // Result = [4;7;10]

此操作&#34;适用&#34;这就是为什么这些被称为&#34; applicative functors&#34;。不只是任何一个&ol;仿函数,但 applicative 。 (他们为什么&#34;仿函数&#34;有点复杂的原因)

到目前为止一切顺利。可是等等!如果我的函数列表中的每个函数都返回另一个函数怎么办?

let f1s = [ (fun x -> fun y -> x+y); (fun x -> fun y -> x-y); (fun x -> fun y -> x*y) ]

或者,如果我记得fun x -> fun y -> ...可以用fun x y -> ...的简短形式写成

let f1s = [ (fun x y -> x+y); (fun x y -> x-y); (fun x y -> x*y) ]

如果apply这样的功能列表符合我的价值观怎么办?嗯,当然,我会得到另一个功能列表:

let f2s = apply f1s xs
// f2s = [ (fun y -> 3+y); (fun y -> 5+y); (fun y -> 7+y) ]
嘿,嘿,这是个主意!由于f2s 也是功能列表,我可以再次应用吗?当然,我可以!

let ys = [1;2;3]
apply f2s ys  // Result: [4;7;10]
等等,什么?刚刚发生了什么? 我首先将第一个函数列表应用于xs,然后得到另一个函数列表。然后我将该结果应用于ys,并获得了一个数字列表 我们可以在没有中间变量f2s的情况下重写它:

let f1s = [ (fun x y -> x+y); (fun x y -> x-y); (fun x y -> x*y) ]
let xs = [3;5;7]
let ys = [1;2;3]
apply (apply f1s xs) ys  // Result: [4;7;10]

为了方便起见,此操作apply通常表示为运算符:

let (<*>) = apply
f1s <*> xs <*> ys

看看我在那里做了什么?使用此运算符,它现在看起来非常类似于仅使用两个参数调用该函数。整齐。

但是等等。我们的原始任务怎么样?在原始要求中,我们没有功能列表,我们只有一个功能 好吧,这可以通过另一个操作轻松修复,让我们称之为&#34;先应用&#34;。此操作将采用单个函数(不是列表)和值列表,并将此函数应用于列表中的每个值:

let applyFirst f xs = List.map f xs
哦,等等。那只是map。傻我:-)
为方便起见,此操作通常还有一个操作员名称:

let (<|>) = List.map

现在,我可以做这样的事情:

let f x y = x + y
let xs = [3;5;7]
let ys = [1;2;3]
f <|> xs <*> ys  // Result: [4;7;10]

或者这个:

let f x y z = (x + y)*z
let xs = [3;5;7]
let ys = [1;2;3]
let zs = [1;-1;100]
f <|> xs <*> ys <*> zs  // Result: [4;-7;1000]

纯!我做到了所以我可以立即将任意函数应用于参数列表!

现在,最后,您可以将其应用于原始问题:

let polynomial a b c (x:double) = a*x + b*x*x + c*x*x*x
let A = [2.0; 3.0; 4.0; 5.0]
let B = [1.5; 1.0; 0.5; 0.2]
let C = [0.8; 0.01; 0.001; 0.0001]

let ps = polynomial <|> A <*> B <*> C
let result = ps |> List.fold (fun x f -> f x) 0.7

列表pspolynomial个实例组成,这些实例部分应用于ABC的相应元素,并且仍然期待最终参数x。在下一行,我简单地折叠这个函数列表,将它们中的每一个应用到前一个函数的结果中。

答案 1 :(得分:1)

您可以检查想法的实施情况:

https://github.com/fsharp/fsharp/blob/master/src/fsharp/FSharp.Core/array.fs

  let fold<'T,'State> (f : 'State -> 'T -> 'State) (acc: 'State) (array:'T[]) =
        checkNonNull "array" array
        let f = OptimizedClosures.FSharpFunc<_,_,_>.Adapt(f)
        let mutable state = acc             
        for i = 0 to array.Length-1 do 
            state <- f.Invoke(state,array.[i])
        state

这里有一些实现:

let fold2<'a,'b,'State> (f : 'State -> 'a -> 'b -> 'State) (acc: 'State) (a:'a array) (b:'b array) =
  let mutable state = acc    
  Array.iter2 (fun x y->state<-f state x y) a b
  state

let iter3 f (a: 'a[]) (b: 'b[]) (c: 'c[]) = 
  let f = OptimizedClosures.FSharpFunc<_,_,_,_>.Adapt(f)
  if a.Length <> b.Length || a.Length <> c.Length then failwithf "length"
  for i = 0 to a.Length-1 do 
    f.Invoke(a.[i], b.[i], c.[i])

let altIter3 f (a: 'a[]) (b: 'b[]) (c: 'c[]) = 
  if a.Length <> b.Length || a.Length <> c.Length then failwithf "length"
  for i = 0 to a.Length-1 do 
    f (a.[i]) (b.[i]) (c.[i])

let fold3<'a,'b,'State> (f : 'State -> 'a -> 'b -> 'c -> 'State) (acc: 'State) (a:'a array) (b:'b array) (c:'c array) =
  let mutable state = acc    
  iter3 (fun x y z->state<-f state x y z) a b c
  state

NB。我们没有iter3,所以,实现它。 OptimizedClosures.FSharpFunc只允许最多5个(或7个?)参数。有可用的有限数量的类型插槽。这说得通。当然,你可以在不使用OptimizedClosures的情况下高于此值。

...无论如何,一般来说,你不想一次迭代过多的列表/数组/序列。所以我要警惕不要过高。

...在这种情况下,更好的方法可能是首先从所述列表/数组构建记录或元组。然后,你可以使用已经烘焙过的map和iter。这就是zip / zip3的全部内容(参见:&#34;(array1。[i],array2。[i],array3。[i] )&#34)

    let zip3 (array1: _[]) (array2: _[]) (array3: _[]) = 
        checkNonNull "array1" array1
        checkNonNull "array2" array2
        checkNonNull "array3" array3
        let len1 = array1.Length
        if len1 <> array2.Length || len1 <> array3.Length then invalidArg3ArraysDifferent "array1" "array2" "array3" len1 array2.Length array3.Length
        let res = Microsoft.FSharp.Primitives.Basics.Array.zeroCreateUnchecked len1 
        for i = 0 to res.Length-1 do 
            res.[i] <- (array1.[i],array2.[i],array3.[i])
        res

我目前正在使用数组,因此我的解决方案与这些相关。对于那个很抱歉。这是列表的递归版本。

let fold3 f acc a b c =
  let mutable state = acc
  let rec fold3 f a b c =
    match a,b,c with
    | [],[],[] -> ()
    | [],_,_
    | _,[],_
    | _,_,[] -> failwith "length"
    | ahead::atail, bhead::btail, chead::ctail -> 
        state <- f state ahead bhead chead
        fold3 f atail btail ctail
    fold3 f a b c 

即。我们在函数中定义一个递归函数,该函数作用于/ mutates /更改外部作用域的可变 acc 变量(函数说话中的闭包)。最后,这将返回。

关于这些功能可以推断出多少类型信息非常酷。在上面的数组示例中,大多数情况下我都明确指出了&#39; b&#39; c。这一次,我们让类型推断启动。它知道我们正在处理来自 :: 运算符的列表。那很整洁。

NB。编译器可能展开这种尾递归方法,这样它只是一个幕后循环。通常,在优化之前获得正确的答案。但是,只要提到这一点,作为后来想到的食物。

答案 2 :(得分:0)

我认为现有的答案提供了很好的选择,如果你想概括折叠,这是你原来的问题。但是,如果我只想在polynomialAB中指定的输入上调用C函数,那么我可能不希望引入相当复杂的构造,如具有花哨的运算符的applicative functors到我的代码库。

如果您转置输入数据,问题会变得容易得多,因此您不必使用包含单个变量列表的列表[A; B; C],而是有一个转置列表,其中包含用于计算每个多项式的输入。为此,我们需要transpose function

let rec transpose = function
  | (_::_)::_ as M -> List.map List.head M :: transpose (List.map List.tail M)
  | _ -> []

现在,您可以使用List.map创建一个包含输入的列表,转置它并计算所有多项式:

transpose [A; B; C]
|> List.map (function 
  | [a; b; c] -> polynomial 0.7 a b c
  | _ -> failwith "wrong number of arguments") 

答案 3 :(得分:0)

有很多方法可以解决这个问题。很少提到像第一个zip3所有三个列表,然后运行它。使用像Fyodor Soikin这样的Applicate Functors描述了可以将任何具有任意数量的参数的函数转换为需要列表而不是单个参数的函数。这是一个很好的通用解决方案,适用于任意数量的列表。

虽然这是一个总的好主意,但我有时会感到震惊的是,很少有人使用更多的低级工具。在这种情况下,最好使用递归并了解有关递归的更多信息。

这里的递归是正确的工具,因为我们有不可变的数据类型。但是你可以考虑如何用可变列表和循环首先实现它,如果这有帮助的话。步骤将是:

  1. 将索引从0循环到列表中的元素数量。
  2. 检查每个列表是否都有索引元素
  3. 如果每个列表都有一个元素,那么您将其传递给&#34;文件夹&#34;功能
  4. 如果至少有一个列表没有元素,则中止循环
  5. 递归版本的工作方式完全相同。只有您不使用索引来访问元素。您将从每个列表中删除第一个元素,然后在剩余列表中进行递归。

    否则List.isEmpty是检查List是否为空的函数。您可以使用List.head删除第一个元素,然后获取剩余列表,其中第一个元素已被List.tail删除。这样你就可以写:

    let rec fold3 f acc l1 l2 l3 =
        let h = List.head
        let t = List.tail
        let empty = List.isEmpty
    
        if   (empty l1) || (empty l2) && (empty l3)
        then acc
        else fold3 f (f acc (h l1) (h l2) (h l3)) (t l1) (t l2) (t l3)
    

    if行检查每个列表是否至少有一个元素。如果这是真的 它执行:f acc (h l1) (h l2) (h l3)。因此它执行f并将每个列表的第一个元素作为参数传递给它。结果是新的累加器 下一个fold3电话。

    现在您已经处理了每个列表的第一个元素,您必须切断每个列表的第一个元素,然后继续使用其余列表。您可以使用List.tail或上面的示例(t l1) (t l2) (t l3)来实现这一目标。这些是下一个fold3来电的剩余列表。

    创建fold4fold5fold6等等并不是很难,我认为这是不言自明的。我的一般建议是学习更多关于递归的知识,并尝试编写没有模式匹配的递归List函数。模式匹配并不总是更容易。

    一些代码示例:

    fold3 (fun acc x y z -> x + y + z :: acc)   [] [1;2;3] [10;20;30] [100;200;300] // [333;222;111]
    fold3 (fun acc x y z -> x :: y :: z :: acc) [] [1;2;3] [10;20;30] [100;200;300] // [3; 30; 300; 2; 20; 200; 1; 10; 100]