Scala中Streams的用例

时间:2010-01-19 20:38:53

标签: scala stream scala-collections

在Scala中有一个Stream类,它非常像迭代器。主题Difference between Iterator and Stream in Scala?提供了对两者之间的相同点和不同点的一些见解。

看到如何使用流非常简单,但我没有很多常见的用例,我会使用流而不是其他工件。

我现在的想法:

  • 如果你需要使用无限系列。但这对我来说似乎不是一个常见的用例,因此它不符合我的标准。 (请纠正我,如果这是常见的,我只是有一个盲点)
  • 如果您有一系列数据需要计算每个元素,但您可能需要多次重复使用。这很弱,因为我可以将它加载到一个列表中,这对于大部分开发人员来说在概念上更容易理解。
  • 可能存在大量数据或计算量很大的系列,并且您需要的项目很可能不需要访问所有元素。但在这种情况下,迭代器将是一个很好的匹配,除非你需要进行多次搜索,在这种情况下你也可以使用一个列表,即使它的效率会稍微低一些。
  • 需要重复使用一系列复杂的数据。这里可以再次使用列表。虽然在这种情况下两种情况都同样难以使用,并且Stream更适合,因为并非所有元素都需要加载。但又不是那么常见......或者是它?

所以我错过了任何大用途吗?或者它大部分是开发者偏好?

由于

4 个答案:

答案 0 :(得分:40)

StreamIterator之间的主要区别在于后者是可变的并且是“一次性”,可以这么说,前者不是。 Iterator的内存占用比Stream更好,但 可变的事实可能不方便。

拿这个经典素数生成器,例如:

def primeStream(s: Stream[Int]): Stream[Int] =
  Stream.cons(s.head, primeStream(s.tail filter { _ % s.head != 0 }))
val primes = primeStream(Stream.from(2))

也可以使用Iterator轻松编写,但Iterator

因此,Stream的一个重要方面是,您可以将其传递给其他函数,而不必先将其复制,或者必须反复生成它。

对于昂贵的计算/无限列表,这些事情也可以用Iterator来完成。无限列表实际上非常有用 - 你只是不知道它,因为你没有它,所以你已经看到了比处理强制有限大小更严格的算法。

答案 1 :(得分:18)

除了Daniel的答案之外,请记住Stream对于短路评估很有用。例如,假设我有一大堆函数需要String并返回Option[String],我想继续执行它们直到其中一个有效:

val stringOps = List(
  (s:String) => if (s.length>10) Some(s.length.toString) else None ,
  (s:String) => if (s.length==0) Some("empty") else None ,
  (s:String) => if (s.indexOf(" ")>=0) Some(s.trim) else None
);

好吧,我当然不想执行整个列表,并且List上没有任何方便的方法说“将这些作为函数对待并执行它们直到其中一个返回None以外的其他内容。该怎么办?也许这个:

def transform(input: String, ops: List[String=>Option[String]]) = {
  ops.toStream.map( _(input) ).find(_ isDefined).getOrElse(None)
}

这需要一个列表并将其视为Stream(实际上并不评估任何内容),然后定义一个新的Stream,它是应用函数的结果(但不是然后评估任何一个),然后搜索定义的第一个 - 在这里,神奇地,它回顾并意识到它必须应用地图,并从原始列表中获取正确的数据 - 然后将其从使用Option[Option[String]] Option[String]getOrElse。{/ p>

以下是一个例子:

scala> transform("This is a really long string",stringOps)
res0: Option[String] = Some(28)

scala> transform("",stringOps)
res1: Option[String] = Some(empty)

scala> transform("  hi ",stringOps)
res2: Option[String] = Some(hi)

scala> transform("no-match",stringOps)
res3: Option[String] = None

但它有效吗?如果我们将println放入我们的函数中,以便我们可以判断它们是否被调用,我们得到

val stringOps = List(
  (s:String) => {println("1"); if (s.length>10) Some(s.length.toString) else None },
  (s:String) => {println("2"); if (s.length==0) Some("empty") else None },
  (s:String) => {println("3"); if (s.indexOf(" ")>=0) Some(s.trim) else None }
);
// (transform is the same)

scala> transform("This is a really long string",stringOps)
1
res0: Option[String] = Some(28)

scala> transform("no-match",stringOps)                    
1
2
3
res1: Option[String] = None

(这与Scala 2.8有关;遗憾的是,2.7的实现有时会超过一个。请注意,当你的失败累积时,你会积累一长串的None,但是据推测,与你在这里的真实计算相比,这是便宜的。)

答案 2 :(得分:7)

我可以想象,如果您实时轮询某些设备,Stream会更方便。

想象一下GPS追踪器,如果你问它,它会返回实际位置。您无法预先计算5分钟内的位置。您可能只使用它几分钟来实现OpenStreetMap中的路径,或者您可以将它用于沙漠或雨林中六个月的探险。

或者数字温度计或其他类型的传感器,只要硬件处于活动状态并且打开,就会重复返回新数据 - 日志文件过滤器可能是另一个例子。

答案 3 :(得分:3)

StreamIteratorimmutable.Listmutable.List。支持不变性可以防止出现一类错误,偶尔会以性能为代价。

scalac本身并不能免疫这些问题:http://article.gmane.org/gmane.comp.lang.scala.internals/2831

正如丹尼尔指出的那样,偏袒懒惰而不是严格可以简化算法并使其更容易编写。