In Scala, how to use Ordering[T] with List.min or List.max and keep code readable

前端 未结 6 559
轮回少年
轮回少年 2020-12-23 20:58

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:

相关标签:
6条回答
  • 2020-12-23 21:39

    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)
    
    0 讨论(0)
  • 2020-12-23 21:40

    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.

    0 讨论(0)
  • 2020-12-23 21:46

    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.

    0 讨论(0)
  • 2020-12-23 21:50
    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.

    0 讨论(0)
  • 2020-12-23 21:51

    In Scala 2.9, you can do list minBy { _._2 }.

    0 讨论(0)
  • 2020-12-23 22:03

    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 contramap 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.maContravariantImplicit[Ordering, Int](Ordering.Int).contramap { x: (_, Int) => x._2 }
    res8: scala.math.Ordering[Tuple2[_, Int]] = scala.math.Ordering$$anon$2@4fa666bf
    
    0 讨论(0)
提交回复
热议问题