Search code examples
sortinggroup-byjava-8java-streamflatmap

Java 8 Map & Stream - Sorting by value desc and group


I need to sort by descending order the response data by count sum after they grouped up in Java 8.

I have a view table query result like:

count(bigint) category(varchar) myEnum(int)
10 A 0
35 B 0
30 A 1
25 C 1

I have a projection interface for the view table for customizing the result of JPA Queries.

public interface MyView {

    Long getCount();

    String getCategory();

    MyEnum getMyEnum();
} 

And this one is my DTO for the response:

public class MyResponse {

    private List<Long> count = new ArrayList<>();

    private List<String> categories = new ArrayList<>();

    private List<List<MyEnum>> myEnums = new ArrayList<>();

    // ctors, getters and setters
} 

I need to group the data by category and sum the total counts, then collect the Enum types in a list for each category. According to this, the count of category A should be 40 and has 0,1 enum types. So, client-side needs to get the result like following after the get request:

{
  "count": [
    40,
    35,
    25
  ],
  "categories": [
    "A",
    "B",
    "C"
  ],
  "myEnums": [
    [
      "ENUM_A",
      "ENUM_B"
    ],
    [
      "ENUM_A",
    ],
    [
      "ENUM_B",
    ]
  ]
}

This is the related function in my service:

    public MyResponse foo() {
        // This list have the list of MyView.
        List<MyView> myList = myRepository.getCountView());

        Map<String, List<MyView>> myMap = myList.stream().collect(Collectors.groupingBy(MyView::getCategory));

        MyResponse response = new MyResponse();

        myMap.forEach((key, value) -> {
                    response.getCategories().add(key);
                    response.getCount().add(value.stream().mapToLong(MyView::getCount).sum());
                    response.getMyEnums().add(value.stream().flatMap(v -> Stream.of(v.getMyEnum())).collect(Collectors.toList()));
                }
        );

        return response;
    }

Alright, I have completed grouping by category and summing the counts but couldn't be able to sort them. The result is true but I need to order the data by total count by descending. I would be appreciate to you for any suggestions. Thanks!


Solution

  • You can map each entry in the map to a CategorySummary, then reduce to a MyResponse:

    List<MyView> myList = Arrays.asList(
            new MyView(30, "B", MyEnum.ENUM_B),
            new MyView(10, "A", MyEnum.ENUM_A),
            new MyView(35, "B", MyEnum.ENUM_A),
            new MyView(25, "C", MyEnum.ENUM_B)
    );
    
    Map<String, List<MyView>> myMap = myList.stream()
            .collect(Collectors.groupingBy(MyView::getCategory, TreeMap::new, Collectors.toList()));
    
    MyResponse myResponse = myMap.entrySet().stream()
            .map(e -> new CategorySummary(
                    e.getValue().stream().mapToLong(MyView::getCount).sum(),
                    e.getKey(),
                    e.getValue().stream().flatMap(v -> Stream.of(v.getMyEnum())).collect(Collectors.toList())
            ))
            .sorted(Comparator.comparing(CategorySummary::getCount).reversed())
            .reduce(new MyResponse(),
                    (r, category) -> {
                        r.addCategory(category);
                        return r;
                    }, (myResponse1, myResponse2) -> { // the combiner won't be invoked because it's sequential stream.
                        myResponse1.getMyEnums().addAll(myResponse2.getMyEnums());
                        myResponse1.getCount().addAll(myResponse2.getCount());
                        myResponse1.getCategories().addAll(myResponse2.getCategories());
                        return myResponse1;
                    });
    
    System.out.println(new ObjectMapper().writeValueAsString(myResponse));
    

    Output:

    {
        "count": [65, 25, 10],
        "categories": ["B", "C", "A"],
        "myEnums": [
            ["ENUM_B", "ENUM_A"],
            ["ENUM_B"],
            ["ENUM_A"]
        ]
    }
    

    CategorySummary:

    @AllArgsConstructor
    @Getter
    public class CategorySummary {
        private long count;
        private String name;
        private List<MyEnum> myEnums;
    }
    

    I also had to add an additional method in MyResponse class:

    public void addCategory(CategorySummary categorySummary){
        this.count.add(categorySummary.getCount());
        this.categories.add(categorySummary.getName());
        this.myEnums.add(categorySummary.getMyEnums());
    }