给出以下课程(简化问题):
public static class Match {
private final String type;
private final int score;
public Match(String type, int score) {
this.type = type;
this.score = score;
}
public String getType() {
return type;
}
public int getScore() {
return score;
}
}
我有一个Stream<Match>
,其中包含该类的多个实例,相同类型多次出现,但分数不同:
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));
我现在想要收集流,结果是List<Match>
只包含每种类型得分最高的实例。
以下代码正在运行,但我不确定它是否是“最佳”解决方案(除了可怕的阅读和格式化):
.collect(Collectors.collectingAndThen(
Collectors.groupingBy(Match::getType, Collectors.collectingAndThen(
Collectors.toList(),
l -> l.stream().max(Comparator.comparing(Match::getScore)).get())), Map::values))
.forEach(m -> System.out.println(m.getType() + ": " + m.getScore()));
和
.collect(Collectors.collectingAndThen(
Collectors.groupingBy(Match::getType, Collectors.maxBy(Comparator.comparing(Match::getScore))), Map::values))
.forEach(m -> m.ifPresent(ma -> System.out.println(ma.getType() + ": " + ma.getScore())));
输出(正确):
A:10
B:12
C:1
此外,我无法提取返回收集器的通用静态方法,以便我可以在以下方式使用它:
.collect(distinctMaxByProperty(Match::getType, Match::getScore)
非常感谢任何帮助!
答案 0 :(得分:5)
当你可以在第一时间收集最大元素时,不要收集到List
,只提取一个值,例如
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)));
但是当你遇到在Optional
的上下文中提取groupingBy
的必要性时,值得检查带有合并函数的toMap`是否可以给出更简单的结果:
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))));
获得Map
后,您可以通过
result.values().forEach(m -> System.out.println(m.getType() + ": " + m.getScore()));
但是如果你不需要实际的Match
个实例,你可以做得更简单:
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));
答案 1 :(得分:3)
我有两种方法。
可能的实施方法是使用Collectors.toMap()
来解决您的问题。
stream.collect(Collectors.toMap(Match::getType, Match::getScore, Math::max));
如果您希望获得List<Match>()
,则可以重新映射结果
stream
.collect(Collectors.toMap(Match::getType, Match::getScore, Math::max))
.entrySet()
.stream()
.map(e -> new Match(e.getKey(), e.getValue()))
.collect(Collectors.toList());
正如您在问题中所说,可以在自定义收集器中提取此逻辑。你可以这样做:
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();
}
}
并像这样使用它:
stream.collect(new MaxMatch());
希望这可以帮助你:)
答案 2 :(得分:1)
尝试使用TreeMap
:
<强>更新强>
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());
如果您的Match
类实现Comparable
。你可以简化这个:
() -> new TreeSet<>(Comparator.comparing(Match::getScore))
到此:
TreeSet::new