zoukankan      html  css  js  c++  java
  • 《使用 F# 的排列与组合》学习笔记

    在做projectEuler 23题的时候需要时候排列组合的知识。

    原文链接:MSDN杂志 《使用 F# 的排列与组合》


    let temp = [| for i in 0..k-1 -> data.[i] |] // find "x" - right-most index to change let mutable x = k-1 while x > 0 && temp.[x] = n - k + x do x <- x - 1 temp.[x] <- temp.[x] + 1 // increment value at x // increment all values to the right of x for j = x to k-2 do temp.[j+1] <- temp.[j] + 1 // use primary ctor let result = new Combination(n, k, temp) result

    首先,我将当前 Combination 对象上下文的值复制到名为 temp 的可变数组。接下来,我定义一个名为 x 的索引变量,并将其置于 temp 数组的末尾。我必须使用 mutable 关键字,这样才能递减此索引变量,因为默认情况下 F# 中的大多数变量是不可变的。我使用了 <- 赋值运算符。

    每次定位到当前 Combination 对象的键索引后,我便递增一次该值及键索引右边的所有值。然后,我将 temp 数组(现在其值为 Combination 后继元素的值)传递到主构造函数,并返回新创建的对象。

    请注意,我到达最后一个 Combination 元素时并未返回 null(在 F# 中,这种做法被认为是不好的风格)。我在本文中展示的代码的风格不太符合 F# 风格。F# 专家可能会使用递归方法,但由于我这里认为读者是 F# 的初学者,因此我的初衷是尽量使 F# 代码为读者所熟悉。

    另一种编写 Successor 函数的做法是实现 .NET IEnumerable 接口。

    通过 ApplyTo 函数可将一个组合元素映射到一个字符串值集合:

     
    member this.ApplyTo(a : string[]) : string[] =
      if a.Length <> n then failwith 
        "Invalid array size in ApplyTo()"
      // array of int
      let result = Array.zeroCreate k
      for i = 0 to k-1 do
        // bind to array of string
        result.[i] <- a.[data.[i]]
      result

    在成员函数中执行输入参数的检查时,我不需要像在类型构造函数中那样要使用 do 关键字。静态方法 Array.zeroCreate 将创建一个整数数组,且全部初始化为 0 值。ApplyTo 函数比较简单,因为子集大小为 k (0..k-1) 的数学组合中的值范围与大小为 k 的 .NET 数组的索引是完全相同的。

    被覆盖的 ToString 成员函数仅用于构建一个由上下文对象的值组成的字符串:

     
    override this.ToString() : string =
      let mutable s : string = "^ "
      for i in 0..k-1 do
        s <- s + data.[i].ToString() + " "
      s <- s + "^"
      s

    我决定用 ^(脱字号,以字母 c 开头)字符来分隔 Combination 元素,而用 %(百分号,以 p 开头)字符分隔 Permutation 元素,从而帮我识别某个数字字符串代表的是 Combination 对象还是 Permutation 对象。

    静态函数 Choose 的代码如图 3 所示。 

    图 3 Choose 函数

     
    static member Choose(n : int, k : int) : BigInteger =
      if n < 0 || k < 0 then failwith 
        "Negative argument in Choose()"
      if n < k then failwith 
        "Subset size k is larger than n in Choose()"
      let (delta, iMax) =
        if k < n-k then
          (n-k, k)
        else
          (k, n-k)
      let mutable answer : BigInteger = 
        bigint delta + bigint 1 
      for i = 2 to iMax do
        answer <- (answer * (bigint delta + bigint i )) 
          / bigint i
      answer

    我没有使用本文前面介绍的定义来计算 Choose,而是使用了两次优化。首先,我使用一个原理 Choose(n, k) = Choose(n, n-k)。例如,Choose(9,6) = Choose(9,3)。其次,我不计算三个单独的阶乘(每个阶乘都会很大),而是计算一组部分乘积。为了将整数值显式转换为 BigInteger 类型,我使用了内置的 F# bigint 函数。

    Permutation 类型的实现与 Combination 类型的实现非常相似。您可以从 Microsoft 代码库网站 (code.msdn.microsoft.com) 获取 CombinationLib 库的完整源代码。

    使用 CombinatoricsLib

    本节中,我将讲述如何调用 CombinatoricsLib 库中的函数来得到图 1 的屏幕截图所示的运行结果。首先启动 Visual Studio 2010,并新建一个名为 CombinatoricsDemo 的 F# 应用程序项目。完整程序如图 4 所示。

    图 4 使用 CobinatoricsLib

     
    open System
    open Module1 // the Combinatorics Lib
    
    try
    
      printfn "\nBegin combinations and permutations with F# demo\n"
      printfn "All combinations of 5 items 3 at a time in lexicographical order are: \n"
      let mutable c = new Combination(5,3)
      printfn "%A" c // print initial combination
    
      // objects cannot be null in F# so use an explicit method
      while c.IsLast() = false do 
        c <- c.Successor()
        printfn "%A" c
       
      printf "\nThe last combination applied to array [| \"ant\"; \"bat\"; \"cow\"; \"dog\"; \"elk\" |] is: \n"
      let animals = [| "ant"; "bat"; "cow"; "dog"; "elk" |]
      //let result =  c.ApplyTo(animals)
      let result = animals |> c.ApplyTo
      printfn "%A" result
    
      printfn "\nThe number of ways to Choose 200 items 10 at a time = Choose(200,10) ="
      let Choose_200_10 = Combination.Choose(200,10).ToString("000,000")
      printfn "%s" Choose_200_10
    
      printfn "\nThe number of ways to arrange 52 cards = 52! = "
      let Factorial_52 = Combination.Factorial(52).ToString("000,000")
      printfn "%s" Factorial_52
    
      printfn "\nAll permutations of 3 items in lexicographical order are: \n"
      let mutable p = new Permutation(3)
      printfn "%A" p // print initial permutation
      while p.IsLast() = false do
        p <- p.Successor() 
        printfn "%A" p
          
      printfn "\nEnd demo\n"
      Console.ReadLine() |> ignore
    
    with
      | Failure(errorMsg) -> printfn "Fatal error: %s" errorMsg
    
    // end program

    编写代码之前,我在 Visual Studio 的“解决方案资源管理器”窗口中右键单击该项目名称,然后从上下文菜单中选择“添加引用”选项。然后,选择“浏览”选项卡,并导航到 CombinatoricsLib.dll 程序集。

    在演示程序代码的开头,我将 open 语句添加到 System 和 Module1 程序集。请回顾一下前文,CombinatorcsLib 的模块名称为 Module1。我将所有程序语句封装在一个 try/with 块中,以便捕获并处理异常。我使用辅助构造函数实例化 Combination 对象,从而得到初始的五选三数学组合对象 c:{0,1,2}。我使用的是简明的 F# %A 格式说明符,该符号会指示 F# 判定如何输出 Combination 对象。我也可以使用 %s 字符串格式。

    接着,我使用 F# while..do 循环来循环访问并显示全部 10 个 Combination(5,3) 元素。此时,Combination 对象 c 成为最后一个元素,因此我调用 ApplyTo 函数将该组合映射到一个字符串数组。

    请注意,我是在 Combination 类型的上下文中调用 Choose 和 Factorial 函数的,而不是在 c Combination 对象的上下文中进行调用。在以类似方式调用 Permutation 类型代码后,演示程序使用 Console.ReadLine 方法(在该方法中,我将返回值传送给内置的 ignore 对象)暂停用户输入,从而结束程序。我使用了 with 块来处理可能发生的异常,这里只是简单地显示异常错误消息。

    除了刚才演示的在 F# 程序中调用 F# 库,您还可以在任何兼容 .NET 的语言中调用 F# 库。此外,在 Visual Studio 中还可以使用很方便的 F# 交互式窗口进行临时调用,如图 5 所示。

     
    图 5 交互使用 F# 库

    在屏幕底部的 F# 交互式窗口中,我通过键入以下内容添加了对 CombinatoricsLib 程序集的引用:

     
    #r @"C:\(path)\CombinatoricsLib.dll";;

    在本例中,#r 表示添加引用,;; 表示终止交互式 F# 语句。现在我能够以交互方式调用库中的函数了。太巧妙了!

     我认为,使用 F# 有几项优缺点。就缺点而言,我发现学习 F# 的过程比我预期的要难得多。以函数式风格编程对我而言也是一个巨大的风格转变。另外,较之其他语言更大的不同在于,F# 可以用多种方式来完成某个特定任务的编码工作,因此我感觉不容易确定自己写的 F# 代码是否就是最佳的编码方式。

    但至少就我而言,我觉得学习 F# 的收获大于付出,绝对值得。在与经验丰富的 F# 编码人员交流时,大部分人都告诉我:在多数情况下,尽管某个任务实际会有多种编码方法,但具体采用哪种方法更多的是一种个人喜好问题,而不在于技术效率。另外,通过攻克 F# 语法和编码风格(例如克服定势思维),我更深入地理解了过程性语言(如 C#)的编码。

     

    James McCaffrey 博士 供职于 Volt Information Sciences, Inc.,他在公司负责管理对华盛顿州雷蒙德市沃什湾 Microsoft 总部园区的软件工程师进行的技术培训。他参与过多项 Microsoft 产品的研发工作,其中包括 Internet Explorer 和 MSN Search。McCaffrey 博士是《.NET 软件测试自动化之道》(Apress,2006)的作者。可通过 jammc@microsoft.com 与他联系。

    衷心感谢以下技术专家,感谢他们审阅了本文: Brian McNamara、Paul Newson 和 Matthew Podwysocki

  • 相关阅读:
    关于代码风格
    python笔记-glob模块
    python笔记-mysql安装与配置
    python笔记-shutil模块
    python笔记-os模块
    python笔记-内存分析
    python笔记-装饰器
    python笔记-正则表达式
    python笔记-迭代器-生成器-对象生成式
    python笔记-模块和包
  • 原文地址:https://www.cnblogs.com/jiangzhen/p/2367762.html
Copyright © 2011-2022 走看看