Reputation: 8601
How would be an elegant Scala solution to find an element that matches any of a group of conditions, but each condition having different priorities?
case class A(p1:Int, p2:Int)
List(A(2,3), A(4,4), A(3,5))
Say I want to query: find a item that p1 == p2
, if you don't
find it, give me item with p2 > p1
, if you don't find it, give the p1 = square(p2)
and so on. Always => Bool
My first guess is a recursive find function that tries one condition each loop, but I would like to know if there is better/simpler ways to do that.
Ideally, if the item matches more conditions it should be selected, but still respecting conditions priority.
Upvotes: 1
Views: 512
Reputation: 32719
l.find{x => x.p1 == x.p2}.getOrElse(l.maxBy(_.p2))
UPDATE: in your updated question you say "give me item with biggest p2, if you don't find it, give the lowest p1 and so on". But there is always a biggest value, by definition (unless the list is empty, but I gather the list can't be empty). So this is really just "give me item with biggest p2" (hence the above solution).
UPDATE 2: so the question changed again, and is now "find a item that p1 == p2, if you don't find it, give me item with p2 > p1 , if you don't find it, give the p1 = square(p2) and so on". I guess you really need a generic solution after all. So here's my take:
// The list of predicates, in order of priority
// (you can add/remove predicates as you see fit)
val predicates = List[A => Boolean](
x => x.p1 == x.p2,
x => x.p2 > x.p1,
x => x.p1 == x.p2*x.p2
)
val indexedPredicates = predicates.reverse.zipWithIndex
def score( x: A ): Option[Int] = indexedPredicates.find(_._1(x)).map(_._2)
def priorityFind( l: List[A] ): A = l.maxBy(score)
The idea is that you attribute a score to each element: None if the element does not match any predicate, Some(0) if it matches the last predicate, Some(1) if it matches the last but one predicate, and so on. Then you just take the one with the highest score (None
is "smaller" than any Some
instance, so this is consistent with what we want).
If you want to handle properly the case where no element matches any predicate, you would change priorityFind
like this:
def priorityFind( l: List[A] ): Option[A] = {
val filtered = l.view.flatMap{x => score(x).map(x -> _) }
if ( filtered.isEmpty ) None
else Some( filtered.maxBy(_._2)._1 )
}
Upvotes: 6
Reputation: 8495
Okay, I came up with this:
case class A(p1:Int, p2:Int)
val list = List(A(3,2), A(4,4), A(10,2), A(4,2))
val filter:PartialFunction[A,A] = {
case a@A(p1,p2) if p1==p2 => a
case a@A(p1,p2) if p2>p1 => a
case a@A(p1,p2) if p1==p2*p2 => a
}
list.collect(filter).foreach(println)
Now, if you need the best fit, I would make a ranking function instead which maps A => Int and then sort by it. You can save some work by filtering first and then sorting with your ranking afterwards. (The collect takes list.size time whereas sorting takes list.size * log(list.size) on average)
Upvotes: 0
Reputation: 4216
list.find(i => i.p1 == i.p2).getOrElse(list.sortBy(-_.p2).head)
Update, should fit your new requirements.
list.find(i => i.p1 == i.p2).getOrElse(
list.find(i => i.p2 > i.p1).getOrElse(
list.find(i => i.p1 == i.p2 * i.p2)))
Upvotes: 1