在处理大型馆藏时,我们通常会听到“懒惰评估”一词。我想更好地展示 strict 和 lazy 评估之间的区别,所以我尝试了以下示例 - 从列表中获取前两个偶数:
scala> var l = List(1, 47, 38, 53, 51, 67, 39, 46, 93, 54, 45, 33, 87)
l: List[Int] = List(1, 47, 38, 53, 51, 67, 39, 46, 93, 54, 45, 33, 87)
scala> l.filter(_ % 2 == 0).take(2)
res0: List[Int] = List(38, 46)
scala> l.toStream.filter(_ % 2 == 0).take(2)
res1: scala.collection.immutable.Stream[Int] = Stream(38, ?)
我注意到当我使用toStream
时,我得到了Stream(38, ?)
。什么是“?”这意味着什么这与懒惰评估有关吗?
此外,懒惰评估的一些好例子是什么,何时应该使用它以及为什么?
答案 0 :(得分:2)
使用懒惰集合的一个好处是" save"记忆,例如映射到大型数据结构时。考虑一下:
val r =(1 to 10000)
.map(_ => Seq.fill(10000)(scala.util.Random.nextDouble))
.map(_.sum)
.sum
使用懒惰评估:
val r =(1 to 10000).toStream
.map(_ => Seq.fill(10000)(scala.util.Random.nextDouble))
.map(_.sum)
.sum
第一个语句将生成大小为10000的10000 Seq
并将它们保存在内存中,而在第二种情况下,一次只有一个Seq
需要存在于内存中,因此速度要快得多。 ..
另一个用例是实际只需要部分数据。我经常将惰性集合与take
,takeWhile
等
答案 1 :(得分:0)
让我们采取一个真实的生活场景 - 你有一个大的日志文件,你要提取前10行包含"成功"。
直接的解决方案是逐行读取文件,一旦你有一行包含"成功",打印它并继续下一行。
但是既然我们喜欢函数式编程,那么我们就不想使用传统的循环。相反,我们希望通过编写函数来实现我们的目标。
首次尝试:
Source.fromFile("log_file").getLines.toList.filter(_.contains("Success")).take(10)
让我们试着了解这里发生了什么:
我们读了整个文件
过滤相关行
采用前10个元素
如果我们尝试打印Source.fromFile("log_file").getLines.toList
,我们将获得整个文件,这显然是一种浪费,因为并非所有行都与我们相关。
为什么我们得到了所有的行,然后才进行过滤?这是因为List是一个严格的数据结构,因此当我们调用toList
时,它会立即评估 ,并且只有在获得整个数据后才会应用过滤。
幸运的是,Scala提供了 lazy 数据结构, stream 就是其中之一:
Source.fromFile("log_file").getLines.toStream.filter(_.contains("Success")).take(10)
为了证明这种差异,让我们试试:
Source.fromFile("log_file").getLines.toStream
现在我们得到类似的东西:
Scala.collection.immutable.Stream[Int] = Stream(That's the first line, ?)
toStream
仅计算一个元素 - 文件中的第一行。下一个元素由"?"表示,表示该流未评估下一个元素,因为toStream
是延迟函数,仅在使用时评估下一个项目。
现在我们应用过滤器功能之后,它将开始读取下一行,直到我们得到第一行包含"成功":
> var res = Source.fromFile("log_file").getLines.toStream.filter(_.contains("Success"))
Scala.collection.immutable.Stream[Int] = Stream(First line contains Success!, ?)
现在我们应用take
函数。仍然没有执行任何操作,但它知道应该选择10行,因此在我们使用结果之前它不会进行评估:
res foreach println
最后,我现在打印res
,我们将按照我们的预期获得包含前10行的Stream。