tags:

views:

1018

answers:

5

In Scala 2.8, I had a need to call List.min and provide my own compare function to get the value based on the second element of a Tuple2. I had to write this kind of code:

val list = ("a", 5) :: ("b", 3) :: ("c", 2) :: Nil

list.min( new Ordering[Tuple2[String,Int]] { 
  def compare(x:Tuple2[String,Int],y:Tuple2[String,Int]): Int = x._2 compare y._2 
} )

Is there a way to make this more readable or to create an Ordering out of an anonymous function like you can do with list.sortBy(_._2)?

+4  A: 

One thing you can do is use the more concise standard tuple type syntax instead of using Tuple2:

val min = list.min(new Ordering[(String, Int)] { 
  def compare(x: (String, Int), y: (String, Int)): Int = x._2 compare y._2 
})

Or use reduceLeft to have a more concise solution altogether:

val min = list.reduceLeft((a, b) => (if (a._2 < b._2) a else b))

Or you could sort the list by your criterion and get the first element (or last for the max):

val min = list.sort( (a, b) => a._2 < b._2 ).first

Which can be further shortened using the placeholder syntax:

val min = list.sort( _._2 < _._2 ).first

Which, as you wrote yourself, can be shortened to:

val min = list.sortBy( _._2 ).first

But as you suggested sortBy yourself, I'm not sure if you are looking for something different here.

Fabian Steeg
I haven't look at the library code, but the reason I prefer to use `min` is I assume that would it would be linear in time whereas `sortBy` would be o(n.log(n)). I also think using a method called `min` makes the intent clearer, although `list.sortBy(_._2).first` is pretty clear too.
huynhjl
+2  A: 

You could always define your own implicit conversion:

implicit def funToOrdering[T,R <% Ordered[R]](f: T => R) = new Ordering[T] {
  def compare(x: T, y: T) = f(x) compare f(y)
}

val list = ("a", 5) :: ("b", 3) :: ("c", 2) :: Nil

list.min { t: (String,Int) => t._2 }  // (c, 2)

EDIT: Per @Dario's comments.

Might be more readable if the conversion wasn't implicit, but using an "on" function:

def on[T,R <% Ordered[R]](f: T => R) = new Ordering[T] {
  def compare(x: T, y: T) = f(x) compare f(y)
}

val list = ("a", 5) :: ("b", 3) :: ("c", 2) :: Nil

list.min( on { t: (String,Int) => t._2 } ) // (c, 2)
Mitch Blevins
+1 Nice. Was about to post the same. In Haskell, you'd have an `on` combinator defined for this purpose. E.g. `sort (compare 'on' length)` to sort a list by length
Dario
Yes, I like the explicit conversion better. Reads better with "on".
Mitch Blevins
The trick is that on is basically an infix operator for functions.It's just `f 'on' g = \x y -> f (g x) (g y)` for all fitting functions. But yours is nice as well
Dario
Personally, I'd write `on` to take another `Ordering`.
Daniel
Turns out `on` already exists in the library, saw that when I looked up how `sortBy` is implemented while checking another answer. That led me to this `list.min( Ordering.Int.on[(String,Int)] (_._2) )`.
huynhjl
+2  A: 
list.min(Ordering.fromLessThan[(String, Int)](_._2 < _._2))

Which is still too verbose, of course. I'd probably declare it as a val or object.

Daniel
+5  A: 

C'mon guys, you made the poor questioner find "on" himself. Pretty shabby performance. You could shave a little further writing it like this:

list min Ordering[Int].on[(_,Int)](_._2)

Which is still far too noisy but that's where we are at the moment.

extempore
`list min Ordering.by((_: (_, Int))._2)`
Seth Tisue
+1  A: 

The function Ordering#on witnesses the fact that Ordering is a contra-variant functor. Others include Comparator, Function1, Comparable and scalaz.Equal.

Scalaz provides a unified view on these types, so for any of them you can adapt the input with value comap f, or with symbolic denotation, value ∙ f

scala> import scalaz._
import scalaz._

scala> import Scalaz._
import Scalaz._

scala> val ordering = implicitly[scala.Ordering[Int]] ∙ {x: (_, Int) => x._2}
ordering: scala.math.Ordering[Tuple2[_, Int]] = scala.math.Ordering$$anon$2@34df289d

scala> List(("1", 1), ("2", 2)) min ordering  
res2: (java.lang.String, Int) = (1,1)

Here's the conversion from the Ordering[Int] to Ordering[(_, Int)] in more detail:

scala> scalaz.Scalaz.maCofunctorImplicit[Ordering, Int](Ordering.Int).comap { x: (_, Int) => x._2 }
res8: scala.math.Ordering[Tuple2[_, Int]] = scala.math.Ordering$$anon$2@4fa666bf
retronym
Scalaz is black belt level stuff. Could be dangerous for me to try now... But it may provide me an angle on Scalaz that I can relate to. So the unified view is provided by `implicitly`? What is `comap`? Is it similar to `map`? Where can I find more documentation?
huynhjl