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

你知道的越多,你不知道的越多
点赞再看,养成习惯
如果您有疑问或者见解,欢迎指教:
企鹅: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;

/**
 * 

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

* @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;

/**
 * 

2022全国区划和残联区划差异-分层级

* * @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;

/**
 * 

2022全国区划和残联区划差异

* * @author xymy * @date 2023/7/10 15:28 */
@Data @AllArgsConstructor @NoArgsConstructor @EqualsAndHashCode public class AreaDifferenceDto { //区划代码 private String deptCode; //区划名称 private String deptName; }
  • 将残联区划数据和全国区划数据做匹配,将其匹配结果输出到 4 个 excel 文件中
	/**
     * 

将全国区划和A单位区划进行筛选,找出区划代码和区划名称都不同的数据,存入两个excel中,分别命名为

* @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字段有重复数据,不影响后续的业务,这里不用处理。
全国区划代码数据筛选重组_第1张图片

组装完整的区划名称方法

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

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

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

  • 异常的方法
/**
     * 

将区划补充完整(省市区镇街村居五级)

* @author xymy * @date 2023-08-03 14:27:27 * @param areaList : 需要补充区划名称的数据 * @param allAreaList : 完整的区划数据 * @return java.util.List **/
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; } /** *

递归设置完整区划名称

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

导致出错的数据节选

  • 修复后的方法
/**
     * 

将区划补充完整(省市区镇街村居五级)

* @author chendw * @date 2023-08-03 14:27:27 * @param areaList : 需要补充区划名称的数据 * @param allAreaList : 完整的区划数据 * @return java.util.List **/
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; } /** *

递归设置完整区划名称

* @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;

/**
 * 

根据相似度建立残联区划和全国区划的映射关系

* @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;
    /**
     * 

筛选精确的映射关系数据

* @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

你可能感兴趣的:(工作需求解决方案,java,数据匹配,数据筛选,数据清洗,大数据)