4

I want to chain a bunch of filters but do not want the overhead associated with creating multiple lists.

type StringFilter = (String) => Boolean

def nameFilter(value: String): StringFilter = 
    (s: String) => s == value

def lengthFilter(length: Int): StringFilter = 
    (s: String) => s.length == length

val list = List("Apple", "Orange")

Problem is this builds a list after each filter:

list.filter(nameFilter("Apples")).filter(lengthFilter(5))

// list of string -> list of name filtered string -> list of name and length filtered string

I want:

// list of string -> list of name and length filtered string

I find out which filters are needed at run-time so I must add filters dynamically.

// Not sure how to implement add function.
val filterPipe: StringFilter = ???

// My preferred DSL (or very close to it)
filterPipe.add(nameFilter("Apples")
filterPipe.add(lengthFilter(5))

// Must have DSL
list.filter(filterPipe)

How can I implement filterPipe?

Is there some way to recursively AND the filter conditions together in a filterPipe (which is itself a StringFilter)?

BAR
  • 15,909
  • 27
  • 97
  • 185

3 Answers3

4

You can use withFilter:

list.withFilter(nameFilter("Apples")).withFilter(lengthFilter(5))...
Lee
  • 142,018
  • 20
  • 234
  • 287
  • ooo... So `withFilter(filter)` chains them together without creating a new list for each filter? – BAR Feb 20 '15 at 21:11
  • Looking at `withFilter` implementation code now, my outlook on Scala keeps getting better! Thanks for the help :D – BAR Feb 20 '15 at 21:17
1

A blog post suggest another alternative using an implicit class to allow aggregating multiple predicates using custom operators

implicit class Predicate[A](val pred: A => Boolean) {
  def apply(x: A) = pred(x)

  def &&(that: A => Boolean) = new Predicate[A](x => pred(x) && that(x))
  def ||(that: A => Boolean) = new Predicate[A](x => pred(x) || that(x))
  def unary_! = new Predicate[A](x => !pred(x))
}

Then you can apply the predicate chain as follows

list.filter { (nameFilter("Apple") && lengthFilter(5)) (_) }

You can also chain the predicates dynamically

val list = List("Apple", "Orange", "Meat")
val isFruit = nameFilter("Apple") || nameFilter("Orange")
val isShort = lengthFilter(5)

list.filter { (isFruit && isShort) (_) }

As you can see the benefit of this approach compared to the withFilter approach is that you can combine the predicates arbitrarily

mucaho
  • 2,119
  • 20
  • 35
  • It does not look like the filters can be added dynamically here. Assuming dynamic addition is not a requirement, why not simply create a `nameAndLengthFilter(s: String, i: Int): (String) => Boolean`? – BAR Feb 20 '15 at 21:38
  • @BAR Updated with dynamic predicate append example – mucaho Feb 20 '15 at 21:51
  • I am using filters dynamically by adding them if the `if` statement that wraps them is true. So I still could not use your example dynamically enough with as concise of code as the `withFilter` approach. Your answer did show me some interesting points so +1 for you. – BAR Feb 21 '15 at 03:20
  • 1
    I realized your Predicate code is basically the implementation of `withFilter` in `TraversableLike` https://github.com/scala/scala/blob/v2.10.3/src/library/scala/collection/TraversableLike.scala – BAR Feb 21 '15 at 05:04
  • _"I still could not use your example dynamically enough"_ Sure you can dynamically add to a `Predicate` as you can to a `WithFilter`. However, for your particular use case where you only need `&&` filter chaining, there is no doubt that the library supported method is just better to use. It would be interesting to add additional filter chaining methods like `||` or `!` to the `WithFilter` class instead, but my preliminary attempts failed due to access modifiers. – mucaho Feb 21 '15 at 10:21
  • Your right about the dynamic usage, I misspoke. The `!` case is implemented by the standard library using `filterNot`. In the case one wants to use `||` remember that three `nots` and an `and` can make an `or` gate. Now trying to implement that with the library as is gets more complicated than your example. And since in my actual use case I could definitely need an `or` filter, I will be using your example in my code. – BAR Feb 21 '15 at 10:46
  • Please check out this new question when you can. http://stackoverflow.com/questions/28659506/scala-implicit-class-with-multiple-predicate-types Thanks again for the example, lots of good points to study there! – BAR Feb 22 '15 at 15:32
1

Consider also a view on the filters, like this,

list.view.filter(nameFilter("Apples")).filter(lengthFilter(5))

This prevents intermediate collections, namely for each entry in list it applies the subsequent filters.

Community
  • 1
  • 1
elm
  • 20,117
  • 14
  • 67
  • 113
  • This is closely related to `withFilter`. I have been studying its implementation code, thanks for pointing it out. – BAR Feb 21 '15 at 06:48
  • After further comparing the `withFilter` and `view` implementations I have found that although `view` does not create any lists, it does have to traverse twice, once for each filter, albeit on a limited view after the first filter. While using `withFilter` allows for the list to be traversed once by checking for both filter conditions simultaneously. – BAR Feb 21 '15 at 10:03