4

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);
4
  • 1
    I'm not sure I fully understand the input/output of the method you're trying to implement. It doesn't appear that the output type contains any "n" nested groupings. Can you please show how you intend to call doGrouping with your example data, and what the expected output would be? Commented May 5, 2017 at 5:12
  • Are you saying that if the columns argument of doGrouping contains N elements, then you need to create nested groups by each one of its N elements? And that you want the key of each group to be concatenated rather than nested? Commented May 5, 2017 at 5:43
  • 2
    I don't see anything in your output or interface that would suggest n-level nesting. It looks like you're just grouping by multiple keys at the top level. Commented May 5, 2017 at 6:20
  • 1
    I see no attempt at concatenation in your code. Wouldn’t groupingBy(map -> map.get("ip") + map.get("uuid")) work? For a list of keys, maybe listOfKeys.stream().map(map::get).collect(Collectors.joining()). Commented May 5, 2017 at 8:19

2 Answers 2

6

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.

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

2 Comments

You can also use c -> elem.getOrDefault(c, "").
@shmosel You are correct, however that would propagate (is that the right term?) the empty string to the terminal operation. In this case it would work, because joining an empty string has no effect on the final joined string, however I felt it wasn't the best to keep elements in the stream just to be filtered out during collection.
1

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}]}

2 Comments

Upvoted, as this is a correct answer. Just a suggestion... You could change your if/else block to output.computeIfAbsent(key, k -> new ArrayList<>()).add(map).
That seems to be nice suggestion. commuting right now, will give it a try after a while and re-post

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.