2021年安徽省大数据与人工智能应用竞赛——MapReduce(数据预处理)题目解答(第三题)

前两题的链接
2021年安徽省大数据与人工智能应用竞赛——MapReduce(数据预处理)题目解答
2021年安徽省大数据与人工智能应用竞赛——MapReduce(数据预处理)题目解答(第二题)

请使用MapReduce统计 calls.txt中的 被叫省份中 被叫次数最高的前三条记录
返回格式:省 ,被叫号码,被叫次数

数据calls.txt 通话记录
样例:18620192711,15733218050,1506628174,1506628265,650000,810000
字段分别为:
呼叫者手机号,接受者手机号,开始时间戳,接受时间戳,呼叫者地址省份编码,接受者地址省份编码

package Demo.mapreduce;

import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Job;
import org.apache.hadoop.mapreduce.Mapper;
import org.apache.hadoop.mapreduce.Reducer;
import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;
import org.apache.log4j.BasicConfigurator;

import java.io.IOException;
import java.net.URI;
import java.util.*;

public class subject3 {
    public static class demoMapper extends Mapper<LongWritable,Text,Text,Text> {
        @Override
        protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
            String line = value.toString();
            String[] split = line.split(",");
            String province = split[5];
            String phone = split[1];
            context.write(new Text(province),new Text(phone));
        }
    }

    public static class demoReducer extends Reducer<Text,Text,Text,IntWritable> {
        @Override
        protected void reduce(Text key, Iterable<Text> values, Context context) throws IOException, InterruptedException {
            //定义一个map集合用来存放手机号码,和手机号出现的次数
            Map<String,Integer> map = new HashMap<>();
            for (Text value : values) {
                //遍历values,获取当前省内的每一个手机号
                String val = value.toString();
                //getOrDefault(key,defaultValue)
                //按照key查询Map里面的value值,查询成功则返回对应的value值,如果没有找到则返回defaultValue作为结果
                Integer receive_num = map.getOrDefault(val, 0);
                receive_num++;
                //map集合自动去重,插入相同key的键值对,会自动覆盖之前的键值对
                map.put(val,receive_num);
            }

            //将Map的所有键值对对象Entry<>作为元素放入list集合中
            List<Map.Entry<String,Integer>> list = new ArrayList<>(map.entrySet());
            //利用Collentions.sort对象list列表里面的元素进行排序
            Collections.sort(list, new Comparator<Map.Entry<String, Integer>>() {
                @Override
                public int compare(Map.Entry<String, Integer> o1, Map.Entry<String, Integer> o2) {
                    return o2.getValue() - o1.getValue();
                }
            });

            int count = 0;
            for (Map.Entry<String, Integer> entry : list) {
                if(count<3){
                    count++;
                    String phone = entry.getKey();
                    int receive_num = entry.getValue();
                    System.out.println(phone+"---"+receive_num);
                    context.write(new Text(key+","+phone),new IntWritable(receive_num));
                }
            }
        }
    }

    public static void main(String[] args) throws Exception{
        BasicConfigurator.configure();
        // 配置mapreduce
        Job job = Job.getInstance();
        job.setJobName("zhang");
        job.setJarByClass(subject3.class);
        job.setMapperClass(demoMapper.class);
        job.setMapOutputKeyClass(Text.class);
        job.setMapOutputValueClass(Text.class);
        job.setReducerClass(demoReducer.class);
        job.setOutputKeyClass(Text.class);
        job.setOutputValueClass(IntWritable.class);
        //指定路径
        Path input1 = new Path("hdfs://master:9000/data/calls.txt");
        FileInputFormat.addInputPath(job,input1);

        Path output = new Path("hdfs://master:9000/output");//输出路径不能已存在

        //获取文件系统对象fs,利用fs来对hdfs中的文件进行操作
        FileSystem fs = FileSystem.get(new URI("hdfs://master:9000"),new Configuration());
        if(fs.exists(output)){
            fs.delete(output,true);
        }

        FileOutputFormat.setOutputPath(job,output);
        //启动
        job.waitForCompletion(true);
    }
}

2021年安徽省大数据与人工智能应用竞赛——MapReduce(数据预处理)题目解答(第三题)_第1张图片

map阶段只需要将省份作为key,将接收者电话号码作为value传入reduce端即可。

但是在reduce端要对手机号码进行统计数量以及排序,因此需要分别解决这两个问题,才能获取最终结果

  1. 统计数量

这里的思路是,定义一个HashMap集合,将手机号码作为key,被叫次数作为value。Map集合中不允许出现重复的key值,所以一旦有新的<手机号码,被叫次数>放入map集合中,就会覆盖原先的相同key的键值对。

确定了这一点后,还需要做到如何修改手机号码的被叫次数,否则每次都是一样的key-value,即使覆盖也没有意义。这里采取的方式,首先遍历values,也就是当前分区的全部手机号码,然后利用hashmap的一个方法
public V getOrDefault(Object key, V defaultValue)
这个方法的作用是按照key查询Map里面的value值,查询成功则返回对应的value值,如果没有找到则返回defaultValue作为结果。在一开始,map集合为空,所以必然返回defaultValue的值也就是0给receive_num,即被叫次数。下面再借助receive_num++,让0变成1。

然后第二次按照这个key查询时,获取到了value值为1,此时说明有两个相同的手机号码,应该累加在一起,receive_num++,变成2,然后<手机号码,2>会覆盖掉原先<手机号码,1>

这样就完成了数量的统计

  1. 按照被叫次数进行排序
    将map集合中的全部键值对对象作为元素传入list集合里面,利用Collections.sort对这些键值对按照 new Comparator() 定义的比较器来进行排序。排序成功后,再利用for循环遍历list里面的键值对对象Entry,获取里面的key值和value值。

TreeSet也可以对元素进行排序,但是选择list而不是TreeSet的原因是,list集合的排序可以通过 Collections.sort触发,而TreeSet是将元素加入到Set集合里面的时候触发排序。这里要将所有Map集合里面的键值对对象传入后,整体一起排序,触发比较器,所以选择list集合更合适。

TreeMap也可以进行排序,但是如果想让元素传入TreeMap的时候按顺序排列,就需要在定义TreeMap的时候传入比较器,实现Comparator接口。但是这里实现Comparator接口,就会很难实现Compare方法。所以还是使用将map元素放入list集合的方式最为省力。

另外还要求取前三条记录,所以加上一个次数判断,仅在count<3的时候才会输出

你可能感兴趣的:(MapReduce,mapreduce,大数据竞赛,HashMap,topn)