一
前段日子要实现一个推荐算法,其中需要实现一个功能:把出id(某个标识)下的所有类目id。
由于当时没有标识id与类目id的直接对应关系表,所以计划将id与一级类目id在代码中写死,然后通过类目id之间的层级关系获取所有类目id。
忘记当时是怎么想的,我写的代码是这样的:
val catLevel1idParentid: RDD[(String, String)] = sc.parallelize(List(("catid1","id1"), ("catid2","id1"), ("catid3","id1"), ("catid4","id1"), ("catid5","id2")))
也就是把一级类目id和标识id一个个关联起来。
当时只需要两个标识id和5个一级类目id,所以也没觉得有什么问题。但后来把标识id扩展到10个,一级类目id数量也翻了几倍,问题马上就显现出来了。
后来我就改成了下面这样子:
val catLevel1idParentid = sc.parallelize(List(("id1","catid1:catid2:catid3:catid4"), ("id2","catid5"))).flatMapValues(_.split(":"))
较之前的写法,这种方案更简洁,也更易读,也不容易出错。若是别人看前一段代码,他一定会问哪个是标识id,哪个是类目id。而后一种就不会出现这种情况,别人也能一眼看出各个标识id下的一级类目id的数量。
二
类目id之间是有层级关系的,有时需要获得一个catid的所有上级类目id。
原先的函数核心部分写了七行,而且刚开始接手时我确实是没看懂。
后来我改成下面这样子:
/**
* 获得 catid 的所有上级类目id
* @param catid2parentid catid -> parentid
* @return cate_levle1id:...:catid
*/
def factorial(catid2parentid: Map[String, String],catid: String): String ={
if (!catid2parentid.contains(catid.split(":")(0)) || catid2parentid(catid.split(":")(0)) == "-1") catid
else factorial(catid2parentid,catid2parentid(catid.split(":")(0))+":"+catid)
}
核心部分只用了两行,而且便于理解。
三
有时候作为最后的填充,或者是作为冷启动项目使用,需要给每个类目id推荐GMV最高的一个商品。
最初接手的代码中是用spark sql完成这项功能的。统计好每个商品的GMV后,使用row_number
函数获得排序值,并选取第一个商品。
由于spark sql对内存的要求更大,运行效率比RDD低些,所以后来我把代码中的sql改成了RDD。
为了完成这项任务,我用reduceBykey
函数把同一catid下的商品聚合起来,再对这样聚合的商品列表按GMV排序(sortBy),然后选取第一个商品。
当时根本就没想到这样做有什么问题,想法也很简单,就是使用row_number的功能,然后选取第一个商品。
后来意识到,我压根不需要排序。我的目的只是获得GMV最大的那个商品,使用maxBy
函数就行了,没必要做多余的事情。
总结
我们接到一个需求时,脑子会蹦出一个想法。很可能后面就会按照这个想法去实现,即使这个想法并不合理,但当时是不会想到有什么不合理的地方的。
就像一、三先说的那样,其实我至少能想到两种方案的,但仅仅是由于第一时间想到是较为低效的方案,我就按照这种方案实施了。
像二中的写的代码,其实也很简单,原先的同事肯定也是能写出来的。但也许就是因为当时想出的方案是那个样子,后面也没想过其它的方案,就写出了一段让人百思不得其解的代码。
问题的根源既然是自己的惰性,那么解决方案是每次实现某个新功能时,至少提出两种解决方案,逼自己停下来先想清楚,这就大大降低了出现上面这些情况的风险。
网友评论