图的三角形计数

需求:输入一份社交图谱,每行是两个ID,中间用空格分开,代表这两个人有联系,组成一个无向图,计算这个无向图中三角形的个数。

首先我需要将输入文件转化成一个无向图,这个比较简单,使用map操作将两个ID提取出来,如果两个ID相同,说明自己指向了自己,那么舍弃掉这个输入,map的输出在传递给reduce前会将相同key的值排序组成一个列表,这样我们就有了表示无向图的邻接表。相应的map部分的代码如下所示:

static class GBMapper extends Mapper<LongWritable, Text, Text, Text> {
        @Override
        public void map(LongWritable key, Text value, Context context)
                throws IOException, InterruptedException {
            String[] strs = value.toString().split(" ");
            if (!strs[0].equals(strs[1]))
                context.write(new Text(strs[0]), new Text(strs[1]));
        }
    }

接下来我出现了一个错误,我以为不需要写reduce部分的代码了,因为传递给reduce时已经将相同key的值已经组合在一起了,那么默认的reduce会将输入原原本本地输出来。后来查看中看中间结果的时候,发现存储下来的还是每一行两个ID,为什么呢?(TODO)

因为,即使传递给reduce的键值对,相同key的值已经组合在一起了,但是每一次调用reduce时,将会遍历每一个value,并写出去,默认的Reducer中的reduce源码如下:

protected void reduce(KEYIN key, Iterable<VALUEIN> values, Context context
                        ) throws IOException, InterruptedException {
    for(VALUEIN value: values) {
      context.write((KEYOUT) key, (VALUEOUT) value);
    }
  }

所以我尝试自定义了一个reduce,就是将Iterable<Text>里面的值写入到输出中,代码如下:

static class GBReducer extends Reducer<Text, Text, Text, Text> {
        private StringBuilder newValue = new StringBuilder();
        @Override
        public void reduce(Text key, Iterable<Text> values, Context context)
                throws IOException, InterruptedException {
            for (Text value : values)
                newValue.append(value.toString() + " ");
            context.write(key, new Text(newValue.toString()));
            newValue = new StringBuilder();
        }
    }

在写上面代码的时候,又踩了一个坑。当时跑代码的时候,reduce消耗了好长时间,自己推算了一下不应该这么长时间,后来发现是因为每次append完之后没有将newValue还原,所以对于下一个输入,newValue会接着append,导致newValue越来越大,极大地减慢了运行速度,而且最后生成的文件将会占据很大的空间。其实应该将newValue定义为reduce的局部变量,而不是成员变量。

 

以上代码我们将输入文件转化成了图的邻接表,现在需要统计三角形的个数。思路是这样的:首先得到的邻接表中key是一个顶点,values是与这个顶点连在一起的点,那么可以与这个顶点构成的三角形中需要的另一条边将是values中两两组合得到的边,所以我们需要统计已经存在的边,以及需要的边,key就是边的两个点,value就是表示已经存在(“e”)或者被需要(”n“),最后统计某条边的值是否既有“e“又有”n“,这样”n“的个数代表这条边可以构成三角形的数目,最后将”n”的个数累加即可。代码如下:

static class CMapper extends Mapper<Text, Text, Text, Text> {
        static final Text exist = new Text("e");
        static final Text need = new Text("n");
        private String newKey;


        // Input is Sequence Text
        @Override
        public void map(Text key, Text value, Context context)
                throws IOException, InterruptedException {
            String keyStr = key.toString();
            String[] values = value.toString().split(" ");
            if (values.length != 0) {
                for (int i = 0; i < values.length; ++i) {
                    if (values[i].compareTo(keyStr) > 0)
                        newKey = keyStr + values[i];
                    else
                        newKey = values[i] + keyStr;
                    context.write(new Text(newKey), exist);
                    for (int j = i + 1; j < values.length; ++j) {
                        if (values[i].compareTo(values[j]) > 0)
                            newKey = values[j] + values[i];
                        else
                            newKey = values[i] + values[j];
                        context.write(new Text(newKey), need);
                    }
                }
            }
        }
    }
// Reduce number must be 1
    static class CReducer extends Reducer<Text, Text, Text, Text> {
        private static long totalCount = 0;
        @Override
        public void reduce(Text key, Iterable<Text> values, Context context)
                throws IOException, InterruptedException {
            boolean existState = false;
            long needCount = 0;
            for (Text value : values) {
                if (!existState && value.toString().equals("e"))
                    existState = true;
                if (value.toString().equals("n"))
                    ++needCount;
            }
            totalCount += needCount;
        }

        @Override
        public void cleanup(Context context) throws IOException, InterruptedException {
            context.write(new Text("The total number of triangle:"), new Text(Long.toString(totalCount)));
        }
    }

上面的代码中,我们需要的reduce的数目必须为1,因为每个键值对得到的“n”的个数要累计到totalCount中。

posted @ 2016-11-24 19:01  传奇魔法师  阅读(1045)  评论(0编辑  收藏  举报