MapReduce编程-类似qq的好友推荐(朋友的朋友)[文章最后 面试相关问题讲解]

案例:好友推荐(朋友的朋友)

案例场景:在qq 微博等众多社交平台中,用户a有n位好友,在这n位好友里面的好友中有m位不是a的直接好友(例如用户b)。但是通过朋友的朋友,a与b之间有多位共同好友,换而言之,a是b可能认识的人。

1.数据集

小明	老王	如花	林志玲
老王	小明	凤姐
如花	小明	李刚	凤姐
林志玲 小明 李刚 凤姐 郭美美
李刚	如花	凤姐	林志玲
郭美美 凤姐 林志玲
凤姐	如花	老王	林志玲 郭美美

2.代码实现

那如何得到用户a可能认识的人呢?方法:利用两个map/resuce方法,第一个map/reduce得到朋友的朋友出现的次数,第二个map/reduce得到每个用户 可能认识的好友 降序输出结果(即,共同好友越多的越靠前)。

2.1 第一个mr程序得到间接好友的个数

mr程序运行的主方法
/**
	 * run1()函数中 调用map 和reducer方法 得到朋友的朋友出现的次数
	 * @param config
	 * @return
	 */
	public static boolean run1(Configuration config) {
		try {
			FileSystem fs =FileSystem.get(config);
			Job job =Job.getInstance(config);
			job.setJarByClass(RunJob.class);
			job.setJobName("friend");
			
			job.setMapperClass(FofMapper.class);
			job.setReducerClass(FofReducer.class);
			job.setMapOutputKeyClass(Fof.class);
			job.setMapOutputValueClass(IntWritable.class);
			
			job.setInputFormatClass(KeyValueTextInputFormat.class);
			
			FileInputFormat.addInputPath(job, new Path("/mapreduce/friend/input"));
			
			Path outpath =new Path("/mapreduce/friend/output/run1");
			if(fs.exists(outpath)){
				fs.delete(outpath, true);
			}
			FileOutputFormat.setOutputPath(job, outpath);
			
			boolean f= job.waitForCompletion(true);
			return f;	//函数成功执行后的返回值位true
		} catch (Exception e) {
			e.printStackTrace();
		}
		return false;
	}
调用的map 和reduce方法
/**
	 * Map方法
	 * Mapper<Text, Text, Fof, IntWritable>前两个参数是输入的参数,后两个是输出的参数
	 *
	 */
	static class FofMapper extends Mapper<Text, Text, Fof, IntWritable>{
		protected void map(Text key, Text value,
				Context context)
				throws IOException, InterruptedException {
			String user =key.toString();	//user表示当前用户
			String[] friends =StringUtils.split(value.toString(), '\t');	//表示当前用户的朋友
			for (int i = 0; i < friends.length; i++) {
				String f1 = friends[i];
				Fof ofof =new Fof(user, f1);	
				context.write(ofof, new IntWritable(0));
				//f1与当前用户已经是朋友,则写入数值为0 表示这组数据不再考虑(user和f1),因为二者已经是朋友了
				
				for (int j = i+1; j < friends.length; j++) {
					String f2 = friends[j];
					Fof fof =new Fof(f1, f2);
					context.write(fof, new IntWritable(1));	
					//f1和f2在当前用户user不是直接好友关系,所以写入数值1 表示二者是间接好友关系 有一个间接好友user
				}
			}
		}
	}
	
	/**
	 *reducer方法
	 */
	static class FofReducer extends Reducer<Fof, IntWritable, Fof, IntWritable>{
		protected void reduce(Fof arg0, Iterable<IntWritable> arg1,
				Context arg2)
				throws IOException, InterruptedException {
			int sum =0;
			boolean f =true;
			for(IntWritable i: arg1){
				if(i.get()==0){	//对应map方法中的设置为0  二者已经是朋友 则不予考虑
					f=false;
					break;
				}else{
					sum=sum+i.get();	//统计二者的间接好友个数
				}
			}
			if(f){	//f为true,则表示 不存在直接朋友关系 将其写入最后的输出中
				arg2.write(arg0, new IntWritable(sum));
			}
		}
	}

Fof类:规范map方法输出的key值(统一为a:b)

public class Fof extends Text{
	public Fof(){
		super();
	}
	public Fof(String a,String b){
		super(getFof(a,b));	//这里的功能是规定 a b之间的顺序(将原数据集中a b和b a视为同一种朋友的朋友关系)
	}
	public static String getFof(String a,String b){
		int r=a.compareTo(b);	//两个字符串a b从首字母开始比较,得出相差大小 
		//这里写这个 conpareTo进行比较和排序 主要是为了按照字母有序排列
		if(r<0){
			return a+"\t"+b;
		}else{
			return b+"\t"+a;
		}
	}
}
运行结果
凤姐	小明	3
如花	林志玲	3
如花	老王	2
如花	郭美美	1
小明	李刚	2
小明	郭美美	1
李刚	郭美美	1
林志玲	老王	2
老王	郭美美	1

2.2 第2个map/reduce得到每个用户 可能认识的好友 降序输出结果

mr程序运行的主方法
/**
	 * run2()函数中 调用map reducer方法 排序得到每个用户 可能认识的好友 降序输出结果
	 * @param config
	 */
	public static void run2(Configuration config) {
		try {
			FileSystem fs =FileSystem.get(config);
			Job job =Job.getInstance(config);
			job.setJarByClass(RunJob.class);
			
			job.setJobName("fof2");
			
			job.setMapperClass(SortMapper.class);
			job.setReducerClass(SortReducer.class);
			job.setSortComparatorClass(FoFSort.class);
			job.setGroupingComparatorClass(FoFGroup.class);
			job.setMapOutputKeyClass(User.class);
			job.setMapOutputValueClass(User.class);
			
			job.setInputFormatClass(KeyValueTextInputFormat.class);
			
			//设置MR执行的输入文件
			FileInputFormat.addInputPath(job, new Path("/mapreduce/friend/output/run1"));
			
			//该目录表示MR执行之后的结果数据所在目录,必须不能存在
			Path outputPath=new Path("/mapreduce/friend/output/run2");
			if(fs.exists(outputPath)){
				fs.delete(outputPath, true);
			}
			FileOutputFormat.setOutputPath(job, outputPath);
			
			boolean f =job.waitForCompletion(true);
			if(f){
				System.out.println("job 成功执行");
			}
			
		} catch (Exception e) {
			e.printStackTrace();
		} 
	}
调用的map reduce方法
static class SortMapper extends Mapper<Text, Text, User, User>{
		
		protected void map(Text key, Text value,
				Context context)
				throws IOException, InterruptedException {
			String[] args=StringUtils.split(value.toString(),'\t');
			String other=args[0];
			int friendsCount =Integer.parseInt(args[1]);
			
			context.write(new User(key.toString(),friendsCount), new User(other,friendsCount));
			context.write(new User(other,friendsCount), new User(key.toString(),friendsCount));
		}
	}
	
	static class SortReducer extends Reducer<User, User, Text, Text>{
		protected void reduce(User arg0, Iterable<User> arg1,
				Context arg2)
				throws IOException, InterruptedException {
			String user =arg0.getUname();
			StringBuffer sb =new StringBuffer();
			for(User u: arg1 ){
				sb.append(u.getUname()+":"+u.getFriendsCount());
				sb.append(",");
			}
			arg2.write(new Text(user), new Text(sb.toString()));
		}
	}
map方法 输出时 数据集按照用户名 FoFGroup.class
public class FoFGroup extends WritableComparator{

	public FoFGroup() {
		super(User.class,true);
	}
	
	public int compare(WritableComparable a, WritableComparable b) {
		User u1 =(User) a;
		User u2=(User) b;
		
		return u1.getUname().compareTo(u2.getUname());
	}
}
map方法 输出时 同一个用户的间接好友 按照数值降序排列
public class FoFSort extends WritableComparator{

	public FoFSort() {
		super(User.class,true);
	}
	
	public int compare(WritableComparable a, WritableComparable b) {
		User u1 =(User) a;
		User u2=(User) b;
		
		int result =u1.getUname().compareTo(u2.getUname());
		//同一个用户a的间接好友 降序排列
		if(result==0){
			return -Integer.compare(u1.getFriendsCount(), u2.getFriendsCount());
		}
		return result;
	}
}
运行结果
凤姐	小明:3,
如花	林志玲:3,老王:2,郭美美:1,
小明	凤姐:3,李刚:2,郭美美:1,
李刚	小明:2,郭美美:1,
林志玲	如花:3,老王:2,
老王	如花:2,林志玲:2,郭美美:1,
郭美美	小明:1,如花:1,李刚:1,老王:1,

案例问题,实现思路以及代码上文已经详细讲述。完整代码见:mapreduce编程-好友推荐

最后讲述之前我在一次小米面试中 与此相关的问题:
1.在微博中,用户的直接关注与间接关注问题。用户a关注n位明星或者网红达人(直接关注),这n位关注人里面他们关注的人中 有a没有关注的(间接关注) ,我们如何得到a的间接关注用户:

解决办法:在上文的第一个mr程序中,我们可以举一反三得到问题的解。当然 这里我的思路不一定是最好的。

我们从原始数据集中得到用户a的n位直接关注人名单以及这n位用户他们的直接关注人名单。map方法以a和后面一个用户人姓名作为key/value对(例如,a:c) 输出 表示二者直接关注关系,以此对其他n位用户进行操作(这里map方法可以对文件中的内容分割成适当的块并行操作)。 reduce方法对map的输出结果进行聚合,将其他n个用户的key/value和用户a的key/value进行比较 其他用户的key或者value在a中不存在时,则将其进行中间保存(间接关注关系),最后输出所有简介好友关系。

2. 在mapreduce的工作机制中,我们知道最关键的是shuffle阶段,我们在编程的时候会涉及shufffle阶段的编程吗?

这个问题 有点迷,我现在也不完全清楚问题的最标准回答是什么。当时我回答的是有涉及shuffle阶段的,但是面试官强调了几次是shuffle阶段额,好像对我的回答表示质疑。
按照我的理解,MapReduce确保每个reducer的输入都按key进行排序,系统执行排序的过程——将map输出作为输入传给reducer——称之为shuffle。shuffle可以将其理解为从map产生输出到reduce的消化输入的整个过程。shuffle就是combine,partition,combine的组合
第一个是 map端的combine,是在map本地把同key的放在一起成列表 (Combiner 阶段)
第二个是 partition分割,把键值对按照key对应分配到reduce (Copy phase )
第三个是 reduce端的combine,把同key的再合并得到最后的reduce输入( Sort phase 应该为合并阶段 merge,因为排序是在map进行的)

但是在mr编程中,我们更多的是编写map方法 和reduce方法。关于shuffle阶段的编程比较少,有的博客里说shuffle就是map和reduce之间的过程,包含了两端的combine和partition。它比较难以理解,因为我们摸不着,看不到它,它只是理论存在的,而且确实存在,它属于mapreduce的框架,编程的时候,我们用不到它,它属于mapreduce框架。 我暂时不知道对错。
我比较赞同以下一个大佬给我的解释:这种问题,怎么理解都可以,比如写map/reduce代码,也有可能需要写一个partition,那么这个partition 实际上也只需要写一些分区规则的代码,可以说算写shuffle阶段也可以说不算。

  • 3
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值