Java 8 嵌套分组

4

我有两个问题无法解决。第一个问题是我需要一种方法来实现动态嵌套分组,其中用户可以传递1-n个嵌套组。

第二个问题是我需要展平结果,其中键是连接而不是嵌套的。

我的示例输入如下:

    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);

最终结果将会是一个映射键的连接:
{
  "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"
  ]
}

请注意,键是连接而不是嵌套的映射。
我试图创建一个可以动态分组的groupingby,但一直没有成功:
 fakeData.stream()
                .collect(groupingBy(map -> map.get("ip"),
                        groupingBy(map -> map.get("uuid"),
                                ... nested "n" times)));

这是我正在尝试实现的接口:
public Map<String, List<Map<String, String>>> doGrouping(List<String> columns, 
                                                   List<Map<String, String>> data);

1
我不确定我完全理解你要实现的方法的输入/输出。它似乎输出类型不包含任何“n”个嵌套分组。你能展示一下你打算如何使用你的示例数据调用doGrouping,以及期望的输出是什么吗? - 4castle
你的意思是说,如果 doGroupingcolumns 参数包含 N 个元素,那么你需要通过每个 N 元素创建嵌套分组?并且你希望每个分组的键被连接而不是嵌套? - fps
2
我在你的输出或接口中没有看到任何暗示n级别嵌套的迹象。看起来你只是在顶层按多个键进行分组。 - shmosel
1
我在你的代码中没有看到任何尝试进行字符串连接的操作。使用groupingBy(map -> map.get("ip") + map.get("uuid"))不好吗?对于键列表,也许可以使用listOfKeys.stream().map(map::get).collect(Collectors.joining()) - Ole V.V.
2个回答

6

请尝试以下操作:

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())));
}

首先,我将数据流(即映射列表)收集到一个映射列表中,使用的是带有按流元素计算的键的Collectors.groupingBy方法。
计算键是棘手的部分。为此,我对给定的列列表进行了流处理,并通过Stream.map方法将每个列转换为其相应的流元素值。我使用elem::map作为映射函数,最后使用Collectors.joining将这个内部流收集到单个字符串中,它以高效的方式将流的每个元素连接成一个最终字符串。
编辑:如果columns的所有元素都存在于data的映射元素中,则上述代码有效。要更加安全,请使用以下代码:
return data.stream()
    .collect(Collectors.groupingBy(
        elem -> columns.stream()
            .map(elem::get)
            .filter(Objects::nonNull)
            .collect(Collectors.joining())));

这个版本从流中过滤出null元素,这可能是因为某些映射元素不包含在columns列表中指定的键。


2
你也可以使用 c -> elem.getOrDefault(c, "") - shmosel
@shmosel 你是正确的,但是这将把空字符串传播(这是正确的术语吗?)到终端操作。在这种情况下它会起作用,因为连接一个空字符串对最终连接的字符串没有影响,但我觉得在收集期间保留元素以进行过滤并不是最好的选择。 - fps

1

如果您不确定是否使用流,但如果您更喜欢普通的Java方式,那么这将变得容易得多。 如果我正确理解了您的问题,这是您想要构建的方法。您可能需要稍微调整一下以使其更快。

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

测试:

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
因为这是正确的答案,所以我点了个赞。只是一个建议...你可以将你的if/else块改成output.computeIfAbsent(key, k -> new ArrayList<>()).add(map) - fps
那似乎是个不错的建议。我现在正在通勤,稍后会尝试并重新发布。 - Amit Phaltankar

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接