Reputation: 1520
I have seen this question How to sort a list in Scala by two fields?
This is similar but not a duplicate.
I can easily sort a List[DataPoint]
using the answer from the earlier question:
case class DataPoint(keys: List[String], value: Double)
listOfDataPoints.sortBy(point => (point.keys(0), point.keys(1)))
However I don't know the number of items in keys
. What I do know is that every DataPoint
in a given list will have the same number of keys, so there is never a case of sorting List("a")
and List("a", "b")
.
So how can I sort the list by an unknown number of keys?
Upvotes: 3
Views: 571
Reputation: 15074
You could define your own Ordering[List[String]]
. For example, you could define:
class ListOrdering[A](implicit aOrd: math.Ordering[A]) extends Ordering[List[A]] {
def compare(a1: List[A], a2: List[A]) = (a1, a2) match {
case (Nil, _) => if (a2.isEmpty) 0 else -1
case (_, Nil) => 1
case (h1 :: t1, h2 :: t2) => if (aOrd.compare(h1, h2) == 0) compare(t1, t2) else aOrd.compare(h1, h2)
}
}
Then making the following available somewhere in scope:
implicit val listOrd = new ListOrdering[String]
you can write:
dps.sortBy(_.keys)
and it should work.
Note that my definition of ListOrdering
is generalised to be useable for any type A
with an implicit Ordering[A]
in scope, and can handle lists of variable length (even though you say that in your case your key lists are always the same length).
Upvotes: 1
Reputation: 12092
What you want to do is
datapoints.sortby(_.keys)
This evidently doesn't work. When we take a look at the signature of sortby, it becomes evident why it doesn't work:
sortBy[B](f: (A) ⇒ B)(implicit ord: math.Ordering[B]): List[A]
Your B
is a List[String]
and you don't have an instance of Ordering[List[String]]
. So what do we do? We supply one!
What we need to do for that is implement the method
def compare(x: T, y: T): Int
We want to compare on the following:
Our T's here are Strings, but all we need for the T's is to be comparable for this, so we can be a little more general.
def listOrdering[T](implicit ord: Ordering[T]): Ordering[List[T]] = new Ordering[List[T]] {
def compare(x: List[T], y: List[T]): Int = {
(x, y) match {
case (Nil, Nil) => 0 //both empty => equal
case (Nil, _) => -1 //one of the two empty => empty is the smallest
case (_, Nil) => 1 //one of the two empty => empty is the smallest
case (xhead :: xtail, yhead :: ytail) => {
val headdiff = ord.compare(xhead, yhead)
if (headdiff == 0) compare(xtail, ytail) //recursively compare the tails if equivalent
else (headdiff ) //otherwise, the difference in the heads
}
}
}
}
now we can supply the ordering to the sortby method explicitly:
datapoints.sortby(_.keys)(listOrdering)
or provide them in implicit scope
[1]: you indicated this never happens, so any choice is good enough
Upvotes: 3