Sorting a list of generic objects

2020-07-24 21:07发布

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.

标签: scala
3条回答
我只想做你的唯一
2楼-- · 2020-07-24 21:33

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))
查看更多
Melony?
3楼-- · 2020-07-24 21:33

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))
查看更多
▲ chillily
4楼-- · 2020-07-24 21:37

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)
}
查看更多
登录 后发表回答