【问题标题】:Infinite streams in ScalaScala 中的无限流
【发布时间】:2011-09-18 11:42:36
【问题描述】:

假设我有一个函数,例如旧的最爱

def factorial(n:Int) = (BigInt(1) /: (1 to n)) (_*_)

现在我想找到n 的最大值,其中factorial(n) 适合Long。我可以的

(1 to 100) takeWhile (factorial(_) <= Long.MaxValue) last

这可行,但 100 是一个任意大的数字;在左侧我真正想要的是一个无限流,它会不断生成更高的数字,直到满足takeWhile 条件。

我想出了

val s = Stream.continually(1).zipWithIndex.map(p => p._1 + p._2)

但是有更好的方法吗?

(我也知道我可以递归地得到一个解决方案,但这不是我想要的。)

【问题讨论】:

    标签: scala stream


    【解决方案1】:
    Stream.from(1)
    

    创建一个从 1 开始并以 1 递增的流。全部在 API docs 中。

    【讨论】:

    • Stream 自 2.13.0 起已弃用。请改用LazyList。所以这将是LazyList.from(1)
    【解决方案2】:

    使用迭代器的解决方案

    您也可以使用Iterator 代替StreamStream 保留所有计算值的引用。因此,如果您计划只访问每个值一次,则迭代器是一种更有效的方法。不过,迭代器的缺点是它的可变性。

    有一些很好的便捷方法可以创建在其companion object 上定义的Iterators。

    编辑

    不幸的是,我知道没有短的(支持库的)方法来实现类似

    Stream.from(1) takeWhile (factorial(_) <= Long.MaxValue) last
    

    我为一定数量的元素推进Iterator 的方法是drop(n: Int)dropWhile

    Iterator.from(1).dropWhile( factorial(_) <= Long.MaxValue).next - 1
    

    - 1 用于此特殊目的,但不是通用解决方案。但是使用 pimp my library 在Iterator 上实现last 方法应该没有问题。问题是获取无限迭代器的最后一个元素可能是有问题的。所以它应该被实现为像lastWith这样集成takeWhile的方法。

    可以使用sliding 完成一个丑陋的解决方法,它是为Iterator 实现的:

    scala> Iterator.from(1).sliding(2).dropWhile(_.tail.head < 10).next.head
    res12: Int = 9
    

    【讨论】:

    • 我想知道 Stream.from(1) takeWhile (factorial(_) &lt;= Long.MaxValue) last 的迭代器的等价物是什么,给出答案 20?
    【解决方案3】:

    正如@ziggystar 指出的那样,Streams 将先前计算的值列表保存在内存中,因此使用Iterator 是一个很大的改进。

    为了进一步改进答案,我认为“无限流”通常是根据预先计算的值计算(或可以计算)的。如果是这种情况(在你的阶乘流中肯定是这样),我建议改用Iterator.iterate

    大概是这样的:

    scala> val it = Iterator.iterate((1,BigInt(1))){case (i,f) => (i+1,f*(i+1))}
    it: Iterator[(Int, scala.math.BigInt)] = non-empty iterator
    

    那么,你可以这样做:

    scala> it.find(_._2 >= Long.MaxValue).map(_._1).get - 1
    res0: Int = 22
    

    或使用@ziggystar sliding 解决方案...

    另一个容易想到的例子是斐波那契数:

    scala> val it = Iterator.iterate((1,1)){case (a,b) => (b,a+b)}.map(_._1)
    it: Iterator[Int] = non-empty iterator
    

    在这些情况下,您不必每次都从头开始计算新元素,而是对每个新元素进行 O(1) 运算,这将进一步提高您的运行时间。

    【讨论】:

      【解决方案4】:

      原来的“阶乘”函数不是最优的,因为阶乘每次都是从头开始计算的。使用 memoization 的最简单/不可变的实现是这样的:

      val f : Stream[BigInt] = 1 #:: (Stream.from(1) zip f).map { case (x,y) => x * y }
      

      现在,答案可以这样计算:

      println( "count: " + (f takeWhile (_<Long.MaxValue)).length )
      

      【讨论】:

        【解决方案5】:

        以下变体不测试当前,而是下一个整数,以便查找并返回最后一个有效数字:

        Iterator.from(1).find(i => factorial(i+1) > Long.MaxValue).get
        

        在这里使用.get 是可以接受的,因为无限序列上的find 永远不会返回None

        【讨论】:

          猜你喜欢
          • 2012-12-11
          • 1970-01-01
          • 1970-01-01
          • 2016-07-13
          • 2021-05-12
          • 1970-01-01
          • 2016-03-20
          • 1970-01-01
          • 2011-01-07
          相关资源
          最近更新 更多