【问题标题】:F# Power issues which accepts both arguments to be bigintsF# Power 问题,它接受两个参数都是 bigints
【发布时间】:2012-01-11 12:02:03
【问题描述】:

我目前正在尝试使用 F#。在 Internet 上找到的文章很有帮助,但作为 C# 程序员,我有时会遇到一些情况,我认为我的解决方案会有所帮助,但它没有或只是部分帮助。

所以我对 F# 的了解(很可能是编译器的工作原理)可能是我有时完全惊呆的原因。

例如,我编写了一个 C# 程序来确定完美数。它使用已知形式的欧几里得证明,完美数可以由梅森素数 2p-1(2p-1) 形成(其中 2p-1 是素数,p 表示为的幂)。

由于 F# 的帮助表明 '**' 可用于计算幂,但使用浮点数,因此我尝试使用位移运算符 (

 let PowBitShift (y:int32) = 1 <<< y;;

但是,在运行测试并寻求性能改进时,我还尝试了一种我记得使用 Miranda(也是一种函数式编程语言)的形式,它使用递归和模式匹配器来计算功率。主要好处是我可以将变量 y 用作 64 位整数,这是标准位移运算符无法实现的。

    let rec Pow (x : int64) (y : int64) = 
    match y with
        | 0L -> 1L
        | y -> x * Pow x (y - 1L);;

事实证明,这个函数实际上更快,但我(还)不明白其中的原因。也许这是一个不那么理智的问题,但我仍然很好奇。

那么第二个问题是,在计算完美数时,您会遇到这样一个事实,即 int64 无法显示在找到第 9 个完美数(由 31 的幂形成)后交叉的大数。我试图找出你是否可以使用 BigInteger 对象(或 bigint 类型),但在这里我对 F# 的了解有点阻碍我。是否可以创建一个同时接受两个参数为 bigint 的 powerfunction?

我目前有这个:

let rec PowBigInt (x : bigint) (y : bigint) = 
    match y with
        | bigint.Zero -> 1I
        | y -> x * Pow x (y - 1I);;

但它会抛出一个错误,即 bigint.Zero 未定义。所以我在那里也做错了什么。 0I 不被接受作为替代品,因为它给出了这个错误:

Non-primitive numeric literal constants cannot be used in pattern matches because they    
can be mapped to multiple different types through the use of a NumericLiteral module.  
Consider using replacing with a variable, and use 'when <variable> = <constant>' at the 
end of the match clause.    

但是模式匹配器不能使用'when'语句。有其他解决方案吗?

先谢谢了,请原谅我的长帖。我只是想尽可能清楚地表达我的“挑战”。

【问题讨论】:

  • 第一个问题很好,欢迎来到 Stack Overflow!

标签: f# pattern-matching perfect-numbers


【解决方案1】:

我不明白为什么你需要 y 成为 int64bigint。根据this link,已知最大的梅森数是p = 43112609,其中p确实在int的范围内。

y 用作整数,您可以改用标准运算符pown : ^T -&gt; int -&gt; ^T,因为:

let Pow (x : int64) y = pown x y
let PowBigInt (x: bigint) y = pown x y

关于你的模式匹配bigint的问题,错误信息非常清楚地表明你可以通过when守卫使用模式匹配:

let rec PowBigInt x y = 
    match y with
    | _ when y = 0I -> 1I
    | _ -> x * PowBigInt x (y - 1I)

【讨论】:

  • 啊,这很清楚,我还不明白您可以在模式匹配器中添加下划线。关于 bigint 的需要,我正在编写一个程序,它可以显示第 10 个(及以上)完美数,实际上它不能放在 64 位整数(或某些人喜欢的长整数)内。
  • 你误会了什么。 2^p-1 应该是bigint,但对于pint 已经绰绰有余了。所以参数y不必是int64bigint。这与pown 的第二个参数为int 的原因相同。
  • 这确实是正确的,我真是太傻了。然而,第 47 个完美数的幂是 43.112.609,目前还不知道下一个幂是多少。这是我发现的:en.wikipedia.org/wiki/List_of_perfect_numbers 我的目标是编写一个程序,它至少可以尝试找到所有 47 个完美数字(希望更多,但这可能是一个详尽的尝试)。
  • @RvdV79 - 希望更多!
  • 非常有趣的cmets。我希望我有一个妻子和一个博客可以谈论:D。
【解决方案2】:

我认为定义PowBigInt 的最简单方法是使用if 而不是模式匹配:

let rec PowBigInt (x : bigint) (y : bigint) =  
  if y = 0I then 1I   
  else x * PowBigInt x (y - 1I) 

问题在于bigint.Zero 是一个返回值的静态属性,但模式只能包含(常量)文字或 F# 活动模式。它们不能直接包含属性(或其他)调用。但是,如果您仍然喜欢match,您可以在where 子句中编写额外的约束:

let rec PowBigInt (x : bigint) (y : bigint) =  
  match y with 
  | y when y = bigint.Zero -> 1I 
  | y -> x * PowBigInt x (y - 1I)

作为旁注,您可能可以使用 tail-recursion 使函数更有效(这个想法是,如果一个函数将递归调用作为最后一件事,那么它可以被编译得更多有效):

let PowBigInt (x : bigint) (y : bigint) =   
  // Recursive helper function that stores the result calculated so far
  // in 'acc' and recursively loops until 'y = 0I'
  let rec PowBigIntHelper (y : bigint) (acc : bigint) =
    if y = 0I then acc 
    else PowBigIntHelper (y - 1I) (x * acc)
  // Start with the given value of 'y' and '1I' as the result so far
  PowBigIntHelper y 1I

关于 PowBitShift 函数 - 我不确定它为什么会变慢,但它绝对不能满足您的需求。只有当基数为 2 时,才使用位移来实现幂。

【讨论】:

  • 好吧,我需要重新表述一下我的问题,我放置的代码不正确,因为计算需要 2 的幂,我认为在 1 上向左移动(指数部分) (一个作为基地)会做的伎俩。在我的代码中,x 填充了值 1,它可以被常量值本身替换。我将不得不做另一个测试,看看它是否更快。感谢您的广泛回答托马斯,我会尝试一下。您的回答让我想到了找到一个较小的错误,我也会尝试修复它。
【解决方案3】:

您不需要创建 Pow 函数。 (**) 运算符对 bigint -> int -> bigint 有重载。 只有第二个参数应该是一个整数,但我认为这对你的情况来说不是问题。 试试看

大整数 10 ** 32 ;;

val it : System.Numerics.BigInteger =
  100000000000000000000000000000000 {IsEven = true;
                                     IsOne = false;
                                     IsPowerOfTwo = false;
                                     IsZero = false;
                                     Sign = 1;}

【讨论】:

    【解决方案4】:

    另一种选择是内联您的函数,使其适用于所有数字类型(支持所需的运算符:(*)(-)get_Oneget_Zero)。

    let rec inline PowBigInt (x:^a) (y:^a) : ^a =  
      let zero = LanguagePrimitives.GenericZero 
      let one = LanguagePrimitives.GenericOne
      if y = zero then one
      else x * PowBigInt x (y - one) 
    
    let x = PowBigInt 10 32     //int
    let y = PowBigInt 10I 32I   //bigint
    let z = PowBigInt 10.0 32.0 //float
    

    我可能会建议按照 Tomas 的建议将其设为尾递归。

    【讨论】:

    • 对不起,丹尼尔没有发表评论,我只是错过了你的反应。尾递归引起了我的兴趣,但是您在这里的示例帮助我越来越多地理解了 F# 语言。我向大家学习的越多,我就越热情!无论如何,非常感谢丹尼尔的努力,非常感谢!
    猜你喜欢
    • 2011-09-20
    • 2011-09-13
    • 1970-01-01
    • 2022-09-23
    • 2014-12-13
    • 1970-01-01
    • 2012-10-22
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多