MapReduce之可能认识的好友
背景
目前有大量的社交网络有一个共同的特性,就是可以推荐联系人,基本思想是:如果A是B的好友,而A又是C的好友(也就是说,A是B和C的共同好友,但B和C彼此并不认识),那么社交网络系统喔咕就会向B推荐C,或者向C推荐B。换句话说,如果两个人有一组共同好友,但这两个人本身不是好友,那么社交网络就会推荐他们相互联络
原理
所有用户之间的好友关系可以表示为一个图,在数学中,图是一个有序对 G = ( V , E ) G=(V,E) G=(V,E),包括顶点集 V V V,以及边集 E E E,边集 E E E是 V V V的两元素子集(也就是说,边与两个顶点相关,这个关系表示为关于这个特定边的一个无序的顶点对)。这种图可以准确的描述无向图或简单图,在MapReduce解决方案中,假设人们之间的好友关系可以用一个无向图表示(如果A是B的一个好友,那么B也是A的好友),社交网络都使用双向好友关系
在这里,图是一个有序对$ G=(V,E) $,其中:
- V V V是由人(社交网络的用户)构成的一个有限集
- E E E是 V V V上的一个二元关系,称为边集,其中的元素称为一个好友关系。
从图论的角度来看,对于一个特定的社交网络的各个人或成员来说,如果他在某个人A的两个度内,会统计这个人与A之间存在多少条不同的路径(包括两个连接边),然后根据路径数对这个列表中所有人评分,显示出A要联系的人,因此目标是为社交网络中的每一个成员计算推荐好友
输入
社交网络图通常非常稀疏,假设输入记录是一个按照名字排列的邻接表,输入中每一行分别包括一个成员的ID,后面是他的直接好友,如下
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
这是一个8人的社交网络,因为1 与所有用户都是好友,所以不向1推荐任何人,另一方面,3与1和2是好友,我们可以向3推荐4,5,6,7和8,因为他们是3和1或2的共同好友
输出
输出格式如下:
<
U
S
E
R
>
<
:
>
<
F
(
M
:
[
I
1
,
I
2
,
I
3
,
.
.
.
]
)
,
.
.
.
>
< USER >< : >< F ( M : [ I_1, I_2, I_3, ...]), ...>
<USER><:><F(M:[I1,I2,I3,...]),...>
其中:
- F是推荐给USER的一个好友
- M是共同好友数
-
I
1
,
I
2
,
I
3
,
.
.
.
I_1 , I_2 , I_3 , . . .
I1,I2,I3,... 是共同好友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]),
如图所示
mapper阶段任务
找出直接好友和将来能认识的好友,使用Tuple2来标记是否是直接好友或者可能认识的好友,其中,Tuple2设计如下:
package com.deng.RecommendFriends;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.io.Writable;
import org.apache.hadoop.io.WritableComparable;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
public class Tuple2 implements Writable, WritableComparable<Tuple2>{
private String _1;
private String _2;
public Tuple2(){
}
public Tuple2(String _1,String _2){
set(_1,_2);
}
private void set(String String, String String1) {
_1=String;
_2=String1;
}
public String first() {
return _1;
}
public void setFirst(String _1) {
this._1 = _1;
}
public String second() {
return _2;
}
public void setSecond(String _2) {
this._2 = _2;
}
@Override
public int compareTo(Tuple2 o) {
return 0;
}
@Override
public void write(DataOutput dataOutput) throws IOException {
Text.writeString(dataOutput,_1);
Text.writeString(dataOutput,_2);
}
@Override
public void readFields(DataInput dataInput) throws IOException {
_1=Text.readString(dataInput);
_2=Text.readString(dataInput);
}
}
mapper阶段编码
package com.deng.RecommendFriends;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Mapper;
import java.io.IOException;
public class recommendFriendsMapper extends Mapper<LongWritable, Text,Text, Tuple2> {
public Tuple2 directFriend;
public Tuple2 possibleFriend1,possibleFriend2;
public String[] People; // 所有人ID
public void map(LongWritable key,Text value,Context context) throws IOException,InterruptedException{
String line=value.toString();
People=line.split(" ");
// 使用切割字符将用户和他的好友分割出来
String person=People[0],friendTotal=People[1];
String[] friends=friendTotal.split(",");
// 将所有的直接好友标记为-1
for(String friend:friends){
directFriend= new Tuple2(friend,"-1");
try {
context.write(new Text(person),directFriend);
} catch (IOException e) {
e.printStackTrace();
}
}
for(int i=0;i<friends.length;i++){
for(int j=i+1;j<friends.length;j++){
possibleFriend1=new Tuple2(friends[j],person);
try {
context.write(new Text(friends[i]),possibleFriend1);
} catch (IOException e) {
e.printStackTrace();
}
possibleFriend2=new Tuple2(friends[i],person);
try{
context.write(new Text(friends[j]),possibleFriend2);
}catch (IOException e){
e.printStackTrace();
}
}
}
}
}
reducer阶段任务
这个阶段会找出特定的人与值Tuple2列表的共同好友,如果某个value有一个共同好友,则不做推荐,最后进行格式化输出
reducer阶段编码
package com.deng.RecommendFriends;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Reducer;
import java.io.IOException;
import java.util.*;
public class recommendFriendsReducer extends Reducer<Text,Tuple2,Text,Text> {
public void reduce(Text key, Iterable<Tuple2> values, Context context) {
Map<String,List> mutualFriends= new HashMap<String, List>();
for(Tuple2 t2:values){
String toUser=t2.first();
String mutualFriend=t2.second();
// 判断是否是直接好友
int alreadyFriend=(mutualFriend.compareTo("-1"));
//如果存在这个用户,则将可能认识的好友添加到链表中,如果不存在,新建链表并将可能认识的好友添加到链表中
if(mutualFriends.containsKey(toUser)){
if(alreadyFriend==0){
mutualFriends.put(toUser,null);
}else if(mutualFriends.get(toUser)!=null){
mutualFriends.get(toUser).add(mutualFriend);
}
}else{
if(alreadyFriend==0){
mutualFriends.put(toUser,null);
}else{
mutualFriends.put(toUser,new ArrayList<String>());
mutualFriends.get(toUser).add(mutualFriend);
}
}
}
String reducerOutput=buildOutput(mutualFriends);
try{
context.write(key,new Text(reducerOutput));
}catch (InterruptedException e){
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
// 格式化输出
public String buildOutput(Map<String,List> map){
String output="";
for(Map.Entry<String, List> entry:map.entrySet()){
String k= entry.getKey();
List v=entry.getValue();
if(v==null) continue;
output+=k+"("+v.size()+": "+v+"),";
}
return output;
}
}
驱动程序如下
package com.deng.RecommendFriends;
import com.deng.FileUtil;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Job;
import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;
public class recommendFriendDriver {
public static void main(String[] args) throws Exception{
FileUtil.deleteDirs("output");
Configuration conf=new Configuration();
String[] otherArgs=new String[]{"input/RecommendFriends.txt","output"};
Job job=new Job(conf,"recommendFriendDriver");
job.setJarByClass(recommendFriendDriver.class);
job.setMapperClass(recommendFriendsMapper.class);
job.setReducerClass(recommendFriendsReducer.class);
job.setOutputKeyClass(Text.class);
job.setOutputValueClass(Tuple2.class);
FileInputFormat.addInputPath(job,new Path(otherArgs[0]));
FileOutputFormat.setOutputPath(job,new Path(otherArgs[1]));
System.exit(job.waitForCompletion(true)?0:1);
}
}