scala 排序函数使用和代码分析
序列图引用 转载 请注明出处
scala & java 关系不清,爱者,为止疯狂。怨者,无力吐槽。但在一个方面是无可争议的事实、一个让人趋之若鹜特性 -- scala丰富集合库。
所谓集合库:一种用来存储各种对象和数据的容器,且提供丰富的操作集。本篇我们来介绍scala中集合库常用功能:排序功能。
浏览API我们会发现,排序的功能存在于多个组合库中,那么
- 它源于哪里?
- 有哪些具体操作方法?
- 如何满足我们的排序需求哪?
本文将分别说明,并结合示例分析源码。
操作方法
在API文档中,List Set Map BitSet Option都存在通用的api,在其中有三个接口,满足我们对排序诉求。函数完整签名如下:
//sort api 1 2 3
def sorted[B >: A](implicit ord: Ordering[B]): Repr //1
def sortBy[B](f: A => B)(implicit ord: Ordering[B]): Repr //2
def sortWith(lt: (A, A) => Boolean): Repr //3
api呈现内容:
-
api提供默认排序功能。那么,默认排序是什么哪?
-
api1,2 隐式参数转换是进行排序的基础(
关于隐式转换先挖坑,以后写一篇文章
),前置提供默认排序方式,后者进行类型转换 A->
B,然后进行排序。2者排序的关键是后面的隐式转换参数implicit ord: Ordering[B]
。trait Ordering
实现了基本类型的排序方法。 -
api 3 是个
似是而非的
接口,看似没有Ordering
的功能,但实现暴露了本质-- API 1的特殊形式。完整实现代码:
def sortWith(lt: (A, A) => Boolean): Repr = sorted(Ordering fromLessThan lt)
。sortWith是sorted的封装。
来源
排序是scala的集合库提供基础功能。我们在查看基础类继承关系图中,SeqLike
总是很特别的一个,我们的排序功能就源于此。为显示方便,UML图中裁剪SeqLike
无关函数。
蓝色部分scala提供的排序三剑客
。SeqLike
继承和内部实现类,有兴趣可以分析源码。Uml图源于[我的 github开源uml工具]:
scaladiagram 分析scala源码生成UML图,阅读源码事半功倍。
在了解是什么和为什么之后,我们要了解如何用即如何进行排序、以及如何定制化排序?
排序
数据排序无外乎升序,降序和自定义排序。我们使用scala的repl进行操作排序,如下:
-
sorted
使用
scala> List(1,6,4,5).sorted res0: List[Int] = List(1, 4, 5, 6)
scala> val goat = List("Ronaldo", "Johan Cruyff", "Diego M", "Pele")
scala> goat.sorted res0: List[String] = List(Diego M, Johan Cruyff, Pele, Ronaldo)
以上例子表明scala默认以升序排序。哪如何进行降序排列方法有哪些?
- 使用集合类库的
reverse
函数
scala> res0.reverse res1: List[String] = List(Ronaldo, Pele, Johan Cruyff, Diego M)
- 使用sorted的排序方法
排序作为通用的方法,scala会提供完善的API的。那么如何使用sorted进行排序哪?猫腻在隐式参数转换Ordering
参数上。查询Ordering
api中包含reverse
可以满足降序排列。
/** Return the opposite ordering of this one. */
override def reverse: Ordering[T] = new Ordering[T] {
override def reverse = outer
def compare(x: T, y: T) = outer.compare(y, x) //compare方法 x,y有(x,y)变成(y,x)
}
发现踪迹后,实现降序排列只需要显示调用Ordering
参数即可。
scala> goat.sorted(Ordering[String].reverse) //效果和 goat.sorted.reverse一致 res5: List[String] = List(Ronaldo, Pele, Johan Cruyff, Diego M)
整型数组按降序进行排序:
scala> List(1,6,4,5).sorted(Ordering[Int].reverse) res4: List[Int] = List(6, 5, 4, 1) //降序
,隐式转换很奇妙呀
-
sortBy
排序
上面的示例是对一种对象类型进行排序,即对整型、字符串的数组进行。在应用时,我们遇到一个对象多属性进行排序,如信息包含个人名称和年龄,从这2个维度进行排序要怎样进行哪?
val idol = List(("Ronaldo",41), ("Johan Cruyff",72), ("Diego M",56), ("Riva",41))
这种类型数据, sortBy
可解决该问题。
scala> idol .sortBy{case (name,age) => (age, name)} res6: List[(String, Int)] = List((Riva,41), (Ronaldo,41), (Diego M,56), (Johan Cruyff,72))
scala> goat.sortBy{case (name,age) => (name, age)} res7: List[(String, Int)] = List((Diego M,56), (Johan Cruyff,72), (Riva,41), (Ronaldo,41))
按照 age-name排序,注意2种排序方式差异,age相同比较name,Riva\Ronaldo 显示排序的差别。
name按照降序排序,age按照升序排序,示例:
scala> goat.sortBy{case (name,age) => (name, age)}(Ordering.Tuple2(Ordering.String.reverse,Ordering.Int)) res9: List[(String, Int)] = List((Ronaldo,41), (Riva,41), (Johan Cruyff,72), (Diego M,56))
Ordering
是伴生对象,sorted
中引用的类型是trait。
sortBy函数签署参数是 f:A=>B表示能够对不同类型的参数进行转换。那么,不同业务排序方式不同,这种情况下,如何进行按照业务进行排序哪?
- 自定义排序方式
自定义排序是自己编写排序的方法。
class Person(id:String,age:Int,address:String)
val t:List[Person] = ??? // 对Person的列表进行排序
//编写person的比较规则,使用隐式转换自动的进行转换
//编写Person的排序规则也很简单
object tx {
trait PersonOrdering extends Ordering[Person] { //比较规则
def compare(x: Person, y: Person): Int =
// 排序规则实现, 地址改成小写,然后进行字符串比较
if(x.address.toLowerCase > y.address.toLowerCase) 1 else -1
}
implicit object PersonOrdered extends PersonOrdering //隐式转换
}
//如何使用
import tx._
t.sorted(Ordering[Person])
代码实现的方式是导入object 完成隐式转换,也可以尝试使用 隐式类、隐式函数或变量的方式。
-
sortWith 排序
sortWith也是自定义排序的一种方式,通过改变参数的类型完成比较。因为原理上使用
sorted
方法,使用方法和sorted
类似,使用方法不赘述。
总结
本篇介绍scala里面的三种排序函数,以及如何进行升降序排列。每种都有其各自的应用场景:
sorted
:适合单集合的升降序
sortBy
:适合对单个或多个属性的排序,代码量比较少,推荐使用这种
sortWith
:适合定制化场景比较高的排序规则,用法和sorted
类似。
另外,粗略介绍了隐式转换在排序中使用。