2015-06-15 3 views
7

Я новичок в Scala и пытаюсь найти лучший способ фильтровать & Карта коллекции. Вот пример игрушки, чтобы объяснить мою проблему.Scala: Лучший способ фильтровать и отображать на одной итерации

Подход 1: Это очень плохо, так как я повторяю в списке дважды и вычисляя одно и то же значение на каждой итерации.

val N = 5 
val nums = 0 until 10 
val sqNumsLargerThanN = nums filter { x: Int => (x * x) > N } map { x: Int => (x * x).toString } 

подход 2: Это немного лучше, но мне еще нужно вычислить (x * x) дважды.

val N = 5 
val nums = 0 until 10 
val sqNumsLargerThanN = nums collect { case x: Int if (x * x) > N => (x * x).toString } 

Итак, можно вычислить это без перебора коллекции дважды и не повторять одни и те же расчеты?

ответ

2

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

val sqNumsLargerThanN = nums collect { 
    case (x: Int) if (x * x) > N => (x * x).toString 
} 
+0

Почему кто-то вниз голосованиями этот ответ? 'collect' кажется очень идиоматичным способом сделать это. –

+0

Разве это не то же самое, что мой «подход 2»? –

+0

Да, это то же самое, что и Подход № 2 выше, и, исходя из определения _collect_, это кажется мне совершенно разумным; он говорит точно, что он делает. Это не означает, что другие подходы, описанные выше, лучше или хуже. – Nirmalya

4

Типичный подход заключается в использовании iterator (если это возможно) или view (если iterator не будет работать). Это не точно избежать двух обходов, но это не позволяет создать полноразмерную промежуточную коллекцию. Затем вы map первый и filter потом, а затем map снова, если это необходимо:

xs.iterator.map(x => x*x).filter(_ > N).map(_.toString) 

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

Если вы спрашиваете, потому что это узкое место в производительности, тогда, как правило, обычно нужно написать функцию возврата хвоста или использовать метод цикла while в старом стиле. Например, в вашем случае

def sumSqBigN(xs: Array[Int], N: Int): Array[String] = { 
    val ysb = Array.newBuilder[String] 
    def inner(start: Int): Array[String] = { 
    if (start >= xs.length) ysb.result 
    else { 
     val sq = xs(start) * xs(start) 
     if (sq > N) ysb += sq.toString 
     inner(start + 1) 
    } 
    } 
    inner(0) 
} 

Вы можете также передать параметр вперед в inner вместо того, чтобы использовать внешний строитель (особенно полезно для сумм).

+0

Hi Rex - что вы подразумеваете под этим, точно не избегает двух обходов? – sourcedelica

+0

@sourcedelica - Каждый итератор, прогуливаясь сам по списку, также (обязательно) проходит предыдущие итераторы. Таким образом, все они проходят в режиме блокировки, но если вы сопоставляете, затем фильтруете, а затем сопоставляете, на самом деле у вас есть следующий/hasNext вызовы, вложенные в три глубины. –

7

Может использовать foldRight

nums.foldRight(List.empty[Int]) { 
    case (i, is) => 
    val s = i * i 
    if (s > N) s :: is else is 
    } 

foldLeft также достичь той же цели, но результирующий список будет в обратном порядке (за счет ассоциативности foldLeft.

В качестве альтернативы, если вы хотите как играть с Scalaz

import scalaz.std.list._ 
import scalaz.syntax.foldable._ 

nums.foldMap { i => 
    val s = i * i 
    if (s > N) List(s) else List() 
} 
+0

Обратите внимание, что с помощью 'foldRight' по умолчанию вы переполняете свой стек, если ваш список превышает тысячу элементов. Кроме того, версия Scalaz не имеет никакого преимущества перед «flatMap». –

3

Очень простой подход, сть. Это также лениво, поэтому он будет выполнять код только тогда, когда это необходимо.

nums.view.map(x=>x*x).withFilter(x => x> N).map(_.toString) 

Посмотрите here к различиям между filter и withFilter.

+0

Это очень интересно. В потоке, к которому вы привязались, есть комментарий «Я не думаю, что вы должны использовать withFilter самостоятельно (кроме неявно внутри for-expressions)». Есть ли причина не использовать 'withFilter' –

+0

Я использую' filter' только тогда, когда хочу создать новую коллекцию, которая будет использоваться дальше по дороге. Если я просто хочу, чтобы фильтр был промежуточным этапом конвейера операций, я всегда использую 'withFilter'. – marios

2

Я до сих пор, чтобы подтвердить, что это действительно один проход, но:

val sqNumsLargerThanN = nums flatMap { x => 
    val square = x * x 
    if (square > N) Some(x) else None 
    } 
+0

Я хочу спросить, будет ли загрузка обертывания каждого элемента для Option Layer быть более легким, чем вычисление x * x дважды? Стоимость создания объекта Option может быть проигнорирована? (Я новичок в Scala от C++.) –

+1

Чтобы ответить на ваш вопрос напрямую, нет, выделение опций не является бесплатным. Это дешево.На протяжении многих лет JVM GC отлично справлялся с распределением и сбором небольших объектов в цикле. Поэтому, хотя и не бесплатно, это почти никогда не место, где я бы начал оптимизировать. – triggerNZ

+2

Кроме того, я должен упомянуть, что, хотя это забавная головоломка для решения, попытка минимизировать количество проходов над коллекцией в мире функционального программирования обычно не является лучшим способом получения производительности. Эти вещи распространены в мире C/C++ и гораздо менее распространены в JVM. Сказав это, давайте предположим, что ваша коллекция огромна, скажем, 8 ГБ. Тогда вы действительно хотите пройти только один раз, и я буду придерживаться сбора или использования ленивых коллекций. Двойное умножение будет оптимизировано JIT – triggerNZ

2

Рассмотрим это для понимания,

for (x <- 0 until 10; v = x*x if v > N) yield v.toString 

который разворачивается к flatMap над диапазон и (ленивый) withFilter на только что вычисленный квадрат и дает коллекцию с отфильтрованными результатами. Следует отметить одну итерацию и один расчет квадрата (в дополнение к созданию диапазона).

+0

@ErikMadsen действительно, спасибо кучу, исправлено :) – elm

0

Вы можете использовать flatMap.

val sqNumsLargerThanN = nums flatMap { x => 
    val square = x * x 
    if (square > N) Some(square.toString) else None 
} 

Или с Scalaz,

import scalaz.Scalaz._ 

val sqNumsLargerThanN = nums flatMap { x => 
    val square = x * x 
    (square > N).option(square.toString) 
} 

решает задаваемый вопрос о том, как сделать это с помощью одной итерации. Это может быть полезно при потоковой передаче данных, например, с помощью Iterator.

Однако ... если вы вместо этого хотите абсолютное быстрая реализация, это не так. На самом деле, я подозреваю, что вы будете использовать mutable ArrayList и цикл while. Но только после профилирования вы бы точно знали. В любом случае, это для другого вопроса.

0

Использования для понимания будет работать:

val sqNumsLargerThanN = for {x <- nums if x*x > N } yield (x*x).toString 

Кроме того, я не уверен, но я думаю, что компилятор Scala смекалки фильтра перед картой и буду делать только 1 проход, если это возможно.

-2

Я тоже новичок сделал это следующим образом

for(y<-(num.map(x=>x*x)) if y>5) { println(y)} 

 Смежные вопросы

  • Нет связанных вопросов^_^