Prabhath
Prabhath

Reputation: 605

Sorting and sub listing from lambda expression

I have an array list with the following elements:

List<Record> list = new ArrayList<>();
list.add(new Record(3, "32"));
list.add(new Record(4, "42"));
list.add(new Record(1, "12"));
list.add(new Record(1, "11"));
list.add(new Record(2, "22"));
list.add(new Record(5, "52"));
list.add(new Record(5, "53"));
list.add(new Record(5, "51"));

Record is a simple POJO that has id and name

I want to do those to the list.

I have tried like above and looks like this is not a good way. Can anyone suggest a better way?

Upvotes: 16

Views: 2222

Answers (5)

Oleksandr Pyrohov
Oleksandr Pyrohov

Reputation: 16276

You can use Java 8 Collectors.collectingAndThen() method:

Map<Integer, List<Record>> map = list.stream()
    .collect(Collectors.groupingBy(
        Record::getId,
        Collectors.collectingAndThen(
            Collectors.toList(),
            records -> records.stream()
                              .sorted(Comparator.comparing(Record::getName))
                              .limit(limit)
                              .collect(Collectors.toList()))));

Upvotes: 11

Eugene
Eugene

Reputation: 121048

list.stream()
    .collect(Collectors.groupingBy(
          Record::getId,
          Collectors.collectingAndThen(
                   Collectors.toList(),
                   x -> x.stream()
                         .sorted(Comparator.comparing(Record::getName))
                         .limit(limit)
                         .collect(Collectors.toList())))); 

Upvotes: 4

Holger
Holger

Reputation: 298549

You can use

Map<Integer, List<Record>> map = list.stream()
    .collect(Collectors.groupingBy(Record::getId,Collectors.toCollection(ArrayList::new)));
map.values().forEach(l -> {
    list.sort(Comparator.comparing(Record::getName));
    l.subList(limit, l.size()).clear();
});

Using Collectors.toCollection(ArrayList::new) we ensure that the result list will be mutable. Then we sort the list in-place and remove the unnecessary values. Instead of building a sublist containing the elements we want (which would keep a reference to the complete list), we build a sublist of the element we do not want and clear() it, to remove those element from the original list efficiently.

You may also write it as a single statement:

    Map<Integer, List<Record>> map = list.stream()
        .collect(Collectors.groupingBy(Record::getId,
            Collectors.collectingAndThen(
                Collectors.toCollection(ArrayList::new),
                l -> {
                    list.sort(Comparator.comparing(Record::getName));
                    l.subList(limit, l.size()).clear();
                    l.trimToSize();
                    return l;
                })));

as a bonus, I also added l.trimToSize(); which directs the ArrayList to use a smaller array if the preceding .subList(limit, l.size()).clear() removed a lot of elements. Since this may imply a copy operation, it’s a trade-off between CPU time and memory here. So if the result is only use for a rather short time afterwards, you would not use trimToSize().


The operation becomes even simpler (and potentially more efficient) when you use StreamEx:

Map<Integer, List<Record>> map = list.stream()
    .collect(Collectors.groupingBy(Record::getId,
             MoreCollectors.least(Comparator.comparing(Record::getName), limit)));

Upvotes: 5

fps
fps

Reputation: 34470

You could use Collectors.collectingAndThen:

Map<Integer, List<Record>> result = list.stream()
    .collect(Collectors.groupingBy(
         Record::getId,
         Collectors.collectingAndThen(
             Collectors.toCollection(ArrayList::new),
             v -> {
                 v.sort(Comparator.comparing(Record::getName));
                 return v.subList(0, Math.min(LIMIT, v.size()));
             })));

This solution avoids creating a new stream for each list group.

As pointed out in this answer, by using Collectors.toCollection(ArrayList::new) we ensure that the list is mutable, so that we can later sort it in place.

Upvotes: 9

tobias_k
tobias_k

Reputation: 82949

You could just sort before collecting the items in a Map. For the limiting bit, you can use collectingAndThen to post-process the list and stream.limit it.

Map<Integer, List<Record>> map = list.stream()
        .sorted(Comparator.comparing(Record::getName))
        .collect(Collectors.groupingBy(Record::getId, 
                Collectors.collectingAndThen(Collectors.toList(), 
                        l -> l.stream().limit(limit).collect(Collectors.toList()))));

With limit = 2, this results in

{1=[Record(id=1, name=11), Record(id=1, name=12)], 
 2=[Record(id=2, name=22)], 
 3=[Record(id=3, name=32)], 
 4=[Record(id=4, name=42)], 
 5=[Record(id=5, name=51), Record(id=5, name=52)]}

Upvotes: 1

Related Questions