希望自己早日学会总结、学会善于总结。
需求
以下是qq的好友列表数据,冒号前是一个用户,冒号后是该用户的所有好友(数据中的好友关系是单向的)
A:B,C,D,F,E,O
B:A,C,E,K
C:F,A,D,I
D:A,E,F,L
E:B,C,D,M,L
F:A,B,C,D,E,O,M
G:A,C,D,E,F
H:A,C,D,E,O
I:A,O
J:B,O
K:A,C,D
L:D,E,F
M:E,F,G
O:A,H,I,J
求出哪些人两两之间有共同好友,及他俩的共同好友都有谁?
思路
需求中给出的传入数据格式为:
用户:该用户拥有的好友们
user1:frined1,friend2,friend3……
user2:frined1,friend2,friend3……
user3:frined1,friend2,friend3……
user4:frined1,friend2,friend3……
……
要求传出的格式为:
两个用户:两个用户的共同好友
user1-user2:friend1,friend2,friend3……
user1-user3:friend1,friend2,friend3……
user1-user4:friend1,friend2,friend3……
user2-user3:friend1,friend2,friend3……
user2-user4:friend1,friend2,friend3……
user3-user4:friend1,friend2,friend3……
……
我们可以先将传入数据格式转换成:
所有用户的好友们:拥有该好友的用户
friend1:user1,user2,user3,user4……
friend2:user1,user2,user3,user4……
friend3:user1,user2,user3,user4……
……
再转换成:
两个用户:两个用户的共同好友
user1-user2:friend1,friend2,friend3……
user1-user3:friend1,friend2,friend3……
user1-user4:friend1,friend2,friend3……
user2-user3:friend1,friend2,friend3……
user2-user4:friend1,friend2,friend3……
user3-user4:friend1,friend2,friend3……
…….
代码实现
第一个Mapper:
package com.xianshun.mapper;
import java.io.IOException;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Mapper;
public class CommonFriendsStepOneMapper extends Mapper<LongWritable, Text, Text, Text>{
protected void map(LongWritable key, Text value, Mapper<LongWritable, Text, Text, Text>.Context context)
throws IOException, InterruptedException {
// 传进来的 value: A:B,C,D,F,E,O
// 将 value 转换成字符串,line 表示行数据,为"A:B,C,D,F,E,O"
String line = value.toString();
// 分割字符串,得到用户和好友们,用 userAndFriends表示,为{"A","B,C,D,F,E,O"}
String[] userAndFriends = line.split(":");
// userAndFriends 0位置为用户,user为"A"
String user = userAndFriends[0];
// userAndFriends 1位置为好友们,这里以","分割分割字符串,得到每一个好友
// friend为{"B","C","D","F","E","O"}
String[] friends = userAndFriends[1].split(",");
// 循环遍历,以<B,A>,<C,A>,<D,A>......的形式传给reducer
for (String friend : friends) {
context.write(new Text(friend), new Text(user));
}
}
}
第一个Reducer:
package com.xianshun.reducer;
import java.io.IOException;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Reducer;
public class CommonFriendsStepOneReducer extends Reducer<Text, Text, Text, Text>{
protected void reduce(Text friend, Iterable<Text> users, Context context)
throws IOException, InterruptedException {
// 传进来的数据 <好友,用户> <B,A>,<C,A>,<D,A>,<A,B>,<C,B>,<E,B>,<K,B>......
// 新建 stringBuffer, 用于存放 拥有该好友的用户们
StringBuffer stringBuffer = new StringBuffer();
// 遍历所有的用户,并将用户放在stringBuffer中,以","分隔
for (Text user : users) {
stringBuffer.append(user).append(",");
}
// 以好友为key,用户们为value传给下一个mapper
context.write(friend, new Text(stringBuffer.toString()));
}
}
第一个MR结果:
A I,K,C,B,G,F,H,O,D,
B A,F,J,E,
C A,E,B,H,F,G,K,
D G,C,K,A,L,F,E,H,
E G,M,L,H,A,F,B,D,
F L,M,D,C,G,A,
G M,
H O,
I O,C,
J O,
K B,
L D,E,
M E,F,
O A,H,I,J,F,
第二个Mapper:
package com.xianshun.mapper;
import java.io.IOException;
import java.util.Arrays;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Mapper;
public class CommonFriendsStepTwoMapper extends Mapper<LongWritable, Text, Text, Text>{
protected void map(LongWritable key, Text value, Context context)
throws IOException, InterruptedException {
// 传进来的数据 A I,K,C,B,G,F,H,O,D,
// 将传进来的数据转换成字符串并以"\t"分割,得到fiendsAndUsers
String[] friendAndUsers = value.toString().split("\t");
// fiendsAndUsers 0位置为好友 "A"
String friend = friendAndUsers[0];
// fiendsAndUsers 1位置为拥有上面好友用户们
// 以 ","进行分割字符串,得到每一个用户,{"I","K",....}
String[] users = friendAndUsers[1].split(",");
// 将user进行排序,避免重复
Arrays.sort(users);
// 以用户-用户为key,好友们做value传给reducer
for(int i=0; i<users.length-2; i++) {
for(int j=i+1; j<users.length-1; j++) {
context.write(new Text(users[i] + "-" + users[j]), new Text(friend));
}
}
}
}
第二个Reducer:
package com.xianshun.reducer;
import java.io.IOException;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Reducer;
public class CommonFriendsStepTwoReducer extends Reducer<Text, Text, Text, Text>{
protected void reduce(Text user_user, Iterable<Text> friends, Context context)
throws IOException, InterruptedException {
// 传进来的数据 <用户1-用户2,好友们>
// 新建 stringBuffer, 用于用户的共同好友们
StringBuffer stringBuffer = new StringBuffer();
// 遍历所有的好友,并将这些好友放在stringBuffer中,以" "分隔
for (Text friend : friends) {
stringBuffer.append(friend).append(" ");
}
// 以好友为key,用户们为value传给下一个mapper
context.write(user_user, new Text(stringBuffer.toString()));
}
}
最终结果:
A-B C E
A-C F D
A-D E F
A-E B C D
A-F C D B E O
A-G D E F C
A-H E O C D
A-I O
A-K D
A-L F E
B-C A
B-D E A
B-E C
B-F E A C
B-G C E A
B-H E C A
B-I A
B-K A
B-L E
C-D F A
C-E D
C-F D A
C-G F A D
C-H A D
C-I A
C-K D A
C-L F
D-F E A
D-G A E F
D-H A E
D-I A
D-K A
D-L F E
E-F C D B
E-G D C
E-H D C
E-K D
F-G C E D A
F-H C A D E O
F-I A O
F-K D A
F-L E
G-H D E C A
G-I A
G-K A D
G-L F E
H-I A O
H-K A D
H-L E
I-K A
结语
在我看来,编写MR程序最主要的是 key 和 value 的选择,只有好的选择,需求才会有好的解法。现在是刚刚起步,自己算法方面的知识非常薄弱,希望能通过以后的学习慢慢积累,不断进步。最后引用 Steve Jobs 的一句话共勉:
Stay hungry, stay foolish.