Ответ 1
Большинство коллекций Scala с нетерпением применяют операции и (если вы не используете библиотеку макросов, которая делает это для вас) не будут сдерживать операции. Таким образом, filter
, за которым следует map
, обычно создаются две коллекции (и даже если вы используете Iterator
или somesuch, промежуточная форма будет временно создана, хотя и только элемент за раз), тогда как collect
не будет.
С другой стороны, collect
использует частичную функцию для реализации совместного теста, а частичные функции медленнее, чем предикаты (A => Boolean
) при проверке, находится ли что-то в коллекции.
Кроме того, могут быть случаи, когда проще читать их, чем другие, и вы не заботитесь о различиях в производительности или использовании памяти в 2 раза или около того. В этом случае используйте то, что более понятно. Как правило, если у вас уже есть функции с именами, лучше читать
xs.filter(p).map(f)
xs.collect{ case x if p(x) => f(x) }
но если вы поставляете встроенные блокировки, collect
обычно выглядит более чистым
xs.filter(x < foo(x, x)).map(x => bar(x, x))
xs.collect{ case x if foo(x, x) => bar(x, x) }
хотя это не обязательно короче, потому что вы только ссылаетесь на переменную один раз.
Теперь, насколько велика разница в производительности? Это меняется, но если мы рассмотрим такой набор:
val v = Vector.tabulate(10000)(i => ((i%100).toString, (i%7).toString))
и вы хотите выбрать вторую запись, основанную на фильтрации первой (так что операции с фильтром и картой очень просто), тогда мы получим следующую таблицу.
Примечание: можно получить ленивые представления в коллекциях и собирать там операции. Вы не всегда возвращаете исходный тип, но всегда можете использовать to
получить правильный тип коллекции. Таким образом, xs.view.filter(p).map(f).toVector
, из-за представления, не создаст промежуточного элемента. Это также проверено ниже. Было также высказано предположение, что можно xs.flatMap(x => if (p(x)) Some(f(x)) else None)
и что это эффективно. Это не так. Он также тестировался ниже. И можно избежать частичной функции, явно создав конструктор: val vb = Vector.newBuilder[String]; xs.foreach(x => if (p(x)) vb += f(x)); vb.result
, а результаты для этого также перечислены ниже.
В приведенной ниже таблице проверены три условия: отфильтровать ничего, отфильтровать половину, отфильтровать все. Время нормализовано для фильтрации/отображения (100% = в то же время, что и фильтр/карта, лучше - ниже). Границы ошибок составляют + - 3%.
Производительность различных альтернатив фильтра/карты
====================== Vector ========================
filter/map collect view filt/map flatMap builder
100% 44% 64% 440% 30% filter out none
100% 60% 76% 605% 42% filter out half
100% 112% 103% 1300% 74% filter out all
Таким образом, filter/map
и collect
, как правило, довольно близки (при выигрыше collect
, когда вы держите много), flatMap
намного медленнее во всех ситуациях, и создание строителя всегда выигрывает. (Это верно для Vector
. Другие коллекции могут иметь несколько иные характеристики, но тенденции для большинства будут похожи, потому что различия в операциях аналогичны.) Представления в этом тесте, как правило, выигрывают, но они не всегда работают без проблем (и они не намного лучше, чем collect
, за исключением пустого случая).
Итак, нижняя строка: предпочитайте filter
, затем map
, если она помогает ясности, когда скорость не имеет значения, или предпочитает ее для скорости, когда вы отфильтровываете почти все, но все же хотите сохранить функциональность (так что don Не хотите использовать строитель); и в противном случае используйте collect
.