Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

scala, filter a collection based on several conditions

Tags:

scala

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))
like image 814
David Portabella Avatar asked Jul 01 '13 14:07

David Portabella


People also ask

How do you write multiple conditions in a filter?

To filter an array with multiple conditions:Use the && (And) operator to check for multiple conditions. The Array. filter() method will return all elements that satisfy the conditions.

How to filter Data in scala?

Syntax. The following is the syntax of filter method. Here, p: (A) => Boolean is a predicate or condition to be applied on each element of the list. This method returns the all the elements of list which satisfiles the given condition.

How do you filter elements in a list Scala?

To use filter on your collection, give it a predicate to filter the collection elements as desired. Your predicate should accept a parameter of the same type that the collection holds, evaluate that element, and return true to keep the element in the new collection, or false to filter it out.

What is array filter in Scala?

Scala filter is a method that is used to select the values in an elements or collection by filtering it with a certain condition.


2 Answers

You could use an implicit class to give you this syntax:

  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)

I would have used if as the method name but it's a reserved word.

like image 183
Noah Avatar answered Sep 22 '22 17:09

Noah


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")
like image 36
Ben James Avatar answered Sep 26 '22 17:09

Ben James