【问题标题】:Combining two lists to make a unique list (based on certain field logic)将两个列表组合成一个唯一的列表(基于一定的字段逻辑)
【发布时间】:2020-09-25 14:08:08
【问题描述】:

我有一个特定的问题要解决,需要一些帮助。

我有一个包含三个字段的 POJO:

  • 身份证
  • 日期
  • 加权数

此 POJO 可以从 DB 或外部端点填充,并将返回所述 POJO 的列表。 现在我们最终得到了两个具有相似信息的单独列表。这些列表将无序且大小不一(有时为空)。

如何组合这两个列表以根据 ID 创建一个唯一列表?

  • 如果ID相同,选择日期最晚的一个
  • 如果 ID 和日期相同,则选择权重较高的那个
  • 如果所有内容都相同,那没关系 - 任选其一。

我的解决方案非常繁琐且难以阅读 - 它涉及两个嵌套循环和 3 对 if-else 语句。有没有更好、更有效的方法。

这里是一些示例代码:

import java.util.Arrays;
import java.util.Calendar;
import java.util.Date;
import java.util.List;

public class Scrap2 {

    public static void main(String[] args) {

        MyEntity one = new MyEntity("A", new Date(2020 - 1900, Calendar.SEPTEMBER, 25), 10);
        MyEntity two = new MyEntity("B", new Date(2020 - 1900, Calendar.SEPTEMBER, 25), 10);
        MyEntity three = new MyEntity("C", new Date(2020 - 1900, Calendar.SEPTEMBER, 25), 10);
        MyEntity four = new MyEntity("D", new Date(2020 - 1900, Calendar.SEPTEMBER, 25), 10);

        List<MyEntity> listOne = Arrays.asList(one, two, three, four);

        MyEntity aaa = new MyEntity("A", new Date(2020 - 1900, Calendar.SEPTEMBER, 25), 10);
        MyEntity bbb = new MyEntity("B", new Date(2020 - 1900, Calendar.OCTOBER, 25), 20);
        MyEntity ccc = new MyEntity("D", new Date(2020 - 1900, Calendar.SEPTEMBER, 25), 20);
        MyEntity ddd = new MyEntity("E", new Date(2020 - 1900, Calendar.SEPTEMBER, 25), 20);

        List<MyEntity> listTwo = Arrays.asList(aaa, bbb, ccc, ddd);

        for (MyEntity listItem : combineTwoLists(listOne, listTwo)) {
            System.out.println(listItem);
        }

    }

    private static List<MyEntity> combineTwoLists(List<MyEntity> listOne, List<MyEntity> listTwo) {
       
        return Arrays.asList(listOne.get(0), listTwo.get(1), listOne.get(2), listTwo.get(2), listTwo.get(3));
    }
}


class MyEntity {

    private final String id;
    private final Date date;
   private final Integer weightedNumber;

    public MyEntity(String id, Date date, Integer weightedNumber) {
        this.id = id;
        this.date = date;
        this.weightedNumber = weightedNumber;
    }

    @Override
    public String toString() {
        return "MyEntity{" +
                "id='" + id + '\'' +
                ", date=" + date +
                ", weightedNumber=" + weightedNumber +
                '}';
    }

    public String getId() {
        return id;
    }

    public Date getDate() {
        return date;
    }

    public Integer getWeightedNumber() {
        return weightedNumber;
    }
}

输出应该是这样的(可以是无序的/无序的):

MyEntity{id='A', date=Fri Sep 25 2020, weightedNumber=10} 
MyEntity{id='B', date=Sun Oct 25 2020, weightedNumber=20} 
MyEntity{id='C', date=Fri Sep 25 2020, weightedNumber=10} 
MyEntity{id='D', date=Fri Sep 25 2020, weightedNumber=20} 
MyEntity{id='E', date=Fri Sep 25 2020, weightedNumber=20}

【问题讨论】:

  • 两个列表的顺序和/或大小是否始终相同?
  • @JamieBisotti 不,顺序和大小可以不同。甚至可能存在一个列表可能为空的情况。

标签: java list lambda java-8


【解决方案1】:

您可以先加入两个列表,然后按日期获取最大值,然后使用 ComparatorBinaryOperator.maxBy 为同一 id 获取 weightedNumber,以使用 Collectors.toMap 创建地图。然后在新的 ArrayList 中获取地图的值。

return new ArrayList<MyEntity>(
    Stream.concat(listOne.stream(), listTwo.stream())
          .collect(Collectors.toMap(MyEntity::getId, Function.identity(),
                   BinaryOperator.maxBy(Comparator.comparing(MyEntity::getDate)
                                          .thenComparing(MyEntity::getWeightedNumber))))
          .values());

【讨论】:

  • 如果结果不需要排序并且可以是无序/无序的。您的解决方案将如何改变?
  • 如果不想排序,合并列表前的默认顺序是什么?
  • 没有默认顺序。这些列表将来自 DB 或 rest api - 它们的顺序可能会有很大差异。有时甚至是空的。
  • 我的意思是你想保持列表中的任何顺序吗?如果不是,为什么你不想排序?
  • 赞成使用 BinaryOperator.maxBy 这是一个鲜为人知的非常好的实用程序
【解决方案2】:

以下使用Collectors.groupingByCollectors.maxBy 的方法应该可以工作:

  1. 连接两个列表的流
  2. 使用groupingBy按ID对列表进行分组
  3. 然后使用maxBy 收集器按dateweightedNumber 分组。
  4. Map.Entry&lt;String, Optional&lt;MyEntity&gt;&gt; 的结果映射转换为List&lt;MyEntity&gt;
private static List<MyEntity> combineTwoLists(List<MyEntity> listOne, List<MyEntity> listTwo) {
    return Stream.concat(listOne.stream(), listTwo.stream())
                 .collect(Collectors.groupingBy(MyEntity::getId,
                          Collectors.maxBy(                                                     
                              Comparator.comparing(MyEntity::getDate)
                                        .thenComparing(MyEntity::getWeightedNumber))
                       )).entrySet() 
                 .stream()  // Stream<String, Optional<MyEntity>>
                 .map(Map.Entry::getValue) // take optional value 
                 .map(Optional::get)       // get from optional
                 .collect(Collectors.toList());
}

【讨论】:

    【解决方案3】:

    这是另一种方法,无需流,但功能强大:

    Map<String, MyEntity> map = new LinkedHashMap<>();
    Consumer<MyEntity> addAction = e -> map.merge(
            e.getId(), 
            e, 
            BinaryOperator.maxBy(Comparator.comparing(MyEntity::getDate)
                                           .thenComparing(MyEntity::getWeightedNumber)));
    listOne.forEach(addAction);
    listTwo.forEach(addAction);
    
    List<MyEntity> result = new ArrayList<>(map.values());
    

    这会迭代两个列表并在每个元素上执行addAction 消费者。 addAction 消费者通过应用Map.merge 方法由地图中的合并 实体组成。

    【讨论】:

      猜你喜欢
      • 2018-10-05
      • 2016-05-31
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-11-18
      • 1970-01-01
      • 2019-04-03
      • 1970-01-01
      相关资源
      最近更新 更多