Stream - Collect by property and max

前端 未结 3 990
没有蜡笔的小新
没有蜡笔的小新 2021-01-18 09:15

Problem Statement

Given the following class (simplified for the question):

public static class Match {

  private final String type;
  private fina         


        
相关标签:
3条回答
  • 2021-01-18 10:09

    I have two methods for you.

    First Method: Collectors.toMap()

    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());
    

    Second Method: Custom Collector

    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 :)

    0 讨论(0)
  • 2021-01-18 10:11

    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
    
    0 讨论(0)
  • 2021-01-18 10:14

    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));
    
    0 讨论(0)
提交回复
热议问题