首页 > 其他分享 >全国区划代码数据筛选重组

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

时间:2023-10-15 12:06:23浏览次数:39  
标签:info log area 代码 残联 筛选 数据 区划


你知道的越多,你不知道的越多
点赞再看,养成习惯

文章目录

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


前言

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

数据现状:

  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);
            }
        }
    }

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

  • 修复后的方法
/**
     * <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


标签:info,log,area,代码,残联,筛选,数据,区划
From: https://blog.51cto.com/u_9735550/7869952

相关文章

  • 代码随想录算法训练营-动态规划-3-(0-1背包问题)|416. 分割等和子集、1049. 最后一块石
    416.分割等和子集01背包的递推公式为:dp[j]=max(dp[j],dp[j-weight[i]]+value[i]);如果dp[j]==j说明,集合中的子集总和正好可以凑成总和j,理解这一点很重要。1classSolution:2defcanPartition(self,nums:List[int])->bool:3_sum=......
  • Django 代码部署运行(Windows)
     安装git 准备好文件夹E:\_prjct\boshi_xinxi_caiji\codes2 启动gitbash$pwd/E/_prjct/boshi_xinxi_caiji/codes2$gitclonehttps://gitee.com/xautstar/doctoral-dissertation-collection.git   创建数据库collection,修改用户和密码  在sqlyog......
  • 深入理解 JavaScript 时间分片:原理、应用与代码示例解析
    JavaScript时间分片(TimeSlicing)是一种优化技术,用于将长时间运行的任务拆分为多个小任务,以避免阻塞主线程,提高页面的响应性和性能。本文将详细解释JavaScript时间分片的原理、应用场景,并通过代码示例帮助读者更好地理解和应用该技术。本文首发于:kelen.cc概念时间分片(TimeSl......
  • 解决VS Code/Code insiders右键python代码无法“转到定义”问题
    最近怀疑自己用了个假的VSCode,同门的能丝滑跳转定义、跳转引用,自己的偏偏不行(合着这么爽的功能我从来没享受到(。﹏。*)),网上各种教程试了个遍都不行,最后自己摸索出了解决方案。记录在此备忘:按以下顺序依次Check:确保安装这些插件:Python、Pylance、IntelliCode(用远程服......
  • 【科研03】【代码复现】TransUnet道路提取
    目录1.数据准备dataprocess2.文件更名filesrename2.1.数据更名npzrename2.2.文档更名txtrename3.代码修改codechange3.1.目录调整contents3.2.数据读取code13.2.训练参数parameterset3.2.1.目标类别numclasses3.2.2.运行轮次maxepochs3.2.3.批次传入......
  • 【科研02】【代码复现】【代码分享】TransUnet-RoadExtract 道路提取【数据预处理-ras
    目录1.数据处理dataprocess1.1.类型转换RastertoPng1.2.边缘填充Resize1.2.1.填充Resizeimage1.2.1.填充Resizelabel1.3.批量裁剪Clip1.4.波段缩减3bandsto1band1.5.筛选图像Choose1.6.转换格式Transformtonpz1.7.读取列表ReadFilesToList1.数......
  • Go 代码块与作用域,变量遮蔽问题详解
    Go代码块与作用域详解目录Go代码块与作用域详解一、引入二、代码块(Block)2.1代码块介绍2.2显式代码块2.3隐式代码块2.4空代码块2.5支持嵌套代码块三、作用域(Scope)3.1作用域介绍3.2作用域划定原则3.3标识符的作用域范围3.3.1预定义标识符作用域3.3.2包代码块级......
  • Vue3 + Quasar系列-代码配置以及报错汇总记录(不断更新中)
    1.Vue3+Quasar系列-代码配置打包去掉hash后缀去掉hashhttps://quasar.dev/quasar-cli-vite/developing-pwa/configuring-pwa2.Vue3+Quasar改变主题背景quasar的样式和其他的框架修改不太一样,需要我们使用动态的方式来进行变更,一般来说有两种方案进行主题修改方案一:......
  • 【科研01】【代码复现】TransUnet-文件目录安排
    目录1.信息TransUnet1.1.时间opentime1.2.链接Linkgithub1.3.应用Use2.自用TransUnet2.1.目录Tree2.2.修改Change1.信息TransUnet1.1.时间opentime20211.2.链接Linkgithubhttps://github.com/Beckschen/TransUNet1.3.应用Use  本是应用于......
  • pycharm连接远程服务器,代码成功运行,但一些基本python属性和函数会报红线(例如print)解决
    状况:pycharm连接远程服务器,代码成功运行,但一些常见python属性和方法报红线,例如print。当你在程序中输入print这种基本方法时,pycharm是不会有输入提示的,输入后也会报红线解决方法:将远程服务器中的环境变量添加至pycharm中查出服务器中环境变量:在xshell中输入vim~/.bashrc执......