I want to perform Group By processing with Stream (group-by-count, group-by-sum, group-by-max)

For example, suppose a user and the amount paid by that user are given in the following form:

public class Payment {
    
    public static void main(String[] args) {
        var payments = List.of(
            new Payment("A", 10),
            new Payment("B", 20),
            new Payment("B", 30),
            new Payment("C", 40),
            new Payment("C", 50),
            new Payment("C", 60)
        );
    }

    private String name;
    private int value;
    
    public Payment(String name, int value) {
        this.name = name;
        this.value = value;
    }
    public String getName() { return name; }
    public int getValue() { return value; }
}

Now, for each user, we want to find the number of payments, the total amount paid, or the maximum amount. In SQL, it seems that you can easily find it by combining GROUP BY and window function, but in Java Stream, how should you write it?

select name, count(*) from payment group by name;
select name, sum(value) from payment group by name;
select name, max(value) from payment group by name;

The overall policy is to use Collectors.groupingBy. First, the number of payments, that is, group-by-count.

var counts = payments.stream().collect(Collectors.groupingBy(Payment::getName, Collectors.counting()));
counts.entrySet().stream().map(e -> e.getKey() + "=" + e.getValue()).forEach(System.out::println);
// A=1
// B=2
// C=3

There is a well-known method called Collectors.counting, so it seems good to use it. The total amount paid next. The point is group-by-sum, but this also has a method with a descriptive name of Collectors.summingInt, so just use this.

var sums = payments.stream().collect(Collectors.groupingBy(Payment::getName, Collectors.summingInt(Payment::getValue)));
sums.entrySet().stream().map(e -> e.getKey() + "=" + e.getValue()).forEach(System.out::println);
// A=10
// B=50
// C=150

Finally, "maximum amount paid" = group-by-max, but I personally feel that it is the most controversial. As a basic policy, it seems to be quick to use Collectors.maxBy.

var maxs = payments.stream().collect(Collectors.groupingBy(Payment::getName, Collectors.maxBy(Comparator.comparingInt(Payment::getValue))));
maxs.entrySet().stream().map(e -> e.getKey() + "=" + e.getValue().get().getValue()).forEach(System.out::println);
// A=10
// B=30
// C=60

At this time, the type of the variable maxs isMap <String, Optional <Payment >>. ʻOptional is like a marker to remind you that it may be null, but here in business logic the value of maxs cannot be null. In short, ʻOptional here doesn't make much sense, so I want to get rid of it. In other words, I want to set the type of maxs toMap <String, Payment>, but in such a case, it seems to be quick to do as follows.

var maxs = payments.stream().collect(Collectors.groupingBy(Payment::getName, Collectors.collectingAndThen(Collectors.maxBy(Comparator.comparing(Payment::getValue)), Optional::get)));
maxs.entrySet().stream().map(e -> e.getKey() + "=" + e.getValue().getValue()).forEach(System.out::println);
// A=10
// B=30
// C=60

However, at this point, the feeling of black magic begins to be good, so I want to moderate it (´ ・ ω ・ `)

Recommended Posts

I want to perform Group By processing with Stream (group-by-count, group-by-sum, group-by-max)
I want to perform aggregation processing with spring-batch
I want to perform asynchronous processing and periodic execution with Rail !!!
[Java] I want to perform distinct with the key in the object
I want to use DBViewer with Eclipse 2018-12! !!
I want to sort by tab delimited by ruby
I want to use java8 forEach with index
I want to play with Firestore from Rails
[Rails] I want to load CSS with webpacker
I want to delete files managed by Git
I tried what I wanted to try with Stream softly.
List processing to understand with pictures --java8 stream / javaslang-
I learned stream (I want to convert List to Map <Integer, List>)
I want to dark mode with the SWT app
I want to monitor a specific file with WatchService
I want to get along with Map [Java beginner]
I want to redirect sound from Ubuntu with xrdp
I want to write a loop that references an index with Java 8's Stream API
I tried to increase the processing speed with spiritual engineering
I want to push an app made with Rails 6 to GitHub
I want to make a list with kotlin and java!
I want to make a function with kotlin and java!
[Rails] I tried to implement batch processing with Rake task
Even in Java, I want to output true with a == 1 && a == 2 && a == 3
I want to manually send an authorization email with Devise
List processing to understand with pictures --java8 stream / javaslang --bonus
I want to distinct the duplicated data with has_many through
I want to implement various functions with kotlin and java!
I want to pass the startup command to postgres with docker-compose.
[Java] I want to test standard input & standard output with JUnit
I want to convert characters ...
I want to make a button with a line break with link_to [Note]
I want to connect SONY headphones WH-1000XM4 with LDAC on ubuntu 20.04! !!
I want to convert an array to Active Record Relation with Rails
I want to hook log file generation / open with log4j # FileAppender
I want to add a browsing function with ruby on rails
I want to understand the flow of Spring processing request parameters
I want to return to the previous screen with kotlin and java!
I want to INSERT Spring Local Time with MySQL Time (also milliseconds)
I want to avoid OutOfMemory when outputting large files with POI
I want to operate cron with GUI, so I will install Dkron
I want to limit the input by narrowing the range of numbers
[Rails] I want to add data to Params when transitioning with link_to
I want to extract between character strings with a regular expression
I tried to make a group function (bulletin board) with Rails