将循环转换为纯函数

时间:2011-09-12 00:28:12

标签: f#

我在c ++中为Project Euler问题编写了这段代码:

int sum = 0;

for(int i =0; i < 1000; i++)
{
    //Check if multiple of 3 but not multiple of 5 to prevent duplicate
    sum += i % 3 == 0 && i % 5 != 0 ? i: 0;
    //check for all multiple of 5, including those of 3
    sum += i % 5 == 0 ? i: 0;
}
    cout << sum;

我正在尝试学习f#并在f#中重写此内容。这就是我到目前为止所做的:

open System

//function to calculate the multiples
let multiple3v5 num =
    num

//function to calculate sum of list items
let rec SumList xs =
    match xs with
    | []    -> 0
    | y::ys -> y + SumList ys

let sum = Array.map multiple3v5 [|1 .. 1000|]

我所拥有的可能是完全无稽之谈......请帮忙吗?

4 个答案:

答案 0 :(得分:4)

您的sumList功能是一个好的开始。它已经在整个列表中迭代(递归),因此您不需要将其包装在另外的Array.map中。您只需要扩展sumList,以便仅在匹配指定条件时才添加数字。

这是一个简化问题的解决方案 - 添加所有可被3整除的数字:

open System

let rec sumList xs =
  match xs with
  | []    -> 0 // If the list is empty, the sum is zero
  | y::ys when y % 3 = 0 -> 
     // If the list starts with y that is divisible by 3, then we add 'y' to the
     // sum that we get by recursively processing the rest of the list
     y + sumList ys
  | y::ys -> 
     // This will only execute when y is not divisible by 3, so we just
     // recursively process the rest of the list and return 
     /// that (without adding current value)
     sumList ys

// For testing, let's sum all numbers divisble by 3 between 1 and 10.
let sum = sumList [ 1 .. 10 ]

这是使用显式递归编写函数的基本方法。在实践中, jpalmer 的解决方案也是我解决它的方法,但是如果你正在学习F#,自己编写一些递归函数是很有用的。

sashang 提到的累加器参数是一种更高级的写入方法。如果要在大输入上运行该函数,则需要这样做(欧拉问题可能就是这种情况)。使用累加器参数时,可以使用尾递归编写该函数,这样即使处理长列表也可以避免堆栈溢出。

基于累加器的版本的想法是该函数采用附加参数,该参数表示到目前为止计算的总和。

let rec sumList xs sumSoFar = ...

当你最初打电话时,你写sumList [ ... ] 0。递归调用不会调用y + sumList xs,而是将y添加到累加器,然后进行递归调用sumList xs (y + sumSoFar)。这样,F#编译器可以进行尾调用优化,它会将代码转换为循环(类似于C ++版本)。

答案 1 :(得分:2)

我不确定从命令式语言解决方案转换是否是开发功能性思维模式的好方法,因为工具(在您的情况下为C ++)已经定义了一种(命令式)解决方案,因此最好坚持原始问题费用。

来自Project Euler的总体任务非常适合掌握许多F#设施。例如,您可以使用列表推导,如下面的代码段

// multipleOf3Or5 function definition is left for your exercise
let sumOfMultiples n =
    [ for x in 1 .. n do if multipleOf3Or5 x then yield x] |> List.sum
sumOfMultiples 999

或者你可以通过利用懒惰来概括@jpalmer建议的解决方案:

Seq.initInfinite id
|> Seq.filter multipleOf3Or5
|> Seq.takeWhile ((>) 1000)
|> Seq.sum

或者你甚至可以利用这个机会来掌握活跃的模式:

let (|DivisibleBy|_) divisior num = if num % divisor = 0 the Some(num) else None
{1..999}
|> Seq.map (fun i ->
    match i with | DivisibleBy 3 i -> i | DivisibleBy 5 i -> i | _ -> 0)
|> Seq.sum

上面的所有三个变体都实现了一个共同的模式,即创建一个具有搜索属性的成员序列,然后通过计算总和来折叠它。

答案 2 :(得分:1)

F#还有更多功能而不仅仅是map - 这个问题建议使用filter和sum,我的方法就像是

let valid n = Left as an exercise

let r =
    [1..1000]
    |> List.filter valid
    |> List.sum
printfn "%i" r

我不想做整个问题,但填写缺失的功能不应该太难

答案 3 :(得分:0)

这是将循环与计数器转换为递归函数的方法。您可以通过将累加器参数传递给保存当前循环计数的循环函数来完成此操作。

例如:

let rec loop acc =
    if acc = 10 then
      printfn "endloop"
    else
      printfn "%d" acc
      loop (acc + 1)

loop 0

acc为10时,这将停止。