簡體   English   中英

F#延遲遞歸

[英]F# lazy recursion

我在惰性計算的遞歸中遇到一些問題。 我需要通過牛頓拉夫森法計算平方根。 我不知道如何進行懶惰的評估。 這是我的代碼:

let next x z = ((x + z / x) / 2.);
let rec iterate f x = 
    List.Cons(x, (iterate f (f x)));

let rec within eps list =
    let a = float (List.head list);
    let b = float (List.head (List.tail list));
    let rest = (List.tail (List.tail (list)));
    if (abs(a - b) <= eps * abs(b))
        then b
        else within eps (List.tail (list));
let lazySqrt a0 eps z = 
    within eps (iterate (next z) a0);

let result2 = lazySqrt 10. Eps fvalue;
printfn "lazy approach";
printfn "result: %f" result2;

當然,堆棧溢出異常。

您正在使用渴望評估的F#列表。 在您的示例中,您需要惰性評估和分解列表,因此F#PowerPack的LazyList適合使用:

let next z x = (x + z / x) / 2.

let rec iterate f x = 
    LazyList.consDelayed x (fun () -> iterate f (f x))

let rec within eps list =
    match list with
    | LazyList.Cons(a, LazyList.Cons(b, rest)) when abs(a - b) <= eps * abs(b) -> b
    | LazyList.Cons(a, res) -> within eps res
    | LazyList.Nil -> failwith "Unexpected pattern"

let lazySqrt a0 eps z = 
    within eps (iterate (next z) a0)

let result2 = lazySqrt 10. Eps fvalue
printfn "lazy approach"
printfn "result: %f" result2

請注意,我使用的模式匹配比headtail更慣用。

如果您不介意稍微不同的方法,則Seq.unfold在這里很自然:

let next z x = (x + z / x) / 2.

let lazySqrt a0 eps z =
    a0
    |> Seq.unfold (fun a -> 
            let b = next z a
            if abs(a - b) <= eps * abs(b) then None else Some(a, b))
    |> Seq.fold (fun _ x -> x) a0

如果需要惰性計算,則必須使用適當的工具。 List不是惰性的,它被計算到最后。 您的iterate函數永遠不會結束,因此整個代碼堆棧會在此函數中溢出。

您可以在這里使用Seq
注意Seq.skip幾乎不可避免地導致O(N ^ 2)復雜性。

let next N x = ((x + N / x) / 2.);
let rec iterate f x = seq {
    yield x
    yield! iterate f (f x)
}

let rec within eps list =
    let a = Seq.head list
    let b = list |> Seq.skip 1 |> Seq.head
    if (abs(a - b) <= eps * abs(b))
        then b
        else list |> Seq.skip 1 |> within eps
let lazySqrt a0 eps z = 
    within eps (iterate (next z) a0);

let result2 = lazySqrt 10. 0.0001 42.;
printfn "lazy approach";
printfn "result: %f" result2;
// 6.4807406986501

還有一種方法是使用F#PowerPack中的 LazyList 該代碼在本文中可用。 出於完整性考慮,將其復制到我的答案中:

open Microsoft.FSharp.Collections.LazyList 

let next N (x:float) = (x + N/x) / 2.0

let rec repeat f a = 
    LazyList.consDelayed a (fun() -> repeat f (f a))

let rec within (eps : float)  = function
    | LazyList.Cons(a, LazyList.Cons(b, rest)) when (abs (a - b)) <= eps -> b
    | x -> within eps (LazyList.tail x)

let newton_square a0 eps N = within eps (repeat (next N) a0)

printfn "%A" (newton_square 16.0 0.001 16.0)

一些小注意事項:

  • 您的next功能是錯誤的;
  • eps的含義是相對准確性,而在大多數學術著作中我都看到了絕對准確性 兩者之間的區別在於是否針對b測量,這里: <= eps * abs(b) FPish的代碼將eps視為絕對精度

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM