51

在 Scala 中有一个非常像迭代器的 Stream 类。Scala中迭代器和流的区别?对两者之间的异同提供了一些见解。

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

我现在的想法:

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

所以我错过了任何重要的用途吗?还是在很大程度上是开发人员的偏好?

谢谢

4

4 回答 4

41

Streama和 an之间的主要区别在于Iterator后者是可变的和“一次性的”,可以这么说,而前者不是。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))

它也可以很容易地用 an 来编写Iterator,但 anIterator不会保留到目前为止计算的素数。

因此,a 的一个重要方面Stream是您可以将它传递给其他函数,而无需先复制它,也不必一次又一次地生成它。

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

于 2010-01-19T22:27:08.173 回答
19

除了丹尼尔的回答,请记住这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.

这是一个例子:

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

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

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,但与您在此处的真实计算相比,这可能是便宜的。)

于 2010-01-20T04:04:24.280 回答
7

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

想想一个 GPS 追踪器,如果你问它,它会返回实际位置。您无法预先计算 5 分钟后您将到达的位置。您可能会使用它几分钟来实现 OpenStreetMap 中的路径,或者您可能会使用它在沙漠或热带雨林中进行超过六个月的探险。

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

于 2011-07-18T12:59:41.473 回答
3

Stream就是Iterator这样。immutable.List_ mutable.List偏爱不变性可以防止一类错误,有时会以性能为代价。

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

正如 Daniel 指出的那样,偏爱懒惰而不是严格可以简化算法并使它们更容易组合。

于 2010-01-20T08:59:38.017 回答