Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Java 8 nested groupingby

I have 2 issues that I can't seem to solve. The first one is I need a way to have dynamic nested grouping by where there could be 1-n nested groups that the user can pass in.

The second issue is that I need the results to be flatten where the keys are concat rather than nested.

My example input looks like this:

    List<Map<String, String>> fakeData = new LinkedList<>();
    Map<String, String> data1 = new HashMap<>();
    data1.put("ip","10.0.1.0");
    data1.put("uid","root");
    data1.put("group","admin");
    fakeData.add(data1);

    Map<String, String> data2 = new HashMap<>();
    data2.put("ip","10.0.1.1");
    data2.put("uid","tiger");
    data2.put("group","user");
    fakeData.add(data2);

    Map<String, String> data3 = new HashMap<>();
    data3.put("ip","10.0.1.1");
    data3.put("uid","woods");
    data3.put("group","user");
    fakeData.add(data3);

The end result have a concat of map keys:

{
  "10.0.1.1user": [
    {
      "uid": "tiger",
      "ip": "10.0.1.1",
      "group": "user"
    },
    {
      "uid": "woods",
      "ip": "10.0.1.1",
      "group": "user"
    }
  ],
  "10.0.1.0admin": [
    "uid": "root",
    "ip": "10.0.1.0",
    "group": "admin"
  ]
}

Notice the keys are concat rather than nested maps within maps.

I'm trying to create a groupingby where it can be dynamic without any luck:

 fakeData.stream()
                .collect(groupingBy(map -> map.get("ip"),
                        groupingBy(map -> map.get("uuid"),
                                ... nested "n" times)));

This is the interface that I'm trying to implement:

public Map<String, List<Map<String, String>>> doGrouping(List<String> columns, 
                                                   List<Map<String, String>> data);
like image 463
Xuan Avatar asked Mar 09 '23 04:03

Xuan


2 Answers

Try the following:

public Map<String, List<Map<String, String>>> doGrouping(
        List<String> columns,
        List<Map<String, String>> data) {

    return data.stream()
        .collect(Collectors.groupingBy(
            elem -> columns.stream()
                .map(elem::get)
                .collect(Collectors.joining())));
}

First, I streamed the data, which is a list of maps. I immediately collected the stream to a map of lists using Collectors.groupingBy with a key that is calculated for each element of the stream.

Calculating the key was the tricky part. For this, I streamed the given list of columns and I transformed each one of these columns into its corresponding value of the element of the stream. I did this by means of the Stream.map method, passing elem::map as the mapping function. Finally, I collected this inner stream into a single string by using Collectors.joining, which concatenates each element of the stream into a final string in an efficient manner.

Edit: The code above works well if all the elements of columns exist as keys of the map elements in data. To be more secure use the following:

return data.stream()
    .collect(Collectors.groupingBy(
        elem -> columns.stream()
            .map(elem::get)
            .filter(Objects::nonNull)
            .collect(Collectors.joining())));

This version filters out null elements from the stream, which might occur if some map element does not contain a key specified in the columns list.

like image 80
fps Avatar answered Mar 17 '23 09:03

fps


Not sure about using streams, but if you prefer plain java way, it is a lot easier. If I correctly understand your problem here is the method you want to build. You may be required to tweak in a bit to make it faster.

public Map<String, List<Map<String, String>>> doGrouping(List<String> columns, List<Map<String, String>> data) {
    Map<String, List<Map<String, String>>> output = new HashMap<>();
    for (Map<String, String> map : data) {
        String key = "";
        for(String column :  columns) key += "".equals(key) ? (map.get(column)) : (":" + map.get(column));
        output.computeIfAbsent(key, k -> Arrays.asList(map));
    }
    return output;
}

Test:

doGrouping(Arrays.asList("ip", "group"), fakeData)
>> {10.0.1.1:user=[{uid=tiger, ip=10.0.1.1, group=user}, {uid=woods, ip=10.0.1.1, group=user}], 10.0.1.0:admin=[{uid=root, ip=10.0.1.0, group=admin}]}

doGrouping(Arrays.asList("group"), fakeData)
>> {admin=[{uid=root, ip=10.0.1.0, group=admin}], user=[{uid=tiger, ip=10.0.1.1, group=user}, {uid=woods, ip=10.0.1.1, group=user}]}
like image 31
Amit Phaltankar Avatar answered Mar 17 '23 11:03

Amit Phaltankar