Java利用stream流,判断列表中对象的某个字段的值是否与其它对象重复【批量导入,字段重复性的校验】


Java利用stream流,判断列表中对象的某个字段的值是否与其它对象重复;尤其是在批量导入的时候,进行数据的重复性校验时;

通过toMapgroupBy可以实现判断一个字段的重复性,还可以判断对象中某几个字段拼接后内容的重复性;

实例:校验学员学号stuNumber的重复性

[
    {
        "classUuid":"685806c0-4b1e-495f-b3fa-b02f089b7421",
        "stuUuid":"2c1e85df-2464-4b77-81f5-5f958519c1d8",
        "stuNumber":"1231",
        "stuName":"测试学员1",
        "fee":"100"
    },
    {
        "classUuid":"685806c0-4b1e-495f-b3fa-b02f089b7421",
        "stuUuid":"b0632666-8334-4618-b4d1-a6c856b0e522",
        "stuNumber":"1232",
        "stuName":"测试学员2",
        "fee":"100"
    },
    {
        "classUuid":"685806c0-4b1e-495f-b3fa-b02f089b7421",
        "stuUuid":"742ce0e3-d1e4-4694-a5bb-68ed7e44b7fc",
        "stuNumber":"1232",
        "stuName":"测试学员3",
        "fee":"100"
    }
]

上面的列表中的数据,stuNumber为学员学号,应该是唯一存在的;但是测试学员2和测试学员3的学号重复,均为1232;因此,列表中有不符合要求的重复字段数据;

1、通过toMap

转为map,键为要比较的字段,值为该键存在的次数(使用"merge"操作:Integer::sum)

然后利用过滤,获取值大于1的数据

如果数据存在,则表明重复

filter中存放的是留下的元素需满足的条件

//import java.util.Map.Entry;

/** 验证列表中是否存在相同的学员学号 */
  private void checkIfStuNumDuplicate(List<ClassStuCreateParam> params) throws ServiceException {
    List<String> list =
        params.stream()
            .map(ClassStuCreateParam::getStuNumber)
            .collect(Collectors.toMap(e -> e, e -> 1, Integer::sum))
            .entrySet()
            .stream()
            .filter(entry -> entry.getValue() > 1)
            .map(Entry::getKey)
            .collect(Collectors.toList());
    if (CollectionUtils.isNotEmpty(list)) {
      LOGGER.warn("stuNumber duplicate: [{}]", list);
      throw new ServiceException(
          OrgErrorConst.DUPLICATE_STU_NUMBER,
          String.format("%s:%s", OrgErrorConst.DUPLICATE_STU_NUMBER_MSG, String.join(",", list)));
    }
  }

注:
toMap的第三个参数Integer::sum表示当键重复时,值所需做的操作,就是将旧值和新值进行相加求和;
其它实例:【遇到重复键的时候,用新值替换旧值】

Map<String, String> courseMap =
          params.stream()
              .collect(
                  Collectors.toMap(
                      ClassCreateParam::getCourseUuid,
                      ClassCreateParam::getCourseName,
                      (oldValue, newValue) -> newValue));

【保留旧值,就是将上面代码的->符号后的newValue替换为oldValue】

2、通过groupBy

1、复杂一点的写法【不推荐,但可以学习一下】

// 获取学号列表
List<String> stuNumList =
        classStuCreateParams.stream()
            .map(ClassStuCreateParam::getStuNumber)
            .filter(Objects::nonNull)
            .collect(Collectors.toList());
if (CollectionUtils.isNotEmpty(stuNumList)) {
      Map<String, List<Integer>> stuIdIndexMap =
          IntStream.range(0, stuNumList.size())
              .boxed()
              .collect(Collectors.groupingBy(stuNumList::get));
      List<String> numErr =
          stuIdIndexMap.values().stream()
              .map(
                  integers -> {
                    List<Integer> stuErrNum = new ArrayList<>();
                    if (integers.size() > 1) {
                      for (Integer base : integers) {
                        Integer baseNum = base + 3;
                        stuErrNum.add(baseNum);
                      }
                      String err = stuErrNum.toString();
                      err = err.substring(1, err.length() - 1);
                      return ("第" + err + "行学号填写重复");
                    } else {
                      return "";
                    }
                  })
              .filter(StringUtils::isNotBlank)
              .collect(Collectors.toList());
      errList.addAll(numErr);
    }
  1. 将要比较的字段提取成列表(eg. 将学员学号提取出来为stuNumList)
    在这里插入图片描述
  2. 通过groupBy根据stuNum进行分组
    在这里插入图片描述
  3. 根据size大小进行判断

2、简单版写法

  /**
   * 方法作用:筛选出列表中的重复的元素
   *
   * @param orgData 初始字符串列表
   * @return List 列表中的重复数据
   */
  private List<String> getDuplicateData(List<String> orgData) {
    if (CollectionUtils.isEmpty(orgData)) {
      return Collections.emptyList();
    }
    Map<String, Long> resMap =
        orgData.stream().collect(Collectors.groupingBy(Function.identity(), Collectors.counting()));
    return orgData.stream().filter(data -> resMap.get(data) > 1).collect(Collectors.toList());
  }

3、其它:通过list和set

说到重复性,其实第一个想到的是集合的特性–互异性;
如下所示,courseCodes 是原有的list数据,courseCodeSet 是去重后的数据;
通过CollectionUtils.subtract求两个Collection的差集,可以知道重复的元素是什么;
如果想单纯的判断courseCodes 中是否有重复的,可以直接比较’list’和’set’的大小

// 相同编码的课程不能导入
    List<String> courseCodes =
        teachMergeParams.stream()
            .map(CourseCreateParam::getCourseCode)
            .collect(Collectors.toList());

    // 校验表单中是否有重复的
    Set<String> courseCodeSet = teachMergeParams.stream().map(CourseCreateParam::getCourseCode).collect(Collectors.toSet());
    Collection<String> duplicateCourses = CollectionUtils.subtract(courseCodes, courseCodeSet);

方法有很多,灵活使用。

4、补充【判断拼接字段的处理⭐】

在这里插入图片描述
这里其实就是测试课程1有三个教师,分别为测试教师1测试教师2测试教师3
测试课程2有两个教师,分别为测试教师2测试教师5
入参:

[
    {
        "courseName":"测试课程1",
        "courseCode":"cskc1",
        "courseType":"学科",
        "courseObj":"成年人",
        "teacherNames":["测试教师1_teac1"],
        "teacherWorks":[]
    },
    {
        "courseName":"测试课程2",
        "courseCode":"cskc2",
        "courseType":"学科",
        "courseObj":"成年人",
        "teacherNames":["测试教师2_teac2"],
        "teacherWorks":[]
    },
    {
        "courseName":"测试课程1",
        "courseCode":"cskc1",
        "courseType":"学科",
        "courseObj":"成年人",
        "teacherNames":["测试教师2_teac2"],
        "teacherWorks":[]
    },
    {
        "courseName":"测试课程1",
        "courseCode":"cskc1",
        "courseType":"学科",
        "courseObj":"成年人",
        "teacherNames":["测试教师3_teac3"],
        "teacherWorks":[]
    },
    {
        "courseName":"测试课程2",
        "courseCode":"cskc2",
        "courseType":"学科",
        "courseObj":"成年人",
        "teacherNames":["测试教师5_teac5"],
        "teacherWorks":[]
    }
]

函数:

private List<CourseExcelParam> handleAddBatchNotByMacro(List<CourseExcelParam> params) {
    // 处理一个课程对应多个老师的list
    List<CourseExcelParam> teachMergeParams = new ArrayList<>();
    params.parallelStream()
        .collect(
            Collectors.groupingBy(
                p -> (p.getCourseCode() + p.getCourseName()), Collectors.toList()))
        .forEach(
            (id, transFer) -> {
              transFer.stream()
                  .reduce(CourseExcelParam::merge)
                  .ifPresent(
                      course -> {
                        if (CollectionUtils.size(transFer) == 1) {
                          course.setTeacherWorks(
                              course.getTeacherNames().stream()
                                  .map(tw -> tw.split("_")[1])
                                  .collect(Collectors.toList()));
                          course.setTeacherNames(
                              course.getTeacherNames().stream()
                                  .map(tw -> tw.split("_")[0])
                                  .collect(Collectors.toList()));
                        }
                        Map<String, String> teacherMap =
                            getTeacherWorkAndUuidMap(course.getTeacherWorks());
                        course.setTeacherUuids(new ArrayList<>(teacherMap.values()));
                        teachMergeParams.add(course);
                      });
            });

    return teachMergeParams;
  }

merge:reduce规约操作要执行的函数
注:当传给reduce的stream流里面只有一个元素时,要额外处理
if (CollectionUtils.size(transFer) == 1)
具体情况具体分析

/**
   * merge操作里要实现把同一课程的教师工号放到一个列表里
   *
   * @param dto
   * @return
   */
  public CourseExcelParam merge(CourseExcelParam dto) {
    List<String> targetWorks = new ArrayList<>();
    List<String> targetNames = new ArrayList<>();
    if (CollectionUtils.isEmpty(this.teacherWorks)) {
      this.teacherWorks =
          this.getTeacherNames().stream().map(tw -> tw.split("_")[1]).collect(Collectors.toList());
      this.teacherNames =
          this.getTeacherNames().stream().map(tw -> tw.split("_")[0]).collect(Collectors.toList());
    }
    if (CollectionUtils.isNotEmpty(dto.getTeacherNames())) {
      List<String> dtoWorks =
          dto.getTeacherNames().stream().map(tw -> tw.split("_")[1]).collect(Collectors.toList());
      List<String> dtoNames =
          dto.getTeacherNames().stream().map(tw -> tw.split("_")[0]).collect(Collectors.toList());
      targetWorks.addAll(this.teacherWorks);
      targetWorks.addAll(dtoWorks);

      targetNames.addAll(this.teacherNames);
      targetNames.addAll(dtoNames);
      this.teacherNames = targetNames;
      this.teacherWorks = targetWorks;
    }

    return this;
  }

最终得到的整合结果:

[
    {
        "courseName":"测试课程1",
        "courseCode":"cskc1",
        "courseType":"学科",
        "courseObj":"成年人",
        "teacherNames":["测试教师1","测试教师2","测试教师3"],
        "teacherWorks":["teac1","teac2","teac3"]
    },
    {
        "courseName":"测试课程2",
        "courseCode":"cskc2",
        "courseType":"学科",
        "courseObj":"成年人",
        "teacherNames":["测试教师2","测试教师5"],
        "teacherWorks":["teac2","teac5"]
    }
]

此处,groupingBy在使用时,以p.getCourseCode() + p.getCourseName()为键,list为值;
形如下面的key-value:

"测试课程1cskc1":[
        {
            "courseName":"测试课程1",
            "courseCode":"cskc1",
            "courseType":"学科",
            "courseObj":"成年人",
            "teacherNames":["测试教师1"]
        },
        {
            "courseName":"测试课程1",
            "courseCode":"cskc1",
            "courseType":"学科",
            "courseObj":"成年人",
            "teacherNames":["测试教师2"]
        },
        {
            "courseName":"测试课程1",
            "courseCode":"cskc1",
            "courseType":"学科",
            "courseObj":"成年人",
            "teacherNames":["测试教师3"]
        },
        {
            "courseName":"测试课程2",
            "courseCode":"cskc2",
            "courseType":"学科",
            "courseObj":"成年人",
            "teacherNames":["测试教师5"]
        }
    ],
    "测试课程2cskc2":[
        {
            "courseName":"测试课程2",
            "courseCode":"cskc2",
            "courseType":"学科",
            "courseObj":"成年人",
            "teacherNames":["测试教师2"]
        },
        {
            "courseName":"测试课程2",
            "courseCode":"cskc2",
            "courseType":"学科",
            "courseObj":"成年人",
            "teacherNames":["测试教师5"]
        }
    ]
  1. 课程名称不同,课程编号同时,则必定为"错误数据,可能就是课程编号输入重复的数据"
  2. 课程名称相同,课程编号同时,指的就是同一个课程
  3. 课程名称相同,课程编号不同时,指的就是同名不同课程
  4. 课程名称不同,课程编号不同时,指的也是不同课程

再结合业务要求,一个课程配有多个教师时,是新起一行,除教师外,其它信息保持一致;所以他要处理的是同一课程的教师信息的合并操作;结合上述分析的四种情况,只有第2种情况符合要求;所以groupingBy在使用时,要以p.getCourseCode() + p.getCourseName()为键;
此外,第一种情况,也是需要处理的;处理的情景如下:

courseMergeParam = [
    {
        "courseName":"测试课程1",
        "courseCode":"cskc1",
        "courseType":"学科",
        "courseObj":"成年人",
        "teacherNames":["测试教师1","测试教师2","测试教师3"],
        "teacherWorks":["teac1","teac2","teac3"]
    },
    {
        "courseName":"测试课程2",
        "courseCode":"cskc2",
        "courseType":"学科",
        "courseObj":"成年人",
        "teacherNames":["测试教师2","测试教师5"],
        "teacherWorks":["teac2","teac5"]
    },
    {
        "courseName":"测试课程3",
        "courseCode":"cskc2",
        "courseType":"学科",
        "courseObj":"成年人",
        "teacherNames":["测试教师2","测试教师5"],
        "teacherWorks":["teac2","teac5"]
    }
]

测试课程2和测试课程3这两个课程,不同名但同课程编号;意味着课程编号填写重复。
此时,再针对courseMergeParamcourseCode的重复性校验处理

<think>嗯,用户想了解如何用Spring Boot和DeepSeek实现自动检测表格中的逻辑矛盾、重复录入、字段缺失等问题,并生成修正清单。首先,我得确认DeepSeek在这里指的是哪个工具。可能是指深度求索公司的AI模型,或者是某个数据处理工具?假设是AI模型的话,可能需要结合其自然语言处理或数据分析能力。 接下来,用户的需求是处理表格数据中的常见问题。需要分析这些问题的类型:逻辑矛盾比如数不符合业务规则,重复录入可能是同一数据多次出现,字段缺失则是必填项为空。解决方案的大致程应该是数据读取、规则定义、问题检测、AI辅助分析、生成报告。 然后,技术选型方面,Spring Boot作为后端框架,处理HTTP请求、数据管理和业务逻辑。DeepSeek的API可能用于增强数据分析,比如检测复杂模式或提供修正建议。数据库可能用MySQL或PostgreSQL存储数据和检测结果。 具体实现步骤可能需要分模块:数据导入、规则引擎、检测逻辑、AI集成、报告生成。比如,数据导入部分用Apache POI或EasyExcel处理Excel文件。规则引擎可以用Drools或者自定义注解和反射机制来定义校验规则。检测逻辑部分,每个问题类型有不同的检测方法,比如重复录入可能需要哈希表记录已出现的数据。AI部分调用DeepSeek的API,传入问题数据获取建议。最后,生成修正清单,可能用模板引擎生成Excel或PDF。 还需要考虑异常处理,比如文件格式错误或API调用失败。性能方面,大数据量时的处理效率,可能用批处理或异步处理。扩展性方面,规则可配置,方便后期增加新规则。 可能用户的实际场景是处理企业报表或数据录入系统,需要自动化检测数据质量,减少人工检查。用户可能希望有一个用户界面上传文件并查看报告,或者集成到现有系统中。需要确认是否需要实时处理还是批量处理,数据规模有多大,以及是否需要持久化存储检测结果。 此外,安全性和权限控制也是需要考虑的,比如文件上传的安全扫描,API调用的权限认证。用户可能还希望有日志记录功能,方便追踪检测过程和结果。 总之,整个方案需要结合Spring Boot的后端能力和DeepSeek的AI分析,通过规则引擎和自定义检测逻辑,实现高效准确的数据问题检测修正建议生成。</think>使用 **Spring Boot + DeepSeek** 实现表格数据的自动检测修正功能,可以通过以下技术方案实现: --- ### 一、核心目标 1. **问题检测** - 逻辑矛盾(如年龄为负数、日期冲突) - 重复录入(如相同ID多次出现) - 字段缺失(如必填字段为空) 2. **修正建议** - 通过规则引擎或AI模型(DeepSeek)生成修正建议。 3. **生成报告** - 输出包含问题详情和修复方案的清单(Excel/PDF)。 --- ### 二、技术选型 | 组件 | 用途 | |----------------|-----------------------------| | **Spring Boot** | 后端框架,提供REST API、数据管理、逻辑控制 | | **DeepSeek API**| 调用AI模型分析复杂逻辑矛盾,生成自然语言建议 | | **规则引擎** | 基于Drools或自定义注解实现校验规则 | | **Apache POI** | 解析Excel/CSV表格文件 | | **MySQL** | 存储检测记录和修正历史 | --- ### 三、实现步骤 #### 1. **数据导入模块** ```java // 使用Apache POI读取Excel文件 public List<DataRow> readExcel(MultipartFile file) { Workbook workbook = new XSSFWorkbook(file.getInputStream()); Sheet sheet = workbook.getSheetAt(0); List<DataRow> rows = new ArrayList<>(); for (Row row : sheet) { DataRow dataRow = new DataRow( row.getCell(0).getStringCellValue(), // ID row.getCell(1).getNumericCellValue() // 年龄 ); rows.add(dataRow); } return rows; } ``` #### 2. **规则引擎定义** ```java // 自定义注解标记校验规则 @Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) public @interface CheckRule { String ruleType(); // 如 "NOT_NULL", "RANGE" String message() default ""; } // 实体类字段校验示例 public class DataRow { @CheckRule(ruleType = "NOT_NULL", message = "ID不能为空") private String id; @CheckRule(ruleType = "RANGE", message = "年龄需在1-150之间") private int age; } ``` #### 3. **问题检测逻辑** - **重复录入检测** ```java public Map<String, List<DataRow>> findDuplicates(List<DataRow> rows) { return rows.stream() .collect(Collectors.groupingBy(DataRow::getId)) .entrySet().stream() .filter(e -> e.getValue().size() > 1) .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); } ``` - **逻辑矛盾检测(结合DeepSeek API)** ```java public String checkLogicWithAI(DataRow row) { String prompt = "检测数据逻辑矛盾:年龄=" + row.getAge() + ",职业=" + row.getJob(); DeepSeekResponse response = deepSeekClient.callAPI(prompt); return response.getSuggestions(); } ``` #### 4. **生成修正清单** ```java // 生成Excel格式的修正报告 public void generateReport(List<Issue> issues) { Workbook workbook = new XSSFWorkbook(); Sheet sheet = workbook.createSheet("问题清单"); int rowNum = 0; for (Issue issue : issues) { Row row = sheet.createRow(rowNum++); row.createCell(0).setCellValue(issue.getField()); row.createCell(1).setCellValue(issue.getDescription()); row.createCell(2).setCellValue(issue.getSuggestion()); } FileOutputStream output = new FileOutputStream("修正清单.xlsx"); workbook.write(output); } ``` #### 5. **API接口设计** ```java @RestController @RequestMapping("/api/data") public class DataController { @PostMapping("/upload") public ResponseEntity<String> uploadFile(@RequestParam("file") MultipartFile file) { List<DataRow> rows = dataService.readExcel(file); List<Issue> issues = dataService.detectIssues(rows); dataService.generateReport(issues); return ResponseEntity.ok("检测完成,已生成修正清单"); } } ``` --- ### 四、扩展优化方向 1. **性能优化** - 使用多线程处理大规模数据(如`CompletableFuture`)。 2. **动态规则配置** - 通过数据库管理校验规则,支持实时更新。 3. **AI深度集成** - 利用DeepSeek微调模型,针对业务场景优化建议生成。 4. **可视化界面** - 集成前端框架(如Vue),提供文件上传和报告预览功能。 --- ### 五、典型应用场景 - **企业数据清洗**:自动处理ERP、CRM系统中的脏数据。 - **金融合规检查**:检测财务报表中的逻辑错误。 - **科研数据处理**:校验实验数据的完整性。 通过结合规则引擎的效率和AI模型的灵活性,可显著提升数据质量管理的自动化水平。
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值