Puneeth Reddy V
Puneeth Reddy V

Reputation: 1568

Sorting a list of generic objects

I have a requirement to write a generic code that perform sorting on the Seq[T] objects. I know it won't be possible to perform sorting operation until we know the base class and its attributes. After taking a look into this answer I took this code and my requirement is to handle as many custom data type as possible.

case class Country(name: String, id : Int)
type CountrySorter = (Country, Country) => Boolean
def byName : CountrySorter = (c1:Country, c2:Country) => c1.name < c2.name
def byId : CountrySorter = (c1:Country, c2:Country) => (c1.id < c2.id)

val sortingMap = Map[String, CountrySorter](
  "sortByCountryName" -> byName ,
  "soryByCountryId" -> byId
 )

Function call

def sort[T]( input : Seq[T], criteria : String) : Seq[T] = {
  input.sortWith(sortingMap(criteria))
}

input.sortWith(sortingMap(criteria)) here I get error as sortWith function only takes Country type and not T type.

Upvotes: 3

Views: 1561

Answers (3)

Puneeth Reddy V
Puneeth Reddy V

Reputation: 1568

After using the above answers I have fulfilled this requirement with below code

Generic trait which is parent for all the child case classes i.e. contains only the fields on which sorting is performed

 sealed trait Generic{
    def name : String = ???
    def id : Int = ???
    def place : String = ???
  }

 //case class which need to be sorted 
  case class Capital( 
      countryName : String, 
      override val id: Int, 
      override val place:String
 ) extends Generic

  case class Country(
         override val name: String, 
         override val id: Int
  ) extends Generic

Sorting types

  type Sorter[T] = (T, T) => Boolean
  type CountrySorter = Sorter[Generic]
  type CapitalSorter = Sorter[Generic]

Sorting orders

  def byName : CountrySorter = (c1, c2) => c1.name < c2.name

  def byId : CountrySorter = (c1, c2) => c1.id < c2.id

  def byPlace : CapitalSorter = (s1, s2) => s1.place > s2.place

Sorting method

  def sort[T](input: Seq[T], sorter: Sorter[T]): Seq[T] = {
    input.sortWith(sorter)
  }

A data structure to hold sorting order with a name.

  val mapper = Map[String, Sorter[Generic]](
        "name" -> byName, 
        "id" -> byId, 
        "place" -> byPlace
       )

Input

  val countries = List(Country("Australia", 61), Country("USA", 1), Country("France", 33))
  val headQuaters = List(
    Capital("Australia", 61, "Melbourne"), 
    Capital("America", 1, "New York"), 
    Capital("France", 33, "Paris"), 
    Capital("India", 65, "New Delhi")
 )

Output

  println(sort(countries,mapper("id")))
 //List(Country(USA,1), Country(France,33), Country(Australia,61))

  println(sort(headQuaters , mapper("place")))
  //List(Capital(France,33,Paris), Capital(America,1,New York), Capital(India,65,New Delhi), Capital(Australia,61,Melbourne))

Upvotes: 0

Yuval Itzchakov
Yuval Itzchakov

Reputation: 149538

Sorting country by using a Map with stringly typed keys is error prone. A better alternative is to leverage the mechanism for ordering in Scala via the Ordering[A] type class.

You can use it like this:

def sort[T](input : Seq[T])(implicit order: Ordering[T]): Seq[T] = {
  input.sorted
}

The catch here is to have the right ordering in scope. You can create a single ad hoc ordering in scope:

def main(args: Array[String]): Unit = {
  implicit val byIdOrdering = Ordering.by((country: Country) => country.id)

  val countries: Seq[Country] = ???
  sort(countries)
}

You can define the ordering in the companion of the case class and explicitly import it:

object Country {
  implicit val byIdOrdering: Ordering[Country] = 
     Ordering.by((country: Country) => country.id)

  implicit val byNameOrdering: Ordering[Country] = 
     Ordering.by((country: Country) => country.name)
}

import Country.byNameOrdering
def main(args: Array[String]): Unit = {
  val countries: Seq[Country] = ???
  sort(countries)
}

You can also use the low priority implicits trick if you have such ordering rules:

trait LowPriorityCountryImplicits {
  implicit val byNameOrdering: Ordering[Country] = 
    Ordering.by((country: Country) => country.name)
}

object HighPriorityCountryImplicits extends LowPriorityCountryImplicits {
  implicit val byIdOrdering: Ordering[Country] = 
    Ordering.by((country: Country) => country.id)
}

import HighPriorityCountryImplicits._
def main(args: Array[String]): Unit = {
  val countries: Seq[Country] = ???
  sort(countries)
}

Or even explicitly pass the ordering if needed:

def main(args: Array[String]): Unit = {
  val countries: Seq[Country] = ???
  sort(countries)(Country.byNameOrdering)
}

Upvotes: 3

Leo C
Leo C

Reputation: 22449

Here's an approach if you want to define your ordering using sortWith :

case class Country(name: String, id : Int)

type Sorter[T] = (T, T) => Boolean
type CountrySorter = Sorter[Country]

def byName : CountrySorter = (c1, c2) => c1.name < c2.name
def byId : CountrySorter = (c1, c2) => c1.id < c2.id

def sort[T](input: Seq[T], sorter: Sorter[T]): Seq[T] = {
  input.sortWith(sorter)
}

val countries = List(Country("Australia", 61), Country("USA", 1), Country("France", 33))

sort(countries, byName)
// res1: Seq[Country] = List(Country(Australia,61), Country(France,33), Country(USA,1))

sort(countries, byId)
// res2: Seq[Country] = List(Country(USA,1), Country(France,33), Country(Australia,61))

Upvotes: 6

Related Questions