Scala 使用过程中经常要要用到排序模块,自带的方法有sorted,sortBy,sortWith,同时也提供了接口和类,例如Ordering,Ordered,Comparable,Comparator等,下面就盘一下他们的基本用法:
sorted 是基于Ordering 排序
/** Sorts this $coll according to an Ordering.
*
* The sort is stable. That is, elements that are equal (as determined by
* `lt`) appear in the same order in the sorted sequence as in the original.
*
* @see [[scala.math.Ordering]]
*
* @param ord the ordering to be used to compare elements.
* @return a $coll consisting of the elements of this $coll
* sorted according to the ordering `ord`.
*/
def sorted[B >: A](implicit ord: Ordering[B]): Repr = {
val len = this.length
val b = newBuilder
if (len == 1) b ++= this
else if (len > 1) {
b.sizeHint(len)
val arr = new Array[AnyRef](len) // Previously used ArraySeq for more compact but slower code
var i = 0
for (x <- this) {
arr(i) = x.asInstanceOf[AnyRef]
i += 1
}
java.util.Arrays.sort(arr, ord.asInstanceOf[Ordering[Object]])
i = 0
while (i < arr.length) {
b += arr(i).asInstanceOf[A]
i += 1
}
}
b.result()
}
通过源码我们可以看到sorted有一个隐式参数,这个隐式参数后续可以自己定义排序方法从而让sorted在无形中改变排序规则,另一点,sorted是基于scala编写,但底层调用的是java.util.Arrays.sort。sorted是默认的排序,在scala里适合对单集合进行排序,例如Array[T],ArrayBuffer[T]。sorted默认从小到大排序,如果想要逆序就再调用.reverse即可
test:
val arr = Array(0,2,1)
arr.sorted.reverse.foreach(println)
最常见的数组排序,然后逆序返回:
2
1
0
trait Ordering[T] extends Comparator[T] with PartialOrdering[T] with Serializable
Ordering是继承Compartor实现的接口,其中实现了基本的比大小,最大最小还有反转等基本功能,这里内部的reverse函数比较好玩,reverse实现就是把compare(x:T, y:T)的参数互换位置,实现反比,有兴趣可以看看~ 另一个Ordered和Ordering实现的功能类似,但是使用起来Ordering灵活
test:
// 用户类
class Person(name:String,age:Int) {
val name = name
val age = age
}
object CompareWithTrait {
def main(args: Array[String]): Unit = {
val p1 = new Person("a", 24)
val p2 = new Person("a", 22)
val p3 = new Person("b", 15)
val list = List(p1, p2, p3)
// sorted接口需要
implicit object PersonOrdering extends Ordering[Person] {
override def compare(p1: Person, p2: Person): Int = {
p1.name == p2.name match {
case false => -p1.name.compareTo(p2.name)
case _ => p1.age - p2.age
}
}
}
list.sorted.foreach(x => println(x.name,x.age))
}
通过定义源码中的隐函数参数,实现了自定义sorted。这里首先定义一个Demo的用户类,只有两个简单属性name和age,下面测试函数我们只需要初始化几个用户,然后自己重写override compare方法,注意这里采用implicit隐式定义,正常情况下如果我们使用别人提供的包,一些隐式方法我们是感知不到的,例如我会默认把我类里的Int变成String,这样我有其他函数需要传String参数时,传Int也不会报错;
implicit def intToString(number: Int): String ={
number.toString
}
这里隐式函数就是给Sorted使用,正常情况我们也无法感知,compare通过返回的Int判断两边的大小,大于0是前者大于后者,小于0同理,等于0就默认顺序了。这里compare首先按照姓名逆序排列,然后再按照年龄顺序排列,所以看下结果:
(b,15)
(a,22)
(a,24)
Ordered的写法这里就不写了,用了一下确实没有Ordering好使~
刚才的Ordering继承了Comparable,二者一个是scala的接口,一个是java的接口,这里略去很多的注释,这个接口我们只需要实现compareTo,与Ordering类似,需要通过Int的正负返回比较的两个对象的大小,o就是要比的,this就是自己
public interface Comparable {
/**
...
* @param o the object to be compared.
* @return a negative integer, zero, or a positive integer as this object
* is less than, equal to, or greater than the specified object.
*
* @throws NullPointerException if the specified object is null
* @throws ClassCastException if the specified object's type prevents it
* from being compared to this object.
*/
public int compareTo(T o);
}
test:
object CompareWithComparable {
def main(args: Array[String]): Unit = {
val p1 = new CompareWithComparable(5)
val p2 = new CompareWithComparable(10)
val p3 = new CompareWithComparable(8)
val stArr = Array(p1,p2,p3)
stArr.sorted.foreach(x => println(x.age))
}
}
class CompareWithComparable(val age: Int) extends java.lang.Comparable[CompareWithComparable] {
override def compareTo(p: CompareWithComparable): Int = {
this.age - p.age
}
}
和上面的person类似,这里只是简单的比较了年龄大小,当然里面也可以写很复杂的逻辑,不过这里我们应该遵循如下规则,否则代码将会出现一些奇怪的异常:
对于所有的x,y,z
tips:sgn是常用的符号函数,返回+1,-1,0
对称性: sgn(x.compareTo(y)) == -sgn(y.compareTo(x))
传递性: x.compareTo(y) > 0 && y.compareTo(z) > 0 则 x.compareTo(z) > 0
等价替换: x.compareTo(y) == 0 则 sgn(x.compareTo(z)) == sgn(y.compareTo(z))
相等性(待定): x.compareTo(y) == 0 等价于 x.equals(y)
前3个特性和重写equals的要求比较类似,第四个则并不是需要严格执行,参考sorted-Ording例子中,我们有可能存在两个人同名,同龄,但是其他属性不同,所以这里compare并不像equals那样严格要求相等。还有一个类似的comparator接口
public interface Comparator {
int compare(T o1, T o2);
boolean equals(Object obj);
}
两个compare的区别是前者需要override compare()方法,后者则override compareTo方法,上面说到的Ordering其实就是继承了Comparator,所以我们复写了compare方法。
如果不定义隐函数或者继承Comparable直接对一个类进行sorted会报错:
Error:(35, 22) No implicit Ordering defined for xxx
sortedBy需要指定你要通过哪个元素排序,可以针对普通数组,也可以针对有参数的类
val arr = Array((1,2),(2,1),(3,3))
arr.sortBy(_._2).foreach(println)
指定通过元祖的第二位排序
(2,1)
(1,2)
(3,3)
class person(val age: Int) {
def getAge(): Int = age
}
val arr = Array(new person(1),new person(10),new person(5))
arr.sortBy(-_.getAge()).foreach(p => println(p.getAge()))
简单类的排序,通过-号可以实现逆序
10
5
1
def sortWith(lt: (A, A) => Boolean): Repr = sorted(Ordering fromLessThan lt)
前面的排序通过返回的Int做排序的判断依据,sortWith采用Boolean,换汤不换药,把减法变成了判断两个变量大小,这里看源码发现其实sortWith也是sorted的一种特殊形态
test:
def fn(p1: person, p2: person) = (p1.age > p2.age)
A:arr.sortWith(fn).foreach(p => println(p.age))
B:arr.sortWith((p1,p2) => p1.age > p2.age).foreach(p => println(p.age))
通过定义返回boolean的函数,判断两个类,A,B两种写法均可,A适合写复杂逻辑
10
5
1