Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Use of ordering to sort array of arrays, ascending and descending

I would like to sort an array of tuples by 3rd and first element so I used the following code:

import scala.util.Sorting
val pairs = Array(("a", 5, 2), ("c", 3, 1), ("b", 1, 3))

// sort by the 3rd element, then 1st
Sorting.quickSort(pairs)(Ordering[(Int, String)].on(x => (x._3, x._1)))

My question is that, in the previous example I can sort by both the third and the first elements ascending or both of them descending (use reverse). but how to sort by the third element ascending and the 1st element descending.

Please, in your answer consider the following case:

Array[Array[People]]  

where in this case I do not know the exact size of the inner array (depends on the file schema that I read into this array) and I want to sort by all the items in side (some ascending and some descending).

Edit: It looks like, i got miss understood.

Here is my full case: I have the following classes:

  sealed trait GValue extends Serializable with Ordered[GValue]{
def compare(o: GValue): Int = {
  o match {
    case GDouble(v) => this.asInstanceOf[GDouble].v compare v
    case GString(v) => this.asInstanceOf[GString].v compare v
  }
}

case class GDouble(v: Double) extends GValue

case class GString(v: String) extends GValue

and i want to do a code like this.

// (startInterval, StopInterval, ArrayOfColumns)
    val intervals: Array[(Long,Long,Array[GValue])] =
Array((10,20,Array(GDouble(10.2), GString("alarm"), GString("error"),GDouble("100.234"))),
    (30,2000,Array(GDouble(-10.2), GString("alarm"), GString("warn"),GDouble("0.234"))))

The schema or the inner array would change based on the input file (in the example it is Double,String, String, Double but it might be Double, Double or something else). I would like to find a way to sort with out covering all the cases of the inner array (in regards to type and length), ascending and descending.

what i do currently is to change the inner array to Iterable and then use Ordering[Iterable[GValue]] for sorting or Ordering[Iterable[GValue]].reverse. But i want to sort in separated directions (ascending for the first column and then descending for the second then ascending to the third and so on)

like image 271
Abdulrahman Avatar asked Sep 27 '22 05:09

Abdulrahman


2 Answers

Using Régis Jean-Gilles' CompositeOrdering from another question, we can compose multiple Orderings.

// Ordering to sort an Array[GValue] by column "col"
def orderByColumn(col: Int) = Ordering.by { ar: Array[GValue] => ar(col - 1) }

val `By Col3-Desc/Col1` = 
  CompositeOrdering(orderByColumn(3).reverse, orderByColumn(1))

val `By Col1/Col2/Col3` = 
  CompositeOrdering(orderByColumn(1), orderByColumn(2), orderByColumn(3))

Now we can sort an array of type Array[GValue], and you could sort your intervals using sortBy :

intervals.sortBy(_._3)(`By Col3-Desc/Col1`)

If you want to sort the intervals array with Sorting.quickSort, we need an Ordering for the tuple :

type Interval = (Long, Long, Array[GValue])
implicit object tupleByArray extends Ordering[Interval] {
  def compare(a: Interval, b: Interval) = a._3 compare b._3
}

Now you can sort your intervals using Sorting.quickSort:

implicit val arrOrd = `By Col3-Desc/Col1`
Sorting.quickSort(intervals)

// Array[(Long, Long, Array[GValue])] = 
// Array(
//  (30,2000,Array(GDouble(-10.2), GString(alarm), GString(warn),  GDouble(0.234))), 
//  (10,20,Array(GDouble(10.2), GString(alarm), GString(error), GDouble(100.234)))
// )

I leave my answer from before the question was updated :

There is a great article by Eric Loots on sorting on multiple fields.

In your case of Array[People] this could look like :

case class People(name: String, age: Int)

object PeopleOrdering {
  // sort by name descending and age ascending
  implicit object `By Name-Rev/Age` extends Ordering[People] {
    def compare(a: People, b: People): Int = {
      import scala.math.Ordered._
      implicit val ord = Ordering.Tuple2[String, Int]
      (b.name, a.age) compare (a.name, b.age)
    }
  }
}

val people = Array(People("Alice", 40), People("Bob", 50), People("Charlie", 20))
Sorting.quickSort(people)(PeopleOrdering.`By Name-Rev/Age`)
// > people
// Array[People] = Array(People(Bob,20), People(Bob,50), People(Alice,40))

val array = Array(people, Array(People("B", 1), People("C", 2)))
array.foreach(ps => Sorting.quickSort(ps)(PeopleOrdering.`By Name-Rev/Age`))
// > array
// Array[Array[People]] = Array(
//   Array(People(Bob,20), People(Bob,50), People(Alice,40)), 
//   Array(People(C,2), People(B,1))
// )
like image 53
Peter Neyens Avatar answered Nov 15 '22 09:11

Peter Neyens


For your example where you want to order Strings desceding add this before sorting

  implicit object ReverseStringOrdering extends Ordering[String] {
    def compare(x: String, y: String) = -1 * x.compareTo(y)
  }

So, in general just add implicit Ordering object of type you want to sort descending with overridden compare method. This solution works only if you sort by different types.

If you want to sort by first item ascending and second descending add this before sorting:

 implicit def AscFirstDescSecondTuple2[T1, T2](implicit ord1: Ordering[T1], ord2: Ordering[T2]): Ordering[(T1, T2)] =
    new Ordering[(T1, T2)]{
      def compare(x: (T1, T2), y: (T1, T2)): Int = {
        val compare1 = ord1.compare(x._1, y._1)
        if (compare1 != 0) return compare1
        val compare2 = -1 * ord2.compare(x._2, y._2)
        if (compare2 != 0) return compare2
        0
      }
    } 
like image 33
ka4eli Avatar answered Nov 15 '22 07:11

ka4eli