【问题标题】:Good way to filter list distincted by property and ordered by date按属性过滤列表并按日期排序的好方法
【发布时间】:2019-05-07 06:46:18
【问题描述】:

我有很简单的事情要做,我有这样的人名单:

[{
    name: John,
    date: 01-01-2018,
    attend: true
},
{
    name: Adam,
    date: 01-01-2018,
    attend: false
},
{
    name: Adam,
    date: 01-02-2018,
    attend: true
},
{
    name: JOHN,
    date: 01-02-2018,
    attend: false
}]

这个数组的结果应该是:Adam (true), John (false)

所以我需要返回用户的最新条目列表,在这种情况下,约翰首先确认他正在参加,然后他改变主意并告诉他没有参加所以我要返回他的最后一个条目(请注意,有时写的是 JOHN,有时写的是 John,但它是同一个人,这有点棘手)

我的问题是过滤掉这种列表的最佳方法是什么,我正在考虑应用“按属性 java 流唯一”,但首先需要按日期降序和名称(大写/小写)对人员进行排序,然后我需要以某种方式获取最新条目。

有人知道什么是最好的方法吗?

【问题讨论】:

  • 提示:使用标准 ISO 8601 格式格式化日期字符串:YYYY-MM-DD。这些格式专为数据交换而设计,避免了歧义,使机器易于解析,人类易于跨文化阅读。在解析/生成文本时,这些格式默认在 java.time 类中使用。
  • 是的,格式已经是这样了,但是我写的时候出错了,反正在这种情况下根本不重要,但是谢谢你的建议

标签: java arrays java-8 java-stream


【解决方案1】:

你可以使用Collectors.toMap来做同样的事情:

List<Person> finalList = new ArrayList<>(people.stream()
        .collect(Collectors.toMap(a -> a.getName().toLowerCase(),  // name in lowercase as the key of the map (uniqueness)
                Function.identity(), // corresponding Person as value
                (person, person2) -> person.getDate().isAfter(person2.getDate()) ? person : person2)) // merge in case of same name based on which date is after the other
        .values()); // fetch the values

注意:以上假设最小Person类为

class Person {
    String name;
    java.time.LocalDate date;
    boolean attend;
    // getters and setters
}

【讨论】:

    【解决方案2】:

    您可以使用toMap 收集器:

    Collection<Person> values = source.stream()
                        .collect(toMap(e -> e.getName().toLowerCase(),
                                Function.identity(),
                                BinaryOperator.maxBy(Comparator.comparing(Person::getDate))))
                        .values();
    

    see this 回答有关 toMap 如何工作的解释

    【讨论】:

      【解决方案3】:

      尽管到目前为止所有答案在功能上都是正确的,但请考虑以下选项:

      final Map<String, Boolean> lastAttendResults =
                  people
                      .stream()
                      .collect(
                          groupingBy(
                              Person::getName, // Define what is the property you are using to group people.
                              () -> new TreeMap<String, Boolean>(String.CASE_INSENSITIVE_ORDER), // Supply a map implementation that ignore name case.
                              collectingAndThen(maxBy(Comparator.comparing(Person::getDate)), // Foreach list of grouped people, select that with last date.
                                  o -> o.get().isAttend()))); // As maxBy returns an Optional<Person> and we are sure that it exists, just get the Person and if he attends.
      

      这个实现很有趣,因为它让集合分组的概念变得显而易见。虽然在深度上它也使用了地图,但在我看来,使用或不使用地图不是程序员的问题,我的意思是,我们正在寻找的是如何按名称对人员进行分组,然后获取最后一个条目。

      如果您希望收到人员名单而不是姓名和参加地图,您可以使用:

      final List<Person> lastAttendResults =
              new ArrayList<>(people
                  .stream()
                  .collect(
                      groupingBy(Person::getName, // Define what is the property you are using to group people.
                          () -> new TreeMap<String, Person>(String.CASE_INSENSITIVE_ORDER), // Supply a map implementation that ignore name case.
                          collectingAndThen(maxBy(Comparator.comparing(Person::getDate)), // Foreach list of grouped people, select that with last date.
                              Optional::get // As maxBy returns an Optional<Person> and we are sure that is exists, just get the Person.
                              ))).values());
      

      【讨论】:

      • 分组对默认语言环境没有隐藏依赖的唯一答案 - toLowerCase() 使用默认语言环境,这可能会产生令人惊讶的结果,而 CASE_INSENSITIVE_ORDER 不考虑语言环境。如有必要,也可以轻松地将其替换为 Collator
      【解决方案4】:

      没有流的紧凑方式:

      Map<String, User> map = new LinkedHashMap<>();
      users.forEach(u -> map.merge(
              u.getName().toLowerCase(), 
              u, 
              BinaryOperator.maxBy(Comparator.comparing(Person::getDate))));
      
      Collection<User> result = map.values();
      

      或者如果你确实需要List:

      List<User> result = new ArrayList<>(map.values());
      

      此代码使用Map.merge,如果没有具有相同键(小写用户名)的条目,则将条目放入映射中,或者,如果映射已经包含键的条目,则应用合并功能,在这种情况下,它选择最大 dateUser 实例。

      【讨论】:

        猜你喜欢
        • 2019-05-01
        • 2012-07-12
        • 2019-06-06
        • 2021-06-24
        • 1970-01-01
        • 1970-01-01
        • 2020-06-30
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多