Reputation: 66851
Let's say I have a class that looks something like this:
class Foo(Prop1:Int, Prop2:Int, Prop3:Int)
{
..
}
And I wanted to create a function that gets the max of some arbitrary property from a list of Foo
s.
Like this:
def getMax(Foos:List[Foo], Property:??) = Foos.map(_.Property).sort(_ > _).head
If I called getMax(myFooList, Prop1)
, it would return the value of the highest Prop1
from this list of Foo
s.
My question is, how can I make this work? I guess I could create some kind of enum (the scala equivalent) for the Property
and do a match
and then run the map
on the appropriate property, but that seems like a lot of work - I'd have to extend my enum and the function each time Foo
is refactored.
Also, not as important, but is there a better way to grab the max value of a list then what I did?
Upvotes: 6
Views: 13004
Reputation: 2959
Please remember to check if the list is not null, or the maxBy method will fail
val myList: List[Foo] = List();
val unsafeMax = myList.maxBy(_.propertyBar).propertyBar
// java.lang.UnsupportedOperationException: empty.max
val safeMax = if (myList.isEmpty) 0 else myList.maxBy(_.propertyBar).propertyBar;
safeMax == 0
Upvotes: 0
Reputation: 1238
You should use standard maxBy
method:
List(("a", 2), ("b", 3), ("c", 4)).maxBy(_._2)
=> (String, Int) = (c,4)
Upvotes: 18
Reputation: 1062
You can do this so simply using existing functionality that writing your own getMax is probably unnecessary:
scala> val fooList = List(Foo(1,2),Foo(2,2),Foo(3,2),Foo(4,2))
fooList: List[Foo] = List(Foo(1,2), Foo(2,2), Foo(3,2), Foo(4,2))
scala> fooList.map(_.p2).max
res12: Int = 2
scala> fooList.map(_.p1).max
res13: Int = 4
If you wanted to specify the property 'getter' elsewhere you could do it like this:
scala> def p1 = (f: Foo) => f.p1
p1: Foo => Int
scala> def p2 = (f: Foo) => f.p2
p2: Foo => Int
scala> fooList.map(p1).max
res14: Int = 4
scala> fooList.map(p2).max
res15: Int = 2
Upvotes: 12
Reputation: 13221
You can use objects inheriting from Product
. It will be simpler and more type safe if you know arity in advance:
def getMax(foos: List[Product2[Int,Int]], f: Product2[Int,Int] => Int) = foos.map{f} ....
Then, you are free to feed getMax
with anything like Tuple
, e.g.
class Foo(val prop1: Int, val prop2: Int) extends Tuple2[Int, Int](prop1, prop2)
// this will duplicate values in an object actually.
getMax((new Foo(1,2)), _._2)
or inherit right from Product
:
class Bar(val prop1: Int, val prop2: Int) extends Product2[Int, Int] {
def _1 = prop1
def _2 = prop2
}
val b = new Bar(2, 3)
getMax(List(b), _._2)
or simply use Scala's tuples:
getMax( (1,10) :: Nil, _._2)
getMax( List(1 -> 10), _._2)
// these are the same
Everything will become more complicated in case you do not know arity beforehand, because generic Product
will let you retrieve elements as Any
only (See Product.productElement(n: Int)
method) -- thus you are loosing type safety.
Upvotes: 1
Reputation: 7243
The way I would do it is by passing to the getMax()
method a function that knows how to extract the required property out of your Foo
, i.e. something of type Foo => Int
.
The way I would do it therefore is as follows:
scala> case class Foo(p1: Int, p2: Int, p3: Int)
defined class Foo
scala> def getMax(foos: List[Foo], prop: Foo => Int) = foos.map(prop).sort(_ > _).head
getMax: (List[Foo],(Foo) => Int)Int
scala> val lst = List(Foo(1,2,3), Foo(2,3,4), Foo(3,4,5))
lst: List[Foo] = List(Foo(1,2,3), Foo(2,3,4), Foo(3,4,5))
scala> getMax(lst, _.p1)
res0: Int = 3
scala> getMax(lst, _.p2)
res1: Int = 4
scala> getMax(lst, _.p3)
res2: Int = 5
-- Flaviu Cipcigan
Upvotes: 1
Reputation: 862
You can simply pass another function into getMax to instruct it how to map each Foo:
case class Foo(p1:Int, p2:Int)
def getMax(foos:List[Foo], mapper:Foo=>Int):Int = foos.map(mapper).foldLeft(Math.MIN_INT)((i,m)=>m.max(i))
val fooList = List(Foo(1,2),Foo(2,2),Foo(3,2),Foo(4,2))
getMax(fooList,_.p1)
//--> 4
Upvotes: 6