使用F#循环vs递归

时间:2012-03-05 00:41:49

标签: recursion f#

此处的示例代码解决了项目Euler问题:

  

从数字1开始,顺时针向右移动   方向a 5乘5螺旋形成如下:

 21 22 23 24 25 
 20  7  8  9 10  
 19  6  1  2 11    
 18  5  4  3 12 
 17 16 15 14 13
  

可以验证对角线上的数字之和是   101。

     

1001乘1001对角线上的数字总和是多少?   螺旋以同样的方式形成?

但我的问题是功能性编程风格而不是如何得到答案(我已经拥有它)。我试图通过避免我的解决方案中的命令性循环来教自己一些关于函数式编程的知识,因此提出了以下递归函数来解决问题28:

let answer = 
    let dimensions = 1001
    let max_number = dimensions * dimensions

    let rec loop total increment increment_count current =
        if current > max_number then total
        else
            let new_inc, new_inc_count =
                if increment_count = 4 then increment + 2, 0
                else increment, increment_count + 1
            loop (total + current) new_inc new_inc_count (current + increment)            
    loop 0 2 1 1

然而,在我看来,我的功能有点混乱。以下命令式版本更短更清晰,即使考虑到F#强制您将变量显式声明为可变且不包含+ =运算符这一事实:

let answer = 
    let dimensions = 1001
    let mutable total = 1
    let mutable increment = 2
    let mutable current = 1

    for spiral_layer_index in {1..(dimensions- 1) / 2} do
        for increment_index in {1..4} do
            current <- current + increment
            total <- total + current 
        increment <- increment + 2
    total

无视具有更多数学能力的人通过分析解决问题的事实,是否有更好的方法以功能性方式实现这一点?我也尝试使用Seq.unfold创建一个值序列,然后将生成的序列传递给Seq.sum,但这最终比我的递归版本更加混乱。

2 个答案:

答案 0 :(得分:6)

由于您没有描述您尝试解决的问题,因此此答案仅基于您发布的F#代码。我同意功能版本有点乱,但我相信它可以更清晰。我不太了解命令式解决方案中的嵌套for循环:

for increment_index in {1..4} do 
  current <- current + increment 
  total <- total + current  

您没有使用increment_index进行任何操作,因此您可以将incrementcurrent乘以4并得到相同的结果:

total <- total + 4*current + 10*increment
current <- current + 4*increment

然后你的命令解决方案变为:

let mutable total = 0 
let mutable increment = 2 
let mutable current = 1 

for spiral_layer_index in {1..(dimensions- 1) / 2} do 
  total <- total + 4*current + 10*increment
  current <- current + 4*increment
  increment <- increment + 2 
total 

如果将其重写为递归函数,它将变为:

let rec loop index (total, current, increment) = 
  if index > (dimensions - 1) / 2 then total 
  else loop (index + 1) ( total + 4*current + 10*increment,
                          current + 4*increment, increment + 2 )
let total = loop 1 (0, 2, 1)

同样的事情也可以使用Seq.fold这样写(这更具“功能性”,因为在函数式编程中,你只使用递归来实现基本函数,比如fold被重复使用):

let total, _, _=
  {1 .. (dimensions - 1) / 2} |> Seq.fold (fun (total, current, increment) _ ->
    (total + 4*current + 10*increment, current + 4 * increment, increment + 2)) (0, 1, 2)

注意:我不确定这是否真正实现了你想要的。它只是对命令式解决方案的简化,然后使用递归函数重写它......

答案 1 :(得分:3)

事实上,这是Project Euler Problem 28my F# solution大约在2011年11月21日与Tomas的回答中建议的非常相似:

let problem028 () =
    [1..500]
    |> List.fold (fun (accum, last) n ->
            (accum + 4*last + 20*n, last + 8*n)) (1,1)
    |> fst

实际上,原始问题的解决方案只需要在所有相关正方形的列表上使用单行简单 fold ,其中对角节点处的角点穿过当前对角线元素的累加和和值。折叠是函数式编程的主要习语之一;有一篇很棒的经典论文A tutorial on the universality and expressiveness of fold涵盖了这种核心模式的许多重要方面。