2016-10-03 6 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) 

Почему оценки должны быть перезапущен (обратно от «Mark») после «Джилл» была найдена? Почему он не продолжал оценку до конца списка?

ответ

1

Зачем нужно было перезагружать оценку (назад от «Марк») после того, как была найдена «Джилл»?

Поскольку каждый filter приводит к новой коллекции, содержащей только отфильтрованные элементы. Используйте поток, чтобы избежать этого:

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

От Stream vs Views vs Iterators

поток ленивый список действительно. Фактически, в Scala поток представляет собой список , чей хвост является ленивым val. После вычисления значение остается вычисленным и равно . Или, как вы говорите, значения кэшируются.

В этом случае вы только сочинял фильтры, так что вы можете также просто объединить два предиката и сделать фильтрацию только один раз:

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

Если ОП хочет только найти последний элемент, «Поток» не очень эффективен с точки зрения памяти. –

+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 
    } 

Первый элемент действительно обращались дважды.