我有时发现自己想要在 Scala 中的无限流上执行嵌套迭代 for 推导式,但指定循环终止条件可能有点棘手。有没有更好的方法来做这种事情?
我想到的用例是,我不一定预先知道我正在迭代的每个无限流中需要多少个元素(但显然我知道它不会是无限个) )。假设每个流的终止条件可能以某种复杂的方式取决于 for 表达式中其他元素的值。
最初的想法是尝试将流终止条件编写为 for 表达式中的 if 过滤子句,但是当循环嵌套的无限流时,这会遇到麻烦,因为无法使第一个无限流上的迭代短路,这最终会导致 OutOfMemoryError。考虑到 for 表达式如何映射到 map、flatMap 和 withFilter 方法调用,我明白为什么会出现这种情况 -我的问题是是否有更好的习惯用法来完成这种事情(也许根本不涉及 for 理解)。
为了给出一个有点人为的示例来说明刚刚描述的问题,请考虑以下(非常简单的)代码来生成数字 1 和 2 的所有配对:
val pairs = for {
i <- Stream.from(1)
if i < 3
j <- Stream.from(1)
if j < 3
}
yield (i, j)
pairs.take(2).toList
// result: List[(Int, Int)] = List((1,1), (1,2))
pairs.take(4).toList
// 'hoped for' result: List[(Int, Int)] = List((1,1), (1,2), (2,1), (2,2))
// actual result:
// java.lang.OutOfMemoryError: Java heap space
// at scala.collection.immutable.Stream$.from(Stream.scala:1105)
显然,在这个简单的示例中,通过将 if 过滤器移动到原始流上的 takeWhile 方法调用中,可以轻松避免该问题,如下所示:
val pairs = for {
i <- Stream.from(1).takeWhile(_ < 3)
j <- Stream.from(1).takeWhile(_ < 3)
}
yield (i, j)
但出于问题的目的,想象一个更复杂的用例,其中流终止条件无法轻松移动到流表达式本身。
最佳答案
一种可能是将 Stream
包装到您自己的类中,该类以不同的方式处理 filter
,在本例中为 takeWhile
:
import scala.collection._
import scala.collection.generic._
class MyStream[+A]( val underlying: Stream[A] ) {
def flatMap[B, That](f: (A) => GenTraversableOnce[B])(implicit bf: CanBuildFrom[Stream[A], B, That]): That = underlying.flatMap(f);
def map[B, That](f: (A) ⇒ B)(implicit bf: CanBuildFrom[Stream[A], B, That]): That = underlying.map(f);
def filter(p: A => Boolean): Stream[A] = underlying.takeWhile(p);
// ^^^^^^^^^^^^^^^^^^^^^^^^
}
object MyStream extends App {
val pairs = for {
i <- new MyStream(Stream.from(1))
if i < 3
j <- new MyStream(Stream.from(1))
if j < 3
} yield (i, j);
print(pairs.toList);
}
这会打印List((1,1), (1,2), (2,1), (2,2))
。
关于scala - Scala 中无限流的嵌套迭代,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/14026617/