2017-05-05 108 views
3

我有两个问题,我似乎无法解决。第一个是我需要一种方式来进行动态嵌套分组,其中可以有1-n个嵌套组,用户可以通过。Java 8嵌套分组

第二个问题是我需要将结果展平concat而不是嵌套。

我的示例输入看起来像这样:

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

最终结果有地图键的的concat:

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

通知的键的concat而不是映射内嵌套的地图。

我试图创建一个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

+0

你是说如果'doGrouping'的'columns'参数包含'N'个元素,那么你需要通过每个'N'元素创建嵌套的组?而且你希望每个组的键都被连接而不是嵌套? –

+2

我在输出或接口中看不到任何会提示n级嵌套的东西。它看起来像你只是在顶层分组的多个键。 – shmosel

回答

5

尝试以下操作:

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

首先,我流了data,这是地图的列表。我立即使用Collectors.groupingBy和流的每个元素计算出的一个密钥,将流收集到一个列表映射表中。

计算密钥是棘手的部分。为此,我给出了给定的列表columns,并且将这些列中的每一列都转换为流的元素的相应值。我通过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()))); 

这个版本过滤掉从流,如果一些地图元素不包含在columns列表中指定的关键可能出现null元素。

+2

您也可以使用'c - > elem.getOrDefault(c,“”)'。 – shmosel

+0

@shmosel你是对的,但是会传播(是正确的词?)空字符串到终端操作。在这种情况下,它会起作用,因为加入一个空字符串对最终加入的字符串没有影响,但是我觉得这并不是保持流中元素仅仅在收集期间被过滤掉的最佳方式。 –

+1

完美!非常感谢费德里科。那就是诀窍。 – Xuan

2

不确定使用流,但如果你喜欢普通的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

Upvoted,因为这是一个正确的答案。只是一个建议...你可以把你的'if/else'块改成'output.computeIfAbsent(key,k - > new ArrayList <>()).add(map)'。 –

+0

这似乎是很好的建议。现在通勤,稍后再尝试并重新发布 –