F#功能耦合迭代:性能问题和首选功能样式

时间:2017-03-18 17:03:58

标签: .net data-structures f# functional-programming purely-functional

我正在阅读优秀的书籍#34;真实世界的功能编程"由Tomas Petricek和Jon Skeet(两位SO大师,顺便说一句,感谢他们两位)。 以下函数(第277页)介绍了一种计算阵列上三点平均值的方法,将边值视为特殊值:

let blurArray (arr:float[]) = 
   let res = Array.create arr.Length 0.0
   res.[0] <- (arr.[0] + arr.[1]) / 2.0
   res.[arr.Length-1] <- (arr.[arr.Length - 2] + arr.[arr.Length -1 ] )/2.0
   for i in 1 .. arr.Length - 2 do
      res.[i] <- (arr.[i-1] + arr.[i] + arr.[i+1]) / 3.0
   res

我理解该函数对于外部世界是不可变的,即使内部采用变异和赋值。而且,虽然它确实是必要的,但我可以编写并采用保留声明式的风格。不过,我试图提出一个功能性解决方案,作为练习以更熟悉更高阶函数等。我将报告我的解决方案,然后将表达我的问题。首先我定义一个辅助函数

let computeElementsAverage (myArray:float[]) myIndex (myElement:float) =
   match myIndex with
   | 0 -> (myArray.[0..1] |> Array.average )
   | lastInd when lastInd = (myArray.Length -1 ) -> (myArray.[lastInd-1..lastInd] |> Array.average )
   | anIndex -> (myArray.[anIndex -1 .. anIndex + 1 ] |> Array.average ) 

(我可以抽象出(indeces - &gt; float列表)这是匹配子句中的模式,但我认为这是一种矫枉过正的行为。)

然后,blurArray的等价变为:

let blurArray' (arr:float[]) = 
   let mappingFcn = arr |> computeElementsAverage 
   arr |> (Array.mapi mappingFcn)

最后我的问题:

首先,最重要的推荐功能是什么?我特别不喜欢这样的事实:由于Array.mapi的要求,我被迫在computeElementsAverage中声明元素类型(float)的最后一个元素。有没有更好的方法来做到这一点,避免一个不会被使用的论点?

其次:表现我的代码要慢得多,这是预期的;但它比原始代码快1/10;任何其他解决方案仍然依赖于高阶函数而不会对性能产生如此大的影响?

最后,在数据结构中执行计算的一般首选方法是什么(例如:数组),这取决于几个多个indeces?正如你所看到的,我提出的方法是使用mapi扫描功能并使用包含结构本身的闭包;你最喜欢的方法是什么?

P.S。:( blurArray的原始版本使用int []作为输入,我刚刚修改为float []以在我的版本中使用List.average)

1 个答案:

答案 0 :(得分:8)

我认为一个更好,功能更强的替代方案是使用Array.init。此函数允许您通过指定用于计算每个位置的元素的函数来创建数组。

这仍然看起来非常像原始代码,但它现在不需要任何显式变异(现在隐藏在Array.init中)。事实上,我可能会在本书的修订版本中使用它: - )。

let blurArray (arr:float[]) = 
  Array.init arr.Length (fun i -> 
    if i = 0 then (arr.[0] + arr.[1]) / 2.0
    elif i = arr.Length - 1 then (arr.[arr.Length - 2] + arr.[arr.Length - 1] )/2.0
    else (arr.[i-1] + arr.[i] + arr.[i+1]) / 3.0 )

接下来,您可以决定要在数组中每个点的邻域执行某些操作时编写更多函数 - 您可能需要指定邻域大小。你可以这样写:

let fillArray offset f (arr:float[]) = 
  Array.init arr.Length (fun i -> 
    f arr.[max 0 (i-offset) .. min (arr.Length-1) (i+offset)])

这里,函数f被调用,其中每个点的邻居子阵列与左边的最多offset个邻居和&amp;正确(由于maxmin检查,这不会超出界限)。现在你可以把模糊写成:

arr |> fillArray 1 Seq.average

fillArray中,创建子数组效率会有点低 - 您可以通过使用ArraySegment或将数组的相关部分复制到本地可变数组来加快速度。但它看起来确实很好用!