'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 |
