在实际开发中,我们经常需要自己定义排序规则对数据进行排序。
例如这有一组数据
val users = Array("li 30 99","zhao 29 999","zhang 28 98","wang 28 99")
数据含义为:名字,年龄,颜值
现在需要对这组数据进行排序,排序规则是,按颜值从高到低进行排序,颜值相同的话,再按年龄从低到高排序。
下面用六种方法进行实现:
一
自定义一个类,用来定义排序规则,然后切分整理数据,将数据以排序规则类的对象的形式保存
代码:
package XXX
import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}
object CustomSort1 {
def main(args: Array[String]): Unit = {
val conf = new SparkConf().setAppName("CustomSort1").setMaster("local[4]")
val sc = new SparkContext(conf)
//排序规则,首先按照颜值从高到低排序,颜值相同,再按年龄从低到高排序
val users = Array("li 30 99","zhao 29 999","zhang 28 98","wang 28 99")
val lines: RDD[String] = sc.parallelize(users)
//切分整理数据
val userRDD: RDD[User] = lines.map(line => {
val fields = line.split(" ")
val name = fields(0)
val age = fields(1).toInt
val fv = fields(2).toInt
//(name, age, fv)
new User(name,age,fv)
})
//数据已经保存到了对象中,所以就按照对象中定义的排序方法进行排序
//返回一个RDD,里面保存的是User对象
val sorted: RDD[User] = userRDD.sortBy(u => u)
val r: Array[User] = sorted.collect()
println(r.toBuffer)
sc.stop()
}
}
//需要让该类继承Ordered,才可以用来排序
//还要让该类继承Serializable,进行序列化,这样才可以将对象保存起来
class User(val name:String,val age:Int,val fv:Int) extends Ordered[User] with Serializable {
override def compare(that: User): Int = {
if (this.fv == that.fv){
this.age - that.age
}else {
-(this.fv - that.fv)
}
}
override def toString: String = s"$name,$age,$fv"
}
二
也要先定义一个类,类中定义排序规则,但是与第一种方法不同的是,这次切分整理数据的时候将数据保存在元组中,然后排序的时候将元组中的数据传入该对象。
这种方法在sortedBy中传入的是一个排序规则,不会改变数据的格式,只会改变顺序
代码:
package XXX
import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}
object CustomSort2 {
def main(args: Array[String]): Unit = {
val conf = new SparkConf().setAppName("CustomSort2").setMaster("local[4]")
val sc = new SparkContext(conf)
val users = Array("li 30 99","zhao 29 999","zhang 28 98","wang 28 99")
val lines: RDD[String] = sc.parallelize(users)
val userRDD1: RDD[(String, Int, Int)] = lines.map(line => {
val fields = line.split(" ")
val name = fields(0)
val age = fields(1).toInt
val fv = fields(2).toInt
(name, age, fv)
})
//排序(传入一个排序规则,不会改变数据的格式,只会改变顺序)
val sorted: RDD[(String, Int, Int)] = userRDD1.sortBy(tp => new Boy(tp._1,tp._2,tp._3))
println(sorted.collect().toBuffer)
sc.stop()
}
}
class Boy(val name:String,val age:Int,val fv:Int) extends Ordered[Boy] with Serializable{
override def compare(that: Boy): Int = {
if (this.fv == that.fv){
this.age - that.age
}else{
-(this.fv - that.fv)
}
}
override def toString: String = s"$name,$age,$fv"
}
三
我们可以使用Scala中的case class,这种类是已经实现了序列化
而且可以直接用类名创建对象
代码:
package XXX
import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}
object CustomSort3 {
def main(args: Array[String]): Unit = {
val conf = new SparkConf().setAppName("CustomSort3").setMaster("local[4]")
val sc = new SparkContext(conf)
val users = Array("li 30 99","zhao 29 999","zhang 28 98","wang 28 99")
val lines: RDD[String] = sc.parallelize(users)
val tpRDD: RDD[(String, Int, Int)] = lines.map(line => {
val fields = line.split(" ")
val name = fields(0)
val age = fields(1).toInt
val fv = fields(2).toInt
(name, age, fv)
})
//排序
val sorted: RDD[(String, Int, Int)] = tpRDD.sortBy(tp => Man(tp._2,tp._3))
println(sorted.collect().toBuffer)
sc.stop()
}
}
case class Man(age:Int,fv:Int) extends Ordered[Man]{
override def compare(that: Man): Int = {
if (this.fv == that.fv){
this.age - that.age
}else{
-(this.fv - that.fv)
}
}
}
四
我们先来看一下sortBy方法的源码:
/**
* Return this RDD sorted by the given key function.
*/
def sortBy[K](
f: (T) => K,
ascending: Boolean = true,
numPartitions: Int = this.partitions.length)
(implicit ord: Ordering[K], ctag: ClassTag[K]): RDD[T] = withScope {
this.keyBy[K](f)
.sortByKey(ascending, numPartitions)
.values
}
该方法要使用一个Ordering的隐式转换
所以我们可以定义一个隐式转换,隐式转换中定义排序规则
关于隐式转换的讲解,请参考博客:https://blog.csdn.net/weixin_43866709/article/details/88358235
SortRules.scala
package XXX
object SortRules {
//定义隐式转换,将XiaoRou转换为Ordering类型
implicit object OrderingToXianRou extends Ordering[XianRou]{
override def compare(x: XianRou, y: XianRou): Int = {
if (x.fv == y.fv){
x.age - y.age
}else{
y.fv - x.fv
}
}
}
}
CustomSort4.scala
package XXX
import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}
object CustomSort4 {
def main(args: Array[String]): Unit = {
val conf = new SparkConf().setAppName("CustomSort4").setMaster("local[4]")
val sc = new SparkContext(conf)
val users = Array("li 30 99","zhao 29 999","zhang 28 98","wang 28 99")
val lines: RDD[String] = sc.parallelize(users)
val tpRDD1: RDD[(String, Int, Int)] = lines.map(line => {
val fields = line.split(" ")
val name = fields(0)
val age = fields(1).toInt
val fv = fields(2).toInt
(name, age, fv)
})
//排序
import SortRules.OrderingToXianRou
val sorted: RDD[(String, Int, Int)] = tpRDD1.sortBy(tp => XianRou(tp._2,tp._3))
println(sorted.collect().toBuffer)
sc.stop()
}
}
//定义类XianRou
case class XianRou(age:Int,fv:Int)
五
重新定义一个类太麻烦了,对于一些简单的业务逻辑来说,我们可以使用元组的比较规则来进行排序。
元组的比较规则是:先比第一个元素,如果第一个元素相等,再比第二个
代码:
package XXX
import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}
object CustomSort5 {
def main(args: Array[String]): Unit = {
val conf = new SparkConf().setAppName("CustomSort5").setMaster("local[4]")
val sc = new SparkContext(conf)
val users = Array("li 30 99","zhao 29 999","zhang 28 98","wang 28 99")
val lines: RDD[String] = sc.parallelize(users)
val topRDD2: RDD[(String, Int, Int)] = lines.map(line => {
val fields = line.split(" ")
val name = fields(0)
val age = fields(1).toInt
val fv = fields(2).toInt
(name, age, fv)
})
//利用元组的比较规则(先比第一个元素,如果第一个元素相等,再比第二个)进行排序
val sorted: RDD[(String, Int, Int)] = topRDD2.sortBy(tp => (-tp._3,tp._2))
println(sorted.collect().toBuffer)
sc.stop()
}
}
六
我们可以使用Ordering中的on方法,不改变元组的形态,就按照元组本来的组成顺序进行排序
代码:
package XXX
import org.apache.spark.{SparkConf, SparkContext}
import org.apache.spark.rdd.RDD
object CustomSort6 {
def main(args: Array[String]): Unit = {
val conf = new SparkConf().setAppName("CustomSort5").setMaster("local[4]")
val sc = new SparkContext(conf)
val users = Array("li 30 99","zhao 29 999","zhang 28 98","wang 28 99")
val lines: RDD[String] = sc.parallelize(users)
val topRDD2: RDD[(String, Int, Int)] = lines.map(line => {
val fields = line.split(" ")
val name = fields(0)
val age = fields(1).toInt
val fv = fields(2).toInt
(name, age, fv)
})
//利用元组的比较规则(先比第一个元素,如果第一个元素相等,再比第二个)进行排序
//Ordering[(Int,Int)]最终比较规则的样式
//on[(String,Int,Int)]未比较之前的数据格式
//(t => (-t._3,t._2))怎样将规则转换成想要比较的格式
implicit val rules: Ordering[(String, Int, Int)] = Ordering[(Int,Int)].on[(String,Int,Int)](t => (-t._3,t._2))
val sorted: RDD[(String, Int, Int)] = topRDD2.sortBy(tp => tp)
println(sorted.collect().toBuffer)
sc.stop()
}
}
总结
这就是六种自定义排序的方法,大多数情况下我们使用第五种方法就可以了,而且是最简单的方法。
在面对更加复杂的排序规则时,我们可以定义一个类来编写排序的规则。