2016-10-03 46 views
2

我有一些關於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) 

爲什麼評估必須(從「標記」回來了)「吉爾」後重新被發現?爲什麼它沒有繼續評估,直到列表結束?

回答

1

爲什麼在發現「Jill」後必須重新啓動評估(從「標記」重新開始) ?

因爲每個filter都會產生一個僅包含過濾項目的新集合。使用流來避免這種情況:

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

Stream vs Views vs Iterators

流是一個懶惰的名單確實如此。事實上,在斯卡拉,Stream是一個列表 ,其尾部是一個懶惰的val。一旦計算出來,一個值將保持計算結果,並重新使用 。或者,如你所說,這些值被緩存。

在這種情況下,你只組成的過濾器,所以你可能也只是將二者結合起來謂詞和做過濾只有一次:

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

如果OP只想找到最後一個元素'Stream'不是非常有效的內存。 –

+0

這種情況下的視圖不會將過濾器方法組合成一個過濾器方法嗎?我不認爲每個過濾器都會在這個例子中產生一個新的集合。 – Samar

+0

過濾器總是會產生一個新的集合,只要查看'filter(p:(A)⇒Boolean):List [A]'的簽名即可。 –

2

這似乎是關係到last在執行view。如果你做這種方式也不會重新啓動:

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

看起來last試圖訪問head兩次,這在你的情況下,意味着你需要找到的people.view.filter(isOlderThan17(_))第一要素兩次,所以view必須重新計算它兩次。

UPDATE

這裏是last的定義TraversableLike

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

的第一個元素的確被訪問兩次。