MapReduce 案例之金庸江湖人物关系网分析

项目设计目的

通过一个综合数据分析案例:”金庸的江湖——金庸武侠小说中的人物关系挖掘“,来学习和掌握MapReduce程序设计。通过本项目的学习,可以体会如何使用MapReduce完成一个综合性的数据挖掘任务,包括全流程的数据预处理、数据分析、数据后处理等。

1 任务1 数据预处理

1.1 任务描述

从原始的金庸小说文本中,抽取出与人物互动相关的数据,而屏蔽掉与人物关系无关的文本内容,为后面的基于人物共现的分析做准备。

数据输入:
1 全本的金庸武侠小说文集(未分词);
2 金庸武侠小说人名列表。

数据输出:分词后,仅保留人名的金庸武侠小说全集。
样例输入:
狄云和戚芳一走到万家大宅之前,瞧见那高墙朱门、挂灯结彩的气派,心中都是暗自嘀咕。戚芳紧紧拉住了父亲的衣袖。戚长发正待向门公询问,忽见卜垣从门里出来,心中一喜,叫道:“卜贤侄,我来啦。”
样例输出:狄云 戚芳 戚芳 戚长发 卜垣

1.2 关键问题

1.2.1 中文分词和人名提取

使用开源的Ansj_seg进行分词。Ansj_seg不仅支持中文分词,还允许用户自定义词典,在分词前,将人名列表到添加用户自定义的词典,可以精确识别金庸武侠小说中的人名。

但实际测试的时候发现,Ansj_seg分词会出现严重的歧义问题,比如“汉子”属于人名列表中的人名(nr),但Ansj_seg可能会错误地将它分类为名词(n)。因此,如果根据词性提取人名,会导致最后提取的人名太少。解决方法是在提取人名的时候,需要在将人名加入用户自定义词典的同时,构造一个包含所有人名的字典,对分词的结果逐个进行测试,如果在字典里,就是人名。

1.2.2 文件传输

使用HDFS传递数据。考虑到人名列表文件已经存放在了HDFS里,所以使用HDFS的方式不需要移动人名列表文件,只需要在Configuration中设置文件在HDFS文件系统中的路径,然后在Mapper的setup()函数里调用HDFS的函数获取文件内容即可。

1.2.3 单词同现算法

两个单词近邻关系的定义:实验要求中已经说明,同现关系为一个段落。

段落划分:非常庆幸的是,小说原文中一个段落就是一行,因此,不需要自己定义FileInputFormat和RecordReader。

1.3 MapReduce设计

1.3.1 Mapper

public class NovelAnalysisMapper extends Mapper<LongWritable, Text,Text, NullWritable> {
    @Override
    protected void setup(Context context) throws IOException, InterruptedException {
        String nameList = context.getConfiguration().get("namelist");
        FileSystem fileSystem =FileSystem.get(context.getConfiguration());
        BufferedReader br = new BufferedReader(new FileReader(nameList));
        String nameline;
        while((nameline = br.readLine()) != null){
                DicLibrary.insert(DicLibrary.DEFAULT,nameline);
        }
    }

    @Override
    protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
        String line = value.toString();
        Result result = DicAnalysis.parse(line);
        List<Term> terms = result.getTerms();
        StringBuilder sb = new StringBuilder();
        if (terms.size()>0) {
            for (int i = 0; i < terms.size(); i++) {
                String word = terms.get(i).getName(); //拿到词
                String natureStr = terms.get(i).getNatureStr(); //拿到词性
                if (natureStr.equals("userDefine")) {
                    sb.append(word + " ");
                }
            }
        }
        String res =sb.length() > 0? sb.toString().substring(0,sb.length()-1):"";
        context.write(new Text(res),NullWritable.get());
    }
}

1.3.2 Reducer

public class NovelAnalysisReducer extends Reducer<Text, NullWritable,Text,NullWritable> {
    @Override
    protected void reduce(Text key, Iterable<NullWritable> values, Context context) throws IOException, InterruptedException {
        context.write(key,NullWritable.get());
    }
}

2 任务2 特征抽取:人物同现统计

2.1 任务描述

完成基于单词同现算法的人物同现统计。在人物同现分析中,如果两个人在原文的同一段落中出现,则认为两个人发生了一次同现关系。我们需要对人物之间的同现关系次数进行统计,同现关系次数越多,则说明两人的关系越密切。

数据输入:任务1的输出
数据输出:在金庸的所有武侠小说中,人物之间的同现次数
样例输入:
狄云 戚芳 戚芳 戚长发 卜垣
戚芳 卜垣 卜垣
样例输出:
<狄云,戚芳> 1 <戚长发,狄云 > 1
<狄云,戚长发> 1 <戚长发,戚芳 > 1
<狄云,卜垣> 1 <戚长发,卜垣 > 1
<戚芳,狄云 > 1 <卜垣,狄云> 1
<戚芳,戚长发 > 1 <卜垣,戚芳> 2
<戚芳,卜垣 > 2 <卜垣,戚长发> 1

2.2 关键问题

2.2.1 人名冗余

在同一段中,人名可能多次出现,任务一只负责提取出所有的人名,没有剔除多余的人名,任务必须在输出同现次数之前处理冗余人名。我的做法是在Mapper中创建一个集合,把所有人名放入集合中,集合会自动剔除冗余的人名。

2.2.2 同现次数统计

两个人物之间应该输出两个键值对,如“狄云”和“戚芳”,应该输出“<狄云,戚芳> 1”和“<戚芳,狄云> 1”。多个段落中允许输出相同的键值对,因此,Reducer中需要整合具有相同键的输出,输出总的同现次数。

2.3 MapReduce设计

2.3.1 Mapper

public class NovelAnalysisMapper1 extends Mapper<LongWritable, Text,Text, IntWritable> {

    @Override
    protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
        Set<String> set = new HashSet<>();
        String line = value.toString();
        String[] names = line.split(" ");
        set.addAll(Arrays.asList(names));
        for (String name:set){
            for (String other_name:set){
                if (name.equals(other_name)){
                    continue;
                }else {
                    context.write(new Text(name+","+other_name),new IntWritable(1));
                }
            }
        }
    }
}

2.3.2 Reducer

public class NovelAnalysisReducer1 extends Reducer<Text, IntWritable,Text, IntWritable> {
    @Override
    protected void reduce(Text key, Iterable<IntWritable> values, Context context) throws IOException, InterruptedException {
        int count = 0;
        for (IntWritable i:values){
            count += i.get();
        }
        context.write(key,new IntWritable(count));
    }
}

3 任务3 特征处理:人物关系图构建与特征归一化

3.1 任务描述

根据任务2人物之间的共现关系,生成人物之间的关系图。人物关系使用邻接表的形式表示,人物是顶点,人物之间关系是边,两个人的关系的密切程度由共现次数体现,共现次数越高,边权重越高。另外需要对共现次数进行归一化处理,确保某个顶点的出边权重和为1。

数据输入:任务2的输出
数据输出:归一化权重后的人物关系图
样例输入:<狄云,戚芳> 1 <戚长发,狄云 > 1
<狄云,戚长发> 1 <戚长发,戚芳 > 1
<狄云,卜垣> 1 <戚长发,卜垣 > 1
<戚芳,狄云 > 1 <卜垣,狄云> 1
<戚芳,戚长发 > 1 <卜垣,戚芳> 2
<戚芳,卜垣 > 2 <卜垣,戚长发> 1
样例输出:
狄云\t戚芳:0.33333;戚长发:0.333333;卜垣:0.333333
戚芳\t狄云:0.25 ;戚长发:0.25;卜垣:0.5
戚长发\t狄云:0.33333;戚芳:0.333333;卜垣:0.333333
卜垣\t狄云:0.25;戚芳:0.5;戚长发:0.25

3.2 关键问题

3.2.1 确保人物的所有邻居输出到相同结点处理

在Mapper结点将输入的键值对“<狄云,戚芳> 1”拆分,输出新的键值对“<狄云> 戚芳:1”,“狄云”的所有邻居会被分配给同一个Reducer结点处理。

3.2.2 归一化

在Reducer结点首先统计该人物与所有邻居同现的次数和sum,每个邻居的的同现次数除以sum就得到共现概率。为了提高效率,在第一次遍历邻居的时候,可以把名字和共现次数保存在链表里,避免重复处理字符串。

3.3 MapReduce设计

3.3.1 Mapper

public class NovelAnalysisMapper2 extends Mapper<LongWritable, Text,Text, Text> {

    @Override
    protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
        String line = value.toString();
        String[] kv = line.split(",");
        context.write(new Text(kv[0]),new Text(kv[1]));
    }
}

3.3.2 Reducer

public class NovelAnalysisReducer2 extends Reducer<Text, Text,Text, NullWritable> {
    @Override
    protected void reduce(Text key, Iterable<Text> values, Context context) throws IOException, InterruptedException {
        double count = 0;
        StringBuilder sb = new StringBuilder();
        List<String> list = new ArrayList<>();
        for (Text str:values){
            list.add(str.toString());
            String[] nv = str.toString().split("\\s+");
            count += Integer.parseInt(nv[1]);
        }

        for (String text:list){
            String[] nv = text.split("\\s+");
            double number = Integer.parseInt(nv[1]);
            double scale =  number/count;
            sb.append(nv[0] + ":" + String.format("%.4f", scale) + ";");
        }
        sb.insert(0,key.toString() + "\t" +"0.1#");
        String res = sb.toString().substring(0,sb.length()-1);

        context.write(new Text(res),NullWritable.get());
    }
}

4 任务4 数据分析:基于人物关系图的PageRank计算

4.1 任务描述

经过数据预处理并获得任务的关系图之后,就可以对人物关系图作数据分析,其中一个典型的分析任务是:PageRank 值计算。通过计算 PageRank,我们就可以定量地获知金庸武侠江湖中的“主角”们是哪些。

4.2 PageRank原理

PageRank算法由Google的两位创始人佩奇和布林在研究网页排序问题时提出,其核心思想是:如果一个网页被很多其它网页链接到,说明这个网页很重要,它的PageRank值也会相应较高;如果一个PageRank值很高的网页链接到另外某个网页,那么那个网页的PageRank值也会相应地提高。

相应地,PageRank算法应用到人物关系图上可以这么理解:如果一个人物与多个人物存在关系连接,说明这个人物是重要的,其PageRank值响应也会较高;如果一个PageRank值很高的人物与另外一个人物之间有关系连接,那么那个人物的PageRank值也会相应地提高。一个人物的PageRank值越高,他就越可能是小说中的主角。

PageRank有两个比较常用的模型:简单模型和随机浏览模型。由于本次设计考虑的是人物关系而不是网页跳转,因此简单模型比较合适。简单模型的计算公式如下,其中Bi为所有连接到人物i的集合,Lj为认为人物j对外连接边的总数:

image.png

在本次设计的任务3中,已经对每个人物的边权值进行归一化处理,边的权值可以看做是对应连接的人物占总边数的比例。设w_{ji}表示人物i在人物j所有边中所占的权重,则PageRank计算公式可以改写为:

image.png

4.3 PageRank在mapreduce上的实现细节

4.3.1 GraphBuilder类

PageRank需要迭代来实现PageRank值的更新,因而需要先给每个人物初始化一个PageRank值以供迭代过程的执行,该部分由GraphBuilder类完成,仅包含一个Map过程。由于算法收敛与初始值无关,所以我们将每个人物的PageRank值都初始化为0.1,以任务3的输出文件作为Map输入,可以得到如下样例格式:

数据输入:任务3的输出
数据输出:人物的PageRank值
样例输入:
一灯大师 完颜萍:0.005037783;小龙女:0.017632242;……
样例输出:
一灯大师 0.1#完颜萍:0.005037783;小龙女:0.017632242;……

4.3.2 PageRanklter类

GraphBuilder将数据处理成可供迭代的格式,PageRank的迭代过程由PageRanklter类实现,包含一个Map和Reduce过程。Map过程产生两种类型的<key,value>:<人物名,PageRrank值>,<人物名,关系链表>。第一个人物名是关系链表中的各个链出人物名,其PR值由[图片上传失败...(image-94ba0d-1537412041730)] 计算得到;第二个人物名是本身人物名,目的是为了保存该人物的链出关系,以保证完成迭代过程。以上面的输出为例,则Map过程产生的键值对为<完颜萍, 1.00.005037>,<小龙女, 1.00.017632>,……,<一灯大师, #完颜萍:0.005037783;……>。

Reduce过程将同一人物名的<key,value>汇聚在一起,如果value是PR值,则累加到sum变量;如果value是关系链表则保存为List。遍历完迭代器里所有的元素后输出键值对<人物名,sum#List>,这样就完成了一次迭代过程。

PR值排名不变的比例随迭代次数变化的关系图如下,由于我们考虑的是找出小说中的主角,所以只要关心PR值前100名的人物的排名的变化情况,可以看到迭代次数在10以后,PR值排名不变的比例已经趋于稳定了,所以基于效率考虑,选取10作为PR的迭代次数。

4.3.3 PageRankViewer类

当所有迭代都完成后,我们就可以对所有人物的PageRank值进行排序,该过程由PageRankViewer类完成,包含一个Map和Reduce过程。Map过程只提取迭代过程输出结果中的人物名以及对应的PageRank值,并以PageRank值作为key,人物名作为value输出。为了实现PageRank值从大到小排序,需要实现DescFloatComparator类来重写compare方法以达成逆序排序。由于可能存在PageRank值相同的情况,所以还需要一个reduce过程来把因PageRank值相同而汇聚到一起的人物名拆开并输出。

PageRankMapper

public class NovelAnalysisMapper3 extends Mapper<LongWritable, Text,Text, Text> {
    @Override
    protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
        String line = value.toString();
        int index_dollar = line.indexOf("$");
        if (index_dollar != -1){
            line = line.substring(index_dollar+1);
        }
        int index_t = line.indexOf("\t");
        int index_j = line.indexOf("#");
        double PR = Double.parseDouble(line.substring(index_t+1,index_j));
        String name = line.substring(0,index_t);
        String names = line.substring(index_j+1);
        for (String name_value:names.split(";")){
            String[] nv = name_value.split(":");
            double relation = Double.parseDouble(nv[1]);
            double cal = PR * relation;
            context.write(new Text(nv[0]),new Text(String.valueOf(cal)));
        }
        context.write(new Text(name),new Text("#"+line.substring(index_j+1)));
    }


}

PageRankReducer

public class NovelAnalysisReducer3 extends Reducer<Text, Text,Text, Text> {
    int index = 0;
    @Override
    protected void reduce(Text key, Iterable<Text> values, Context context) throws IOException, InterruptedException {
        String nameList = "";
        double count = 0;
        for (Text text : values){
            String t = text.toString();
            if (t.charAt(0) == '#'){
                nameList = t;
            }else{
                count += Double.parseDouble(t);
            }
        }
        index++;
        context.write(new Text(index+"$"+key.toString()),new Text(String.valueOf(count) + nameList));
    }
}

5 任务5 数据分析:基于人物关系图的标签传播

5.1 任务描述

标签传播(Label Propagation)是一种半监督的图分析算法,他能为图上的顶点打标签,进行图顶点的聚类分析,从而在一张类似社交网络图中完成社区发现。在人物关系图中,通过标签传播算法可以将关联度比较大的人物分到同一标签,可以直观地分析人物间的关系。

5.2 标签传播算法原理

标签传播算法(Label Propagation Algorithm,后面简称LPA)是由Zhu等人于2002年提出,它是一种基于图的半监督学习方法,其基本思路是用已标记节点的标签信息去预测未标记节点的标签信息。LPA基本过程为:(1)每个结点初始化一个特定的标签值;(2)逐轮更新所有节点的标签,直到所有节点的标签不再发生变化为止。对于每一轮刷新,节点标签的刷新规则如下:对于某一个节点,考察其所有邻居节点的标签,并进行统计,将出现个数最多的那个标签赋值给当前节点。当个数最多的标签不唯一时,随机选择一个标签赋值给当前节点。

LPA与PageRank算法相似,同样需要通过迭代过程来完成。在标签传播算法中,节点的标签更新通常有同步更新和异步更新两种方法。同步更新是指,节点x在t时刻的更新是基于邻接节点在t-1时刻的标签。异步更新是指,节点x在t时刻更新时,其部分邻接节点是t时刻更新的标签,还有部分的邻接节点是t-1时刻更新的标签。若LPA算法在标签传播过程中采用的是同步更新,则在二分结构网络中,容易出现标签震荡的现象。在本次设计中,我们考虑到了两种更新方法,并进行了比较。

5.3 标签传播算法在mapreduce上的实现细节

5.3.1 LPAInit类

在每一行数据的开头添加上唯一标签。
格式如下:
6$丁同 0.008164190287040412#李三:0.2500;霍元龙:0.1250;李文秀:0.5000;老头子:0.1250
(若使用任务4中的代码,生成出来的格式就是这个格式,可以跳过这个阶段)

5.3.2 LPAMapper类

LPAIteration类完成标签的更新过程,其格式与LPAInit的输出格式一致,包含一个Map和Reduce过程。Map过程对输入的每一行进行切割,输出四种格式的<key,value>:<人物名,关系链表>,<人物名,PageRank值>,<人物名,标签>,<链出人物名,标签#起点人物名>。第四种格式个键值对是为了将该节点的标签传给其所有邻居。

public class NovelAnalysisMapper5 extends Mapper<LongWritable, Text,Text, Text> {

    @Override
    protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
        String line = value.toString();
        int index_t = line.indexOf("\t");
        int index_j = line.indexOf("#");
        int index_dollar = line.indexOf("$");
        String PR = line.substring(index_t+1,index_j);
        String name = line.substring(index_dollar+1,index_t);
        String nameList = line.split("#")[1];
        String label = line.substring(0,index_dollar);
        StringTokenizer tokenizer = new StringTokenizer(nameList,";");
        while(tokenizer.hasMoreTokens()){
            String[] element = tokenizer.nextToken().split(":");
            context.write(new Text(element[0]),new Text(label+"#"+name));
        }
        context.write(new Text(name),new Text("#"+nameList));
        context.write(new Text(name),new Text("$"+label));
        context.write(new Text(name),new Text("@"+PR));
    }
}

5.3.2 LPAReducer类

Reduce过程对value值进行识别,识别可以通过Map过程把预先定义好的特殊字符如‘#’、‘@’来实现前缀到value上来实现。由于人物关系图中的各个边都是有权重的,并且代表两个人物的相关程度,所以标签更新过程不是用边数最多的标签而是权重最大标签来更新,我们可以预先把权重最大的若干个保存到一个链表中,如果存在多个权重相同的标签,则随机选取一个作为该人名新的标签。异步方法更新标签需要使用一个哈希表来存储已经更新标签的人物名和它们的新标签,并且在更新标签时使用该哈希表里面的标签。同步方法更新标签则不需要存储已更新的标签。

public class NovelAnalysisReducer5 extends Reducer<Text, Text,Text, Text> {
    Map<String,String> name_label_map = new HashMap<>();

    @Override
    protected void reduce(Text key, Iterable<Text> values, Context context) throws IOException, InterruptedException {
        String label = "";
        String nameList = "";
        String pr = "";
        Map<String,String> relation_name_label = new HashMap<>();
        for(Text text:values){
            String str = text.toString();
            if (str.length() > 0 && str.charAt(0) == '$'){
                label = str.replace("$","");
            }else if (str.length() > 0 &&str.charAt(0) == '@'){
                pr = str.replace("@","");
            }else if (str.length() > 0 &&str.charAt(0) == '#'){
                nameList = str.replace("#","");
            }else if (str.length() > 0){
                String[] element = str.split("#");
                relation_name_label.put(element[1],element[0]);
            }
        }

        Map<String,Float> label_pr_map = new HashMap<>();
        StringTokenizer nameList_Tokenizer = new StringTokenizer(nameList,";");
        while(nameList_Tokenizer.hasMoreTokens()){
            String[] name_pr = nameList_Tokenizer.nextToken().split(":");
            Float current_pr = Float.parseFloat(name_pr[1]);
            String current_label = relation_name_label.get(name_pr[0]);
            Float label_pr;
            if ((label_pr = label_pr_map.get(current_label)) != null){
                label_pr_map.put(current_label,label_pr+current_pr);
            }else{
                label_pr_map.put(current_label,current_pr);
            }
        }


        StringTokenizer tokenizer = new StringTokenizer(nameList,";");
        float maxPr = Float.MIN_VALUE;
        List<String> maxNameList = new ArrayList<>();
        while (tokenizer.hasMoreTokens()){
            String[] element = tokenizer.nextToken().split(":");
            float tmpPr = label_pr_map.get(relation_name_label.get(element[0]));
            if (maxPr < tmpPr){
                maxNameList.clear();
                maxPr = tmpPr;
                maxNameList.add(element[0]);
            }else if (maxPr == tmpPr){
                maxNameList.add(element[0]);
            }
        }

        Random random = new Random();
        int index = random.nextInt(maxNameList.size());
        String target_name = maxNameList.get(index);
        String target_label = relation_name_label.get(target_name);
        if (name_label_map.get(target_name) != null){
            target_label = name_label_map.get(target_name);
        }else{
            name_label_map.put(key.toString(),target_label);
        }
        if (target_label == null){
            System.out.println();
        }
        context.write(new Text(target_label + "$" + key.toString()),new Text(pr + "#" + nameList));
    }
}

6 数据可视化

6.1 可视化工具Gephi

Gephi是一款开源的跨平台的基于JVM的复杂网络分析软件。把PageRank和LPA的结果,转化为gexf格式,在Gephi中绘制图像并分析大数据实验结果,更加直观、易于理解。

gexf实际上是一种特殊的XML文件,python的gexf库提供了接口方便我们编辑和生成gexf文件,因此我们选择使用python处理PageRank和LPA的结果。顶点有两种属性,LPA生成的标签和PageRank计算的PR值,每条边的权重是PageRank计算出的值。在可视化的时候,标签决定顶点显示的颜色,PR值决定标签的大小。

image.png

6.2 可视化预处理

编写一个python程序transform2xml.py,将数据分析部分得到的PR值,标签以及点连接关系处理成一个可供Gephi读取的gexf文件。

结果

image.png

7 输出结果截图

7.1 提取人名

image.png

7.2 同现次数统计

image.png

7.3 归一化输出

image.png

7.4 PageRank

image.png

7.5 LPA输出结果

image.png

标签格式化

public class FormatLabel {
    @Test
    public void format() throws IOException {
        BufferedReader bf = new BufferedReader(new FileReader("output5/Data6/part-r-00000"));
        String line = "";
        Map<String,String> label_mapped = new HashMap<>();
        int index = 1;
        List<String> arrayList = new ArrayList<>();
        while ((line = bf.readLine()) != null){
            int index_dollar = line.indexOf("$");
            String label = line.substring(0,index_dollar);
            String res_string = "";
            String target_label;
            if ((target_label = label_mapped.get(label)) != null){
                res_string = line.replace(label,target_label);
            }else {
                label_mapped.put(label,String.valueOf(index));
                res_string = line.replace(label,String.valueOf(index));
                index++;
            }
            arrayList.add(res_string);
        }
        bf.close();
        arrayList.sort((o1, o2) -> {
            int index_dollar1 = o1.indexOf("$");
            int index_dollar2 = o2.indexOf("$");
            int label1 = Integer.parseInt(o1.substring(0,index_dollar1));
            int label2 = Integer.parseInt(o2.substring(0,index_dollar2));
            return label1 - label2;
        });
        BufferedWriter bw = new BufferedWriter(new FileWriter("last_result.txt"));
        for (String str : arrayList){
            int index_t = str.indexOf("\t");
            bw.write(str);
            bw.newLine();
        }
        bw.flush();
        bw.close();
    }
}

生成gexf文件

public class GephiUtil {
    @Test
    public void geph4j() throws IOException {
        BufferedReader br = new BufferedReader(new FileReader("last_result.txt"));
        Gexf gexf = new GexfImpl();
        Calendar date = Calendar.getInstance();

        gexf.getMetadata()
                .setLastModified(date.getTime())
                .setCreator("Gephi.org")
                .setDescription("A Web network");
        gexf.setVisualization(true);

        Graph graph = gexf.getGraph();
        graph.setDefaultEdgeType(EdgeType.UNDIRECTED).setMode(Mode.STATIC);

        AttributeList attrList = new AttributeListImpl(AttributeClass.NODE);
        graph.getAttributeLists().add(attrList);

        Attribute attUrl = attrList.createAttribute("class", AttributeType.INTEGER, "Class");
        Attribute attIndegree = attrList.createAttribute("pageranks", AttributeType.DOUBLE, "PageRank");

        String line = "";
        int index = 0;
        int edge_index = 0;
        Map<String,Node> node_map = new HashMap<>();
        while((line = br.readLine())!= null){
            int index_j = line.indexOf("#");
            int index_t = line.indexOf("\t");
            int index_dollar = line.indexOf("$");
            String label = line.substring(0,index_dollar);
            String name = line.substring(index_dollar+1,index_t);
            double PR = Double.parseDouble(line.substring(index_t+1,index_j));
            Node node = node_map.get(name);
            if (node == null){
                index++;
                node = graph.createNode(String.valueOf(index));
                node.setLabel(name)
                        .getAttributeValues()
                        .addValue(attUrl, label)
                        .addValue(attIndegree, String.valueOf(PR));
                node_map.put(name,node);
            }else{
                node.getAttributeValues().addValue(attIndegree,String.valueOf(PR));
            }
            String nameList = line.substring(index_j+1);
            for (String namePair:nameList.split(";")){
                String[] name_value_pair = namePair.split(":");
                Node name_node = node_map.get(name_value_pair[0]);
                if (name_node == null){
                    index++;
                    name_node = graph.createNode(String.valueOf(index));
                    name_node.setLabel(name_value_pair[0])
                            .getAttributeValues()
                            .addValue(attUrl, label);
                    node_map.put(name_value_pair[0],name_node);
                    edge_index++;
                    try {
                        node.connectTo(String.valueOf(edge_index), name_node).setWeight(Float.parseFloat(name_value_pair[1]));
                    }catch (Exception e){
                        System.out.println(edge_index+"----------"+label +name);
                    }
                }else{
                    edge_index++;
                    node.connectTo(String.valueOf(edge_index), name_node).setWeight(Float.parseFloat(name_value_pair[1]));
                }
            }
        }
        StaxGraphWriter graphWriter = new StaxGraphWriter();
        File f = new File("static_graph_sample.gexf");
        Writer out;
        try {
            out =  new FileWriter(f, false);
            graphWriter.writeToStream(gexf, out, "UTF-8");
            System.out.println(f.getAbsolutePath());
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

参考资料

[1] 深入理解大数据:大数据处理与编程实践,机械工业出版,2015年6月,黄宜华.

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,684评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,143评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,214评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,788评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,796评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,665评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,027评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,679评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,346评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,664评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,766评论 1 331
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,412评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,015评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,974评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,073评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,501评论 2 343

推荐阅读更多精彩内容

  • 结束了军旅生涯,转业待安置在家已经8个月了。失落过、彷徨过、无聊过,却也思考了人生,浅读了几本书,锻炼了厨艺,照...
    国修阅读 551评论 1 0
  • 安小轩来到了我的学校,一切是那么的突如其来,不给人一点准备,好像突然一棒把我从一个不太美好的梦里敲醒。 上午第二节...
    缥缈林中叶阅读 446评论 0 0
  • 即将开启新的生活…
    Safety战阅读 216评论 0 0
  • 一 远观 电影中的死亡,或英勇,比如《勇敢的心》死前高呼“FREEDOM”的威廉·华莱士;或鼓舞,比如《蜘蛛侠》中...
    笑眼朋朋阅读 320评论 2 1