Jake
Jake

Reputation: 4650

scala: Dynamically filtering with predicates

I have a Map[String, Int]

val labelMap: mutable.HashMap[String, Int] = sparse0.sparseOperationCountRowsByTarget()

I want to filter that map in one statement. The filter may filter both by String or Int

In crudest form it looks like this:

 val labelMapFiltered = labelMap.filter(label => label._1.startsWith("REL") || label._2 < 400)

Now I already have general utility predicates for String and Int functions. String predicates are:

object StringPredicates
{
  def stringEquals(required:String)(input:String) = input == required
  def stringStartsWith(required:String)(input:String) = input.startsWith(required)
  def stringContains(required:String)(input:String) = input.contains(required)



  def and(predicates:Seq[String => Boolean])(input:String) = predicates.forall(predicate => predicate(input))
  def or(predicates:Seq[String => Boolean])(input:String) = predicates.exists(predicate => predicate(input))
}

Int predicates are same pattern as above.

This allows for the following filter:

val sw1=stringContains("#")
val sw2=stringStartsWith("REL")
val sw3=intGT(400)



      val labelMapFiltered = labelMap.filter( label =>sw1(label._1) || sw2(label._1) || sw3(label._2) )

I want to pass the predicates into the function (as a Seq, I assume) and then filter. So I am looking for something like:

val labelMapFiltered = labelMap.filter( myFunction(myPredSeq))

Can I do this using the predicates I already have? Writing predicates for Tuple2[String,Int] that match the specific Map in this function seems too specific. I would then have to write predicates for every type of Map I want to filter.

Upvotes: 1

Views: 1212

Answers (2)

Joe K
Joe K

Reputation: 18424

You can transform your predicates into accepting the appropriate type using the compose method, e.g.:

val sw1: (String, Int) => Boolean = stringContains("#").compose(kv: (String, Int) => kv._1)

Or even do the same but mapping over your predicates:

val predsOfTuples1 = predsOfStrings.map(_.compose(kv: (String, Int) => kv._1)))
val predsOfTuples2 = predsOfInts.map(_.compose(kv: (String, Int) => kv._2))
val preds = predsOfTuples1 ++ predsOfTuples2

The last thing you would need to do is make your and and or methods generic so you can use them on predicates of tuples:

def and[A](predicates:Seq[A => Boolean])(input:A) = predicates.forall(predicate => predicate(input))
def or[A](predicates:Seq[A => Boolean])(input:A) = predicates.exists(predicate => predicate(input))

Upvotes: 5

stefanobaghino
stefanobaghino

Reputation: 12794

Let's simplify the input and consider a simple list of Ints:

List(0, 1, 2, 3, 4, 5, 6, 7, 8, 9)

You can put all your predicates in a Stream (so that they're evaluated lazily and you don't have to evaluate all of them):

val predicates = Stream((n: Int) => n == 0, (n: Int) => n % 2 == 1, (n: Int) => n >= 8)

The rules are that numbers will be kept if they are 0, odd or greater or equal to 8.

Now we ask to filter by looking if there is at least one predicate in our collection that returns true:

list.filter(n => predicates.exists(p => p(n)))

The output is a list of numbers that respect at least one of the rules:

List(0, 1, 3, 5, 7, 8, 9)

Upvotes: 0

Related Questions