'scala, filter a collection based on several conditions

I have a code such as:

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)

now, f1 and f2 should be applied based on a condition, such as:

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

is there a nicer way to do this, without using a temporary variable tmp1?

one way would to filter based on a list of functions, such as:

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

but then I would need to build a list of functions based on the conditions (and so, I would move the problem of using a temporary string list variable, to using a temporary function list variable (or I should need to use a mutable list)).

I am looking something like this (of course this does not compile, otherwise I would already have the answer to the question):

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


Solution 1:[1]

You can do this by summing your predicate functions.

Observe that a filter predicate, A => Boolean, has an append operation:

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

And an identity value:

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

which satisfies the condition that for any predicate p: A => Boolean, append(p, id) === p.

This simplifies the problem of including/excluding a predicate based on a condition: if the condition is false, simply include the id predicate. It has no effect on the filter because it always returns true.

To sum the predicates:

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

Note that we fold onto id, so if ps is empty, we get the identity predicate, i.e. a filter that does nothing, as you'd expect.

Putting this all together:

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

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

Note that the list strs was only traversed once.


Now, for the Scalaz version of the above:

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

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

Solution 2:[2]

@Noah's answer is good, and you can take it and generalize it further if you want to be able to perform any type of action on a list then returns a new List given a condition if you make the following change:

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

Then use it like this:

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

Solution 3:[3]

It would be rather simple to just include the condition in your block for the filter, like so:

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

whereby the result would apply a filter if the condition is met, or simply return the same list.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2 cmbaxter
Solution 3 James