从一个函数的三次迭代得到的收获

Photo by Frans Van Heerden from Pexels

学习一种新的编程语言,总得经历从陌生到熟悉,再到熟练,再到精通的过程。随着代码写得越来越多,有那么一段时间感觉上会认为自己进步了,但感觉这事往往不太靠谱。

那有没有什么量化的指标来显示自己的进步呢?

目前我想到两种:

  1. 开发时间。面对同样工作量的开发任务,若是所花的时间较之前短了,那说明进步了。
  2. 函数的迭代次数。一个合格的项目代码至少是由多个函数组成,而不仅仅只有一个主函数。每个函数都表示一个特定的功能,函数的迭代表示使用更精简、更易懂的代码完成同样的功能。
    每次迭代,都能表示你更熟悉当前的语言,掌握了更多的技巧。

在前期,应该更注重第二种方式。不是说开发时长不重要,而是在前期,作为新手,开发时长一定会快速的下降。甚至不需要多少主动学习,只要多写几行代码,很自然的就能把开发时间降下来。

而对一个函数进行迭代更需要主动性。迭代前的函数也能完成任务,为什么还要花时间去优化呢?你越主动学习新的知识,越花时间思考怎么应用新学的知识到代码里,那你的能力就提升的越快。

下面介绍下我对一个函数的三次迭代以及收获。

最近几周都在做类目相关的推荐,算法中需要写个函数用来获得每个类目id下gmv最高的商品。

在接手这个项目时,原开发者是用spark-sql写的代码:

// 获取各商品的GMV值
val goodsGMVRaw = spark.sql( 
  "select goods_id, sum(goods_revenue) as gmv " +
    "from table_name group by goods_id "
)

// 商品GMV信息中添加类目信息
val goodsGMV = goodsGMVRaw
  .map(line => (line(0).toString, brGoods2Catid.value(line(0).toString), line(1).toString.toDouble))
  .toDF("goods_id", "cat_id", "gmv")
goodsGMV.createTempView("goods_gmv")
println("goods_gmv finish")

// 每个类目根据gmv排序
val goodsGMVBest = spark.sql( 
  "select t.goods_id, t.cat_id from " +
    "(select goods_id, cat_id, row_number() over(partition by cat_id order by gmv desc) as rank from goods_gmv)t " +
    "where t.rank = 1 "
)

第一次迭代
由于 spark sql 运算会占用更多的内存,接手这个项目后我先把算法中关于 sql 的操作替换成 RDD 操作。除了占用资源会更少,后续的算法优化也会更方便些。

当时相法很简单,sql 中是通过 row_number 来获得 gmv 最高的商品的,那我通过 sortBy 来获得和row_number 同样的效果就行了。于是我修改成下面这样:

/**
  * 获取每个catid下被gmv最高的一个商品
  * @param spark
  * @param order_goods_info (goodsid, goods_revenue)
  * @param brGoods2Catid goodsid -> cat_id
  * @return (catid, goodsid)
  */
def getGoodsGmvTop(spark: SparkSession,
    order_goods_info: RDD[(String, String)], 
    brGoods2Catid: Broadcast[Map[String, String]]): RDD[(String, String)] ={
  order_goods_info.map(x => (x._1,x._2.toDouble))
    .reduceByKey(_+_)
    .map(x => (brGoods2Catid.value(x._1),x._1+","+x._2.toString))
    .reduceByKey(_+":"+_)
    .mapValues(x => {
      x.split(":")
        .map(p => (p.split(",")(0), p.split(",")(1).toFloat))
        .toList
        .sortBy(_._2)
        .take(1)
        .map(p => p._1)
        ._toString()
    })
}

该函数的想法是通过第一个reduceByKey获得各个商品的gmv信息;填加类目信息后通过第二个reduceByKey来获得一个商品列表,对其排序后取最gmv最大的商品。

第二次迭代
人的思维很容易被当前看到的东西所限制,第一次迭代时我用了sortBy函数,为的是实现row_number类似的功能。

但事实上我只需要获得gmv最大的商品就行,而不需要对整个商品列表进行排序。于是我就用maxBy函数代替了sortBy,使代码更加高效:

def getGoodsGmvTop(spark: SparkSession,
    order_goods_info: RDD[(String, String)], 
    brGoods2Catid: Broadcast[Map[String, String]]): RDD[(String, String)] ={
  order_goods_info.map(x => (x._1,x._2.toDouble))
    .reduceByKey(_+_)
    .map(x => (brGoods2Catid.value(x._1),x._1+","+x._2.toString))
    .reduceByKey(_+":"+_)
    .mapValues(x => {
      x.split(":")
        .map(p => (p.split(",")(0), p.split(",")(1).toFloat))
        .toList
        .maxBy(_._2)
        ._1
    })
}

第三次迭代
虽然上一次迭代用了maxBy函数,但我的思维还是被排序所限制,或者是因为对reduceByKey的使用还不熟。

我只需要保留每个类目Gmv最高的商品,所以我没必要先聚合每个类目的商品列表,这个操作本身就占用了不少资源。

reduceByKey的作用是把两个同类型的数据转化成单个同类型的数据,上两次迭代就是把商品列表以字符串的形式粘合在一起。

但就像maxBy函数的内部逻辑一样,每两个商品对比时我们只需要保留gmv较大的那个就行了。而reduceByKey可以先轻松的完成这个操作:

def getGoodsGmvTop(spark: SparkSession,
    order_goods_info: RDD[(String, String, String, String, String)], 
    brGoods2Catid: Broadcast[Map[String, String]]): RDD[(String, String)] ={
  order_goods_info.map(x => (x._2,1))
    .reduceByKey(_+_)
    .flatMap(x => (brGoods2Catid.value(x._1), (x._1,x._2)))
    .reduceByKey((x,y) => if (y._2 > x._2) y else x)
    .map(x => (x._1,x._2._1))
}

我是小结
最后一次迭代后的函数看上去就顺眼多了,三次迭代也让我对reduceByKey这个重要的函数有了更深的理解。这次改造后,顺势对原先的算法进行了大面积的优化,算法的运行时间缩短了不少。

另外,从这三次迭代,我总结出两点值得反复提醒自己的准则:

  1. 写新算法或重构别人代码时,首先要清楚这一步我们的目的是什么。
  2. 不要做不需要做的事
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 200,045评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,114评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 147,120评论 0 332
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,902评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,828评论 5 360
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,132评论 1 277
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,590评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,258评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,408评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,335评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,385评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,068评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,660评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,747评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,967评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,406评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,970评论 2 341

推荐阅读更多精彩内容