4

How would you use Collectors in order to group by multiple fields at 2nd level. for example:

"someList": {
        "firstLevelElementX": {
              "secondLevelElementW": 2,
              "secondLevelElementZ": 3,
              "secondLevelElementK": 7
        },
        "firstLevelElementY": {
              "secondLevelElementW": 1,
              "secondLevelElementZ": 3,
              "secondLevelElementK": 10
        }
}

I tried to create a class with "secondLevel" elements, and group-by this class, but couldn't make it work:

@Data
@AllArgsConstructor
public class someClass{
    private String firstLevelElement;
    private Long secondLevelElementW;
    private Long secondLevelElementZ;
    private Long secondLevelElementK= 0L;

}

And this is how I do it :

Map<String,Map<String,Long>> someList =
            events.stream().collect(
                Collectors.groupingBy(
                    someDAO::getFirstLevelElement,
                    Collectors.groupingBy(
                        someClass::getSecondLevelFields,
                        Collectors.counting())
                )
            );

There are some solutions which suggest n-level grouping (using chain), but I prefer to make it less complicated and more clean if possible.

Edit

I will try to provide a better example in order to clarify myself, this is the list that I have :

{
      "date": "2019-04-08 08:28:01.0",
      "source": "maint",
      "severity": "HARMLESS",
      "site": "USA",
      "hostname": "usaHost"
    },
    {
      "date": "2019-04-08 08:28:01.0",
      "source": "CPU_Checker",
      "severity": "MINOR",
      "site": "GERMANY",
      "hostname": "germanyHost"
    },
    {
      "date": "2019-04-02 08:28:01.0",
      "source": "maint",
      "severity": "HARMLESS",
      "site": "USA",
      "hostname": "anotherUsaHost"
    }

I want to use group-by on 'source' and 'severity' at the second level, so the output should look like this :

"eventList": {
        "USA": {
              "maint": 2,
              "HARMLESS": 2
        },
        "GERMANY": {
              "CPU_checker": 1,
              "MINOR": 1
        }
}
2
  • Are you trying to sum up values from secondLevelElementW, secondLevelElementZ and secondLevelElementK? What is someClass::getSecondLevelFields method? It's unclear what are you trying to do when you say "group by multiple fields at 2nd level". Commented Apr 10, 2019 at 11:29
  • Yes, I have a List<someDAO>, and each DAO contains several fields (field1,field2,field3). I want field1 to be the element at the first level, and under it at the second level, I want to sum up field2 and field3 values. (Please see example above of 'someList') Commented Apr 10, 2019 at 15:23

2 Answers 2

2

If you can use Java 9 or higher, you can use Collectors.flatMapping() to achieve that:

Map<String, Map<String, Long>> eventList = list.stream()
        .collect(Collectors.groupingBy(MyObject::getSite, Collectors.flatMapping(
                o -> Stream.of(o.getSource(), o.getSeverity()),
                Collectors.groupingBy(Function.identity(), Collectors.counting())
        )));

The result will be this:

{
    USA={maint=2, HARMLESS=2}, 
    GERMANY={CPU_Checker=1, MINOR=1}
}

If you are not able to use Java 9 you can implement the flatMapping() function yourself. You can take a look at Java 9 Collectors.flatMapping rewritten in Java 8, which should help you with that.

Sign up to request clarification or add additional context in comments.

Comments

1

You could sum up the second level field values in the second groupingBy collector:

Map<String, Map<Long, List<someClass>>> result = elements.stream().collect(
        groupingBy(someClass::getFirstLevelElement,
            groupingBy(s -> 
                s.getSecondLevelElementK() + s.getSecondLevelElementW() + s.getSecondLevelElementZ()))
);

3 Comments

Interesting, thank you, let me check this out. How can I go through this map in order to print the results?
You can just System.out.println(result) to see what's inside if you don't need a particular format.
Thank you for your reply, but unfortunately I can't accept it as it is not answering the question. I made an edit in order to explain my self better.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.