Reputation: 605
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.
Create a map like Map<Integer, List<Record>>
that has a key is id and slimier key add as a list.I have done it as bellow.
Map<Integer, List<Record>> map = list.stream()
.collect(Collectors.groupingBy(Record::getId, HashMap::new, Collectors.toList()));
Now i want to sort the list by name and sub list to provided limit inside map
map.forEach((k, v) -> v.stream().sorted(Comparator.comparing(Record::getName)));
map.forEach((k, v) -> map.put(k, v.subList(0, Math.min(**limit**, v.size()))));
I have tried like above and looks like this is not a good way. Can anyone suggest a better way?
Upvotes: 16
Views: 2222
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
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
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
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
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