【问题标题】:Initializing an infinite list of BigIntegers初始化 BigInteger 的无限列表
【发布时间】:2011-06-09 06:10:59
【问题描述】:

好的, 所以我需要一个所有正整数的列表。 首先想到的是:

let numbers:Seq<bigint>=Seq.initInfinite n...

但 initInfite 实际上并不是 infitint:http://msdn.microsoft.com/en-us/library/ee370429.aspx (与 bigint 不同)唯一的:Int32.MaxValue = 2,147,483,647,这远远不够大。

目前我的计划是用某种手工类替换序列(可能会使用 IEnumerable)。它会很简单(并且可能更适合我的使用)但我想知道如何做到这一点

【问题讨论】:

    标签: f# ienumerable sequence biginteger seq


    【解决方案1】:
    Seq.unfold (fun n -> Some(n, n + 1I)) 0I
    

    【讨论】:

    • I 后缀是否表示字面量是 bigint?
    • 除了 0 不是正整数(其他人都犯了同样的错误)。
    • @Stephen:我怀疑每个人的函数都与Seq.initInfinite 对称,它以0 开头。这样做似乎更有用。如果需要,您可以随时 Seq.skip 1
    • @Brian 什么?你还没有在手机上运行单声道? :)
    【解决方案2】:
    let numbers:bigint seq = 
        let rec loop n = seq { yield n; yield! loop (n+1I) }
        loop 0I
    

    【讨论】:

    • 是的。我希望 F# 有类型类(如在 Haskell 中),这样我们就不需要通用解决方案中的“skip”参数
    • @Joel - 以防未来的读者没有阅读丹尼尔回答下的完整评论线程,我想重申我的版本中没有装箱,只有一个 generic i> 堆上的引用单元格,每次迭代都会更新。
    • 我还用一个示例更新了我的答案,该示例说明了如何使用通用语言原语轻松地编写我的函数版本,以使用内置的 skip 1 更灵活的原始版本。
    【解决方案3】:

    我保留了以下静态约束函数,因为它非常灵活(您可以指定起始值和跳过间隔)并且适用于所有数字类型:

    let inline infiniteRange start skip = 
        seq {
            let n = ref start
            while true do
                yield n.contents
                n.contents <- n.contents + skip
        }
    

    FSI 给出的类型签名:

    val inline infiniteRange :
       ^a ->  ^b -> seq< ^a>
        when ( ^a or  ^b) : (static member ( + ) :  ^a *  ^b ->  ^a)
    

    这是您如何生成所有 整数(BigInts,即——显示在 FSI 中):

    > infiniteRange 1I 1I;;
    val it : seq<System.Numerics.BigInteger> =
      seq [1 {IsEven = false;
              IsOne = true;
              IsPowerOfTwo = true;
              IsZero = false;
              Sign = 1;}; 2 {IsEven = true;
                             IsOne = false;
                             IsPowerOfTwo = true;
                             IsZero = false;
                             Sign = 1;}; 3 {IsEven = false;
                                            IsOne = false;
                                            IsPowerOfTwo = false;
                                            IsZero = false;
                                            Sign = 1;}; 4 {IsEven = true;
                                                           IsOne = false;
                                                           IsPowerOfTwo = true;
                                                           IsZero = false;
                                                           Sign = 1;}; ...]
    

    更新:正如 Daniel 所展示的那样,您可以使用通用语言原语轻松地编写另一个静态约束函数,以 infiniteRange 形式并内置 skip 1:

    let inline infiniteRangeSkip1 start = 
        infiniteRange start LanguagePrimitives.GenericOne
    

    这是类型签名:

    val inline infiniteRangeSkip1 :
       ^a -> seq< ^a>
        when ( ^a or  ^b) : (static member ( + ) :  ^a *  ^b ->  ^a) and
              ^b : (static member get_One : ->  ^b)
    

    【讨论】:

    • 直接访问n.contents 而不是使用(惯用的)!:= 运算符有什么特别的优势吗?
    • @ildjarn - 在这种情况下,我认为没有任何优势(请参阅@Daniels 基准测试)。总的来说,我只是不喜欢这些运算符的外观,并且满足于将 ref 单元格视为普通对象(但也许我应该排队)。但自从回答了一个问题,他们引入了与直接访问 contents 不同的行为:stackoverflow.com/questions/3019446/net-4-spinlock/…
    • @Stephen:很有趣,谢谢。我完全假设上述运算符被定义为inline;我想你不知道他们背后的理由不是inline
    • @Stephen :在这种情况下,我认为这是一个错误而不是疏忽,因为它会改变(并且有时会破坏)ref 的语义。而且我认为现在改变像内置运算符这样基本的东西的语义为时已晚。 :-[
    • 我不知道,但我敢打赌,如果我们问得好,Brian 会告诉我们是否进行了这一特殊更改。 ;-] 另外,关于 SP1 是错误的——它不是;如果您之前安装过 SP1 测试版,则清单文件无法正确更新,但这是一个微不足道的修复,如果您之前没有安装过 SP1 测试版,则完全没有问题.
    【解决方案4】:

    如果这是您经常需要的,您甚至可以考虑扩展 Seq 模块。

    module Seq =
      let initInfiniteBig = 
        seq {
          let i = ref 0I
          while true do 
            yield !i
            i := !i + 1I
        }
    
    let ten = Seq.initInfiniteBig |> Seq.take 10
    

    更新

    我对一些变体进行了基准测试:

    let initInfiniteBig = 
      seq {
        let i = ref 0I
        while true do 
          yield !i
          i := !i + 1I
      }
    
    let initInfiniteBig2 = 
      seq {
        let i = ref 0I
        while true do 
          yield i.contents
          i.contents <- i.contents + 1I
      }
    
    let initInfiniteBig3 = 
      let rec loop i = 
        seq {
          yield i
          yield! loop (i + 1I)
        }
      loop 0I
    
    let initInfiniteBig4 = Seq.unfold (fun n -> Some(n, n + 1I)) 0I
    
    let range s = s |> Seq.take 100000000 |> Seq.length |> ignore
    
    range initInfiniteBig  //Real: 00:00:29.913, CPU: 00:00:29.905, GC gen0: 0, gen1: 0, gen2: 0
    range initInfiniteBig2 //Real: 00:00:30.045, CPU: 00:00:30.045, GC gen0: 0, gen1: 0, gen2: 0
    range initInfiniteBig3 //Real: 00:00:40.345, CPU: 00:00:40.310, GC gen0: 2289, gen1: 5, gen2: 0
    range initInfiniteBig4 //Real: 00:00:30.731, CPU: 00:00:30.716, GC gen0: 1146, gen1: 4, gen2: 1
    

    更新 2

    这是一个通用范围函数,类似于 Stephen 的,但没有 startskip

    let inline infiniteRange() : seq<'a> = 
      let zero : 'a = LanguagePrimitives.GenericZero
      let one : 'a = LanguagePrimitives.GenericOne
      seq {
          let n = ref zero
          while true do
              yield !n
              n := !n + one
      }
    

    这是签名:

    unit -> seq< ^a>
        when  ^a : (static member get_Zero : ->  ^a) and
              ^a : (static member get_One : ->  ^a) and
              ^a : (static member ( + ) :  ^a *  ^a ->  ^a)
    

    还有基准:

    range (infiniteRange() : seq<bigint>) //Real: 00:00:30.042, CPU: 00:00:29.952, GC gen0: 0, gen1: 0, gen2: 0
    

    【讨论】:

    • 我会强烈在这里支持递归而不是使用ref,因为这会不必要地将每个元素都装箱(bigint 是一种值类型)。
    • @ildjarn:你会这么想,但事实并非如此。事实上,递归版本是唯一明显较慢的版本。我用基准更新了我的答案。
    • @Stephen :显然我还不够清醒。我的想法是,因为ref 本身是一个引用类型,所以每次使用ref 都会有效地将它包含的值装箱——就其本身而言,是准确的。但当然,在这段代码中,ref 只使用一次,而不是每个元素使用一次ref。我想我会停止评论几个小时,直到我的大脑做得更好。 :-P
    • @Daniel - 没有startskip 参数的版本将默认为int,除非您在调用它时另行指定。我认为这是它比其他都快的主要原因,这些都硬编码为BigInt。我发现它比infiniteBigint 版本稍慢。
    • @Daniel, @Stephen:我查看了为递归版本生成的 IL,为其状态机生成的代码非常糟糕(可能是病态的情况,但我对此表示怀疑)。据我计算,当第一个元素产生时,有 1 个引用分配和 7 个 bigint 值副本;元素随后产生每个原因 2 个引用分配和 12 个 bigint 值副本,其中 1 个和 5 个(分别)完全未使用/浪费。我还没有查看大致等效的 C# 枚举器的 C# 编译器的输出,但我想它一定比这要好得多... :-/
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-09-13
    • 1970-01-01
    • 1970-01-01
    • 2011-06-05
    • 1970-01-01
    • 2011-08-09
    相关资源
    最近更新 更多