简体   繁体   English

F#功能耦合迭代:性能问题和首选功能风格

[英]F# functional coupled iteration: performance issue and preferred functional style

I am getting through the excellent book "Real-World Functional Programming" by Tomas Petricek and Jon Skeet (two SO gurus, btw, thanks to both of them). 我正在阅读Tomas Petricek和Jon Skeet的优秀书籍“真实世界的功能编程”(两位SO大师,顺便说一遍,感谢他们两位)。 The following function, at page 277, introduces a way of computing a three point average on an array taking into account side values as special ones: 以下函数(第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

I understand the function is immutable for the external world, even if internally adopts mutation and assignment. 我理解该函数对于外部世界是不可变的,即使内部采用变异和赋值。 Also, while it is truly imperative, it can me composed and be adopted preserving a declarative style. 而且,虽然它确实是必要的,但我可以编写并采用保留声明式的风格。 Nonethless, I tried to come up with a functional solution, as exercise to get more familiar with higher order functions and the like. 不过,我试图提出一个功能性解决方案,作为练习以更熟悉更高阶函数等。 I will report my solution and then will express my questions. 我将报告我的解决方案,然后将表达我的问题。 First I define a helper function 首先,我定义一个辅助函数

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 ) 

(I could have abstracted out the (list of indeces -> float) which is a pattern on the match clauses, but I considered it an overkill). (我可以抽象出(indeces - > float列表)这是匹配子句中的模式,但我认为这是一种矫枉过正的行为)。

Then the equivalent of the blurArray becomes: 然后,blurArray的等效变为:

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

Finally my questions: 最后我的问题:

first and foremost what would be the most recommended functional way of proceeding? 首先,最重要的推荐功能是什么? I specifically do not like the fact that I am forced to declare a final element of element type (float) in computeElementsAverage because of the requirements of Array.mapi. 我特别不喜欢这样的事实:由于Array.mapi的要求,我被迫在computeElementsAverage中声明元素类型(float)的最后一个元素。 Are there any better methods to do that, avoiding an argument which will not be used? 有没有更好的方法来做到这一点,避免一个不会被使用的论点?

Secondly: performancewise my code is much slower, which was expected; 其次:在性能方面,我的代码要慢得多,这是预期的; but it runs 1/10 faster than the original code; 但它比原始代码快1/10; any other solution which would still rely on high order functions without getting such a big hit on performance? 任何其他解决方案仍然依赖于高阶函数而不会在性能上受到如此大的打击?

Finally what is the general preferred way to perform computation accross a data structure (eg: arrays) which depends on several multiple indeces? 最后,通过数据结构(例如:数组)执行计算的一般首选方法是什么,这取决于几个多个indeces? The way I came up with,as you can see, is using the mapi scanning function and using a closure which contains the structure itself; 正如你所看到的,我提出的方法是使用mapi扫描功能并使用包含结构本身的闭包; what is your preferred method? 你最喜欢的方法是什么?

PS: (the original version of blurArray uses int[] as input, I have just modified into float[] to use List.average in my version) PS :( blurArray的原始版本使用int []作为输入,我刚刚修改为float []以在我的版本中使用List.average)

I think a nice and more functional alternative would be to use Array.init . 我认为一个更好的,更实用的替代方案是使用Array.init This function lets you create an array by specifying a function that is used to calculate the element for each location. 此函数允许您通过指定用于计算每个位置的元素的函数来创建数组。

This still looks very much like the original code, but it now does not need any explicit mutation (this is now hidden in Array.init ). 这仍然看起来非常像原始代码,但它现在不需要任何显式变异(现在隐藏在Array.init )。 In fact, I'd probably use this in a revised version of the book :-). 事实上,我可能会在本书的修订版本中使用它:-)。

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 )

Next, you could decide that there are more functions you want to write where you do something with the neighbourhood of each point in the array - and you may want to specify the neighbourhood size. 接下来,您可以决定在要对阵列中每个点的邻域执行某些操作时要编写更多函数 - 您可能需要指定邻域大小。 You could write something like: 你可以这样写:

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

Here, the function f is called with a sub-array of neighbours of each point with at most offset neighbours to the left & right (this does not get out of bounds thanks to the max and min checks). 这里,函数f用每个点的邻居的子数组调用,左边和右边最多offset邻居(由于maxmin检查,这不会超出界限)。 Now you can write blur as: 现在你可以把模糊写成:

arr |> fillArray 1 Seq.average

In the fillArray , creating the sub-array will be a bit inefficient - you could probably make it faster by using ArraySegment or by copying relevant parts of the array to a local mutable array. fillArray ,创建子数组效率会有点低 - 通过使用ArraySegment或将数组的相关部分复制到本地可变数组,可能会使其更快。 But it does look quite nice and functional! 但它看起来确实很好用而且功能齐全!

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM