Java 8 nested grouping
I have 2 questions that I cannot resolve. First, I need a way to have dynamic nested grouping, where there can be 1-n nested groups that the user can pass.
The second problem is that I need the results to be flattened when the keys are concat and not 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 has a concat of the 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"
]
}
Note that keys are concat, not nested maps within maps.
I'm trying to create a grouping where it can be dynamic with no luck:
fakeData.stream()
.collect(groupingBy(map -> map.get("ip"),
groupingBy(map -> map.get("uuid"),
... nested "n" times)));
This is the interface I'm trying to implement:
public Map<String, List<Map<String, String>>> doGrouping(List<String> columns,
List<Map<String, String>> data);
source to share
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())));
}
I first passed data
in which is a list of cards. I immediately assembled the stream into a map of lists using Collectors.groupingBy
a key that is calculated for each element of the stream.
Calculating the key was the tricky part. To do this, I passed the given list columns
and I converted each of these columns to the corresponding value of the stream element. I did it with a method Stream.map
passing elem::map
as a display function. Finally, I have compiled this inner stream into one line using Collectors.joining
which effectively concatenates each element of the stream into a final string.
Edit: the code above works well if all elements 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())));
In this version, the null
elements from the stream are distinguished , which can occur if any element of the map does not contain the key specified in the list columns
.
source to share
Not sure about using streams, but if you prefer the simple Java way it is much easier. If I understand your problem correctly, this is the method you want to build. You may need to tweak your settings a little to get it done 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}]}
source to share