Given the following class (simplified for the question):
public static class Match {
private final String type;
private fina
I have two methods for you.
A possible implementation is to use Collectors.toMap()
to answers your problem.
stream.collect(Collectors.toMap(Match::getType, Match::getScore, Math::max));
And if you prefer to get a List<Match>()
, you can remap the result
stream
.collect(Collectors.toMap(Match::getType, Match::getScore, Math::max))
.entrySet()
.stream()
.map(e -> new Match(e.getKey(), e.getValue()))
.collect(Collectors.toList());
Like you say in your question, it is possible to extract this logic in a custom collector. You can do it like this:
public class MaxMatch implements Collector<Match, Map<String, Integer>, List<Match>> {
@Override
public Supplier<Map<String, Integer>> supplier() {
return HashMap::new;
}
@Override
public BiConsumer<Map<String, Integer>, Match> accumulator() {
return (map, match) -> {
Integer score = match.getScore();
if(map.containsKey(match.getType())) {
score = Math.max(score, map.get(match.getType()));
}
map.put(match.getType(), score);
};
}
@Override
public BinaryOperator<Map<String, Integer>> combiner() {
return (mapA, mapB) -> {
mapA.forEach((k, v) -> {
if(mapB.containsKey(k)) { mapB.put(k, Math.max(v, mapB.get(k))); }
else { mapB.put(k, v); }
});
return mapB;
};
}
@Override
public Function<Map<String, Integer>, List<Match>> finisher() {
return (map) -> map.entrySet().stream().map(e -> new Match(e.getKey(), e.getValue())).collect(Collectors.toList());
}
@Override
public Set<Characteristics> characteristics() {
return Collections.emptySet();
}
}
and use it like this:
stream.collect(new MaxMatch());
Hope this help you :)
Try to use a TreeMap
for this :
UPDATE
List<Match> matchStream1 = matchStream.
collect(Collectors.groupingBy(Match::getType,
Collectors.toCollection(() -> new TreeSet<>(Comparator.comparing(Match::getScore)))))
.values()
.stream()
.map(TreeSet::last)
.collect(Collectors.toList());
In case your Match
class implements Comparable
. You can simplify this:
() -> new TreeSet<>(Comparator.comparing(Match::getScore))
to this:
TreeSet::new
Don’t collect into a List
, just to extract one value, when you can collect the maximum element in the first place, e.g.
Map<String,Match> result =
Stream.of(new Match("A", 1), new Match("A", 2), new Match("A", 4), new Match("A", 10),
new Match("B", 3), new Match("B", 6), new Match("B", 12), new Match("C", 1))
.collect(Collectors.groupingBy(Match::getType, Collectors.collectingAndThen(
Collectors.reducing(BinaryOperator.maxBy(
Comparator.comparingInt(Match::getScore))),
Optional::get)));
But whenever you encounter the necessity to extract an Optional
in the context of groupingBy
, it’s worth checking whether toMap` with merge function can give a simpler result:
Map<String,Match> result =
Stream.of(new Match("A", 1), new Match("A", 2), new Match("A", 4), new Match("A", 10),
new Match("B", 3), new Match("B", 6), new Match("B", 12), new Match("C", 1))
.collect(Collectors.toMap(Match::getType, Function.identity(),
BinaryOperator.maxBy(Comparator.comparingInt(Match::getScore))));
Once you have the Map
you can produce your desired output via
result.values().forEach(m -> System.out.println(m.getType() + ": " + m.getScore()));
But if you don’t need the actual Match
instances, you can do it even simpler:
Stream.of(new Match("A", 1), new Match("A", 2), new Match("A", 4), new Match("A", 10),
new Match("B", 3), new Match("B", 6), new Match("B", 12), new Match("C", 1))
.collect(Collectors.toMap(Match::getType, Match::getScore, Math::max))
.forEach((type,score) -> System.out.println(type + ": " + score));