懒惰的评估步骤:过滤列表

时间:2016-10-03 20:00:47

标签: list scala collections filter lazy-evaluation

我对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”开始)?为什么不在评估结束前继续评估?

2 个答案:

答案 0 :(得分:3)

这似乎与lastview的实施有关。如果你这样做,它就不会重新开始:

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

last似乎试图访问head两次,在您的情况下,您需要找到people.view.filter(isOlderThan17(_))的第一个元素两次,因此view必须重新计算它两次。

<强>更新

以下是lastTraversableLike的定义:

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

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

答案 1 :(得分:1)

  

为什么必须重新开始评估(再次来自&#34; Mark&#34;)   之后&#34;吉尔&#34;已被发现?

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

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

来自Stream vs Views vs Iterators

  

Stream确实是一个懒惰的列表。实际上,在Scala中,Stream是一个List   谁的尾巴是懒惰的。一旦计算出来,值就会保持计算并且是   重复使用。或者,如您所说,值被缓存。

在这种情况下,你只是编写过滤器,所以你也可以只组合两个谓词并只进行一次过滤:

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