Scala sortBy参数作为序列

时间:2013-08-14 14:22:05

标签: scala

我已经定义了一个

case class User(var firstName: String, var lastName: String, var city: String, var price: Int)

并希望排序

val users = List(
  User("Peter", "Fox", "Berlin", 30),
  User("Otto",  "Schmidt", "Berlin", 20),
  User("Carl",  "Schmidt", "Berlin", 30),
  User("Igor",  "Schmidt", "Berlin", 10),
  User("Hugo",  "Schmidt", "Berlin", 50))

我可以这样做,例如由

val sorted = users.sortBy(p => (p.lastName,p.firstName))

是否可以按照这样的序列给sortBy排序标准?

val sortCriteria = Seq(lastname,firstname)
val sorted = test.sortBy(p => sortCriteria)

我希望通过用户请求将序列中的标准与1到n个参数组合,而不定义所有可能的组合。

4 个答案:

答案 0 :(得分:2)

您需要的是比较两个User的方法。逻辑是:比较第一个标准,如果它是1或-1,则表示结果,否则如果它是0,则比较下一个标准。

我们有一个问题,因为只提供Seq[User => Any],我们无法说服编译器Any将有一个已定义的排序。那么如何定义一个能够记住如何比较实例的类而不必记住它所比较的​​字段类型:

case class Criterion[T](crit: User => T)(implicit ord: Ordering[T]) {
  def compare(x: User, y: User): Int = ord.compare(crit(x), crit(y))
}

然后我们只需要实现顺序比较:

def ord(criteria: Seq[Criterion[_]]) = new Ordering[User] {
  def compare(x: User, y: User) = {
    def loop(crits: Seq[Criterion[_]]): Int = crits match {
      case Seq() => 0
      case c +: cs => c.compare(x, y) match {
        case 0 => loop(cs)
        case i => i
      }
    }
    loop(criteria)
  }
}

我们现在可以使用上面的数据

进行尝试
val cs = Seq(Criterion(_.price), Criterion(_.firstName))
val sorted = users.sorted(ord(cs))

/* results:
sorted: List[User] = List(
  User(Igor,Schmidt,Berlin,10),       Sorted by Price
  User(Otto,Schmidt,Berlin,20), 
  User(Carl,Schmidt,Berlin,30),      
  User(Peter,Fox,Berlin,30),      <-- Peter now after Carl
  User(Hugo,Schmidt,Berlin,50)) */

答案 1 :(得分:1)

您需要使用Tuple,而不是Seq

  def sortCritera(user: User): Tuple2[String, String] = (user.firstName, user.lastName)
  println(users.sortBy(user => sortCritera(user)));

  def sortCritera2(user: User): Tuple3[String, String, String] = (user.city, user.firstName, user.lastName)
  println(users.sortBy(user => sortCritera(user)));

答案 2 :(得分:0)

你可以做这样的事情。因为在内部它使用稳定排序(合并排序),因此以下解决方案有效:

val order = List("lastName","firstName")

def sort(ls:List[User],n:String) = n match {
  case "firstName" => ls.sortBy(p => p.firstName)
  case "lastName" => ls.sortBy(p => p.lastName)
  case "city" => ls.sortBy(p => p.city)
  case "price" => ls.sortBy(p => p.price)
}

order.reverse.foldLeft(users)((all,param) => sort(all,param))

基本上以相反的顺序排序,首先按firstName排序。然后按lastName排序。

这与users.sortBy(p => (p.lastName,p.firstName))

的结果相同

答案 3 :(得分:0)

Accepted answer很好,但是我们可以做更多。我们可以动态地将类作为类型传递,因此Criterion可以用于不同的类。排序中的排序方向很重要,因此也应使用。

升级的Criterion类的示例代码应如下所示:

case class Criterion[A,T](crit: A => T, ascending:Boolean)(implicit ord: Ordering[T]) {
  def compare(x: A, y: A): Int = {
    val result = ord.compare(crit(x), crit(y))
    if(ascending) result else result * -1 //If descending change result to opposite value
  }
}

然后为Criterion类创建伴随对象,如下所示:

object Criterion {
  def ord[ A ]( criteria: Seq[ Criterion[ A, _ ] ] ): Ordering[ A ] = ( x: A, y: A ) => {
    def loop( crits: Seq[ Criterion[ A, _ ] ] ): Int = crits match {
      case Seq() => 0
      case c +: cs => c.compare( x, y ) match {
        case 0 => loop( cs )
        case i => i
      }
    }
    loop( criteria )
  }
}

排序如下:

val users = List(
  User("Peter", "Fox", "Berlin", 30),
  User("Otto",  "Schmidt", "Berlin", 20),
  User("Carl",  "Schmidt", "Berlin", 30),
  User("Igor",  "Schmidt", "Berlin", 10),
  User("Hugo",  "Schmidt", "Berlin", 50))

val crits = Seq(
  Criterion[ User, String ]( _.name, true ),
  Criterion[ User, Int]( _.price, false )
)

val sorted = users.sorted( Criterion.ord[ User ]( crits ) )