好友推薦概念
目前大量的社交網站上,它們都有一個共同的特性,就是可以在好友關係的基礎上推薦更多的聯繫人。例如,QQ的“你可能認識的人”特性允許用戶查看它們可能聯繫的人。基本思想就是:如果tom是jack的好友,而tom又是peter的好友,也就是說,tom是jack和peter的共同好友,但是jack和peter可能並不認識你。那麼社交網絡系統可能就會推薦jack與peter聯繫,又或者推薦peter與jack聯繫。簡單通俗的說,如果兩個人有一組共同好友,但是兩人又不是好友關係,那麼推薦系統就會基於他們之間的好友關係推薦他們相互認識。
所有用戶之間的好友關係可以表示爲一個圖,對於一個簡單的案例,可以使用下圖進行表示。
在這裏,圖是一個有序對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的好友推薦算法,我們可以在一個龐大的社交網絡系統中,運用好友關係的大數據能夠快速計算共同好友,並給好友推薦其他的好友。同理,好友推薦算法也衍生到商品推薦及電影推薦。