Spark 好友推薦解決方案

目標:如果用戶A與用戶C同時都跟B是好友,但用戶A與用戶C又不是好友,則向用戶A推薦C,向用戶C推薦A,同時說明A與C的共同好友有哪些

例如:

有如下的好友關係:

1 2,3,4,5,6,7,8
2 1,3,4,5,7
3 1,2
4 1,2,6
5 1,2
6 1,4
7 1,2
8 1

其中每一行空格前的元素爲用戶ID,空格後的元素爲用戶的好友ID列表

其對應的好友關係圖爲



期望輸出爲:

1
2 6(2:[4, 1]),8(1:[1]),
3 4(2:[1, 2]),5(2:[2, 1]),6(1:[1]),7(2:[1, 2]),8(1:[1]),
4 3(2:[2, 1]),5(2:[1, 2]),7(2:[1, 2]),8(1:[1]),
5 3(2:[2, 1]),4(2:[1, 2]),6(1:[1]),7(2:[1, 2]),8(1:[1]),
6 2(2:[1, 4]),3(1:[1]),5(1:[1]),7(1:[1]),8(1:[1]),
7 3(2:[1, 2]),4(2:[2, 1]),5(2:[2, 1]),6(1:[1]),8(1:[1]),
8 2(1:[1]),3(1:[1]),4(1:[1]),5(1:[1]),6(1:[1]),7(1:[1]),


對於用戶1,因爲它以及跟2,3,4,5,6,7,8都是好友,則不向其推薦任何好友

對於用戶2,向其推薦6,因爲2跟6可以通過4或者1認識;向其推薦8,因爲2和8可以通過1認識

對於用戶3,向其推薦4,因爲3跟4可以通過1或者2認識;向其推薦5,因爲3和5可以通過2或者1認識;向其推薦6,因爲3和6可以通過1認識;向其推薦7,因爲3和7可以通過1或者2認識;想起推薦8,因爲3跟8可以通過1認識

...


思路:

對於每一行,例如4 1,2,6

map操作:

生成直接好友鍵值對(4,[1,-1]) (4,[2,-1])  (4,[6,-1])

生成間接好友鍵值對(1,[2,4])    (2,[1,4])    (1,[6,4])    (6,[1,4])    (2,[6,4])    (6,[2,4]]),其中(1,[2,4]),連接爲向1推薦2,因爲可以通過4認識,其他類似


reduce操作:

所有對於同一個用戶的直接好友鍵值對和間接好友鍵值對能夠到達同一個規約器

例如:對於用戶4

key=4

以下鍵值對集合會到達同一個reduce

t2= FriendPair [user1=7, user2=1]
t2= FriendPair [user1=3, user2=2]
t2= FriendPair [user1=2, user2=-1]
t2= FriendPair [user1=6, user2=-1]
t2= FriendPair [user1=1, user2=2]
t2= FriendPair [user1=8, user2=1]
t2= FriendPair [user1=6, user2=1]
t2= FriendPair [user1=5, user2=1]
t2= FriendPair [user1=3, user2=1]
t2= FriendPair [user1=1, user2=6]
t2= FriendPair [user1=2, user2=1]
t2= FriendPair [user1=1, user2=-1]
t2= FriendPair [user1=7, user2=2]
t2= FriendPair [user1=5, user2=2]

對於用戶4,維護一個Map<Long,List<Long>>,用來保存用戶4的推薦好友以及跟該好友的共同好友列表

顯然,對於4的直接好友:即user2爲-1的,應該直接不對其推薦,只需要將<user1,null>放入Map中

對於4的間接好友,應該把推薦ID相同的記錄的共同好友進行累加,如

t2= FriendPair [user1=3, user2=2]

t2= FriendPair [user1=3, user2=1]

則應將給用戶4推薦的用戶3的所有共同好友:用戶2和用戶1進行累加,將<3,[2,1]>放入Map中



import org.apache.spark.SparkConf
import org.apache.spark.SparkContext
object FriendRecommendation {
  def main(args: Array[String]): Unit = {
    val sparkConf = new SparkConf().setAppName("FriendRecommendation").setMaster("local")
    val sc = new SparkContext(sparkConf)
    val input = "file:///media/chenjie/0009418200012FF3/ubuntu/friends2.txt"
    val output = "file:///media/chenjie/0009418200012FF3/ubuntu/friends2"
    val records = sc.textFile(input)
    val pairs = records.flatMap(line => {
      val tokens = line.split("\\s")//用空格隔開
      val person = tokens(0).toLong
      val friends = tokens(1).split(",").map(_.toLong).toList
      val mapperOutput = friends.map(directFriend => (person, (directFriend, -1.toLong)))
      val result = for {
        fi <- friends
        fj <- friends
        possibleFriend1 = (fj, person)
        possibleFriend2 = (fi, person)
        if (fi != fj)
      } yield {
        (fi, possibleFriend1) :: (fj, possibleFriend2) :: List()
      }
      mapperOutput ::: result.flatten
      //flatten可以把嵌套的結構展開.
      //scala> List(List(1,2),List(3,4)).flatten
      //res0: List[Int] = List(1, 2, 3, 4)
    })

    //
    // note that groupByKey() provides an expensive solution 
    // [you must have enough memory/RAM to hold all values for 
    // a given key -- otherwise you might get OOM error], but
    // combineByKey() and reduceByKey() will give a better 
    // scale-out performance
    //  
    val grouped = pairs.groupByKey()

    val result = grouped.mapValues(values => {
      val mutualFriends = new collection.mutable.HashMap[Long, List[Long]].empty
      values.foreach(t2 => {
        val toUser = t2._1
        val mutualFriend = t2._2
        val alreadyFriend = (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.get(toUser).get.contains(mutualFriend)) {
            val existingList = 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.isEmpty).toMap
    })

    result.saveAsTextFile(output)

    //
    // formatting and printing it to console for debugging purposes...
    // 
    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}")
    })

    // done
    sc.stop();
  }
}



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