2009-10-21 14 views
5

Wystarczy ingerować w F # i starałem się tworzyć podstawową funkcję Lagrange'a interpolacji opartej na tej wersji C# (skopiowane z C++ wpisu wiki):Przepisanie kodu C# F #

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; 
    } 

Najlepszym mogę przyjść z pomocą mojego ograniczoną wiedzę na temat F # i programowania funkcjonalnego było:

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) 

ktoś może zapewnić lepszą/porządniej F # w wersji opartej na samym kod C#?

+3

Myślę, że jest to dobry przykład tego, kiedy imperatywny kod jest łatwiejszy do odczytania i utrzymania niż funkcjonalny. – gradbot

Odpowiedz

4

Składanie sekwencji jest popularnym sposobem zamiany pętli na akumulator.

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 
+0

Dzięki za edycję Brian. – gradbot

1
  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) 

Osobiście uważam, że jeśli prosta/Elif/else konstrukcje wyglądają tutaj dużo lepiej bez takich ogólnych jak

match i with 
|i when i=... 
2

Oto nierekursywnych rozwiązanie. To trochę Funky ponieważ algorytm wymaga indeksów, ale mam nadzieję, że to pokazuje, jak funkcje F # 's mogą składać:

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) 
3

myślę, że to działa kod porządku, KONIECZNIE:

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 

ale jeśli chcesz funkcjonalny, niektóre fałdy (wraz z MAPI, ponieważ często trzeba przeprowadzać indeksy wzdłuż) działa dobrze:

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 

nie wiem matematyki tutaj, więc użyłem kilka losowych wartości do przetestowania do (miejmy nadzieję) e Na razie nic nie skręciłem:

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 
+0

Możesz także pozbyć się mapi, ponieważ twój krotnie akumulator jest krotką zawierającą indeks. v |> fold (fun (retVal, i) posi -> newRetValuefunction, i + 1) (0.0, 0) – gradbot

+0

To daje takie same odpowiedzi jak oryginalny kod C# dla moich danych –

4

Częścią, która sprawia, że ​​twoje funkcjonalne rozwiązanie jest brzydkie, jest pomijanie i'th elementu, co oznacza indeksy. Wyciągnij go do funkcji wielokrotnego użytku, tak aby wszystkie brzydkie obsługi indeksu zostały odizolowane. Wzywam mój 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) 
} 

To może być dużo brzydsze, jeśli chcesz produkować wydajną wersję. Nie można znaleźć product w module Seq, więc napisałem własne.

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

teraz wytwarzania kodu jest stosunkowo prosta:

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 zapewnia poz [i] nie jest dołączony do końca Pos w pętli wewnętrznej. Aby dołączyć tablicę val, zapakowałam ją z użyciem macierzy rundy pos.

Lekcja jest taka, że ​​indeksowanie jest bardzo brzydkie w stylu funkcjonalnym. Również odkryłem fajną sztuczkę: |> Seq.append <| podaje składnię infiksów do dołączania sekwencji. Nie jest to jednak tak ładne, jak ^.

1

Jeśli się tylko o tym mówi, to jest wersja podobna do Briana, która używa funkcji curry i operatora krotki.

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)) 
0

Moja próba:

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 poprzez pętli wewnętrznej funkcji. Ponadto możemy uczynić kod prostszym i "zrozumiałym" poprzez zdefiniowanie pewnych znaczących funkcji.

Również ten przepis wskazuje na błąd w oryginalnym kodzie (i wszystkich innych wariantach). Funkcja odległości powinna być:

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

, aby uniknąć ogólnego błędu div-by-zero. Prowadzi to do ogólnego i bezpisowego rozwiązania:

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