Scala中懒惰评估的一些好用例是什么?

时间:2017-11-24 12:11:58

标签: scala lazy-evaluation

在处理大型馆藏时,我们通常会听到“懒惰评估”一词。我想更好地展示 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, ?)。什么是“?”这意味着什么这与懒惰评估有关吗?

此外,懒惰评估的一些好例子是什么,何时应该使用它以及为什么?

2 个答案:

答案 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需要存在于内存中,因此速度要快得多。 ..

另一个用例是实际只需要部分数据。我经常将惰性集合与taketakeWhile

一起使用

答案 1 :(得分:0)

让我们采取一个真实的生活场景 - 你有一个大的日志文件,你要提取前10行包含"成功"。

直接的解决方案是逐行读取文件,一旦你有一行包含"成功",打印它并继续下一行。

但是既然我们喜欢函数式编程,那么我们就不想使用传统的循环。相反,我们希望通过编写函数来实现我们的目标。

首次尝试:

Source.fromFile("log_file").getLines.toList.filter(_.contains("Success")).take(10)

让我们试着了解这里发生了什么:

  1. 我们读了整个文件

  2. 过滤相关行

  3. 采用前10个元素

  4. 如果我们尝试打印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。