Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Filter over List with dynamic filter parameter

Tags:

scala

If I had a

case class User(var firstName: String, var lastName: String, var city: String)

and a list

val users = List(
  new User("Peter", "Fox", "Berlin"),
  new User("Otto",  "Schmidt", "Berlin"),
  new User("Carl",  "Schmidt", "Berlin"),
  new User("Hans",  "Schmidt", "Berlin"),
  new User("Hugo",  "Schmidt", "Berlin"))

define something

val test1 = (user:User,key:String) => user.lastName.equals(key)
val test2 = (user:User,key:String) => user.firstName.startsWith(key)

and filter

val test = users.filter(u => {
  test1(u,"Schmidt") && test2(u,"H")
})

This works fine. But How can I generate something that filters test1, test2 ... testn dynamicly form maybe a list? I want to have a lot of filter conditions predefined and combine them to one condition (like test1(u,"Schmidt") && test2(u,"H")) to filter my list and combine the order of filtering.

like image 481
wapgui Avatar asked Jul 08 '26 01:07

wapgui


2 Answers

Basically what you want is a way to compose predicates. A predicate in this case is a function that takes an User and returns a Boolean. So the type is just User => Boolean

First of all, I would reformulate your "predicate generator methods" so that you can generate predicates. Inside some helper object:

object UserPredicates {
  def lastNameEquals(value:String)(user:User) = user.lastName == value
  def firstNameStartsWith(value:String)(user:User) = user.firstName.startsWith(value)
  ..
}

To generate the predicate firstName starts with "H", you can partially apply the firstNameStartsWith method like so:

import UserPredicates._
val p1: User => Boolean = firstNameStartsWith("H")

Then it is pretty simple to create a predicate from multiple predicates by defining methods that compose predicates. Maybe also inside UserPredicates:

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

Then you can do

import UserPredicates._
val condition1 = firstNameStartsWith("H")
val condition2 = lastNameEquals("Schmidt")
val combined = and(Seq(condition1, condition2))
users.filter(combined)

Or short

users.filter(and(firstNameStartsWith("H"), lastNameEquals("Schmidt")))

.

By the way: you should not use new to create case class instances. Also, you don't have to use equals to compare strings. The scala == operator will call equals and not just check for reference equality like the java == operator.

like image 50
Rüdiger Klaehn Avatar answered Jul 09 '26 17:07

Rüdiger Klaehn


I kind of like using implicits so you can get this kind of syntax:

  case class User(var firstName: String, var lastName: String, var city: String)

  val users = List(
    new User("Peter", "Fox", "Berlin"),
    new User("Otto", "Schmidt", "Berlin"),
    new User("Carl", "Schmidt", "Berlin"),
    new User("Hans", "Schmidt", "Berlin"),
    new User("Hugo", "Schmidt", "Berlin"))

  //Note that these are curried now
  val filterLastName = (key: String) => (user: User) => user.lastName.equals(key)
  val filterFirstNameFirstChars = (key: String) => (user: User) => user.firstName.startsWith(key)

  implicit class FilterHelper[A](l: List[A]) {
    def filter(filters: List[A => Boolean]): List[A] = {
      l.filter(a => filters.forall(f => f(a)))
    }
  }

  //implicit filter takes a list of user predicates
  val test = users.filter(List(
    filterLastName("Schmidt"),
    filterFirstNameFirstChars("H")))
like image 37
Noah Avatar answered Jul 09 '26 18:07

Noah



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!