筛选按属性区分并按日期排序的列表的好方法

时间:2018-12-05 16:30:01

标签: java arrays java-8 java-stream

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

[{
    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首先确认他正在参加,然后他改变了主意并告诉他没有参加,所以我返回了他的最后一个条目(请注意,有时是约翰,有时是约翰,但这是同一个人,这是一个棘手的部分)

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

任何人都知道什么是最好的方法?

4 个答案:

答案 0 :(得分:5)

您可以使用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
}

答案 1 :(得分:4)

您可以使用toMap收集器:

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

see this答案以解释有关toMap的工作原理

答案 2 :(得分:2)

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

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());

答案 3 :(得分:1)

一种没有流的紧凑方式:

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,如果没有相同键(小写的用户名)的条目,则将其放入地图中;如果地图已经包含该键的条目,则应用合并函数,在这种情况下,该函数会选择最大为User的{​​{1}}实例。