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
        }
}
user3819295
  • 861
  • 6
  • 19
  • 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". – Karol Dowbecki Apr 10 '19 at 11:29
  • Yes, I have a List, 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') – user3819295 Apr 10 '19 at 15:23

2 Answers2

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.

Samuel Philipp
  • 10,631
  • 12
  • 36
  • 56
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()))
);
Karol Dowbecki
  • 43,645
  • 9
  • 78
  • 111
  • Interesting, thank you, let me check this out. How can I go through this map in order to print the results? – user3819295 Apr 10 '19 at 16:10
  • You can just `System.out.println(result)` to see what's inside if you don't need a particular format. – Karol Dowbecki Apr 10 '19 at 16:13
  • 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. – user3819295 Apr 10 '19 at 17:02