combineByKey函數詳解

 

如下給出combineByKey的定義,其他的細節暫時忽略(1.6.0版的函數名更新爲combineByKeyWithClassTag)

def combineByKey[C](
      createCombiner: V => C,
      mergeValue: (C, V) => C,
      mergeCombiners: (C, C) => C,
      partitioner: Partitioner,
      mapSideCombine: Boolean = true,
      serializer: Serializer = null)

 

如下解釋下3個重要的函數參數:

  • createCombiner: V => C ,這個函數把當前的值作爲參數,此時我們可以對其做些附加操作(類型轉換)並把它返回 (這一步類似於初始化操作), 這裏的操作僅僅是針對每個partition中的每個key的第一個數據進行操作
  • mergeValue: (C, V) => C,該函數把元素V合併到之前的元素C(createCombiner)上 (這個操作在每個分區內進行)
  • mergeCombiners: (C, C) => C,該函數把2個元素C合併 (這個操作在不同分區間進行)

如下:

val rdd1 = sc.parallelize(List(1,2,2,3,3,3,3,4,4,4,4,4), 2)
val rdd2 = rdd1.map((_, 1))
val rdd3 = rdd2.combineByKey(-_, (x:Int, y:Int) => x + y,
                            (x:Int, y:Int) => x + y)
rdd2.collect
rdd3.collect

以上代碼的輸出如下:

Array((1,1), (2,1), (2,1), (3,1), (3,1), (3,1), (3,1), (4,1), (4,1), (4,1), (4,1), (4,1)) 
Array((4,3), (2,0), (1,-1), (3,0))

在上述代碼中,(1,1), (2,1), (2,1), (3,1), (3,1), (3,1) 被劃分到第一個partition,(3,1), (4,1), (4,1), (4,1), (4,1), (4,1) 被劃分到第二個。於是有如下操作:

(1, 1):由於只有1個,所以在值取負的情況下,自然輸出(1, -1) 
(2, 1):由於有2個,第一個取負,第二個不變,因此combine後爲(2, 0) 
(3, 1):partition1中有3個,參照上述規則,combine後爲(3, 1),partition2中有1個,因此combine後爲(3, -1)。在第二次combine時,不會有初始化操作,因此直接相加,結果爲(3, 0) 
(4, 1):過程同上,結果爲(4, 3)

由此可以看出combineByKey算子的初始化執行流程,即只在partition的combine階段有效,且僅對每個key下的第一個元素進行操作。
 

例子2:

如下看一個使用combineByKey來求解平均數的例子

val initialScores = Array(("Fred", 88.0), ("Fred", 95.0), ("Fred", 91.0), ("Wilma", 93.0), ("Wilma", 95.0), ("Wilma", 98.0))
val d1 = sc.parallelize(initialScores)
type MVType = (Int, Double) //定義一個元組類型(科目計數器,分數)
d1.combineByKey(
  score => (1, score),
  (c1: MVType, newScore) => (c1._1 + 1, c1._2 + newScore),
  (c1: MVType, c2: MVType) => (c1._1 + c2._1, c1._2 + c2._2)
).map { case (name, (num, socre)) => (name, socre / num) }.collect

參數含義的解釋
a 、score => (1, score),我們把分數作爲參數,並返回了附加的元組類型。 以"Fred"爲列,當前其分數爲88.0 =>(1,88.0) 1表示當前科目的計數器,這裏的1,score也僅僅是針對Fred這個key的第一個元素 88.0進行了初始化,所以下面的b中要實現 Fred這個key再加第二個分數時,只能是加上一個後面科目的分數,而不是和一個 (1,score類型相加)

b、(c1: MVType, newScore) => (c1._1 + 1, c1._2 + newScore),注意這裏的c1就是createCombiner初始化得到的(1,88.0)。在一個分區內,我們又碰到了"Fred"的一個新的分數91.0。當然我們要把之前的科目分數和當前的分數加起來即c1._2 + newScore,然後把科目計算器加1即c1._1 + 1

c、 (c1: MVType, c2: MVType) => (c1._1 + c2._1, c1._2 + c2._2),注意"Fred"可能是個學霸,他選修的科目可能過多而分散在不同的分區中。所有的分區都進行mergeValue後,接下來就是對分區間進行合併了,分區間科目數和科目數相加分數和分數相加就得到了總分和總科目數

執行結果如下:

res1: Array[(String, Double)] = Array((Wilma,95.33333333333333), (Fred,91.33333333333333))

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章