简体   繁体   English

在F#中重写C#代码

[英]Rewriting C# code in F#

Just messing about with F# and I was trying to create a basic Lagrange Interpolation function based on this C# version (copied from a C++ wiki entry): 只是搞乱了F#,我试图创建一个基于这个C#版本的基本拉格朗日插值函数(从C ++ wiki条目复制):

    double Lagrange(double[] pos, double[] val, double desiredPos)
    {
        double retVal = 0;

        for (int i = 0; i < val.Length; ++i)
        {
            double weight = 1;

            for (int j = 0; j < val.Length; ++j)
            {
                // The i-th term has to be skipped
                if (j != i)
                {
                    weight *= (desiredPos - pos[j]) / (pos[i] - pos[j]);
                }
            }

            retVal += weight * val[i];
        }

        return retVal;
    }

The best I could come up with using my limited knowledge of F# and functional programming was: 使用我对F#和函数式编程的有限知识,我能想到的最好的是:

let rec GetWeight desiredPos i j (pos : float[]) weight = 
   match i with
   | i when j = pos.Length -> weight
   | i when i = j -> GetWeight desiredPos i (j+1) pos weight 
   | i -> GetWeight desiredPos i (j+1) pos (weight * (desiredPos - pos.[j])/(pos.[i] - pos.[j]) ) 

let rec Lagrange (pos : float[]) (vals : float[]) desiredPos result counter = 
   match counter with
   | counter when counter = pos.Length -> result
   | counter -> Lagrange pos vals desiredPos (result + (GetWeight desiredPos counter 0 pos 1.0)* vals.[counter]) (counter+1)

Can someone provide a better/tidier F# version based on the same C# code? 有人可以根据相同的 C#代码提供更好/更整洁的F#版本吗?

Folding over sequences is a common way to replace loops with an accumulator. 折叠序列是用累加器替换循环的常用方法。

let Lagrange(pos:_[], v:_[], desiredPos) =
  seq {0 .. v.Length-1} 
  |> Seq.fold (fun retVal i -> 
      seq {for j in 0 .. pos.Length-1 do if i <> j then yield j} 
      |> Seq.fold (fun w j -> w * (desiredPos - pos.[j]) / (pos.[i] - pos.[j])) 1.0
      |> (fun weight -> weight * v.[i] + retVal)) 0.0

The part that makes your functional solution ugly is skipping the i'th element, which means indices. 使你的功能解决方案难看的部分是跳过第i个元素,这意味着索引。 Pull that out into a reusable function so that all the ugly index handling is isolated. 将其拉出成可重复使用的功能,以便隔离所有丑陋的索引处理。 I call mine RoundRobin. 我叫我的RoundRobin。

let RoundRobin l = seq {
  for i in {0..Seq.length l - 1} do
    yield (Seq.nth i l, Seq.take i l |> Seq.append <| Seq.skip (i+1) l)
}

It could be a lot uglier if you want to produce an efficient version, though. 但是,如果你想生产一个高效的版本,可能会更加丑陋。

I couldn't find product in the Seq module, so I wrote my own. 我在Seq模块中找不到product ,所以我写了自己的产品。

let prod (l : seq<float>) = Seq.reduce (*) l

Now producing the code is fairly simple: 现在生成代码非常简单:

let Lagrange pos value desiredPos = Seq.sum (seq {
  for (v,(p,rest)) in Seq.zip value (RoundRobin pos) do
    yield v * prod (seq { for p' in rest do yield (desiredPos - p') / (p - p') })
})

RoundRobin ensures that pos[i] is not included with the rest of pos in the inner loop. RoundRobin确保pos [i]不包含在内循环中的其余pos中。 To include the val array, I zipped it with the round-robinned pos array. 为了包含val数组,我用循环的pos数组压缩它。

The lesson here is that indexing is very ugly in a functional style. 这里的教训是索引在功能样式中非常难看。 Also I discovered a cool trick: |> Seq.append <| 我还发现了一个很酷的技巧: |> Seq.append <| gives you infix syntax for appending sequences. 为您提供附加序列的中缀语法。 Not quite as nice as ^ though. 虽然不如^好。

I think this works fine as imperative code: 我认为这可以作为命令式代码正常工作:

let LagrangeI(pos:_[], v:_[], desiredPos) =
    let mutable retVal = 0.0
    for i in 0..v.Length-1 do
        let mutable weight = 1.0
        for j in 0..pos.Length-1 do
            // The i-th term has to be skipped
            if j <> i then
                weight <- weight * (desiredPos - pos.[j]) / (pos.[i] - pos.[j])
        retVal <- retVal + weight * v.[i]
    retVal

but if you want functional, some folds (along with mapi since you often need to carry the indices along) work well: 但是如果你想要功能,一些折叠(以及mapi,因为你经常需要携带索引)工作得很好:

let LagrangeF(pos:_[], v:_[], desiredPos) =
    v |> Seq.mapi (fun i x -> i, x)
      |> Seq.fold (fun retVal (i,vi) ->
        let weight = 
            pos |> Seq.mapi (fun j x -> j<>i, x) 
                |> Seq.fold (fun weight (ok, posj) ->
                    if ok then
                        weight * (desiredPos - posj) / (pos.[i] - posj)
                    else
                        weight) 1.0
        retVal + weight * vi) 0.0

I don't know the math here, so I used some random values to test to (hopefully) ensure I screwed nothing up: 我不知道这里的数学,所以我用了一些随机值来测试(希望)确保我搞砸了:

let pos = [| 1.0; 2.0; 3.0 |]
let v = [|8.0; 4.0; 9.0 |]

printfn "%f" (LagrangeI(pos, v, 2.5))  // 5.375
printfn "%f" (LagrangeF(pos, v, 2.5))  // 5.375

Here's a non-recursive solution. 这是一个非递归的解决方案。 It's a bit funky because the algorithm requires indices, but hopefully it shows how F#'s functions can be composed: 它有点时髦,因为算法需要索引,但希望它能说明F#的函数是如何组成的:

let Lagrange (pos : float[]) (vals : float[]) desiredPos = 
    let weight pos desiredPos (i,v) =
        let w = pos |> Array.mapi (fun j p -> j,p)
                    |> Array.filter (fun (j,p) -> i <> j)
                    |> Array.fold (fun acc (j,p) -> acc * (desiredPos - p)/(pos.[i] - p)) 1.
        w * v
    vals |> Array.mapi (fun i v -> i,v)
         |> Array.sumBy (weight pos desiredPos)
            let rec GetWeight desiredPos i j (pos : float[]) weight = 
               if j = pos.Length then weight
               elif i = j then GetWeight desiredPos i (j+1) pos weight 
               else GetWeight desiredPos i (j+1) pos (weight * (desiredPos - pos.[j])/(pos.[i] - pos.[j]) ) 

            let rec Lagrange (pos : float[]) (vals : float[]) desiredPos result counter = 
               if counter = pos.Length then result
               else Lagrange pos vals desiredPos (result + (GetWeight desiredPos counter 0 pos 1.0)* vals.[counter]) (counter+1)

Personally I think that simple if/elif/else constructs look here much better without such overheads as 就个人而言,我认为简单的if / elif / else结构在没有这样的开销的情况下看起来好多了

match i with   
|i when i=...

If you're just messing about then here's a version similar to Brian's that uses function currying and the tuple pipe operator. 如果你只是搞乱,那么这里有一个类似于Brian的版本,它使用函数currying和元组管道运算符。

let Lagrange(pos:_[], v:_[], desiredPos) =
    let foldi f state = Seq.mapi (fun i x -> i, x) >> Seq.fold f state
    (0.0, v) ||> foldi (fun retVal (i, posi) -> 
        (1.0, pos) ||> foldi (fun weight (j, posj) -> 
            if j <> i then
                (desiredPos - posj) / (posi - posj)
            else
                1.0)
        |> (fun weight -> weight * posi + retVal))

My attempt: 我的尝试:

let Lagrange(p:_[], v, desiredPos) =
    let Seq_multiply = Seq.fold (*) 1.0
    let distance i j = if (i=j) then 1.0 else (desiredPos-p.[j])/(p.[i]-p.[j])
    let weight i = p |> Seq.mapi (fun j _ -> distance i j) |> Seq_multiply
    v |> Seq.mapi (fun i vi -> (weight i)*vi) |> Seq.sum

Refactor by making the inner loop a function. 通过使内循环成为函数来重构。 Also we can make the code more straightforward and "understandable" by defining some meaningful functions. 此外,我们可以通过定义一些有意义的函数使代码更直接和“可理解”。

Also, this rewrite highlights a bug in your original code (and all other variants). 此外,此重写突出显示了原始代码(以及所有其他变体)中的错误。 The distance function should actually be: 距离函数实际上应该是:

let distance i j = if (p.[i]=p.[j]) then 1.0 else (desiredPos-p.[j])/(p.[i]-p.[j])

to avoid the general div-by-zero error. 避免一般的div-by-zero错误。 This leads to a generic and indexless solution: 这导致了一个通用的无索引解决方案:

let Lagrange(p, v, desiredPos) =
    let distance pi pj = if (pi=pj) then 1.0 else (desiredPos-pj)/(pi-pj)
    let weight pi vi = p |> Seq.map (distance pi) |> Seq.fold (*) vi
    Seq.map2 weight p v |> Seq.sum

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

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