【问题标题】:How to refactor F# code to not use a mutable accumulator?如何重构 F# 代码以不使用可变累加器?
【发布时间】:2015-01-23 04:10:39
【问题描述】:

以下 F# 代码给出了Project Euler problem #7 的正确答案:

let isPrime num =
    let upperDivisor = int32(sqrt(float num))   // Is there a better way?
    let rec evaluateModulo a =
        if a = 1 then
            true
        else
            match num % a with
            | 0 -> false
            | _ -> evaluateModulo (a - 1)
    evaluateModulo upperDivisor

let mutable accumulator = 1   // Would like to avoid mutable values.
let mutable number = 2        // ""

while (accumulator <= 10001) do
    if (isPrime number) then
        accumulator <- accumulator + 1
    number <- number + 1

printfn "The 10001st prime number is %i." (number - 1)  // Feels kludgy.
printfn ""
printfn "Hit any key to continue."
System.Console.ReadKey() |> ignore
  1. 我想避免使用 mutableaccumulatornumber。我还想将while 循环重构为尾递归函数。有什么建议吗?
  2. 关于如何删除显示结果的 (number - 1) 杂物有什么想法吗?
  3. 关于此代码的任何一般性 cmet 或有关如何改进它的建议?

【问题讨论】:

    标签: f# primes


    【解决方案1】:

    循环很好,但尽可能地抽象循环更习惯。

    let isPrime num =
        let upperDivisor = int32(sqrt(float num))
        match num with
        | 0 | 1 -> false
        | 2 -> true
        | n -> seq { 2 .. upperDivisor } |> Seq.forall (fun x -> num % x <> 0)
    
    let primes = Seq.initInfinite id |> Seq.filter isPrime
    let nthPrime n = Seq.nth n primes
    
    printfn "The 10001st prime number is %i." (nthPrime 10001)
    printfn ""
    printfn "Hit any key to continue."
    System.Console.ReadKey() |> ignore
    

    序列是你的朋友 :)

    【讨论】:

    • 嗨朱丽叶,序列是我的朋友! Seq.initInfinate (fun i -> 2*i + 1) |> Seq.filter isPrime 如何避免测试偶数?
    • 哦,我们可以做得更好,看看这个线程中的巫术:stackoverflow.com/questions/2053691
    • 多么快乐。我特别喜欢您如何使用序列表达式来跳过非候选人。有点太困了,无法完全欣赏所有算法,但我期待稍后找到时间:)
    • 优秀。多么漂亮的代码。是的,我正在努力编写更惯用的代码。谢谢。
    • 小修正:你需要nthPrime 10000,因为Seq.nth是从零开始的。
    【解决方案2】:

    你可以参考我的F# for Project Euler Wiki:

    我得到了第一个版本:

    let isPrime n =
        if n=1 then false
        else
            let m = int(sqrt (float(n)))
            let mutable p = true
            for i in 2..m do
                if n%i =0 then p <- false
                               // ~~ I want to break here!
            p
    
    let rec nextPrime n =
        if isPrime n then n
        else nextPrime (n+1)
    
    let problem7 =
        let mutable result = nextPrime 2
        for i in 2..10001 do
            result <- nextPrime (result+1)
        result
    

    在这个版本中,虽然看起来更好,但我仍然不会在数字不是质数时提前打破循环。在 Seq 模块中,exist 和 forall 方法支持提前停止:

    let isPrime n =
        if n<=1 then false
        else
            let m = int(sqrt (float(n)))
            {2..m} |> Seq.exists (fun i->n%i=0) |> not
            // or equivalently :
            // {2..m} |> Seq.forall (fun i->n%i<>0)
    

    请注意,在这个版本的 isPrime 中,通过检查 2 以下的数字,该函数最终在数学上是正确的。

    或者你可以使用尾递归函数来做while循环:

    let isPrime n = 
        let m = int(sqrt (float(n)))
        let rec loop i =
            if i>m then true
            else 
                if n%i = 0 then false
                else loop (i+1)
        loop 2
    

    problem7 的一个更实用的版本是使用 Seq.unfold 生成一个无限素数序列并取该序列的第 n 个元素:

    let problem7b =
        let primes =
            2 |> Seq.unfold (fun p ->
                let next = nextPrime (p+1) in
                Some( p, next ) )
        Seq.nth 10000 primes
    

    【讨论】:

    • 谢谢,我不知道您的 Wiki。看来我们都本能地想要break 一个循环,据我所知,这在 F# 中是不可能的。我喜欢你使用Seq.unfold 的方式。你考虑过使用递归吗?
    • 一般来说,'while' 循环可以用'tail recursion' 代替。在答案中查看我的isPrime 示例。
    【解决方案3】:

    这是我的解决方案,它使用尾递归循环模式,始终允许您避免可变变量并获得中断功能:http://projecteulerfun.blogspot.com/2010/05/problem-7-what-is-10001st-prime-number.html

    let problem7a =
        let isPrime n =
            let nsqrt = n |> float |> sqrt |> int
            let rec isPrime i =
                if i > nsqrt then true //break
                elif n % i = 0 then false //break
                //loop while neither of the above two conditions are true
                //pass your state (i+1) to the next call
                else isPrime (i+1) 
            isPrime 2
    
        let nthPrime n = 
            let rec nthPrime i p count =
                if count = n then p //break
                //loop while above condition not met
                //pass new values in for p and count, emulating state
                elif i |> isPrime then nthPrime (i+2) i (count+1)
                else nthPrime (i+2) p count
            nthPrime 1 1 0
    
        nthPrime 10001
    

    现在,专门解决您在解决方案中遇到的一些问题。

    上面的 nthPrime 函数允许您在任意位置找到素数,这就是它看起来适合您专门查找 1001 素数并使用您的变量名的方法(该解决方案是尾递归的,不会使用可变):

    let prime1001 = 
        let rec nthPrime i number accumulator =
            if accumulator = 1001 then number 
            //i is prime, so number becomes i in our next call and accumulator is incremented
            elif i |> isPrime then prime1001 (i+2) i (accumulator+1) 
            //i is not prime, so number and accumulator do not change, just advance i to the next odd
            else prime1001 (i+2) number accumulator
        prime1001 1 1 0
    

    是的,有一种更好的求平方根的方法:编写自己的通用平方根实现(参考 thisthis 的帖子,了解 G 实现):

    ///Finds the square root (integral or floating point) of n
    ///Does not work with BigRational
    let inline sqrt_of (g:G<'a>) n =
        if g.zero = n then g.zero
        else
            let mutable s:'a = (n / g.two) + g.one
            let mutable t:'a = (s + (n / s)) / g.two
            while t < s do
                s <- t
                let step1:'a = n/s
                let step2:'a = s + step1
                t <- step2 / g.two
            s
    
    let inline sqrtG n = sqrt_of (G_of n) n
    let sqrtn = sqrt_of gn //this has suffix "n" because sqrt is not strictly integral type
    let sqrtL = sqrt_of gL
    let sqrtI = sqrt_of gI
    let sqrtF = sqrt_of gF
    let sqrtM = sqrt_of gM
    

    【讨论】:

    • 谢谢。我真的很喜欢let nsqrt = n |&gt; float |&gt; sqrt |&gt; int
    • 哦,是的,管道与转换运算符配合得很好(int 和 float 是编译器优化的函数,而不是它们可能出现的构造函数)。但由于内置 sqrt 基于浮点数,因此使用转换策略不适用于大的 bigint 或小数。我展示的 sqrt_of 实现非常棒,因为它使用奇异的静态成员约束来处理具有除法、加法和静态成员零和一的任何类型。所以 sqrtn、sqrtL、sqrtI、sqrtF 和 sqrtM 在编译时实际上都被替换为特定类型的实现。
    猜你喜欢
    • 2018-01-17
    • 1970-01-01
    • 1970-01-01
    • 2019-09-03
    • 2020-10-11
    • 1970-01-01
    • 1970-01-01
    • 2016-02-09
    • 1970-01-01
    相关资源
    最近更新 更多