全国区划代码数据筛选重组

news2024/11/23 7:23:50

你知道的越多,你不知道的越多
点赞再看,养成习惯
如果您有疑问或者见解,欢迎指教:
企鹅:869192208

文章目录

        • 前言
        • 引入jar包
        • 实现思路
        • 代码实现
          • 验证 Guava工具类找出两个 Map 集合的差异数据
          • 筛选残联区划和全国区划差异
          • 组装完整的区划名称方法
          • 区划名称相似度匹配,筛选出可以建立关联关系的区划数据并建立关联关系
          • 对筛选出来的相似数据进行去重,保证映射关系的准确性
          • 附件

前言

前面的工作中,获取了全国 2022 年行政区划代码的数据,这些数据最终要结合一份第三方公司的区划数据,筛选整合,最终做出同地区不同区划代码的映射表,以下记录相关过程。

  • 数据来源
    国家统计局
  • 爬取好的数据
    2023年中国全国5级行政区划(省、市、县、镇、村)
    2023全国五级行政区划

数据现状:

  1. 2022年全国行政区划数据
  2. 残联区划数据

引入jar包

首先,在 springboot 项目依赖的基础上,针对这次的需求,需要引入 easyexcel 包和 guava 包,还有 hutool 工具,需要在 pom 文件新增以下内容:

<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>easyexcel</artifactId>
    <version>3.3.2</version>
</dependency>

<dependency>
    <groupId>com.google.guava</groupId>
    <artifactId>guava</artifactId>
    <version>32.1.1-jre</version>
</dependency>


<dependency>
    <groupId>cn.hutool</groupId>
    <artifactId>hutool-all</artifactId>
    <version>5.8.16</version>
</dependency>

实现思路

  • 将全国区划数据和残联区划存量数据进行筛选匹配,分别找出

    • 区划代码和区划名称完全一致的数据
    • 区划代码一致,区划名称不一致的数据
    • 区划代码和区划名称都不一致的数据
      • 全国区划不匹配
      • 残联区划不匹配
  • 前面筛选出来的数据,需要处理的部分为“区划代码和区划名称都不一致的数据”,将这部分数据中的两个筛选后的文件(全国区划不匹配和残联区划不匹配),单个级别的区划名称,转成完整的区划名称,如:沙太社区居委会–>广东省广州市天河区兴华街道沙太社区居委会

  • 将需要对比的国家区划数据和残联区划数据进行相似度匹配,筛选出相似的区划名,这部分数据可以近似看做是能建立关联的数据,但是因为是相似度匹配出来的结果,为了保证筛选出的数据的准确性,需要对匹配结果数据中的区划代码进行去重,宁缺毋滥

代码实现

验证 Guava工具类找出两个 Map 集合的差异数据

引入的类:
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.MapDifference;
import com.google.common.collect.Maps;
import lombok.extern.slf4j.Slf4j;
import java.util.Map;

/**
 * <h2>验证 Guava工具类找出两个 Map 集合的差异数据</h2>
 * @author xymy
 * @date 2023-08-02 17:55:31

 * @return void
 **/
public static void testMapDifferenceTypical() {
        Map<Integer, String> left = ImmutableMap.of(1, "a", 2, "b", 3, "c", 4, "d", 5, "e");
        Map<Integer, String> right = ImmutableMap.of(1, "a", 3, "f", 5, "g", 6, "z");

        MapDifference<Integer, String> diff1 = Maps.difference(left, right);
        log.info("两个数据是否相同:{}",diff1.areEqual());
        log.info("只有left有的key数据:{}",diff1.entriesOnlyOnLeft());
        log.info("只有right有的key数据:{}",diff1.entriesOnlyOnRight());
        log.info("left和right的key和value完全相同的数据:{}",diff1.entriesInCommon());
        log.info("key相同value不同的数据:{}",diff1.entriesDiffering());

        Map<Integer, MapDifference.ValueDifference<String>> integerValueDifferenceMap = diff1.entriesDiffering();
        integerValueDifferenceMap.keySet().forEach(f -> {
            MapDifference.ValueDifference<String> stringValueDifference = integerValueDifferenceMap.get(f);

            log.info("key:{},left的value:{}", f, stringValueDifference.leftValue());
            log.info("key:{},right的value:{}", f, stringValueDifference.rightValue());
        });
    }

输出:
两个数据是否相同:false
只有left有的key数据:{2=b, 4=d}
只有right有的key数据:{6=z}
left和right的key和value完全相同的数据:{1=a}
key相同value不同的数据:{3=(c, f), 5=(e, g)}
key:3,left的value:c
key:3,right的value:f
key:5,left的value:e
key:5,right的value:g

可以看出,全国区划数据和A部门存量数据进行筛选匹配时,只需要将区划代码作为 key ,将区划名称作为 value,就能筛选出想要的数据集合。

筛选残联区划和全国区划差异
  • 新建 AreaDifferenceLevelDto 类,用来存储解析出来的残联区划数据和全国区划数据
import com.alibaba.excel.annotation.ExcelProperty;
import com.alibaba.excel.annotation.ExcelIgnore;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;

import java.util.List;

/**
 * <h2>2022全国区划和残联区划差异-分层级</h2>
 *
 * @author xymy
 * @date 2023/7/10 15:28
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
@EqualsAndHashCode
public class AreaDifferenceLevelDto {

    @ExcelProperty("ID")
    private String id;
    @ExcelProperty("DEPT_NAME")
    private String deptName;
    @ExcelProperty("DEPT_LEVEL")
    private String deptLevel;
    @ExcelProperty("DEPT_CODE")
    private String deptCode;
    @ExcelProperty("PARENT_ID")
    private String parentId;
		@ExcelIgnore
    private List<AreaDifferenceLevelDto> childAreaDifferenceLevels;
    @ExcelProperty("ALL_NAME")
    private String allName;
    @ExcelProperty("P_ID")
    //为了一层层往前补充完整区划存储的临时父id,处理到哪个层级的完整区划,就存储这个层级上一级的父id
    private String pId;
}
  • 新建 AreaDifferenceDto 类,用来存储残联区划和全国区划的对比数据
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;

/**
 * <h2>2022全国区划和残联区划差异</h2>
 *
 * @author xymy
 * @date 2023/7/10 15:28
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
@EqualsAndHashCode
public class AreaDifferenceDto {
    //区划代码
    private String deptCode;
    //区划名称
    private String deptName;
}

  • 将残联区划数据和全国区划数据做匹配,将其匹配结果输出到 4 个 excel 文件中
	/**
     * <h2>将全国区划和A单位区划进行筛选,找出区划代码和区划名称都不同的数据,存入两个excel中,分别命名为</h2>
     * @author xymy
     * @date 2023-08-02 18:22:39

     * @return void
     **/
    public static void areaDifferenceTypical() {
        //存储读取到的残联区划信息
        List<AreaDifferenceLevelDto> areaClList = new ArrayList<>();
        //存储读取到的全国区划信息
        List<AreaDifferenceLevelDto> areaQgList = new ArrayList<>();
        //读取残联区划数据
        EasyExcel.read("D:/area_data/area_code_cl.xlsx", AreaDifferenceLevelDto.class,new AnalysisEventListener<AreaDifferenceLevelDto>() {
            @Override
            public void invoke(AreaDifferenceLevelDto clArea, AnalysisContext arg1) {
                // 读取每条数据
                areaClList.add(clArea);
            }

            @Override
            public void invokeHeadMap(Map<Integer, String> headMap, AnalysisContext context) {
                // 读取到列头
                log.info(JSON.toJSONString(headMap));
            }

            @Override
            public void doAfterAllAnalysed(AnalysisContext arg0) {
                // 读取完毕
                log.info("读取残联区划excel结束,数量:{}", areaClList.size());
                //读取全国区划数据
                EasyExcel.read("D:/area_data/area_code_2023.xlsx", AreaDifferenceLevelDto.class,new AnalysisEventListener<AreaDifferenceLevelDto>() {
                    @Override
                    public void invoke(AreaDifferenceLevelDto qgArea, AnalysisContext arg1) {
                        // 读取每条数据
                        areaQgList.add(qgArea);
                    }

                    @Override
                    public void invokeHeadMap(Map<Integer, String> headMap, AnalysisContext context) {
                        // 读取到列头
                        log.info(JSON.toJSONString(headMap));
                    }

                    @Override
                    public void doAfterAllAnalysed(AnalysisContext arg0) {
                        // 读取完毕
                        log.info("读取全国区划excel结束,数量:{}", areaQgList.size());
                        //使用 Stream 将列表对象的两个字段抽取出来形成一个 Map,并且在键冲突时将字段值进行合并。
                        //Collectors.toMap() 方法,传递了三个参数:
                        //    第一个参数 AreaDifferenceLevelDto::getDeptCode 是用于指定作为键的字段名;
                        //    第二个参数 AreaDifferenceLevelDto::getDeptName 是用于指定作为值的字段名;
                        //    第三个参数 (value1, value2) -> value1 + ","+value2 是一个合并函数,用于处理键冲突的情况。在这里,我们将两个字段值通过逗号连接起来。
                        Map<String, String> areaClMap = areaClList.parallelStream().collect(Collectors.toMap(AreaDifferenceLevelDto::getDeptCode, AreaDifferenceLevelDto::getDeptName, (value1, value2) -> value1 + ","+value2));
                        Map<String, String> areaQgMap = areaQgList.parallelStream().collect(Collectors.toMap(AreaDifferenceLevelDto::getDeptCode, AreaDifferenceLevelDto::getDeptName, (value1, value2) -> value1 + ","+value2));

                        MapDifference<String, String> mapDifference = Maps.difference(areaClMap, areaQgMap);
                        log.info("残联区划独有的数据数量:{}",mapDifference.entriesOnlyOnLeft().size());
                        log.info("全国区划独有的数据数量:{}",mapDifference.entriesOnlyOnRight().size());
                        log.info("区划代码和区划名称完全相同的数据数量:{}",mapDifference.entriesInCommon().size());
                        log.info("区划代码相同,区划名称不同的数据数量:{}",mapDifference.entriesDiffering().size());

                        // 使用 Stream 将 Map 转换为列表对象
                        List<AreaDifferenceDto> entriesOnlyOnLeftList = mapDifference.entriesOnlyOnLeft().entrySet().stream()
                                .map(entry -> new AreaDifferenceDto(entry.getKey(), entry.getValue()))
                                .collect(Collectors.toList());

                        List<AreaDifferenceDto> entriesOnlyOnRightList = mapDifference.entriesOnlyOnRight().entrySet().stream()
                                .map(entry -> new AreaDifferenceDto(entry.getKey(), entry.getValue()))
                                .collect(Collectors.toList());

                        List<AreaDifferenceDto> entriesInCommonList = mapDifference.entriesInCommon().entrySet().stream()
                                .map(entry -> new AreaDifferenceDto(entry.getKey(), entry.getValue()))
                                .collect(Collectors.toList());

                        List<AreaDifferenceDto> entriesDifferingList = mapDifference.entriesDiffering().entrySet().stream()
                                .map(entry -> new AreaDifferenceDto(entry.getKey(), entry.getValue().leftValue()+","+entry.getValue().rightValue()))
                                .collect(Collectors.toList());

                        //残联区划独有的数据,将其从所有的残联区划数据中找出来
                        Set<String> clDeptCodes = entriesOnlyOnLeftList.parallelStream().map(x -> x.getDeptCode()).collect(Collectors.toSet());
                        List<AreaDifferenceLevelDto> clAreaDifferenceLevelList = areaClList.parallelStream().filter(f -> clDeptCodes.contains(f.getDeptCode()) ).collect(Collectors.toList());
                        log.info("残联区划独有的数据数量:{}", clAreaDifferenceLevelList.size());
                        //全国区划独有的数据,将其从所有的全国区划数据中找出来
                        Set<String> qgDeptCodes = entriesOnlyOnRightList.parallelStream().map(x -> x.getDeptCode()).collect(Collectors.toSet());
                        List<AreaDifferenceLevelDto> qgAreaDifferenceLevelList = areaQgList.parallelStream().filter(f -> qgDeptCodes.contains(f.getDeptCode()) ).collect(Collectors.toList());
                        log.info("全国区划独有的数据数量:{}", qgAreaDifferenceLevelList.size());
                        
						//调用组装完整的区划名称方法补充完整的区划名称
                        clAreaDifferenceLevelList = setTopLevelAreaName(clAreaDifferenceLevelList, areaClList);
                        qgAreaDifferenceLevelList = setTopLevelAreaName(qgAreaDifferenceLevelList, areaQgList);
                        log.info("补充完整区划名称后的残联区划独有的数据数量:{},补充完整区划名称后的全国区划独有的数据数量:{}", clAreaDifferenceLevelList.size(), qgAreaDifferenceLevelList.size());

                        String fileName1 = "D:/area_data/2023/全国区划和残联区划差异-残联区划比全国区划多出来的数据" + ".xlsx";
                        EasyExcel.write(fileName1, AreaDifferenceLevelDto.class).sheet("sheet1").doWrite(clAreaDifferenceLevelList);
                        String fileName2 = "D:/area_data/2023/全国区划和残联区划差异-全国区划比残联区划多出来的数据" + ".xlsx";
                        EasyExcel.write(fileName2, AreaDifferenceLevelDto.class).sheet("sheet1").doWrite(qgAreaDifferenceLevelList);
                        String fileName3 = "D:/area_data/2023/全国区划和残联区划差异-区划代码和区划名称完全相同的数据" + ".xlsx";
                        EasyExcel.write(fileName3, AreaDifferenceDto.class).sheet("sheet1").doWrite(entriesInCommonList);
                        String fileName4 = "D:/area_data/2023/全国区划和残联区划差异-区划代码相同区划名称不同的数据" + ".xlsx";
                        EasyExcel.write(fileName4, AreaDifferenceDto.class).sheet("sheet1").doWrite(entriesDifferingList);
                    }
                }).sheet().doRead();

            }
        }).sheet().doRead();
    }

输出日志
读取残联区划excel结束,数量:681266
读取全国区划excel结束,数量:664487
残联区划代码独有的数据数量:261755
全国区划代码独有的数据数量:244980
区划代码和区划名称完全相同的数据数量:257947
区划代码相同,区划名称不同的数据数量:161560
残联区划独有的数据数量:261759
全国区划独有的数据数量:244980

日志打印发现“残联区划代码独有的数据数量:261755”和“09:37:15.344 残联区划独有的数据数量:261759”这两个数值不一致,查看残联区划excel数据发现这份数据的DEPT_CODE字段有重复数据,不影响后续的业务,这里不用处理。
残联区划数据有几条重复数据

组装完整的区划名称方法

将前面得到的“全国区划和残联区划差异-残联区划比全国区划多出来的数据”和“全国区划和残联区划差异-全国区划比残联区划多出来的数据”这两份数据,分别将其完整的区划名称组装出来,调用的代码已经在上面给出。

//补充完整的区划名称
clAreaDifferenceLevelList = setTopLevelAreaName(clAreaDifferenceLevelList, areaClList);
qgAreaDifferenceLevelList = setTopLevelAreaName(qgAreaDifferenceLevelList, areaQgList);
log.info("补充完整区划名称后的残联区划独有的数据数量:{},补充完整区划名称后的全国区划独有的数据数量:{}", clAreaDifferenceLevelList.size(), qgAreaDifferenceLevelList.size());

一开始写递归方法来实现,但是加上递归之后,就会报“java.lang.StackOverflowError”,最后折腾许久发现是残联区划有几条错误数据,id和parentId的值是一样的,导致递归进入死循环,于是加了判断条件,记录一下。

  • 异常的方法
/**
     * <h2>将区划补充完整(省市区镇街村居五级)</h2>
     * @author xymy
     * @date 2023-08-03 14:27:27
     * @param areaList : 需要补充区划名称的数据
     * @param allAreaList : 完整的区划数据
     * @return java.util.List<com.minstone.appr.projorder.projorder.init.model.dto.AreaDifferenceLevelDto>
     **/
    public static List<AreaDifferenceLevelDto> setTopLevelAreaName(List<AreaDifferenceLevelDto> areaList, List<AreaDifferenceLevelDto> allAreaList) {
    	//使用 Collectors.toMap() 方法将流中的对象转换为一个 Map。toMap() 方法接受两个参数,第一个参数是键的提取函数,这里使用 id 作为键;第二个参数是值的提取函数,这里使用 Function.identity() 表示将对象本身作为值。
        //这样就得到了一个以 id 为键、AreaDifferenceLevelDto 对象为值的 Map,可以用于快速查找和访问对象。
        Map<String, AreaDifferenceLevelDto> allAreaMap = allAreaList.stream()
                .collect(Collectors.toMap(AreaDifferenceLevelDto::getId, Function.identity()));
        areaList.forEach(area ->{
            setParentName(area, allAreaMap);
        });
        return areaList;
    }
    /**
     * <h2>递归设置完整区划名称</h2>
     * @author xymy
     * @date 2023-08-03 15:22:44
     * @param area :
     * @param allAreaMap :
     * @return void
     **/
    private static void setParentName(AreaDifferenceLevelDto area, Map<String, AreaDifferenceLevelDto> allAreaMap) {
        if (!"root_node_id".equals(area.getPId())) {
            AreaDifferenceLevelDto parentArea = allAreaMap.get(StringUtils.isBlank(area.getPId()) ? area.getParentId() : area.getPId());
            if (parentArea != null) {
                area.setPId(parentArea.getParentId());
                area.setAllName(parentArea.getDeptName() + "-" + (StringUtils.isBlank(area.getAllName()) ? area.getDeptName() : area.getAllName()));
                setParentName(area, allAreaMap);
            }
        }
    }

导致出错的数据节选

  • 修复后的方法
/**
     * <h2>将区划补充完整(省市区镇街村居五级)</h2>
     * @author chendw
     * @date 2023-08-03 14:27:27
     * @param areaList : 需要补充区划名称的数据
     * @param allAreaList : 完整的区划数据
     * @return java.util.List<com.minstone.appr.projorder.projorder.init.model.dto.AreaDifferenceLevelDto>
     **/
    public static List<AreaDifferenceLevelDto> setTopLevelAreaName(List<AreaDifferenceLevelDto> areaList, List<AreaDifferenceLevelDto> allAreaList) {
    	//使用 Collectors.toMap() 方法将流中的对象转换为一个 Map。toMap() 方法接受两个参数,第一个参数是键的提取函数,这里使用 id 作为键;第二个参数是值的提取函数,这里使用 Function.identity() 表示将对象本身作为值。
        //这样就得到了一个以 id 为键、AreaDifferenceLevelDto 对象为值的 Map,可以用于快速查找和访问对象。
        Map<String, AreaDifferenceLevelDto> allAreaMap = allAreaList.stream()
                .collect(Collectors.toMap(AreaDifferenceLevelDto::getId, Function.identity()));
        areaList.forEach(area ->{
            setParentName(area, allAreaMap);
        });
        return areaList;
    }
    /**
     * <h2>递归设置完整区划名称</h2>
     * @author xymy
     * @date 2023-08-03 15:22:44
     * @param area :
     * @param allAreaMap :
     * @return void
     **/
    private static void setParentName(AreaDifferenceLevelDto area, Map<String, AreaDifferenceLevelDto> allAreaMap) {
        if (!"root_node_id".equals(area.getPId()) && !area.getId().equals(area.getParentId())) {
            AreaDifferenceLevelDto parentArea = allAreaMap.get(StringUtils.isBlank(area.getPId()) ? area.getParentId() : area.getPId());
            if (parentArea != null) {
                area.setPId(parentArea.getParentId());
                area.setAllName(parentArea.getDeptName() + "-" + (StringUtils.isBlank(area.getAllName()) ? area.getDeptName() : area.getAllName()));
                setParentName(area, allAreaMap);
            }
        }
    }
区划名称相似度匹配,筛选出可以建立关联关系的区划数据并建立关联关系
  • 相似度匹配方法

需要对 261759 条残联区划数据,循环对比 244980 条国家区划数据,如果直接嵌套循环将进行 261759 * 261759 = 64125719820 次迭代,这是一个非常大的数量级。于是决定先将残联区划的 261759 条数据存到redis的 list 中,然后借助其 rightPop 方法,一个个取出来与国家区划数据进行循环比对。

@Resource
RedisTemplate redisTemplate;

/**
 * <h2>根据相似度建立残联区划和全国区划的映射关系</h2>
 * @author xymy
 * @date 2023-08-04 10:41:36

 * @return void
 **/
public void getClAndQgAreaNear() {
    //存储读取到的残联区划比全国区划多出来的数据
    List<AreaDifferenceLevelDto> areaClList = new ArrayList<>();
    //存储读取到的全国区划比残联区划多出来的数据
    List<AreaDifferenceLevelDto> areaQgList = new ArrayList<>();
    //读取残联区划数据
    EasyExcel.read("D:/area_data/2023/全国区划和残联区划差异-残联区划比全国区划多出来的数据.xlsx", AreaDifferenceLevelDto.class,new AnalysisEventListener<AreaDifferenceLevelDto>() {
        @Override
        public void invoke(AreaDifferenceLevelDto clArea, AnalysisContext arg1) {
            // 读取每条数据
            areaClList.add(clArea);
        }

        @Override
        public void invokeHeadMap(Map<Integer, String> headMap, AnalysisContext context) {
            // 读取到列头
            log.info(JSON.toJSONString(headMap));
        }

        @Override
        public void doAfterAllAnalysed(AnalysisContext arg0) {
            // 读取完毕
            log.info("读取残联区划比全国区划多出来的数据excel结束,数量:{}", areaClList.size());
            //读取全国区划数据
            EasyExcel.read("D:/area_data/2023/全国区划和残联区划差异-全国区划比残联区划多出来的数据.xlsx", AreaDifferenceLevelDto.class,new AnalysisEventListener<AreaDifferenceLevelDto>() {
                @Override
                public void invoke(AreaDifferenceLevelDto qgArea, AnalysisContext arg1) {
                    // 读取每条数据
                    areaQgList.add(qgArea);
                }

                @Override
                public void invokeHeadMap(Map<Integer, String> headMap, AnalysisContext context) {
                    // 读取到列头
                    log.info(JSON.toJSONString(headMap));
                }

                @Override
                public void doAfterAllAnalysed(AnalysisContext arg0) {
                    // 读取完毕
                        log.info("读取全国区划比残联区划多出来的数据excel结束,数量:{}", areaQgList.size());
                        //存储筛选出相似的数据
                        List<ApprAreaNear> apprAreaNears = new ArrayList<>();
                        ListOperations<String, AreaDifferenceLevelDto> listOperations = redisTemplate.opsForList();
                        ListOperations<String, ApprAreaNear> listOperationsNear = redisTemplate.opsForList();
                        //数据量太大,直接循环遍历耗时太长,先将全国区划部分的数据放到redis中
                        listOperations.leftPushAll(AREA_QG_REDIS_KEY, areaQgList);
                        redisTemplate.expire(AREA_QG_REDIS_KEY, 180L, TimeUnit.DAYS);
                        log.info("全国区划部分的数据放到redis完成,数量:{}", listOperations.size(AREA_QG_REDIS_KEY));

                        StopWatch stopWatch = DateUtil.createStopWatch();
                        // 开始计时
                        stopWatch.start();
                        Long size = listOperations.size(AREA_QG_REDIS_KEY);

                        for (int i=0; i < size; i++){
                            AreaDifferenceLevelDto qgAreaDifference = listOperations.rightPop(AREA_QG_REDIS_KEY);
                            String qgAllName = qgAreaDifference.getAllName();
                            String qgDeptLevel = qgAreaDifference.getDeptLevel();
                            //只有区划名称互相包含,完整名称不为空和数据层级相同的数据才需要对比
                            areaClList.parallelStream().forEach(cl -> {
                                if ((qgAreaDifference.getDeptName().contains(cl.getDeptName()) || cl.getDeptName().contains(qgAreaDifference.getDeptName()))
                                    && StringUtils.isNotBlank(qgAllName) && StringUtils.isNotBlank(cl.getAllName())
                                    && qgDeptLevel.equals(cl.getDeptLevel())) {
                                    double v = isNearby(StrUtil.replace(qgAllName, "-", ""), StrUtil.replace(cl.getAllName(), "-", ""));
                                    //这里先初筛相似度大于0.85的数据,并且记录下相似度的值,后面可以在此基础上按需精选
                                    if (v >= 0.85) {
                                        ApprAreaNear apprAreaNear = new ApprAreaNear();
                                        apprAreaNear.setId(IdUtil.simpleUUID());
                                        apprAreaNear.setClDeptName(cl.getDeptName());
                                        apprAreaNear.setClDeptCode(cl.getDeptCode());
                                        apprAreaNear.setClLevel(cl.getDeptLevel());
                                        apprAreaNear.setClName(cl.getAllName());
                                        apprAreaNear.setQgDeptCode(qgAreaDifference.getDeptCode());
                                        apprAreaNear.setQgDeptName(qgAreaDifference.getDeptName());
                                        apprAreaNear.setQgLevel(qgDeptLevel);
                                        apprAreaNear.setQgName(qgAllName);
                                        apprAreaNear.setClData(JSON.toJSONString(cl));
                                        apprAreaNear.setQgData(JSON.toJSONString(qgAreaDifference));
                                        apprAreaNear.setNearLever(String.valueOf(v));
                                        //相似,存入相似表和redis
                                        listOperationsNear.leftPush(AREA_NEAR_REDIS_KEY, apprAreaNear);
                                        areaDao.saveApprAreaNear(apprAreaNear);
                                        apprAreaNears.add(apprAreaNear);
                                    }
                                }
                            });
                        }
                    // 停止计时
                    stopWatch.stop();
                    log.info("全部数据对比完成,相似度大于0.85的数据数量:{}条,耗费时间:{}秒", apprAreaNears.size(), stopWatch.getTotalTimeSeconds());

                    String fileName = "D:/area_data/2023/全国区划和残联区划映射-相似度大于0.85的数据" + ".xlsx";
                    EasyExcel.write(fileName, ApprAreaNear.class).sheet("sheet1").doWrite(apprAreaNears);

                }
            }).sheet().doRead();

        }
    }).sheet().doRead();
}
/**
 * 判断两个字符串的相似
 * @param str1
 * @param str2
 * @return
 */
public static double isNearby(String str1, String str2) {
    Map<Character, int[]> vectorMap = new HashMap<>();
    char[] chArray1 = str1.toCharArray();
    for (char c : chArray1) {
        if (vectorMap.containsKey(c)) {
            vectorMap.get(c)[0]++;
        } else {
            int[] arr = new int[2];
            arr[0] = 1;
            vectorMap.put(c, arr);
        }
    }
    char[] chArray2 = str2.toCharArray();
    for (char c : chArray2) {
        if (vectorMap.containsKey(c)) {
            vectorMap.get(c)[1]++;
        } else {
            int[] arr = new int[2];
            arr[1] = 1;
            vectorMap.put(c, arr);
        }
    }
    double vector1Modulo = 0;
    double vector2Modulo = 0;
    double vectorProduct = 0;
    for (Map.Entry<Character, int[]> entry : vectorMap.entrySet()) {
        int[] arr = entry.getValue();
        vector1Modulo += arr[0] * arr[0];
        vector2Modulo += arr[1] * arr[1];
        vectorProduct += arr[0] * arr[1];
    }
    vector1Modulo = Math.sqrt(vector1Modulo);
    vector2Modulo = Math.sqrt(vector2Modulo);
    if (vector1Modulo == 0 || vector2Modulo == 0) {
        return 0d;
    } else {
        double v = vectorProduct / (vector1Modulo * vector2Modulo);
        return v;
    }
}

全部数据对比完成,相似度大于0.85的数据数量:156137条,耗费时间:8362.1870015秒
可以看出,这种方式耗时还是很长的,所以为了防止中途出现啥异常,可以把数据放到redis之后,一个个拿出来对比,并且把对比的结果存在redis和数据库中
并且筛选出的数据多达156137条,显然还需要进一步的筛选,通过将相似度值的从大到小的排序,我们还需要对数据进行进一步的筛选,去掉区划代码重复的数据

  • 省略建表语句,只保留插入mybatis的sql写法
<insert id="saveApprAreaNear">
        insert into appr_area_near(
            ID,
            CL_DEPT_NAME,
            CL_DEPT_CODE,
            CL_LEVEL,
            QG_DEPT_CODE,
            QG_DEPT_NAME,
            QG_LEVEL,
            CL_DATA,
            QG_DATA,
            NEAR_LEVER)
        values(#{id},#{clDeptName},#{clDeptCode},#{clLevel},#{qgDeptCode},#{qgDeptName},#{qgLevel},
               #{clData},#{qgData},#{nearLever})
    </insert>
  • 另一种方式
//方法二
stopWatch.start();
List<ApprAreaNear> apprAreaNears = areaClList.parallelStream()
        .flatMap(cl -> areaQgList.stream()
                .filter(qg -> (qg.getDeptName().contains(cl.getDeptName()) || cl.getDeptName().contains(qg.getDeptName()))
                        && StringUtils.isNotBlank(qg.getAllName()) && StringUtils.isNotBlank(cl.getAllName())
                        && qg.getDeptLevel().equals(cl.getDeptLevel())
                        && isNearby(StrUtil.replace(qg.getAllName(), "-", ""), StrUtil.replace(cl.getAllName(), "-", "")) > 0.85)
                .map(qg -> new ApprAreaNear(IdUtil.simpleUUID(),
                        cl.getDeptName(),cl.getDeptCode(),qg.getDeptName(), qg.getDeptCode(),
                        cl.getDeptLevel(), qg.getDeptLevel(),JSON.toJSONString(cl), JSON.toJSONString(qg),
                        String.valueOf(isNearby(StrUtil.replace(qg.getAllName(), "-", ""), StrUtil.replace(cl.getAllName(), "-", ""))),
                        cl.getAllName(), qg.getAllName()
                        ))
        )
        .collect(Collectors.toList());
// 停止计时
stopWatch.stop();
log.info("全部数据对比完成,相似度大于0.85的数据数量:{}条,耗费时间:{}秒", apprAreaNears.size(), stopWatch.getTotalTimeSeconds());

读取残联区划比全国区划多出来的数据excel结束,数量:261759
读取全国区划比残联区划多出来的数据excel结束,数量:244980
全部数据对比完成,相似度大于0.85的数据数量:156132条,耗费时间:758.028423秒
数量有误差,后续还要找一下原因。先记录一下。

对筛选出来的相似数据进行去重,保证映射关系的准确性

对前面筛选出来的数据,还不能满足映射的要求,需要对数据进行处理

  1. 只提取同层级,区划名称互相包含的数据,比如:西南村-西南村民委员会
  2. 对数据进行区划代码的去重,保证映射表中区划代码的唯一
  • 筛选精确的映射关系数据
	@Resource
    private SqlSessionFactory sqlSessionFactory;
    /**
     * <h2>筛选精确的映射关系数据</h2>
     * @author xymy
     * @date 2023-08-06 21:51:15

     * @return void
     **/
    public void AccurateFiltrateArea(){
        ListOperations<String, ApprAreaNear> listOperationsNear = redisTemplate.opsForList();
        // 需要处理的数据量大,一次获取会导致redis超时等异常,通过分页获取整个List对象
        long pageSize = 10000L;
        long totalItems = listOperationsNear.size(AREA_NEAR_REDIS_KEY);
        long totalPages = (long) Math.ceil((double) totalItems / pageSize);

        List<ApprAreaNear> apprAreaNearAll = new ArrayList<>();

        for (long pageIndex = 1; pageIndex <= totalPages; pageIndex++) {
            long start = (pageIndex - 1) * pageSize;
            long end = pageIndex * pageSize - 1;

            List<ApprAreaNear> pageDataList = redisTemplate.opsForList().range(AREA_NEAR_REDIS_KEY, start, end);
            apprAreaNearAll.addAll(pageDataList);
        }
        log.info("获取存储在redis中的相似度>0.85的区划List总数:{}", apprAreaNearAll.size());
        //clDeptCode残联区划代码和qgDeptCode全国区划代码去重后再次筛选出apprAreaNearList
        List<ApprAreaNear> apprAreaNearList = apprAreaNearAll.parallelStream().collect(Collectors.groupingBy(ApprAreaNear :: getClDeptCode))
                .values()
                .parallelStream()
                .filter(s -> s.size() == 1)
                .flatMap(ss -> ss.parallelStream())
                .sorted(Comparator.comparing(ApprAreaNear :: getClDeptCode).reversed())
                .collect(Collectors.toList());
        apprAreaNearList = apprAreaNearList.parallelStream().collect(Collectors.groupingBy(ApprAreaNear :: getQgDeptCode))
                .values()
                .parallelStream()
                .filter(s -> s.size() == 1)
                .flatMap(ss -> ss.parallelStream())
                .sorted(Comparator.comparing(ApprAreaNear :: getQgDeptCode).reversed())
                .collect(Collectors.toList());
        Set<String> clDeptCode = apprAreaNearList.parallelStream().map(ApprAreaNear::getClDeptCode).collect(Collectors.toSet());
        Set<String> qgDeptCode = apprAreaNearList.parallelStream().map(ApprAreaNear::getQgDeptCode).collect(Collectors.toSet());
        log.info("apprAreaNearList:{},clDeptCode:{}, qgDeptCode:{}", apprAreaNearList.size(),clDeptCode.size(), qgDeptCode.size());
        //将apprAreaNearList的数据存入数据库的残联区划和全国区划映射表
        //数据量大,如果使用循环插入的方式,耗时会很长,使用mybatis的批量操作提高插入性能
        SqlSession sqlSession=sqlSessionFactory.openSession(ExecutorType.BATCH, false);
        try{
            AreaDao mapper=sqlSession.getMapper(AreaDao.class);
            apprAreaNearList.forEach(apprAreaNear->{
                ApprClArea apprClArea = new ApprClArea();
                apprClArea.setSeq(IdUtil.simpleUUID());
                apprClArea.setClAreaCode(apprAreaNear.getClDeptCode());
                apprClArea.setClAreaName(apprAreaNear.getClDeptName());
                apprClArea.setMcOrgAreaCode(apprAreaNear.getQgDeptCode());
                apprClArea.setMcOrgAreaName(apprAreaNear.getQgDeptName());
                apprClArea.setCreateTime(new Date());
                mapper.addApprClArea(apprClArea);
            });
            sqlSession.clearCache();
            sqlSession.commit();
        }catch(Exception e){
            log.error("插入数据出错:{}", e.getMessage(), e);
        }finally{
            sqlSession.close();
        }
    }

获取存储在redis中的相似度>0.85的区划List总数:156157
apprAreaNearList:147350,clDeptCode:147350, qgDeptCode:147350

下面给出 addApprClArea(ApprClArea apprClArea) 的dao方法

<insert id="addApprClArea" parameterType="com.minstone.appr.projorder.projorder.init.model.ApprClArea">
    insert into appr_cl_area(
        SEQ,
        MC_ORG_AREA_CODE,
        MC_ORG_AREA_NAME,
        CL_AREA_CODE,
        CL_AREA_NAME,
        CREATE_TIME)
    values(#{seq},#{mcOrgAreaCode},#{mcOrgAreaName},#{clAreaCode},#{clAreaName},#{createTime})
</insert>
@ApiModel("残联行政区划信息映照表")
@Data
@AllArgsConstructor
@NoArgsConstructor
public class ApprClArea implements Serializable {
    private static final long serialVersionUID = 1L;

    private String seq;
    private String mcOrgAreaCode;
    private String mcOrgAreaName;
    private String clAreaCode;
    private String clAreaName;
    private Date createTime;
}

最终存储到映射表的数据为147350条。

至此,该需求完成。

附件

处理的区划数据和处理后的区划数据
2022统计局全国区划数据.xlsx
A部门区划数据.xlsx
2022统计局全国区划数据和A部门区划数据映射相似度 > 0.85 的数据.xlsx

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/875283.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

宇凡微电热毯方案开发,多档调节带定时

电热毯在1912年发明&#xff0c;到现在已有百年历史。现在的电热毯更有了许多智能化产品&#xff0c;这么多年来拯救了许多怕冷的小伙伴们&#xff0c;在寒冷的冬季靠它续命。宇凡微推出的电热毯方案&#xff0c;电热毯单片机使用54E&#xff0c;实现的功能有档位调节&#xff…

扬起的沙尘如何形成卷云

被气旋吹到空中的沙尘为冰云的形成提供了成核粒子。 卷云是由空气中的冰粒形成的。 卷云是由纯冰粒子组成的高云&#xff0c;主要在8-17 公里高空出现。 这些云通过散射入射的阳光和吸收地球发出的红外辐射&#xff0c;对地球的气候产生重要影响。 在一项最新的研究中&#xf…

保姆级SPSS图文安装教程

1.SPSS安装包下载 链接&#xff1a;百度网盘 请输入提取码 提取码&#xff1a;rb0n 2.SPSS安装 1.上面压缩包解压后双击解压文件中的setup.exe 2.点击下图绿色框中内容进行安装 3.下一步 4.接受协议&#xff0c;下一步 5.是&#xff0c;下一步 6.接受协议&#xff0c;下一步…

python菱形问题

Python类分为两种&#xff0c;一种叫经典类&#xff0c;一种叫新式类。都支持多继承&#xff0c;但继承顺序不同。 新式类&#xff1a;从object继承来的类。&#xff08;如:class A(object)&#xff09;&#xff0c;采用广度优先搜索的方式继承&#xff08;即先水平搜索&#…

图分类,图机器学习最新进展

图分类&#xff0c;图机器学习最新进展 1.Flat_Pooling TitleVenueTaskCodeDatasetDMLAP: Multi-level attention pooling for graph neural networks: Unifying graph representations with multiple localitiesNeural Networks 20221. Graph ClassificationNonesynthetic, …

Tomcat日志中文乱码

修改安装目录下的日志配置 D:\ProgramFiles\apache-tomcat-9.0.78\conf\logging.properties java.util.logging.ConsoleHandler.encoding GBK

感受RFID服装门店系统的魅力

嘿&#xff0c;亲爱的时尚追随者们&#xff01;今天小编要给你们带来一股时尚新风潮&#xff0c;让你们感受一下什么叫做“RFID服装门店系统”&#xff0c;这个超酷的东西&#xff01; 别着急&#xff0c;先别翻白眼&#xff0c;小编来解释一下RFID是什么玩意儿。它是射频识别…

Android使用Gradle kotlin dsl 优雅配置构建项目

目录 概述1.Gradle Kotlin-DSL配置1.1 在根目录下建立一个buildSrc目录&#xff0c;1.2.新建build.gradle.kts文件并添加Kotlin dsl相关配置 2.Gradle Kotlin DSL 的编写2.1 定义项目的版本号信息2.2.定义Dependencies管理项目中需要使用的库依赖2.3 定义APK的打包脚本构建APK的…

R-Meta分析与【文献计量分析、贝叶斯、机器学习等】多技术融合

Meta分析是针对某一科研问题&#xff0c;根据明确的搜索策略、选择筛选文献标准、采用严格的评价方法&#xff0c;对来源不同的研究成果进行收集、合并及定量统计分析的方法&#xff0c;最早出现于“循证医学”&#xff0c;现已广泛应用于农林生态&#xff0c;资源环境等方面。…

600份国家自然基金申报书--模板

600份国家自然基金申报书--模板 0、引言1、 目录2、网盘链接 ⚠申明&#xff1a; 未经许可&#xff0c;禁止以任何形式转载&#xff0c;若要引用&#xff0c;请标注链接地址。 全文共计3077字&#xff0c;阅读大概需要3分钟 &#x1f308;更多学习内容&#xff0c; 欢迎&#x…

增速骤降2703亿

号外&#xff1a;公众号「刘教链Pro」今日发表《如果牛市停止加仓》。公众号「刘教链内参」今日发表《内参&#xff1a;灰度负溢价继续收窄&#xff0c;微策略跑赢一切》。欢迎点击阅读。 * * * * * * 日前&#xff0c;中国人民银行网站公布了7月份的金融统计数据报告&#xf…

原生信息流广告特点,如何帮APP开发者增加变现收益?

简单来说&#xff1a;原生广告&#xff0c;就是把广告片和账号&#xff0c;一起用消耗推流的买量模式&#xff0c;一同投放出去。 用户看到的广告/内容&#xff0c;与原生视频没有差别——用户可以点头像关注、也可以查看账号历史信息。原生广告本质&#xff0c;是显得真实、原…

头条移动端项目Day03 —— 自媒体素材管理、自媒体文章管理、自媒体文章发布

❤ 作者主页&#xff1a;欢迎来到我的技术博客&#x1f60e; ❀ 个人介绍&#xff1a;大家好&#xff0c;本人热衷于Java后端开发&#xff0c;欢迎来交流学习哦&#xff01;(&#xffe3;▽&#xffe3;)~* &#x1f34a; 如果文章对您有帮助&#xff0c;记得关注、点赞、收藏、…

JDK 17 营销初体验 —— 亚毫秒停顿 ZGC 落地实践 | 京东云技术团队

前言 自 2014 年发布以来&#xff0c; JDK 8 一直都是相当热门的 JDK 版本。其原因就是对底层数据结构、JVM 性能以及开发体验做了重大升级&#xff0c;得到了开发人员的认可。但距离 JDK 8 发布已经过去了 9 年&#xff0c;那么这 9 年的时间&#xff0c;JDK 做了哪些升级&am…

Java 8:Stream API 流式操作

&#x1f497;wei_shuo的个人主页 &#x1f4ab;wei_shuo的学习社区 &#x1f310;Hello World &#xff01; Java 8&#xff1a;Stream API Java 8 中的 Stream API 是一组用于对集合数据进行处理的新特性&#xff1b;提供一种以声明式风格对集合进行操作的方式&#xff0c;简…

pdf加水印怎么加?掌握这几种加水印方法就够了

pdf加水印怎么加&#xff1f;水印可以帮助保护你的文档免受未经授权的复制或滥用。通过在PDF文件上添加水印&#xff0c;可以在每页或指定位置显示作者、公司名称、版权信息或其他标识&#xff0c;以确保他人无法随意盗用你的作品。下面就给大家介绍几种添加水印的方法。 【迅捷…

Openlayers 实战 - 地图视野(View)- 图层 -(layer)- 资源(source)显示等级设置。

Openlayers 实战 - 地图视野&#xff08;View&#xff09;- 图层 -&#xff08;layer&#xff09;- 资源&#xff08;source&#xff09;显示等级设置。 问题原因核心代码完整代码&#xff1a;在线示例 在以往的项目维护中&#xff0c;出现一个问题&#xff0c;使用最新高清底图…

JS图表库LightningChart JS全新发布v4.2——新增多种雷达图表类型

LightningChartJS是Web上性能最高的图表库具有出色的执行性能 - 使用高数据速率同时监控数十个数据源。 GPU加速和WebGL渲染确保您的设备的图形处理器得到有效利用&#xff0c;从而实现高刷新率和流畅的动画。用于贸易&#xff0c;工程&#xff0c;航空航天&#xff0c;医药和其…

随机过程的2个例题探讨

&#xff08;一&#xff09;马氏过程和泊松过程、维纳过程的联系 泊松过程、维纳过程两者都是独立增量过程。独立增量过程是马氏过程的条件&#xff1a; 1. 随机过程是独立增量过程 2. X&#xff08;0&#xff09; 0 满足以上两个条件的随机过程都是马氏过程。 注意&#xff1…

C/C++test两步完成CMake项目静态分析

您可能一直在静态分析中使用CMake。但您是否尝试过将Parasoft C/Ctest与CMake一起使用吗&#xff1f;以下是如何使用C/Ctest在基于CMake的项目中运行静态分析的详细说明。 CMake是用于构建、测试和打包软件的最流行的工具之一。Parasoft C/Ctest通过简化构建管理过程&#xff…