将程序解决方案转换为功能解决方案F#

时间:2013-12-06 08:17:40

标签: f# functional-programming

我正试图用F#包围函数式编程。我正在努力解决Project Euler问题,我觉得我只是在F#中编写过程代码。例如,这是我对#3的解决方案。

let Calc() =
    let mutable limit = 600851475143L
    let mutable factor = 2L // Start with the lowest prime
    while factor < limit do
        if limit % factor = 0L then 
            begin
                limit <- limit / factor
            end
        else factor <- factor + 1L
    limit

这很好用,但我真正做的就是如何在c#中解决这个问题并将其转换为F#语法。回顾我的几个解决方案,这已成为一种模式。我认为我应该能够在不使用mutable的情况下解决这个问题,但是我无法在程序上思考这个问题。

3 个答案:

答案 0 :(得分:4)

为什么不用递归?

let Calc() =
    let rec calcinner factor limit = 
        if factor < limit then 
            if limit % factor = 0L then
                calcinner factor (limit/factor)
            else
                calcinner (factor + 1L) limit
        else limit
    let limit = 600851475143L
    let factor = 2L // Start with the lowest prime
    calcinner factor limit

答案 1 :(得分:4)

对于算法问题(如项目Euler),您可能希望使用递归来编写大多数迭代(如John建议的那样)。但是,如果您使用例如,即使是可变的命令性代码也是有意义的。哈希表或数组并关心性能。

F#工作得很好的一个领域(遗憾的是)Euler练习项目没有真正涵盖的是设计数据类型 - 所以如果你有兴趣从另一个角度学习F#,请看看Designing with types at F# for Fun and Profit

在这种情况下,您还可以使用Seq.unfold来实现解决方案(通常,您通常可以使用Seq函数编写解决方案以处理序列处理问题 - 尽管此处看起来并不优雅)

let Calc() = 
  // Start with initial state (600851475143L, 2L) and generate a sequence
  // of "limits" by generating new states & returning limit in each step
  (600851475143L, 2L) 
  |> Seq.unfold (fun (limit, factor) ->
    // End the sequence when factor is greater than limit
    if factor >= limit then None
    // Update limit when divisible by factor
    elif limit % factor = 0L then 
      let limit = limit / factor
      Some(limit, (limit, factor))
    // Update factor
    else 
      Some(limit, (limit, factor + 1L)) ) 
  // Take the last generated limit value
  |> Seq.last

答案 2 :(得分:2)

在函数式编程中,当我认为可变时,我认为堆和尝试编写功能更强大的代码时,应该使用堆栈而不是堆。

那么如何在堆栈上获取与函数一起使用的值?

  • 将值放在函数的参数中。
  

让result01 = List.filter(有趣的x - &gt; x%2 = 0)[0; 1; 2; 3; 4; 5]

这里一个函数和一个值列表被硬编码到List.filter参数中。

  • 将值绑定到名称,然后引用名称。
let divisibleBy2 = fun x -> x % 2 = 0
let values = [0;1;2;3;4;5]
let result02 = List.filter divisibleBy2 values

这里list.filter的函数参数绑定到divisibleBy2,list.filter的list参数绑定到值。

  • 创建无名数据结构并将其传递到函数中。
let result03 =
    [0;1;2;3;4;5]
    |> List.filter divisibleBy2

这里list.filter的list参数被前传到list.filter函数。

  • 将函数的结果传递给函数
let result04 =
    [ for i in 1 .. 5 -> i]
    |> List.filter divisibleBy2

现在我们已经拥有堆栈中的所有数据,我们如何仅使用堆栈处理数据?

通常与函数式编程一起使用的模式之一是将数据放入结构中,然后使用递归函数一次处理一个项目。结构可以是列表,树,图等,并且通常使用区分联合来定义。具有一个或多个自引用的数据结构通常与递归函数一起使用。

所以这里有一个例子,我们取一个列表并将所有值乘以2,并在我们前进时将结果放回堆栈。保存新值的堆栈上的变量是accumulator

let mult2 values =
    let rec mult2withAccumulator values accumulator =
        match values with
        | headValue::tailValues -> 
            let newValue = headValue * 2
            let accumulator = newValue :: accumulator
            mult2withAccumulator tailValues accumulator
        | [] ->
            List.rev accumulator
    mult2withAccumulator values []

我们使用累加器作为函数的参数而未定义可变的存储在堆栈中。此方法也使用模式匹配和列表区分联合。当我们处理输入列表中的项目时,累加器保存新值,然后当列表中没有更多项目([])时,我们只需反转列表以正确的顺序获取新列表,因为新项目是连接的到accumulator

的头部

要了解您需要查看的列表的数据结构(区别联合),所以这里是

type list =
   | Item of 'a * List
   | Empty

注意项目定义的结尾如何List引用自身,并且列表可以是一个空列表,当与模式匹配一​​起使用时[]

如何构建列表的快速示例

empty list - []
list with one int value - 1::[]
list with two int values - 1::2::[]
list with three int values - 1::2::3::[]

这是与定义的所有类型相同的函数。

let mult2 (values : int list) =
    let rec mult2withAccumulator (values : int list) (accumulator : int list) =
        match (values : int list) with
        | (headValue : int)::(tailValues : int list) -> 
            let (newValue : int) = headValue * 2
            let (accumulator : int list) = 
              (((newValue : int) :: (accumulator : int list)) : int list)
            mult2withAccumulator tailValues accumulator
        | [] ->
            ((List.rev accumulator) : int list)
    mult2withAccumulator values []

因此,将值放到堆栈上并使用带有模式匹配的自引用区分联合将有助于解决函数式编程中的许多问题。