草庐IT

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

Congee_porridge 2023-09-24 原文

文章目录


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的重复性校验处理

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

  1. ruby - 如何从 ruby​​ 中的字符串运行任意对象方法? - 2

    总的来说,我对ruby​​还比较陌生,我正在为我正在创建的对象编写一些rspec测试用例。许多测试用例都非常基础,我只是想确保正确填充和返回值。我想知道是否有办法使用循环结构来执行此操作。不必为我要测试的每个方法都设置一个assertEquals。例如:describeitem,"TestingtheItem"doit"willhaveanullvaluetostart"doitem=Item.new#HereIcoulddotheitem.name.shouldbe_nil#thenIcoulddoitem.category.shouldbe_nilendend但我想要一些方法来使用

  2. ruby-on-rails - 按天对 Mongoid 对象进行分组 - 2

    在控制台中反复尝试之后,我想到了这种方法,可以按发生日期对类似activerecord的(Mongoid)对象进行分组。我不确定这是完成此任务的最佳方法,但它确实有效。有没有人有更好的建议,或者这是一个很好的方法?#eventsisanarrayofactiverecord-likeobjectsthatincludeatimeattributeevents.map{|event|#converteventsarrayintoanarrayofhasheswiththedayofthemonthandtheevent{:number=>event.time.day,:event=>ev

  3. ruby-on-rails - 如何验证非模型(甚至非对象)字段 - 2

    我有一个表单,其中有很多字段取自数组(而不是模型或对象)。我如何验证这些字段的存在?solve_problem_pathdo|f|%>... 最佳答案 创建一个简单的类来包装请求参数并使用ActiveModel::Validations。#definedsomewhere,atthesimplest:require'ostruct'classSolvetrue#youcouldevencheckthesolutionwithavalidatorvalidatedoerrors.add(:base,"WRONG!!!")unlesss

  4. ruby-on-rails - form_for 中不在模型中的自定义字段 - 2

    我想向我的Controller传递一个参数,它是一个简单的复选框,但我不知道如何在模型的form_for中引入它,这是我的观点:{:id=>'go_finance'}do|f|%>Transferirde:para:Entrada:"input",:placeholder=>"Quantofoiganho?"%>Saída:"output",:placeholder=>"Quantofoigasto?"%>Nota:我想做一个额外的复选框,但我该怎么做,模型中没有一个对象,而是一个要检查的对象,以便在Controller中创建一个ifelse,如果没有检查,请帮助我,非常感谢,谢谢

  5. Ruby 写入和读取对象到文件 - 2

    好的,所以我的目标是轻松地将一些数据保存到磁盘以备后用。您如何简单地写入然后读取一个对象?所以如果我有一个简单的类classCattr_accessor:a,:bdefinitialize(a,b)@a,@b=a,bendend所以如果我从中非常快地制作一个objobj=C.new("foo","bar")#justgaveitsomerandomvalues然后我可以把它变成一个kindaidstring=obj.to_s#whichreturns""我终于可以将此字符串打印到文件或其他内容中。我的问题是,我该如何再次将这个id变回一个对象?我知道我可以自己挑选信息并制作一个接受该信

  6. ruby-on-rails - 如果 Object::try 被发送到一个 nil 对象,为什么它会起作用? - 2

    如果您尝试在Ruby中的nil对象上调用方法,则会出现NoMethodError异常并显示消息:"undefinedmethod‘...’fornil:NilClass"然而,有一个tryRails中的方法,如果它被发送到一个nil对象,它只返回nil:require'rubygems'require'active_support/all'nil.try(:nonexisting_method)#noNoMethodErrorexceptionanymore那么try如何在内部工作以防止该异常? 最佳答案 像Ruby中的所有其他对象

  7. ruby-on-rails - 未在 Ruby 中初始化的对象 - 2

    我在Rails工作并有以下类(class):classPlayer当我运行时bundleexecrailsconsole然后尝试:a=Player.new("me",5.0,"UCLA")我回来了:=>#我不知道为什么Player对象不会在这里初始化。关于可能导致此问题的操作/解释的任何建议?谢谢,马里奥格 最佳答案 havenoideawhythePlayerobjectwouldn'tbeinitializedhere它没有初始化很简单,因为你还没有初始化它!您已经覆盖了ActiveRecord::Base初始化方法,但您没有调

  8. java - 等价于 Java 中的 Ruby Hash - 2

    我真的很习惯使用Ruby编写以下代码:my_hash={}my_hash['test']=1Java中对应的数据结构是什么? 最佳答案 HashMapmap=newHashMap();map.put("test",1);我假设? 关于java-等价于Java中的RubyHash,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.com/questions/22737685/

  9. ruby - 如何在 Rails 4 中使用表单对象之前的验证回调? - 2

    我有一个服务模型/表及其注册表。在表单中,我几乎拥有服务的所有字段,但我想在验证服务对象之前自动设置其中一些值。示例:--服务Controller#创建Action:defcreate@service=Service.new@service_form=ServiceFormObject.new(@service)@service_form.validate(params[:service_form_object])and@service_form.saverespond_with(@service_form,location:admin_services_path)end在验证@ser

  10. ruby-on-rails - 在 Rails 和 ActiveRecord 中查询时忽略某些字段 - 2

    我知道我可以指定某些字段来使用pluck查询数据库。ids=Item.where('due_at但是我想知道,是否有一种方法可以指定我想避免从数据库查询的某些字段。某种反拔?posts=Post.where(published:true).do_not_lookup(:enormous_field) 最佳答案 Model#attribute_names应该返回列/属性数组。您可以排除其中一些并传递给pluck或select方法。像这样:posts=Post.where(published:true).select(Post.attr

随机推荐