2013-07-01 4 views
5

у меня есть код, такой как:Scala, фильтр коллекции на основе нескольких условий

val strs = List("hello", "andorra", "trab", "world") 
def f1(s: String) = !s.startsWith("a") 
def f2(s: String) = !s.endsWith("b") 

val result = strs.filter(f1).filter(f2) 

сейчас, f1 и f2 должны применяться на основе условия, такие как:

val tmp1 = if (cond1) strs.filter(f1) else strs 
val out = if (cond2) tmp1.filter(f2) else tmp1 

является там более хороший способ сделать это, не используя временную переменную tmp1?

один способ будет фильтровать на основе списка функций, таких как:

val fs = List(f1 _,f2 _) 
fs.foldLeft(strs)((fn, list) => list.filter(fn)) 

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

Я ищу что-то вроде этого (конечно, это не компилируется, иначе я бы уже есть ответ на вопрос):

val result = 
    strs 
    .if(cond1, filter(f1)) 
    .if(cond2, filter(f2)) 
+0

Похоже, вы хотите получить список кортежей с (условие, filterPredicate). Затем вы можете отфильтровать этот список в зависимости от того, выполняется ли условие (например, _._ 1). Теперь у вас есть список функций, которые вы хотите применить. Затем вы можете сопоставить это со строкой и уменьшить с помощью && (логика и). Извините, если он слишком волнительный. – Felix

+0

спасибо, это тоже хорошая идея, но я искал что-то большее, чем ответ на ной. –

ответ

8

Вы можете легко использовать неявный класс, чтобы дать вам этот синтаксис:

val strs = List("hello", "andorra", "trab", "world") 

    def f1(s: String) = !s.startsWith("a") 

    def f2(s: String) = !s.endsWith("b") 

    val cond1 = true 
    val cond2 = true 

    implicit class FilterHelper[A](l: List[A]) { 
    def ifFilter(cond: Boolean, f: A => Boolean) = { 
     if (cond) l.filter(f) else l 
    } 
    } 

    strs 
    .ifFilter(cond1, f1) 
    .ifFilter(cond2, f2) 

res1: List[String] = List(hello, world) 

я бы использовал if в качестве имени метода, но это зарезервированное слово.

+0

вот что я искал; то я могу обработать его в несколько шагов, как в strs.filter (...). map (...). ifFilter (...). map (...). ifFilter (...). Я бы изменил только «l.filter (a =>! Cond || filter (a))« by »if (cond) l.filter (filter) else l", чтобы избежать использования фильтра с всегда истинным условием. –

+0

Я обновил код, чтобы отразить изменения, не нужно проходить, когда вам это не нужно. – Noah

3

Вы можете сделать это, суммируя свои функции предиката.

Заметим, что фильтр предиката, A => Boolean, имеет операцию на добавление:

def append[A](p1: A => Boolean, p2: A => Boolean): A => Boolean = 
    a => p1(a) && p2(a) 

и значение идентичности:

def id[A]: A => Boolean = 
    _ => true 

, которая удовлетворяет условию, что для любого предиката p: A => Boolean, append(p, id) === p.

Это упрощает проблему включения/исключения предиката, основанного на условии: если условие ложно, просто включите предикат id. Он не влияет на фильтр, потому что он всегда возвращает true.

Подведем предикаты:

def sum[A](ps: List[A => Boolean]): A => Boolean = 
    ps.foldLeft[A => Boolean](id)(append) 

Обратите внимание, что мы фолд на id, так что если ps пусто, мы получаем предикат идентичности, то есть фильтр, который не делает ничего, как можно было ожидать.

Сведя все это вместе:

val predicates = List(cond1 -> f1 _, cond2 -> f2 _) 

strs.filter(sum(predicates.collect { case (cond, p) if cond => p })) 
// List(hello, world) 

Обратите внимание, что список strs был только пройден один раз.


Теперь, для версии Scalaz вышеизложенного:

val predicates = List(cond1 -> f1 _, cond2 -> f2 _) 

strs filter predicates.foldMap { 
    case (cond, p) => cond ?? (p andThen (_.conjunction)) 
} 
// List("hello", "world") 
+0

проблема заключается в том, что если cond1 = false, вы применяете нечто вроде «strs.filter (true)», которое медленнее, чем просто возврат самого списка. –

+1

Дэвид: но тогда, если оба условия верны, мы по-прежнему применяем только «фильтр» один раз. Для предикатов 'n' ваше принятое решение будет запускать' filter' до 'n' раз! –

+0

Вы говорите, что эти операции/функции 'append' и identity' id' существуют или просто они могут быть определены? Я не могу найти их в Scala 2.10. –

3

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

implicit class FilterHelper[A](l: List[A]) { 
    def ifthen[B](cond: Boolean, f:(List[A]) => List[B]) = { 
    if (cond) f(l) else l 
    } 
} 

затем использовать его как это:

val list = List("1", "2")  
val l2 = list.ifthen(someCondition, _.filter(f1) 
val l3 = list.ifthen(someOtherCondition, _.map(_.size)) 
2

Было бы довольно просто, чтобы просто включить условие в вашем блоке для фильтра, например:

val result = strs filter (x => !cond1 || f1(x)) filter (x => !cond2 || f2(x)) 

причем результат будет применять фильтр, если условие выполняется, или просто вернуться в тот же список.

+0

Да, но я думаю, что дело в том, зачем применять эту функцию, если ничего не делать. Зачем перебирать список (скажем, очень большой), если вам не нужно? – cmbaxter

+0

Может написать 'strs filter {x => (! Cond1 || f1 (x)) && (! Cond2 || f2 (x))}', поэтому вы проходите только один раз –

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