Scala中Streams的用例

Jes*_*har 50 scala stream scala-collections

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

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

我现在的想法:

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

所以我错过了任何重大用途吗?或者它是大多数开发人员的偏好?

谢谢

Dan*_*ral 40

a Stream和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))
Run Code Online (Sandbox Code Playgroud)

它也可以很容易地编写Iterator,但是到目前为止Iterator还不会保留计算的素数.

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

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

  • 我想补充的另一个不同之处是,"Stream"在其head元素中从不是懒惰的."流"的头部以评估的形式存储.如果需要一个序列,在请求之前不计算任何元素(包括头部),那么`Iterator`是唯一的选择. (2认同)

Rex*_*err 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
);
Run Code Online (Sandbox Code Playgroud)

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

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

这需要一个列表并将其视为一个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
Run Code Online (Sandbox Code Playgroud)

但它有效吗?如果我们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
Run Code Online (Sandbox Code Playgroud)

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

  • 理所当然的.我应该澄清这不是Stream特定的,或者选择了一个使用多个调用同一个Stream的示例. (2认同)

use*_*own 7

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

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

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