此处的示例代码解决了项目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,但这最终比我的递归版本更加混乱。
答案 0 :(得分:6)
由于您没有描述您尝试解决的问题,因此此答案仅基于您发布的F#代码。我同意功能版本有点乱,但我相信它可以更清晰。我不太了解命令式解决方案中的嵌套for
循环:
for increment_index in {1..4} do
current <- current + increment
total <- total + current
您没有使用increment_index
进行任何操作,因此您可以将increment
和current
乘以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 28和my 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涵盖了这种核心模式的许多重要方面。