创建中间值时我应该存储它吗?
我正在尝试学习 F#,因此我访问了 Project Euler 并且我目前正在研究 问题 3。
13195 的质因数是 5、7、 13 和 29。
最大的素数是多少 数字 600851475143 的因数?
需要考虑的一些事情:
- 我的首要任务是学习良好的功能习惯。
- 我的第二要务是我希望它快速高效。
在下面的代码中,我标记了与此问题相关的部分。
let isPrime(n:int64) =
let rec check(i:int64) =
i > n / 2L or (n % i <> 0L && check(i + 1L))
check(2L)
let greatestPrimeFactor(n:int64) =
let nextPrime(prime:int64):int64 =
seq { for i = prime + 1L to System.Int64.MaxValue do if isPrime(i) then yield i }
|> Seq.skipWhile(fun v -> n % v <> 0L)
|> Seq.hd
let rec findNextPrimeFactor(number:int64, prime:int64):int64 =
if number = 1L then prime else
//************* No variable
(fun p -> findNextPrimeFactor(number / p, p))(nextPrime(prime))
//*************
//************* Variable
let p = nextPrime(prime)
findNextPrimeFactor(number / p, p)
//*************
findNextPrimeFactor(n, 2L)
更新
根据一些反馈,我重构了代码,速度提高了 10 倍。
module Problem3
module private Internal =
let execute(number:int64):int64 =
let rec isPrime(value:int64, current:int64) =
current > value / 2L or (value % current <> 0L && isPrime(value, current + 1L))
let rec nextPrime(prime:int64):int64 =
if number % prime = 0L && isPrime(prime, 2L) then prime else nextPrime(prime + 1L)
let rec greatestPrimeFactor(current:int64, prime:int64):int64 =
if current = 1L then prime else nextPrime(prime + 1L) |> fun p -> greatestPrimeFactor(current / p, p)
greatestPrimeFactor(number, 2L)
let execute() = Internal.execute(600851475143L)
更新
我要感谢大家的建议。这个最新版本是我收到的所有建议的汇编。
module Problem3
module private Internal =
let largestPrimeFactor number =
let rec isPrime value current =
current > value / 2L || (value % current <> 0L && isPrime value (current + 1L))
let rec nextPrime value =
if number % value = 0L && isPrime value 2L then value else nextPrime (value + 1L)
let rec find current prime =
match current / prime with
| 1L -> prime
| current -> nextPrime (prime + 1L) |> find current
find number (nextPrime 2L)
let execute() = Internal.largestPrimeFactor 600851475143L
I am trying to learn F# so I paid a visit to Project Euler and I am currently working on Problem 3.
The prime factors of 13195 are 5, 7,
13 and 29.What is the largest prime
factor of the number 600851475143?
Some things to consider:
- My first priority is to learn good functional habits.
- My second priority is I would like it to be fast and efficient.
Within the following code I have marked the section this question is regarding.
let isPrime(n:int64) =
let rec check(i:int64) =
i > n / 2L or (n % i <> 0L && check(i + 1L))
check(2L)
let greatestPrimeFactor(n:int64) =
let nextPrime(prime:int64):int64 =
seq { for i = prime + 1L to System.Int64.MaxValue do if isPrime(i) then yield i }
|> Seq.skipWhile(fun v -> n % v <> 0L)
|> Seq.hd
let rec findNextPrimeFactor(number:int64, prime:int64):int64 =
if number = 1L then prime else
//************* No variable
(fun p -> findNextPrimeFactor(number / p, p))(nextPrime(prime))
//*************
//************* Variable
let p = nextPrime(prime)
findNextPrimeFactor(number / p, p)
//*************
findNextPrimeFactor(n, 2L)
Update
Based off some of the feedback I have refactored the code to be 10 times faster.
module Problem3
module private Internal =
let execute(number:int64):int64 =
let rec isPrime(value:int64, current:int64) =
current > value / 2L or (value % current <> 0L && isPrime(value, current + 1L))
let rec nextPrime(prime:int64):int64 =
if number % prime = 0L && isPrime(prime, 2L) then prime else nextPrime(prime + 1L)
let rec greatestPrimeFactor(current:int64, prime:int64):int64 =
if current = 1L then prime else nextPrime(prime + 1L) |> fun p -> greatestPrimeFactor(current / p, p)
greatestPrimeFactor(number, 2L)
let execute() = Internal.execute(600851475143L)
Update
I would like to thank everyone for there advice. This latest version is a compilation of all the advice I received.
module Problem3
module private Internal =
let largestPrimeFactor number =
let rec isPrime value current =
current > value / 2L || (value % current <> 0L && isPrime value (current + 1L))
let rec nextPrime value =
if number % value = 0L && isPrime value 2L then value else nextPrime (value + 1L)
let rec find current prime =
match current / prime with
| 1L -> prime
| current -> nextPrime (prime + 1L) |> find current
find number (nextPrime 2L)
let execute() = Internal.largestPrimeFactor 600851475143L
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(4)
通过练习,函数式编程会变得更容易、更自动化,因此,如果您第一次尝试时没有完全正确,请不要担心。
考虑到这一点,让我们看一下您的示例代码:
您的
无变量
版本很奇怪,不要使用它。我喜欢你带有显式 let 绑定的版本。另一种写法是:
这样写ok并且偶尔有用,但仍然有点奇怪。大多数时候,我们使用
|>
来柯里化值而不需要命名我们的变量(以“pointfree”风格)。尝试预测您的函数将如何使用,如果可能的话,重写它,以便您可以将它与管道运算符一起使用,而无需显式声明变量。例如:不再使用命名参数 :)
好的,现在我们已经解决了这个问题,让我们看看您的
isPrime
函数:您可能听说过使用递归而不是循环,仅此而已是对的。但是,只要有可能,您应该使用折叠、映射或高阶函数抽象出递归。这样做的原因有两个:
它的可读性更强一点,
不正确的递归编写会导致堆栈溢出。例如,您的函数不是尾递归的,因此它会在
n
的较大值上崩溃。我会像这样重写
isPrime
:大多数时候,如果您可以抽象出显式循环,那么您只需对输入序列应用转换,直到获得结果:
我们不这样做在此版本中甚至有中间变量。凉爽!
大多数时候,F# 在速度方面与 C# 相当,或者说它“足够快”。如果您发现代码需要很长时间才能执行,则可能意味着您使用了错误的数据结构或错误的算法。有关具体示例,请阅读对此问题的评论。
因此,我编写的代码是“优雅的”,因为它简洁,给出了正确的结果,并且不依赖于任何技巧。不幸的是,它的速度不是很快。首先:
它使用试除法来创建素数序列,而埃拉托斯特尼筛法会快得多。 [编辑:我编写了这个筛子的一个有点幼稚的版本,它不适用于大于 Int32.MaxValue 的数字,因此我删除了代码。]
阅读维基百科关于 素数计数函数,它将为您提供有关计算前
n
个素数以及估计nth
素数的上限和下限。[编辑:我包含了一些带有埃拉托斯特尼筛的有点天真的实现的代码。它仅适用于小于 int32.MaxValue 的输入,因此它可能不适合项目 euler。]
Functional programming becomes easier and more automatic with practice, so don't sweat it if you don't get it absolutely right on the first try.
With that in mind, let's take your sample code:
Your
no variable
version is just weird, don't use it. I like your version with the explicit let binding.Another way to write it would be:
Its ok and occasionally useful to write it like this, but still comes across as a little weird. Most of the time, we use
|>
to curry values without needing to name our variables (in "pointfree" style). Try to anticipate how your function will be used, and if possible, re-write it so you can use it with the pipe operator without explicit declared variables. For example:No more named args :)
Ok, now that we have that out of the way, let's look at your
isPrime
function:You've probably heard to use recursion instead of loops, and that much is right. But, wherever possible, you should abstract away recursion with folds, maps, or higher order functions. Two reasons for this:
its a little more readable, and
improperly written recursion will result in a stack overflow. For example, your function is not tail recursive, so it'll blow up on large values of
n
.I'd rewrite
isPrime
like this:Most of the time, if you can abstract away your explicit looping, then you're just applying transformations to your input sequence until you get your results:
We don't even have intermediate variables in this version. Coolness!
Most of the time, F# is going to be pretty comparable with C# in terms of speed, or it's going to be "fast enough". If you find your code takes a long time to execute, it probably means you're using the wrong data structure or a bad algorithm. For a concrete example, read the comments on this question.
So, the code I've written is "elegant" in the sense that its concise, gives the correct results, and doesn't rely on any trickery. Unfortunately, its not very fast. For start:
it uses trial division to create a sequence of primes, when the Sieve of Eratosthenes would be much faster. [Edit: I wrote a somewhat naive version of this sieve which didn't work for numbers larger than Int32.MaxValue, so I've removed the code.]
read Wikipedia's article on the prime counting function, it'll give you pointers on calculating the first
n
primes as well as estimating the upper and lower bounds for thenth
prime.[Edit: I included some code with a somewhat naive implementation of a sieve of eratosthenes. It only works for inputs less than int32.MaxValue, so it probably isn't suitable for project euler.]
关于“良好的功能习惯”或者说良好的实践,我看到了三件小事。在序列中使用产量比仅过滤更难阅读。类型推断语言中不必要的类型注释会导致重构困难,并使代码更难阅读。如果您发现这很困难,请不要太过分并尝试删除每个类型注释。最后创建一个 lambda 函数,该函数仅将一个值用作临时变量,从而降低了可读性。
就个人风格而言,我更喜欢更多的空间,并且仅当数据分组在一起有意义时才使用元组参数。
我会像这样编写你的原始代码。
您更新的代码最适合您的方法。你必须使用不同的算法,比如 Yin Zhu 的答案,才能走得更快。我编写了一个测试来检查 F# 是否使“检查”函数尾部递归,并且确实如此。
Concerning "good functional habit" or rather good practice I see three minor things. Using the yield in your sequence is a little harder to read than just filter. Unnecessary type annotations in a type inferred language leads to difficult refactoring and makes the code harder to read. Don't go overboard and try to remove every type annotation though if you're finding it difficult. Lastly making a lambda function which only takes a value to use as a temp variable reduces readability.
As far as personal style goes I prefer more spaces and only using tupled arguments when the data makes sense being grouped together.
I'd write your original code like this.
Your updated code is optimal for your approach. You would have to use a different algorithm like Yin Zhu answer to go faster. I wrote a test to check to see if F# makes the "check" function tail recursive and it does.
变量 p实际上是一个名称绑定,而不是一个变量。使用名称绑定并不是一个坏的风格。而且它更具可读性。
nextPrime
的惰性风格很好,它实际上在整个程序中只对每个数字进行一次素数测试。我的解决方案
我基本上将 num 除以 2、3、4..,并且不考虑任何素数。因为如果我们将 num 全部除以 2,那么我们将无法
在这个数字上除以 4,8 等,我的解决方案更快:
the variable p is actually a name binding, not a variable. Using name binding is not a bad style. And it is more readable. The lazy style of
nextPrime
is good, and it actually prime-test each number only once during the whole program.My Solution
I basically divides num from 2, 3, 4.. and don't consider any prime numbers. Because if we divides all 2 from num, then we won't be able to divide it by 4,8, etc.
on this number, my solution is quicker:
我认为带有临时绑定的代码更容易阅读。创建匿名函数然后立即将其应用于其他情况下的值是很不寻常的。如果您确实想避免使用临时值,我认为在 F# 中最惯用的方法是使用
(|>)
运算符将值通过管道传输到匿名函数中,但我仍然认为这不太可读。I think that the code with the temporary binding is significantly easier to read. It's pretty unusual to create an anonymous function and then immediately apply it to a value as you do in the other case. If you really want to avoid using a temporary value, I think that the most idiomatic way to do that in F# would be to use the
(|>)
operator to pipe the value into the anonymous function, but I still think that this isn't quite as readable.