正如我在最近的一个SO问题中提到的,我正在通过学习Euler问题来学习F。
现在,我对问题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个执行路径可能导致对findLarggPrimeFactor
的不同调用,我不确定它是否可以针对尾递归进行优化。所以我想出了这个:
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的尾部调用,我认为它确实会针对尾部递归进行优化。
所以我的问题是:
即使有两个不同的递归调用,第一个实现是否可以针对尾部递归进行优化?
递归分支的数量与尾部递归正交。第一个函数是尾部递归的,因为findLargestPrimeFactor是两个分支上的最后一个操作。如果有疑问,您可以尝试在释放模式下运行该函数(默认情况下,尾部调用优化选项处于打开状态),并观察结果。
如果两个版本都可以针对尾部递归进行优化,是否有一个版本比另一个更好(更“实用”、更快等)?
两个版本之间只有一点不同。第二个版本创建了一个额外的元组,但它不会降低那么多计算速度。我认为第一个函数更具可读性,更直截了当。
为了挑剔,第一个变体使用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
既然底层算法一样,也不会更快。
第一个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子句中的第一个分支(即,如果p=0L,则从IL_0013开始,一直持续到IL_0024,在此无条件分支回函数的入口点。
else子句中的第二个分支从IL_0026开始,一直持续到函数结束,在那里它再次无条件地分支回到函数的开始。对于包含递归调用的else子句的两种情况,F#编译器已将递归函数转换为循环。
我试图了解如何将各种递归函数转换为尾递归。我已经查看了许多将斐波那契和阶乘转换为尾递归的示例,并理解了这些示例,但很难跳到具有某种不同结构的问题。一个例子是: 如何将其转换为尾部递归实现? 我已经看过类似的问题,例如:将正常递归转换为尾部递归,但这些似乎并没有转化为这个问题。
我只是想知道这样的函数是否可以递归地完成。我觉得很难,因为它自己叫了两次。 这是我在javascript中的非尾部递归实现。(是的,我知道大多数javascript引擎不支持TCO,但这只是理论上的。)目标是找到给定数组(arr)中具有特定长度(大小)的所有子列表。例如:getSublistsWithFixedSize([1,2,3],2)返回[[1,2]、[1,3]、[2,3]]
我有一个家庭作业问题,它给出了一个递归函数,我必须使用尾部递归来实现它。函数为f(0)=1 f(n)=1 2*f(n-1) 我并不擅长尾部递归,我试着查找示例,但我发现的都是没有斐波那契序列的示例,这没有多大帮助。 我真正拥有的是 我知道尾递归基本上每次调用都计算函数,我只是不知道如何实现它。 编辑:我打了一个错字f(n)应该是1 2*f(n-1)
假设我编写这样的代码: 我如何让Kotlin优化这些相互递归的函数,以便在不抛出StackOverflower错误的情况下运行main?tailrec关键字适用于单函数递归,但没有更复杂的功能。我还看到一个警告,在使用关键字tailrec的地方没有找到尾部调用。也许这对编译器来说太难了?
我对函数式编程很陌生,尤其是下面使用的Scheme。我正在尝试使以下函数是递归的,尾递归的。基本上,该函数的作用是对两个字符串的对齐方式进行评分。当给定两个字符串作为输入时,它会比较每个“列”字符,并根据在称为 scorer 的函数中实现的评分方案(由下面的代码中的函数调用)来累积该对齐的分数。 我有一个想法,用一个帮助函数来累积分数,但我不太确定如何去做,因此我该如何让下面的函数尾递归呢?
本文向大家介绍C#函数式编程中的递归调用之尾递归详解,包括了C#函数式编程中的递归调用之尾递归详解的使用技巧和注意事项,需要的朋友参考一下 关于递归相信大家已经熟悉的不能再熟悉了,所以笔者在这里就不多费口舌,不懂的读者们可以在博客园中找到很多与之相关的博客。下面我们直接切入正题,开始介绍尾递归。 尾递归 普通递归和尾递归如果仅仅只是从代码的角度出发来看,我们可能发现不了他的特点,所以笔者利用两张堆