功能惯用FFT

时间:2018-11-19 07:49:58

标签: functional-programming f# fft

我编写了这个基数为2的FFT,目的是使其在功能上惯用而又不牺牲太多性能:

let reverse x bits =
    let rec reverse' x bits y =
        match bits with
        | 0 -> y
        | _ -> ((y <<< 1) ||| (x &&& 1))
               |> reverse' (x >>> 1) (bits - 1) 
     reverse' x bits 0 

let radix2 (vector: Complex[]) (direction: int) =
    let z = vector.Length
    let depth = floor(Math.Log(double z, 2.0)) |> int
    if (1 <<< depth) <> z then failwith "Vector length is not a power of 2"

    // Complex roots of unity; "twiddle factors"
    let unity: Complex[] =
        let xpn = float direction * Math.PI / double z
        Array.Parallel.init<Complex> (z/2) (fun i ->
            Complex.FromPolarCoordinates(1.0, (float i) * xpn))

    // Permutes elements of input vector via bit-reversal permutation
    let pvec = Array.Parallel.init z (fun i -> vector.[reverse i depth])

    let outerLoop (vec: Complex[]) =
        let rec recLoop size =
            if size <= z then
                let mid, step = size / 2, z / size
                let rec inrecLoop i =
                    if i < z then
                        let rec bottomLoop idx k =
                            if idx < i + mid then
                                let temp = vec.[idx + mid] * unity.[k]
                                vec.[idx + mid] <- (vec.[idx] - temp)
                                vec.[idx] <- (vec.[idx] + temp)
                                bottomLoop (idx + 1) (k + step)
                        bottomLoop i 0
                        inrecLoop (i + size)
                inrecLoop 0
                recLoop (size * 2)
        recLoop 2
        vec

    outerLoop pvec

outerLoop段是我所写过的最大的嵌套尾递归混乱。我在Wikipedia文章中为Cooley-Tukey algorithm复制了该算法,但是我认为可以使用高阶函数实现的唯一功能构造会严重影响性能和内存效率。是否还有其他解决方案可以产生相同的结果而又不会导致严重的性能下降,同时仍然是惯用的?

1 个答案:

答案 0 :(得分:2)

我不是算法工作原理的专家,因此可能会有不错的功能实现,但是值得注意的是,在F#中使用局部突变是完全习惯用法。

您的radix2函数从外部开始起作用-它以vector数组为输入,从不对其进行任何突变,创建一个新的数组pvec,然后对其进行初始化(使用一些突变)方式),然后将其返回。这与Array.map之类的内置函数使用的模式类似(它将初始化一个新数组,对其进行变异然后返回)。这通常是一种明智的处理方式,因为某些算法最好使用变异来编写。

在这种情况下,也可以使用局部可变变量和循环是完全合理的。与尾递归版本相比,这样做将使您的代码更具可读性。我还没有测试过,但是我对outerLoop函数的幼稚翻译只是使用三个嵌套循环-像这样:

let mutable size = 2
while size <= z do
    let mid, step = size / 2, z / size
    let mutable i = 0
    while i < z do
        for j in 0 .. mid - 1 do
            let idx, k = i + j, step * j
            let temp = pvec.[idx + mid] * unity.[k]
            pvec.[idx + mid] <- (pvec.[idx] - temp)
            pvec.[idx] <- (pvec.[idx] + temp)
        i <- i + size
    size <- size * 2

这可能并不完全正确(我只是重构您的代码),但是在这种情况下,与使用复杂的嵌套尾递归函数相比,它实际上更惯用了。