需求
计算订单中,所包含的商品(单品)是否可以组成套包,且得出这个套包在这个订单中的个数。
即:假设某套包的商品为(a,b,c),某订单中购买了(a,b,c,d,a,b,c)五个商品,则这个订单中包含2个这个套包。
之前是将套包维度表和订单事实表进行笛卡尔积,然后用hive的UDF函数计算,但是在实际使用过程中发现速度很慢。了解到spark 2.0之后的第二代钨丝计划对spark内部函数进行了大量优化,可能性能会比hive的UDF函数好点。考虑进行重构,使用spark的UDF函数进行计算。再此之前先使用DF的map算子进行计算。
思路
经过处理的订单表内容包含订单ID字段和购买商品字段,购买商品字段包含该订单所买的所有商品,以逗号隔开,内容大致如下:
Order_ID Order_Items
1 a,b,c,d,a,b,c,d
2 q,w,e,r
3 a,a,s,d,f
套包维度表与订单表相同,包含套包ID和套包内商品:
Head_ID Head_items
1 a,b,c,d
2 q,w,e
3 q,a,s,d
总的来说,这个需求就是求数组A是否包含数组B,并求出包含几个数组B的问题。
- 将订单表和套包维度表笛卡尔积。
- 对笛卡尔积的每条数据进行计算。
- 将两张表的items已逗号分隔,转换成ArrayBuffer。
- 判断Order的items个数是否大于Head的items个数,如果大于或等于,进入3;否则不可能包含Head的套包,结束。
- 循环判断Head的每个item是否存在Order的items中,如果存在,则删除Order中的该item,并继续判断,直到Head的最后一个Item也存在Order中,则count加1,并返回2;如果不存在,则说明Order中不可能包含Head的所有商品,结束。
实现
1.定义样例类。
package com.zixuan.spark.bean
case class HD(hid:String,hitem:String)
case class Order(oid:String,oitem:String)
case class Result(hid:String,hitem:String,oid:String,oitem:String,result: Int)
2.spark代码
package com.zixuan.spark.test
import com.zixuan.spark.bean.{HD, Order, Result}
import org.apache.spark.sql.SparkSession
import org.apache.spark.{SparkConf, SparkContext}
object BomCount {
def main(args: Array[String]): Unit = {
val conf = new SparkConf().setAppName("test").setMaster("local[1]")
val context = new SparkContext(conf)
//屏蔽spark的info warn信息
context.setLogLevel("ERROR")
val session = SparkSession.builder().config(conf).getOrCreate()
//导入转换DF的隐式转换
import session.implicits._
val hd =
context.textFile("C:\\Users\\zixuan.wu\\Desktop\\sparkTest\\hd.txt")
.map(s=>s.split(" "))
.map(strings=>HD(strings(0),strings(1)))
.toDF()
val order =
context.textFile("C:\\Users\\zixuan.wu\\Desktop\\sparkTest\\order.txt")
.map(s=>s.split(" "))
.map(strings=>Order(strings(0),strings(1)))
.toDF()
//val hd = session.sql("select * from xxx")
//val order = session.sql("select * from xxx")
//val order_product = order.groupBy("order_id","product_id")
//笛卡尔积
val frame = hd.crossJoin(order)
frame.show()
//主要计算逻辑
val result = frame.map(
row => {
//将items以逗号切割,并转换成buffer
var hitems = row.getAs[String]("hitem").split(",").toBuffer
var oitems = row.getAs[String]("oitem").split(",").toBuffer
//初始化count
var count = 0
//while循环控制标记
var continueFlag = 1
//导入scala的break包
import scala.util.control._
//如果oitem的个数大于等于hitem且continueFlag标记为1,则进入循环
while (oitems.length >= hitems.length && continueFlag==1){
//默认不继续while循环,如果oitem中包含hitem再开启循环
continueFlag = 0
//break
val loop = new Breaks
loop.breakable(
//遍历hitems,如果oitems中包含hitem,则移除oitems中的这个item,并将continueFlag置为1;且如果这个item时hitems中的最后一个,则count+1
//否则退出循环;
for(hitem <- hitems){
if (oitems.contains(hitem)){
oitems.remove(oitems.indexOf(hitem))
continueFlag=1
if (hitem.equals(hitems.last)){
count+=1
}
}
else {
loop.break()
}
}
)
}
//返回result
Result(row.getString(0), row.getString(1), row.getString(2), row.getString(3), count)
}
)
result.show()
}
}
3.结果
笛卡尔积的结果:
计算后的结果: