Seq.sortBy extension in Scala

Say I have a list of names.

case class Name(val first: String, val last: String) val names = Name("c", "B") :: Name("b", "a") :: Name("a", "B") :: Nil 

If now I want to sort this list by name (and if this is not enough, by name), this is easy to do.

 names.sortBy(n => (n.last, n.first)) // List[Name] = List(Name(a,B), Name(c,B), Name(b,a)) 

But what if I would like to sort this list based on a different sort for the rows?

Unfortunately, the following does not work:

 val o = new Ordering[String]{ def compare(x: String, y: String) = collator.compare(x, y) } names.sortBy(n => (n.last, n.first))(o) // error: type mismatch; // found : java.lang.Object with Ordering[String] // required: Ordering[(String, String)] // names.sortBy(n => (n.last, n.first))(o) 

is there a way that allows me to reorder without having to write an explicit sortWith method with multiple if - else branches to handle all cases?

+4
source share
3 answers

One solution is to expand the implicitly used Tuple2 order. Unfortunately, this means writing Tuple2 into code.

 names.sortBy(n => (n.second, n.first))(Ordering.Tuple2(o, o)) 
+2
source

Well, this almost does the trick:

 names.sorted(o.on((n: Name) => n.last + n.first)) 

Alternatively, you can also do this:

 implicit val o = new Ordering[String]{ def compare(x: String, y: String) = collator.compare(x, y) } names.sortBy(n => (n.last, n.first)) 

This locally defined implicit will take precedence over that defined in the Ordering object.

+4
source

I am not 100% sure what methods you think a collator should have.

But you have more flexibility if you define order in the case class:

 val o = new Ordering[Name]{ def compare(a: Name, b: Name) = 3*math.signum(collator.compare(a.last,b.last)) + math.signum(collator.compare(a.first,b.first)) } names.sorted(o) 

but you can also provide an implicit conversion from string ordering to name order:

 def ostring2oname(os: Ordering[String]) = new Ordering[Name] { def compare(a: Name, b: Name) = 3*math.signum(os.compare(a.last,b.last)) + math.signum(os.compare(a.first,b.first)) } 

and then you can use any order of strings to sort the names:

 def oo = new Ordering[String] { def compare(x: String, y: String) = x.length compare y.length } val morenames = List("rat","fish","octopus") scala> morenames.sorted(oo) res1: List[java.lang.String] = List(rat, fish, octopus) 

Edit: a convenient trick, if it wasn’t obvious, is that if you want to order N things and you are already using the comparison, you can simply multiply each thing by 3 ^ k (with the first, in order to multiply by the largest degree 3) and add.


If your comparisons are very time consuming, you can easily add a cascading comparison:

 class CascadeCompare(i: Int) { def tiebreak(j: => Int) = if (i!=0) i else j } implicit def break_ties(i: Int) = new CascadeCompare(i) 

and then

 def ostring2oname(os: Ordering[String]) = new Ordering[Name] { def compare(a: Name, b: Name) = os.compare(a.last,b.last) tiebreak os.compare(a.first,b.first) } 

(just be careful to x tiebreak ( y tiebreak ( z tiebreak w ) ) ) them x tiebreak ( y tiebreak ( z tiebreak w ) ) ) so that you do not perform implicit conversion several times in a row).

(If you really need quick comparisons, then you should write all this manually or pack the orders in an array and use a while loop. I assume that you are not so desperate for performance.)

+1
source

Source: https://habr.com/ru/post/1340297/


All Articles