2015-08-19 5 views
0

оценка Рассмотрим следующий список преобразований:Scala преобразование потока и модель

List(1,2,3,4) map (_ + 10) filter (_ % 2 == 0) map (_ * 3) 

Оценивается следующим образом:

List(1, 2, 3, 4) map (_ + 10) filter (_ % 2 == 0) map (_ * 3) 
List(11, 12, 13, 14) filter (_ % 2 == 0) map (_ * 3) 
List(12, 14) map (_ * 3) 
List(36, 42) 

Так есть три прохода и с каждым из них новый список структуры создано.

Итак, первый вопрос: can Stream поможет избежать его, и если да - как? Могут ли все оценки выполняться за один проход и без создания дополнительных структур?

Не следующий поток модель оценки правильно:

Stream(1, ?) map (_ + 10) filter (_ % 2 == 0) map (_ * 3) 
Stream(11, ?) filter (_ % 2 == 0) map (_ * 3) 
// filter condition fail, evaluate the next element 
Stream(2, ?) map (_ + 10) filter (_ % 2 == 0) map (_ * 3) 
Stream(12, ?) filter (_ % 2 == 0) map (_ * 3) 
Stream(12, ?) map (_ * 3) 
Stream(36, ?) 
// finish 

Если это так, то есть такое же количество проходов и такое же количество новых Stream структур, созданных как в случае List. Если это не так, то второй вопрос: что такое Stream оценочная модель в особенности этого типа цепочки трансформации?

+0

Просто потому, что существует новый объект 'Stream', это не означает, что проход был сделан по всей коллекции. –

ответ

2

Нет, вы не можете избежать этого с помощью Stream. Но вы можете избежать этого, используя метод collect, и вы должны помнить, что каждый раз, когда вы используете map после filter, вам может понадобиться collect. Вот код:

scala> def time(n: Int)(call : => Unit): Long = { 
    | val start = System.currentTimeMillis 
    | var cnt = n 
    | while(cnt > 0) { 
    |  cnt -= 1 
    |  call 
    | } 
    | System.currentTimeMillis - start 
    | } 
time: (n: Int)(call: => Unit)Long 

scala> val xs = List.fill(10000)((math.random * 100).toInt) 
xs: List[Int] = List(37, 86, 74, 1, ...) 
scala> val ys = Stream(xs :_*) 
ys: scala.collection.immutable.Stream[Int] = Stream(37, ?) 

scala> time(10000){ xs map (_+10) filter (_%2 == 0) map (_*3) } 
res0: Long = 7182 

//Note call force to evaluation of the whole stream. 
scala> time(10000){ ys map (_+10) filter (_%2 == 0) map (_*3) force } 
res1: Long = 17408 

scala> time(10000){ xs.view map (_+10) filter (_%2 == 0) map (_*3) force } 
res2: Long = 6322 

scala> time(10000){ xs collect { case x if (x+10)%2 == 0 => (x+10)*3 } } 
res3: Long = 2339 
0

Насколько я знаю, если вы всегда перебираете всю коллекцию, Stream не поможет вам. Он будет создавать тот же номер, что и новый Streams, как с List.

Поправьте меня, если я ошибаюсь, но я понимаю, это выглядит следующим образом:

поток является ленивой структурой, поэтому, когда вы делаете:

val result = Stream(1, ?) map (_ + 10) filter (_ % 2 == 0) map (_ * 3) 

результата другого поток, который содержит ссылки на результаты предыдущих преобразований. Таким образом, если оценка силы с Еогеасп (или, например mkString)

result.foreach(println) 

для каждой итерации выше цепи оценивается, чтобы получить текущий элемент.

Однако вы можете уменьшить проходы на 1, если вы замените фильтр withFilter. Затем фильтр применяется с функцией отображения.

List(1,2,3,4) map (_ + 10) withFilter (_ % 2 == 0) map (_ * 3) 

Вы можете уменьшить его один проход с flatMap:

List(1,2,3,4) flatMap { x => 
    val y = x + 10 
    if (y % 2 == 0) Some(y * 3) else None 
} 
3

Один из способов избежать промежуточных коллекций заключается в использовании view.

List(1,2,3,4).view map (_ + 10) filter (_ % 2 == 0) map (_ * 3) 

Он не избегает каждых промежуточного, но это может быть полезно. This page имеет много информации и стоит того.

0

Scala может фильтровать и преобразовывать коллекцию различными способами.

Первый ваш пример:

List(1,2,3,4) map (_ + 10) filter (_ % 2 == 0) map (_ * 3) 

Может быть оптимизированы:

List(1,2,3,4) filter (_ % 2 == 0) map (v => (v+10)*3) 

Или складки могут быть использованы:

List(1,2,3,4).foldLeft(List[Int]()){ case (a,b) if b % 2 == 0 => a ++ List((b+10)*3) case (a,b) => a } 

Или, может быть, для-выражения:

for(v <- List(1,2,3,4); w=v+10 if w % 2 == 0) yield w*3 

Или, может быть, яснее понять, коллекция:

List(1,2,3,4).collect{ case v if v % 2 == 0 => (v+10)*3 } 

Но адресовать ваши вопросы Streams; Да, потоки могут быть использованы и для больших коллекций, где то, что хотели, часто встречаются в начале, потока является хорошим выбором:

def myStream(s:Stream[Int]): Stream[Int] = 
    ((s.head+10)*3) #:: myStream(s.tail.filter(_ % 2 == 0)) 

myStream(Stream.from(2)).take(2).toList // An infinitely long list yields 
             // 36 & 42 where the 3rd element 
             // has not been processed yet 

В этом примере потока фильтр применяется только к следующему элементу как это необходимо, а не всему списку - хорошая вещь, или она никогда не остановится :)