如何过滤复杂对象的列表,以便如果两个具有字段值,我会根据条件删除一个

Posted

技术标签:

【中文标题】如何过滤复杂对象的列表,以便如果两个具有字段值,我会根据条件删除一个【英文标题】:How can I filter a List of complex objects so that if two have value for a field, I remove one based on criteria 【发布时间】:2021-12-14 00:47:46 【问题描述】:

我有一个对象列表,其中一些对象可能与基于某个字段的其他对象相似。在这种情况下,我想根据另一个字段的值过滤一个。

这是我进来的一个例子:

[
    
        employeeId: 1230,
        firstName: "Ronald",
        lastName: "McDonald",
        device: "Laptop"
    ,
    
        employeeId: 2345,
        firstName: "Dennis",
        lastName: "Reynolds",
        device: "Laptop"
    ,
    
        employeeId: 1230,
        firstName: "Ronald",
        lastName: "McDonald",
        device: "Phone"
    
]

我为 Ronald McDonald(员工 1230)准备了 2 个对象。我想要所有员工的笔记本电脑,除非他们有电话,在这种情况下我只想要电话。所以我想用device= "Laptop"过滤掉Ronald McDonald对象,这样我的输出如下:

[
    
        employeeId: 1230,
        firstName: "Ronald",
        lastName: "McDonald",
        device: "Phone"
    ,
    
        employeeId: 2345,
        firstName: "Dennis",
        lastName: "Reynolds",
        device: "Laptop"
    
]

过滤很简单,但无论出于何种原因,我都无法根据条件删除重复项。

是否有任何乐于助人的人可以解决我的情况?

我使用的是 Java 11,因此我可以访问 Lambda 函数、流以及所有这些好东西。

编辑:我的第一次破解非常糟糕。我觉得我分组没问题,但我似乎无法在该组中过滤:

List<Employee> reducedEmpList = empList.stream()
  .collect(Collectors.groupingBy(Employee::getEmployeeId,

     // Obviously this is not right:
     Collectors.maxBy(Comparator.comparing(Employee::getDevice, (e1, e2) -> 
    // Clearly this is not how a comparator works
    if("Laptop".equalsIgnoreCase(e1.getDevice())
        return e2;
      
))));

【问题讨论】:

【参考方案1】:

当有多个设备时,您可以使用Collectors.toMapmergeFunction 选择带有“Phone”的设备:

Collection<Employee> filtered = employees.stream().collect(Collectors
    .toMap(e -> e.getEmployeeId(), e -> e,
    (a, b) -> "Phone".equals(a.getDevice())? a : b,
    LinkedHashMap::new)).values();
employees = new ArrayList<>(filtered);

我将LinkedHashMap 指定为中间存储,以便任何其他员工(如丹尼斯)将留在他们的相对位置,如果这很重要的话。如果没有,您可以只使用toMap 的 3 参数重载并将其关闭。

注意:这假设如果给定员工有多个条目,则其中一个始终具有“电话”设备。

【讨论】:

【参考方案2】:

遍历所有员工,如果设备是Phone,则搜索该员工的列表并添加到列表entriesToRemove,如果该员工存在另一个条目并且设备是Laptop

    List<Employee> entriesToRemove = new ArrayList<>();
      for(Employee employee : list)
         if(employee.getDevice().equals("Phone"))
            entriesToRemove.addAll(list.stream().filter(e ->
            e.getEmployeeId() == employee.getEmployeeId() && e.getDevice().equals("Laptop"))
            .collect(Collectors.toList()));
         
    
                
  

最后用removeAll从列表中删除条目:

list.removeAll(entriesToRemove);
System.out.printf(list.toString());

编辑:使用 Stream API 的解决方案:

List<Employee> reducedEmpList = list.stream()
   .collect(Collectors.groupingBy(Employee::getEmployeeId,
   Collectors.collectingAndThen(toList(), listPerEmployee ->
   listPerEmployee.stream().filter(e -> e.getDevice().equals("Phone")).findFirst()
     .map(Collections::singletonList).orElse(listPerEmployee))))
     .values().stream().flatMap(Collection::stream).collect(toList());

输出:

[EmployeeemployeeId=2345, device='Laptop', EmployeeemployeeId=1230, device='Phone']

【讨论】:

首先感谢您的回复!这绝对是一个有效的解决方案。但是,员工列表通常有数千人,所以我不认为使用 for 循环并在每次迭代中过滤整个列表是一种有效的解决方案。 请看一下我用 Stream API 更新的答案

以上是关于如何过滤复杂对象的列表,以便如果两个具有字段值,我会根据条件删除一个的主要内容,如果未能解决你的问题,请参考以下文章

如何猫鼬列表() - 按子文档字段值过滤值

使用多个条件过滤对象,包括比较两个对象字段

如何向 GET 请求发送巨大的参数列表

如果行包含列中列表中的两个值,如何过滤数据框

如何在暴露过滤器中更改另一个选择列表时更改选择列表的值

具有重复键的对象列表将字段组合到逗号列表中