'filter objects based on an ID and Date Java

I have a class that for which each instance has a unique Id, a name and update date.

ID NAME UPDATE DATE
1200 ANNA* 2022-03-01
1300 JONH* 2022-04-01
1200 ANNA* 2022-04-01
1400 CARL 2022-01-01
1400 CARL 2022-02-01
1300 JONH* 2022-02-01
1500 MARY 2022-03-01
1500 MARY 2022-04-01

I want only one register from ANNA and JOHN using the most recent date, like this:

ID NAME UPDATE DATE
1300 JONH* 2022-04-01
1200 ANNA* 2022-04-01
1400 CARL 2022-01-01
1400 CARL 2022-02-01
1500 MARY 2022-03-01
1500 MARY 2022-04-01

I try this way:

List<Document> filteredDocuments = DocumentData
            .stream()
            .filter(f -> f.getId() == 1200 || f.getId() == 1300)
            .collect(groupingBy(g -> g.getId(),
                    collectingAndThen(maxBy(Comparator.comparing(c -> c.getUpdateDate())), Optional::get)))
            .values().stream().collect(toList());

but this filter all the ID and this is not what I want. I need that the others register don't be filtered.

How filter this using the Java 8 Stream API?



Solution 1:[1]

Here is one approach.

  • create a Set of targetIds
  • create a map with the Id as key - use groupingBy to group by Id
  • Use collectingAndThen to post process the lists
    • for each list
      • if the set contains the id of a list member,
      • sort the list in descending order and return the first one in a new List
      • else return the original list
    • then post process the map by collecting the map values into a List of users.

Data declaration

List<User> users =
        List.of(new User(1200, "ANNA", "2022-03-01"),
                new User(1300, "JOHN", "2022-04-01"),
                new User(1200, "ANNA", "2022-04-01"),
                new User(1400, "CARL", "2022-01-01"),
                new User(1400, "CARL", "2022-02-01"),
                new User(1300, "JOHN", "2022-02-01"),
                new User(1500, "MARY", "2022-03-01"),
                new User(1500, "MARY", "2022-04-01"));

Set<Integer> targetIds = Set.of(1200, 1300);

The solution from list to map to list

List<User> result = users.stream()
        .collect(Collectors.groupingBy(User::getId,
                Collectors.collectingAndThen(
                        Collectors.toList(),
                        lst -> {
                            if (targetIds.contains(lst.get(0).getId())) {       
                                 lst.sort(Comparator.comparing(User::getDate)
                                         .reversed());
                                 return List.of(lst.get(0));
                            }
                            return lst;
                        })))
        .values().stream().flatMap(List::stream).toList();

result.forEach(System.out::println);

prints

[1200, ANNA, 2022-04-01]
[1300, JOHN, 2022-04-01]
[1400, CARL, 2022-01-01]
[1400, CARL, 2022-02-01]
[1500, MARY, 2022-03-01]
[1500, MARY, 2022-04-01]

User class definition

class User {
    int id;
    String name;
    LocalDate date;
    
    public User(int id, String name, String date) {
        this.id = id;
        this.name = name;
        this.date = LocalDate.parse(date);
    }
    
    public int getId() {
        return id;
    }
    
    public String getName() {
        return name;
    }
    
    public LocalDate getDate() {
        return date;
    }
    
    @Override
    public String toString() {
        return String.format("[%s, %s, %s]", id, name, date);
    }   
}

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 WJS