首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >Scala中无限流上的嵌套迭代

Scala中无限流上的嵌套迭代
EN

Stack Overflow用户
提问于 2012-12-25 00:03:07
回答 2查看 1.5K关注 0票数 2

有时,我发现自己希望在Scala 中的无限流上执行嵌套迭代,以获得理解,但是指定循环终止条件可能有点棘手。有更好的方法来做这种事吗?

我想到的用例是,我不一定预先知道我正在迭代的每个无限流需要多少个元素(但很明显,我知道它不会是一个无穷多的数字)。假设每个流的终止条件可能以某种复杂的方式依赖于for表达式中其他元素的值。

最初的想法是尝试将流终止条件写为if filter子句在for表达式中,但是当在嵌套的无限流上循环时会遇到麻烦,因为在第一个无限流上没有短路的方法,这最终会导致OutOfMemoryError。考虑到 for 表达式如何映射到 map flatMapwithFilter方法调用,我理解为什么会这样--我的问题是,做这种事情是否有更好的成语(可能根本不涉及forE 215理解)。

为了给出一个有点做作的例子来说明刚才描述的问题,请考虑下面(非常天真的)代码来生成数字1和2的所有配对:

代码语言:javascript
复制
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方法调用中可以很容易地避免问题,如下所示:

代码语言:javascript
复制
val pairs = for {
  i <- Stream.from(1).takeWhile(_ < 3) 
  j <- Stream.from(1).takeWhile(_ < 3) 
}    
yield (i, j)

但是为了这个问题的目的,想象一个更复杂的用例,在这个用例中,流终止条件不能很容易地移动到流表达式本身。

EN

回答 2

Stack Overflow用户

回答已采纳

发布于 2012-12-26 17:31:08

我已经采纳了Petr的建议,想出了一个我认为更通用的解决方案,因为它不限制的位置,如果在for理解中过滤(尽管它有更多的语法开销)。

这个想法再次将底层流封装在包装对象中,该对象不需要修改就委托flatMapmapfilter方法,但首先对底层流应用takeWhile调用,并使用!isTruncated谓词,其中isTruncated是属于包装器对象的字段。在任何时候对包装器对象调用truncate将翻转isTruncated标志,并有效地结束流上的进一步迭代。这在很大程度上依赖于这样一个事实:对底层流的takeWhile调用是延迟计算的,因此在迭代的后期阶段执行的代码有可能影响其行为。

缺点是您必须保留对您希望能够截断中间迭代的流的引用,方法是将|| s.truncate附加到过滤器表达式(其中s是对包装流的引用)。您还需要确保在流中的每次新迭代之前对包装器对象(或使用新的包装器对象)调用reset,除非您知道每次重复迭代的行为都是相同的。

代码语言:javascript
复制
import scala.collection._
import scala.collection.generic._

class TruncatableStream[A]( private val underlying: Stream[A]) {
  private var isTruncated = false;

  private var active = underlying.takeWhile(a => !isTruncated)

  def flatMap[B, That](f: (A) => GenTraversableOnce[B])(implicit bf: CanBuildFrom[Stream[A], B, That]): That = active.flatMap(f);

  def map[B, That](f: (A) => B)(implicit bf: CanBuildFrom[Stream[A], B, That]): That = active.map(f);

  def filter(p: A => Boolean): Stream[A] = active.filter(p);

  def truncate() = {
    isTruncated = true
    false
  }

  def reset() = {
    isTruncated = false
    active = underlying.takeWhile(a => !isTruncated)
  }
}

val s1 = new TruncatableStream(Stream.from(1))
val s2 = new TruncatableStream(Stream.from(1))

val pairs = for {
  i <- s1

  // reset the nested iteration at the start of each outer iteration loop 
  // (not strictly required here as the repeat iterations are all identical)
  // alternatively, could just write: s2 = new TruncatableStream(Stream.from(1))  
  _ = _s2.reset()      

  j <- s2
  if i < 3 || s1.truncate
  if j < 3 || s2.truncate
} 
yield (i, j)

pairs.take(2).toList  // res1: List[(Int, Int)] = List((1,1), (1,2))
pairs.take(4).toList  // res2: List[(Int, Int)] = List((1,1), (1,2), (2,1), (2,2))

毫无疑问,这是可以改进的,但这似乎是一个合理的解决办法。

票数 1
EN

Stack Overflow用户

发布于 2012-12-25 16:32:30

一种可能的方法是将Stream封装到您自己的类中,这个类处理filter的方式不同,在本例中是takeWhile

代码语言:javascript
复制
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))

票数 3
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/14026617

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档