【问题标题】:Convert procedural solution to functional one. F#将程序解决方案转换为功能解决方案。 F#
【发布时间】:2015-01-22 21:13:57
【问题描述】:

我正在尝试使用 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 的情况下解决这个问题,但是我无法从程序上考虑这个问题。

【问题讨论】:

    标签: f# functional-programming


    【解决方案1】:

    为什么不用递归?

    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
    

    【讨论】:

      【解决方案2】:

      对于算法问题(例如 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
      

      【讨论】:

        【解决方案3】:

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

        那么,如何将值放入堆栈以供函数使用?

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

        让 result01 = List.filter (fun x -> 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 的列表参数绑定到值。

        • 创建一个无名数据结构并将其通过管道传递到函数中。
        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 []
        

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

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2020-06-07
          • 1970-01-01
          • 2016-11-05
          • 1970-01-01
          • 1970-01-01
          • 2016-03-12
          • 2012-08-22
          • 1970-01-01
          相关资源
          最近更新 更多