正如我在recent SO question中提到的那样,我通过解决Project Euler问题来学习F#。
我现在对Problem 3的回答如下:
let rec findLargestPrimeFactor p n =
if n = 1L then p
else
if n % p = 0L then findLargestPrimeFactor p (n/p)
else findLargestPrimeFactor (p + 2L) n
let result = findLargestPrimeFactor 3L 600851475143L
但是,由于有2条执行路径可能导致对findLargestPrimeFactor
的不同调用,我不确定它是否可以针对尾递归进行优化。所以我想出了这个:
let rec findLargestPrimeFactor p n =
if n = 1L then p
else
let (p', n') = if n % p = 0L then (p, (n/p)) else (p + 2L, n)
findLargestPrimeFactor p' n'
let result = findLargestPrimeFactor 3L 600851475143L
由于只有一条路径导致对findLargestPrimeFactor
进行尾调用,因此我认为它确实会针对尾递归进行优化。
所以我的问题:
答案 0 :(得分:8)
你的第一个findLargestPrimeFactor
函数是尾递归的 - 如果所有递归调用都发生在尾部位置,即使有多个递归调用,函数也可以被尾递归。
这是编译函数的IL:
.method public static int64 findLargestPrimeFactor(int64 p,
int64 n) cil managed
{
.custom instance void [FSharp.Core]Microsoft.FSharp.Core.CompilationArgumentCountsAttribute::.ctor(int32[]) = ( 01 00 02 00 00 00 01 00 00 00 01 00 00 00 00 00 )
// Code size 56 (0x38)
.maxstack 8
IL_0000: nop
IL_0001: ldarg.1
IL_0002: ldc.i8 0x1
IL_000b: bne.un.s IL_000f
IL_000d: br.s IL_0011
IL_000f: br.s IL_0013
IL_0011: ldarg.0
IL_0012: ret
IL_0013: ldarg.1
IL_0014: ldarg.0
IL_0015: rem
IL_0016: brtrue.s IL_001a
IL_0018: br.s IL_001c
IL_001a: br.s IL_0026
IL_001c: ldarg.0
IL_001d: ldarg.1
IL_001e: ldarg.0
IL_001f: div
IL_0020: starg.s n
IL_0022: starg.s p
IL_0024: br.s IL_0000
IL_0026: ldarg.0
IL_0027: ldc.i8 0x2
IL_0030: add
IL_0031: ldarg.1
IL_0032: starg.s n
IL_0034: starg.s p
IL_0036: br.s IL_0000
} // end of method LinkedList::findLargestPrimeFactor
else
子句中的第一个分支(即if n % p = 0L
)从IL_0013开始并一直持续到IL_0024,在那里它无条件地分支回到函数的入口点。
else子句中的第二个分支从IL_0026开始并一直持续到函数结束,它再次无条件地分支回函数的开头。对于包含递归调用的else子句的两种情况,F#编译器已将递归函数转换为循环。
答案 1 :(得分:6)
即使有两个不同的递归调用,第一个实现是否可以针对尾递归进行优化?
递归分支的数量与尾递归正交。你的第一个函数是尾递归的,因为findLargestPrimeFactor
是两个分支上的最后一个操作。如果有疑问,您可以尝试在Release
模式下运行该功能(默认情况下打开尾调用优化选项)并观察结果。
如果两个版本都可以针对尾递归进行优化,那么是否有一个更好(更“功能”,更快等)?
两个版本之间略有不同。第二个版本创建了一个额外的元组,但它不会减慢计算量。我认为第一个函数更具可读性和直接性。
要进行挑剔,使用elif
关键字缩短第一个变体:
let rec findLargestPrimeFactor p n =
if n = 1L then p
elif n % p = 0L then findLargestPrimeFactor p (n/p)
else findLargestPrimeFactor (p + 2L) n
另一个版本是使用模式匹配:
let rec findLargestPrimeFactor p = function
| 1L -> p
| n when n % p = 0L -> findLargestPrimeFactor p (n/p)
| n -> findLargestPrimeFactor (p + 2L) n
由于基础算法是相同的,它也不会更快。