項目中有一個需求,求取taxi出行od的最大峯值(taxi od最大交叉值),採用隊列處理:
需求示意圖如下:
案例(自制測試數據):
val data = Seq(
(“A”,“2019-01-05 00:23:20”,“2019-01-05 00:27:20”,“2019-1-05”),
(“A”,“2019-01-05 00:25:20”,“2019-01-05 00:37:20”,“2019-1-05”),
(“A”,“2019-01-05 00:35:20”,“2019-01-05 00:40:20”,“2019-1-05”),
(“A”,“2019-01-05 01:25:20”,“2019-01-05 01:37:20”,“2019-1-05”),
(“A”,“2019-01-05 02:25:20”,“2019-01-05 02:37:20”,“2019-1-05”),
(“A”,“2019-01-05 02:26:20”,“2019-01-05 02:30:20”,“2019-1-05”),
(“A”,“2019-01-05 02:27:20”,“2019-01-05 02:28:20”,“2019-1-05”),
(“A”,“2019-01-05 02:27:40”,“2019-01-05 03:37:20”,“2019-1-05”),
(“A”,“2019-01-05 04:25:20”,“2019-01-05 04:57:20”,“2019-1-05”),
(“A”,“2019-01-05 04:35:20”,“2019-01-05 05:37:20”,“2019-1-05”),
(“A”,“2019-01-05 05:00:20”,“2019-01-05 06:37:20”,“2019-1-05”),
(“A”,“2019-01-05 08:25:20”,“2019-01-05 08:37:20”,“2019-1-05”),
(“A”,“2019-01-05 09:25:20”,“2019-01-05 09:37:20”,“2019-1-05”),
(“A”,“2019-01-05 09:35:20”,“2019-01-05 09:47:20”,“2019-1-05”),
(“A”,“2019-01-05 10:35:20”,“2019-01-05 10:47:20”,“2019-1-05”)
).toDF(“carID”,“departTime”,“arrivalTime”,“date”)
/**
* 提取大OD
*/
def getBigOD(data: DataFrame): DataFrame = {
import data.sparkSession.implicits._
data.groupByKey(row => row.getAs[String]("carID") + "," + row.getAs[String]("date")).flatMapGroups((str, it) => {
val indexArr = ArrayBuffer[(Int, Int, String)]() //標記大od劃分時對應的records記錄下標、最大od數、大od的結束時間
val res = ArrayBuffer[(String, String, Int, String)]()
val info = str.split(",")
val carID = info.head
val date = info.last
val records = it.toList.sortBy(_.getAs[String]("arrivalTime"))
val queue = new Queue[Row]()
val len_records = records.length
//當天記錄數在2條及以上
if (len_records >= 2) {
var maxOdNum = 0 //初始化od數量
var index = 0 //初始化標記
var lastTime = "" //大od結束時的時間
for (record <- records) {
//首條記錄入隊
if (queue.length == 0) {
queue.enqueue(record)
} else {
//判斷當前od是否應該加入隊列
val departTime = record.getAs[String]("departTime")
//遍歷隊列中的所有od
var flag = true
var bg = 0
while (bg < queue.length && flag) {
val que = queue(bg)
val arrivalTime = que.getAs[String]("arrivalTime")
// 當前od的上車時間若大於隊列中od的到達時間,則隊列中已存od出隊
if (departTime > arrivalTime) {
val queueLen = queue.length
if (queueLen > maxOdNum) maxOdNum = queueLen //隊首元素出列前,隊列的最大長度賦值給maxOdNum
queue.dequeue() //隊首元素出列
bg -= 1 //隊首元素出列後,下一次依舊從隊首元素開始判斷(**易忽略**)
} else {
queue.enqueue(record)
flag = false //當前上車時間小於隊首元素的下車時間,則不必再往後查看,直接加入當前od即可
}
bg += 1
}
flag = true //供下一次循環隊列使用
//若隊列清空,則表示一次大od劃分成功
if (queue.length == 0) {
// 出棧元素應該添加至最終的結果集中
lastTime = records(index-1).getAs[String]("arrivalTime")
indexArr.append((index-1, maxOdNum, lastTime))
//清空列表後,需將當前od加入隊列中,成爲隊首元素
queue.enqueue(record)
maxOdNum = 0 //maxOdNum值歸零
}
}
//插入最後一個元素後,需輸出一次
if (index == len_records - 1) {
lastTime = records.last.getAs[String]("arrivalTime")
indexArr.append((index, queue.length, lastTime))
}
index += 1
}
} else {
//只有一條記錄,直接添加進去即可
val lastTime = records.last.getAs[String]("arrivalTime")
indexArr.append((0, 1, lastTime))
}
for (ind <- indexArr) {
res.append((carID, date, ind._2, ind._3))
}
//根據indexArr和records生成最終的結果數據集
res
}).toDF("carID", "date", "maxOdNum", "endTime")
}
result: