【问题标题】:Lazy evaluation steps : filtering a list惰性求值步骤:过滤列表
【发布时间】:2017-02-11 20:43:01
【问题描述】:

我对 Scala 中的惰性求值有一些疑问。 这是示例代码:

val people=List(("Mark", 32), ("Bob", 22), ("Jane", 8), 
         ("Jill", 21), ("nick", 50), ("Nancy", 42), 
         ("Mike", 19), ("Sara", 12), ("Paula", 42), 
         ("John", 21))

def isOlderThan17(person: (String,Int)) = {
  println(s"isOlderThan 17 called for $person")
  val(_,age) = person
  age > 17
}

def nameStartsWithJ(person: (String, Int)) = {
  println(s"isNameStartsWithJ called for $person")
  val (name,_) = person
  name.startsWith("J")
}

println(people.view.filter(p => isOlderThan17(p))
                   .filter(p => nameStartsWithJ(p))
                   .last)

输出:

isOlderThan 17 called for (Mark,32)
isNameStartsWithJ called for (Mark,32)
isOlderThan 17 called for (Bob,22)
isNameStartsWithJ called for (Bob,22)
isOlderThan 17 called for (Jane,8)
isOlderThan 17 called for (Jill,21)
isNameStartsWithJ called for (Jill,21)
isOlderThan 17 called for (Mark,32)      //Here is the problem.
isNameStartsWithJ called for (Mark,32)
isOlderThan 17 called for (Bob,22)
isNameStartsWithJ called for (Bob,22)
isOlderThan 17 called for (Jane,8)
isOlderThan 17 called for (Jill,21)
isNameStartsWithJ called for (Jill,21)
isOlderThan 17 called for (nick,50)
isNameStartsWithJ called for (nick,50)
isOlderThan 17 called for (Nancy,42)
isNameStartsWithJ called for (Nancy,42)
isOlderThan 17 called for (Mike,19)
isNameStartsWithJ called for (Mike,19)
isOlderThan 17 called for (Sara,12)
isOlderThan 17 called for (Paula,42)
isNameStartsWithJ called for (Paula,42)
isOlderThan 17 called for (John,21)
isNameStartsWithJ called for (John,21)
(John,21)

为什么在找到“Jill”之后必须重新开始评估(再次从“Mark”返回)?为什么不继续评估直到列表结束?

【问题讨论】:

    标签: list scala collections filter lazy-evaluation


    【解决方案1】:

    为什么必须重新开始评估(再次从“Mark”返回) 在找到“吉尔”之后?

    因为每个filter 都会生成一个仅包含过滤项的新集合。使用流来避免这种情况:

    println(people.toStream
                  .filter(p => isOlderThan17(p))
                  .filter(p => nameStartsWithJ(p))
                  .last)
    

    来自Stream vs Views vs Iterators

    Stream 确实是一个惰性列表。实际上,在 Scala 中,Stream 是一个 List 它的尾巴是懒惰的 val。一旦计算,一个值将保持计算状态并且是 重复使用。或者,正如您所说,这些值被缓存。

    在这种情况下,您只是组合过滤器,因此您不妨将两个谓词组合起来,只进行一次过滤:

    println(people.filter(p => isOlderThan17(p) && nameStartsWithJ(p))
                  .last)
    

    【讨论】:

    • 如果OP只想找到最后一个元素Stream内存效率不是很高。
    • 这种情况下视图不会将过滤方法组合成一个过滤方法吗?在这个例子中,我认为每个过滤器都不应该产生一个新的集合。
    • 过滤总是产生一个新的集合,看filter(p: (A) ⇒ Boolean): List[A]的签名就行了。
    【解决方案2】:

    好像和view中的last的实现有关。如果你这样做,它不会重新启动:

    people.view
          .filter(isOlderThan17(_))
          .filter(nameStartsWithJ(_))
          .fold(None)((x, y) => Some(y))
    

    看起来last 正在尝试访问head 两次,这意味着您需要两次找到people.view.filter(isOlderThan17(_)) 的第一个元素,因此view 必须重新计算两次。

    更新

    这是TraversableLikelast的定义:

    def last: A = {
        var lst = head
        for (x <- this)
          lst = x
        lst
      }
    

    第一个元素确实被访问了两次。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2022-12-07
      • 1970-01-01
      • 2016-06-27
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多