[Spark好友推薦]

好友推薦概念     

目前大量的社交網站上,它們都有一個共同的特性,就是可以在好友關係的基礎上推薦更多的聯繫人。例如,QQ的“你可能認識的人”特性允許用戶查看它們可能聯繫的人。基本思想就是:如果tom是jack的好友,而tom又是peter的好友,也就是說,tomjackpeter的共同好友,但是jackpeter可能並不認識你。那麼社交網絡系統可能就會推薦jackpeter聯繫,又或者推薦peterjack聯繫。簡單通俗的說,如果兩個人有一組共同好友,但是兩人又不是好友關係,那麼推薦系統就會基於他們之間的好友關係推薦他們相互認識。

       所有用戶之間的好友關係可以表示爲一個圖,對於一個簡單的案例,可以使用下圖進行表示。

在這裏,圖是一個有序對G=(V,E),其中:

       V:是由人(社交網絡中的用戶)構成的衣一個有限集合。

G:是V上的二元關係,稱爲邊集,其中的元素稱爲一個好友關係。

好友推薦問題可以表述爲:對於每一個用戶P,其好友列表P1,P2,…P20,表示用戶P一共有20個好友。即P:{ P1,P2,…P20}

好友推薦樣本數據

假設輸入記錄是一個按照用戶ID排序的列表,輸入的沒有行數據分別代表用戶ID(如用戶ID表示爲P),其後面的數據表示該用戶的好友列表,則好友關係可表示爲:

       P,P1 P2 … Pn,其中P1<P2<…Pn.

其樣本數據如下所示:

P1,P2 P3 P4 P5 P6 P7
P2,P1 P3 P4
P3,P1 P2 P4 P5
P4,P1 P2 P3
P5,P1 P3 P7
P6,P1
P7,P2

Spark代碼實現

/**
  * Spark共同好友推薦
  **/
object FriendRecommendation {
    def main(args: Array[String]): Unit = {
        if (args.length < 2) {
            println("Usage: FriendRecommendation <input-path> <output-path>")
            sys.exit(1)
        }

        val inputPath: String = args(0)
        val outputPath: String = args(1)
        val sparkConf: SparkConf = new SparkConf()
            .setMaster("local[1]")
            .setAppName("FriendRecommendation")
        val sc: SparkContext = SparkContext.getOrCreate(sparkConf)

        val records: RDD[String] = sc.textFile(inputPath)
        //計算兩兩之間的可能好友關係
        val pairs: RDD[(String, (String, String))] = records.flatMap(line => {
            val tokens: Array[String] = line.split(",")
            val person: String = tokens(0)
            val friends: List[String] = tokens(1).split("\\s+").toList
            val mapperOutput: List[(String, (String, String))] = friends.map(directFriend => (person, (directFriend, "-1")))
            val result: List[List[(String, (String, String))]] = for {
                fi <- friends
                fj <- friends
                possibleFriend1 = (fj, person)
                possibleFriend2 = (fi, person)
                if fi != fj
            } yield {
                (fi, possibleFriend1) :: (fj, possibleFriend2) :: List()
            }
            mapperOutput ::: result.flatten
        })

        //合併同一個鍵對應的共同好友
        val combineValue: RDD[(String, List[(String, String)])] = pairs.combineByKey(
            (v: (String, String)) => List(v),
            (c: List[(String, String)], v: (String, String)) => c :+ v,
            (c1: List[(String, String)], c2: List[(String, String)]) => c1 ::: c2
        )

        //這裏是測試,可以使用collect()生成集合後進行打印
        pairs.collect().foreach(println);
        combineValue.foreach(println)

        //共同好友個數計算及好友推薦
        val result: RDD[(String, Map[String, List[String]])] = combineValue.mapValues(values => {
            val mutualFriends: mutable.HashMap[String, List[String]] = new collection.mutable.HashMap[String, List[String]].empty
            values.foreach(f = t2 => {
                val toUser: String = t2._1
                val mutualFriend: String = t2._2
                val alreadyFriend: Boolean = mutualFriend == "-1"
                if (mutualFriends.contains(toUser)) {
                    if (alreadyFriend) {
                        mutualFriends.put(toUser, List.empty)
                    } else if (mutualFriends.get(toUser).isDefined
                        && mutualFriends.get(toUser).get.size > 0
                        && !mutualFriends(toUser).contains(mutualFriend)) {
                        val existingList: List[String] = mutualFriends.get(toUser).get
                        mutualFriends.put(toUser, mutualFriend :: existingList)
                    }
                } else {
                    if (alreadyFriend) {
                        mutualFriends.put(toUser, List.empty)
                    } else {
                        mutualFriends.put(toUser, List(mutualFriend))
                    }
                }
            })
            mutualFriends.filter(_._2.nonEmpty).toMap
        })

        //result.saveAsTextFile(outputPath)

        //打印結果
        result.foreach(f => {
            val friends = if (f._2.isEmpty) "" else {
                val items = f._2.map(tuple => (tuple._1, "(" + tuple._2.size + ": " + tuple._2.mkString("[", ",", "]") + ")")).map(g => "" + g._1 + " " + g._2)
                items.toList.mkString(",")
            }
            println(s"${f._1}: ${friends}")
        })

        sc.stop()
    }
}

運行結果

可能推薦的好友關係

(P1,(P2,-1))
(P1,(P3,-1))
(P1,(P4,-1))
(P1,(P5,-1))
(P1,(P6,-1))
(P1,(P7,-1))
(P2,(P3,P1))
(P3,(P2,P1))
(P2,(P4,P1))
(P4,(P2,P1))
(P2,(P5,P1))
(P5,(P2,P1))
(P2,(P6,P1))
(P6,(P2,P1))
(P2,(P7,P1))
(P7,(P2,P1))
(P3,(P2,P1))
(P2,(P3,P1))
(P3,(P4,P1))
(P4,(P3,P1))
(P3,(P5,P1))
(P5,(P3,P1))
(P3,(P6,P1))
(P6,(P3,P1))
(P3,(P7,P1))
(P7,(P3,P1))
(P4,(P2,P1))
(P2,(P4,P1))
(P4,(P3,P1))
(P3,(P4,P1))
(P4,(P5,P1))
(P5,(P4,P1))
(P4,(P6,P1))
(P6,(P4,P1))
(P4,(P7,P1))
(P7,(P4,P1))
(P5,(P2,P1))
(P2,(P5,P1))
(P5,(P3,P1))
(P3,(P5,P1))
(P5,(P4,P1))
(P4,(P5,P1))
(P5,(P6,P1))
(P6,(P5,P1))
(P5,(P7,P1))
(P7,(P5,P1))
(P6,(P2,P1))
(P2,(P6,P1))
(P6,(P3,P1))
(P3,(P6,P1))
(P6,(P4,P1))
(P4,(P6,P1))
(P6,(P5,P1))
(P5,(P6,P1))
(P6,(P7,P1))
(P7,(P6,P1))
(P7,(P2,P1))
(P2,(P7,P1))
(P7,(P3,P1))
(P3,(P7,P1))
(P7,(P4,P1))
(P4,(P7,P1))
(P7,(P5,P1))
(P5,(P7,P1))
(P7,(P6,P1))
(P6,(P7,P1))
(P2,(P1,-1))
(P2,(P3,-1))
(P2,(P4,-1))
(P1,(P3,P2))
(P3,(P1,P2))
(P1,(P4,P2))
(P4,(P1,P2))
(P3,(P1,P2))
(P1,(P3,P2))
(P3,(P4,P2))
(P4,(P3,P2))
(P4,(P1,P2))
(P1,(P4,P2))
(P4,(P3,P2))
(P3,(P4,P2))
(P3,(P1,-1))
(P3,(P2,-1))
(P3,(P4,-1))
(P3,(P5,-1))
(P1,(P2,P3))
(P2,(P1,P3))
(P1,(P4,P3))
(P4,(P1,P3))
(P1,(P5,P3))
(P5,(P1,P3))
(P2,(P1,P3))
(P1,(P2,P3))
(P2,(P4,P3))
(P4,(P2,P3))
(P2,(P5,P3))
(P5,(P2,P3))
(P4,(P1,P3))
(P1,(P4,P3))
(P4,(P2,P3))
(P2,(P4,P3))
(P4,(P5,P3))
(P5,(P4,P3))
(P5,(P1,P3))
(P1,(P5,P3))
(P5,(P2,P3))
(P2,(P5,P3))
(P5,(P4,P3))
(P4,(P5,P3))
(P4,(P1,-1))
(P4,(P2,-1))
(P4,(P3,-1))
(P1,(P2,P4))
(P2,(P1,P4))
(P1,(P3,P4))
(P3,(P1,P4))
(P2,(P1,P4))
(P1,(P2,P4))
(P2,(P3,P4))
(P3,(P2,P4))
(P3,(P1,P4))
(P1,(P3,P4))
(P3,(P2,P4))
(P2,(P3,P4))
(P5,(P1,-1))
(P5,(P3,-1))
(P5,(P7,-1))
(P1,(P3,P5))
(P3,(P1,P5))
(P1,(P7,P5))
(P7,(P1,P5))
(P3,(P1,P5))
(P1,(P3,P5))
(P3,(P7,P5))
(P7,(P3,P5))
(P7,(P1,P5))
(P1,(P7,P5))
(P7,(P3,P5))
(P3,(P7,P5))
(P6,(P1,-1))
(P7,(P2,-1))

合併推薦好友關係

(P6,List((P2,P1), (P3,P1), (P4,P1), (P5,P1), (P2,P1), (P3,P1), (P4,P1), (P5,P1), (P7,P1), (P7,P1), (P1,-1)))
(P5,List((P2,P1), (P3,P1), (P4,P1), (P2,P1), (P3,P1), (P4,P1), (P6,P1), (P7,P1), (P6,P1), (P7,P1), (P1,P3), (P2,P3), (P4,P3), (P1,P3), (P2,P3), (P4,P3), (P1,-1), (P3,-1), (P7,-1)))
(P2,List((P3,P1), (P4,P1), (P5,P1), (P6,P1), (P7,P1), (P3,P1), (P4,P1), (P5,P1), (P6,P1), (P7,P1), (P1,-1), (P3,-1), (P4,-1), (P1,P3), (P1,P3), (P4,P3), (P5,P3), (P4,P3), (P5,P3), (P1,P4), (P1,P4), (P3,P4), (P3,P4)))
(P1,List((P2,-1), (P3,-1), (P4,-1), (P5,-1), (P6,-1), (P7,-1), (P3,P2), (P4,P2), (P3,P2), (P4,P2), (P2,P3), (P4,P3), (P5,P3), (P2,P3), (P4,P3), (P5,P3), (P2,P4), (P3,P4), (P2,P4), (P3,P4), (P3,P5), (P7,P5), (P3,P5), (P7,P5)))
(P7,List((P2,P1), (P3,P1), (P4,P1), (P5,P1), (P6,P1), (P2,P1), (P3,P1), (P4,P1), (P5,P1), (P6,P1), (P1,P5), (P3,P5), (P1,P5), (P3,P5), (P2,-1)))
(P3,List((P2,P1), (P2,P1), (P4,P1), (P5,P1), (P6,P1), (P7,P1), (P4,P1), (P5,P1), (P6,P1), (P7,P1), (P1,P2), (P1,P2), (P4,P2), (P4,P2), (P1,-1), (P2,-1), (P4,-1), (P5,-1), (P1,P4), (P2,P4), (P1,P4), (P2,P4), (P1,P5), (P1,P5), (P7,P5), (P7,P5)))
(P4,List((P2,P1), (P3,P1), (P2,P1), (P3,P1), (P5,P1), (P6,P1), (P7,P1), (P5,P1), (P6,P1), (P7,P1), (P1,P2), (P3,P2), (P1,P2), (P3,P2), (P1,P3), (P2,P3), (P1,P3), (P2,P3), (P5,P3), (P5,P3), (P1,-1), (P2,-1), (P3,-1)))

好友推薦結果

P6: P3 (1: [P1]),P5 (1: [P1]),P2 (1: [P1]),P7 (1: [P1]),P4 (1: [P1])
P5: P4 (2: [P3,P1]),P6 (1: [P1]),P2 (2: [P3,P1])
P2: P7 (1: [P1]),P6 (1: [P1]),P5 (2: [P3,P1])
P1: 
P7: P3 (2: [P5,P1]),P5 (1: [P1]),P1 (1: [P5]),P4 (1: [P1]),P6 (1: [P1])
P3: P7 (2: [P5,P1]),P6 (1: [P1])
P4: P7 (1: [P1]),P6 (1: [P1]),P5 (2: [P3,P1])

推薦結果解讀

數輸出結果採用以下格式輸出:

     <USER>:P(N:[U1,U2,...,Un])

其中:

      P是推薦給好友USER的一個好友。

      N是他們之間的共同好友數。

      U1,U2,...,Un是共同好友的ID。

從輸出的結果中我們可以看出,用戶P1沒有任何好友推薦,因爲P1y與所有用戶都已經是好友了。

結論

基於Spark的好友推薦算法,我們可以在一個龐大的社交網絡系統中,運用好友關係的大數據能夠快速計算共同好友,並給好友推薦其他的好友。同理,好友推薦算法也衍生到商品推薦及電影推薦。

 

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