说我有如下输入:
(1,2)(2,1)(1,3)(3,2)(2,4)(4,1)
预计输出如下:
(1,(2,3,4)) -> (1,3) //second index is total friend #
(2,(1,3,4)) -> (2,3)
(3,(1,2)) -> (3,2)
(4,(1,2)) -> (4,2)
我知道如何在java中使用hashset执行此操作。但不知道这是如何使用mapreduce模型的。任何人都可以就此问题抛出任何想法或示例代码吗?我会很感激。
-------------------------------------------- ----------------------------------------
这是我的天真解决方案:1个映射器,2个减速器。 映射器将组织输入(1,2),(2,1),(1,3);
将输出组织为
的 的 *(1,HashSet的&LT 2 - ),(2,HashSet的&LT 1为卤素),(1,HashSet的&LT 2 - ),(2 ,HashSet的&LT 1为卤素),(1,HashSet的&3; 1+),(3,HashSet的&LT 1为卤素;) *
Reducer1 :
将mapper的输出作为输入和输出:
*(1,hashset&lt; 2,3&gt;),(3,hashset&lt; 1&gt;)和(2,hashset&lt; 1&gt;)< EM> *
Reducer2 :
将reducer1的输出作为输入输出:
*(1,2),(3,1)和(2,1) *
这只是我天真的解决方案。我不确定这是否可以通过hadoop的代码完成。
答案 0 :(得分:3)
我认为应该有一种简单的方法来解决这个问题。
Mapper Input: (1,2)(2,1)(1,3)(3,2)(2,4)(4,1)
为每对发出两条记录,如下所示:
Mapper Output/ Reducer Input:
Key => Value
1 => 2
2 => 1
2 => 1
1 => 2
1 => 3
3 => 1
3 => 2
2 => 3
2 => 4
4 => 2
4 => 1
1 => 1
在减速机方面,你会得到4个不同的组:
Reducer Output:
Key => Values
1 => [2,3,4]
2 => [1,3,4]
3 => [1,2]
4 => [1,2]
现在,您可以根据需要格式化结果。 :) 如果有人能在这种方法中看到任何问题,请告诉我
答案 1 :(得分:2)
在开始工作之前,重要的是要理解,在一个简单的方法中,reducer的值应该按升序排序。第一个想法是传递未排序的值列表,并在每个键的reducer中进行一些排序。这有两个缺点:
1)对于大值列表,很可能效率不高
和
2)如果这些对在集群的不同部分处理,框架将如何知道(1,4)是否等于(4,1)?
在Hadoop中执行此操作的方法是通过创建合成密钥以某种方式“模拟”框架。
所以我们的地图功能而不是“概念上更合适”(如果我可以这么说)
map(k1, v1) -> list(k2, v2)
如下:
map(k1, v1) -> list(ksynthetic, null)
正如您注意到我们放弃了值的使用(reducer仍会获得null
值的列表,但我们并不关心它们)。这里发生的是ksynthetic
中的这些值实际上是包含。以下是有问题的示例:
`map(1, 2) -> list([1,2], null)
但是,还需要进行一些操作,以便对键进行适当的分组和分区,并在reducer中获得正确的结果。
我们将实现一个名为FFGroupKeyComparator
的班级和一个班级FindFriendPartitioner
。
这是我们的FFGroupKeyComparator
:
public static class FFGroupComparator extends WritableComparator
{
protected FFGroupComparator()
{
super(Text.class, true);
}
@Override
public int compare(WritableComparable w1, WritableComparable w2)
{
Text t1 = (Text) w1;
Text t2 = (Text) w2;
String[] t1Items = t1.toString().split(",");
String[] t2Items = t2.toString().split(",");
String t1Base = t1Items[0];
String t2Base = t2Items[0];
int comp = t1Base.compareTo(t2Base); // We compare using "real" key part of our synthetic key
return comp;
}
}
此类将充当我们的分组比较器类。它控制将哪些键组合在一起以便单次调用Reducer.reduce(Object, Iterable, org.apache.hadoop.mapreduce.Reducer.Context)
这非常重要,因为它确保每个reducer获取适当的合成键(通过真实键来判断)。
由于Hadoop在具有许多节点的群集中运行,因此确保减少任务与分区一样多很重要。它们的数量应与真实密钥(非合成)相同。所以,通常我们用哈希值来做这件事。在我们的例子中,我们需要做的是根据真实密钥的哈希值(在逗号之前)计算合成密钥所属的分区。所以我们的FindFriendPartitioner
如下:
public static class FindFriendPartitioner extends Partitioner implements Configurable
{
@Override
public int getPartition(Text key, Text NullWritable, int numPartitions)
{
String[] keyItems = key.toString().split(",");
String keyBase = keyItems[0];
int part = keyBase.hashCode() % numPartitions;
return part;
}
所以现在我们都准备好写出实际的工作并解决我们的问题。
我假设您的输入文件如下所示:
1,2
2,1
1,3
3,2
2,4
4,1
我们将使用TextInputFormat
。
以下是使用Hadoop 1.0.4的作业驱动程序的代码:
public class FindFriendTwo
{
public static class FindFriendMapper extends Mapper<Object, Text, Text, NullWritable> {
public void map(Object, Text value, Context context) throws IOException, InterruptedException
{
context.write(value, new NullWritable() );
String tempStrings[] = value.toString().split(",");
Text value2 = new Text(tempStrings[1] + "," + tempStrings[0]); //reverse relationship
context.write(value2, new NullWritable());
}
}
请注意,我们还在map
函数中传递了反向关系。
例如,如果输入字符串是(1,4),我们一定不要忘记(4,1)。
public static class FindFriendReducer extends Reducer<Text, NullWritable, IntWritable, IntWritable> {
private Set<String> friendsSet;
public void setup(Context context)
{
friendSet = new LinkedHashSet<String>();
}
public void reduce(Text syntheticKey, Iterable<IntWritable> values, Context context)
throws IOException, InterruptedException {
String tempKeys[] = syntheticKey.toString().split(",");
friendsSet.add(tempKeys[1]);
if( friendsList.size() == 2 )
{
IntWritable key = Integer.parseInt(tempKeys[0]);
IntWritable value = Integer.parseInt(tempKeys[1]);
write(key, value);
}
}
}
最后,我们必须记住在Main Class中包含以下内容,以便框架使用我们的类。
jobConf.setGroupingComparatorClass(FFGroupComparator.class);
jobConf.setPartitionerClass(FindFriendPartitioner.class);
答案 2 :(得分:1)
我会按如下方式解决这个问题。
关于我的方法的说明:
我们从很多
开始(x,y)
我们知道数据集中没有所有关系。
Mapper:创建所有关系
Input: () -> (x,y)
Output: (x,y) -> (x,y)
(y,x) -> (y,x)
Reducer:删除重复项(只是从迭代器输出第一个)
Input: (x,y) -> [(x,y),(x,y),(x,y),(x,y),.... ]
Output: () -> (x,y)
Mapper:“Wordcount”
Input: () -> (x,y)
Output: (x) -> (x,1)
减速器:计算它们
Input: (x) -> [(x,1),(x,1),(x,1),(x,1),.... ]
Output: () -> (x,N)
答案 3 :(得分:1)
在众多优秀工程师的帮助下,我终于尝试了解决方案。
只有一个Mapper和一个Reducer。这里没有组合器。
输入Mapper:
1,2
2,1
1,3
3,1
3,2
3,4
5,1
Mapper的输出:
1,2
2,1
1,2
2,1
1,3
3,1
1,3
3,1
4,3
3,4
1,5
5,1
减速机的输出:
1 3
2 2
3 3
4 1
5 1
第一个是用户,第二个是朋友#。
在reducer阶段,我将hashSet添加到助手分析中。 谢谢@Artem Tsikiridis @Ashish 你的回答给了我一个很好的线索。
<强>编辑:强>
//映射器
public static class TokenizerMapper extends
Mapper<Object, Text, Text, Text> {
private Text word1 = new Text();
private Text word2 = new Text();
public void map(Object key, Text value, Context context)
throws IOException, InterruptedException {
String line = value.toString();
StringTokenizer itr = new StringTokenizer(line,",");
if(itr.hasMoreElements()){
word1.set(itr.nextToken().toLowerCase());
}
if(itr.hasMoreElements()){
word2.set(itr.nextToken().toLowerCase());
}
context.write(word1, word2);
context.write(word2, word1);
//
}
}
// reducer
public static class IntSumReducer extends
Reducer<Text, Text, Text, IntWritable> {
private IntWritable result = new IntWritable();
public void reduce(Text key, Iterable<Text> values,
Context context) throws IOException, InterruptedException {
HashSet<Text> set = new HashSet<Text>();
int sum = 0;
for (Text val : values) {
if(!set.contains(val)){
set.add(val);
sum++;
}
}
result.set(sum);
context.write(key, result);
}
}