【问题标题】:Java8 streams : Transpose map with values as listJava8流:将值作为列表的转置映射
【发布时间】:2016-07-20 01:49:57
【问题描述】:

我的映射键为字符串,值为列表。列表可以有 10 个唯一值。我需要将此映射转换为键为整数,值为列表。示例如下:

输入:

“Key-1”:1,2,3,4

“Key-2”:2,3,4,5

“Key-3”:3,4,5,1

预期输出:

1 : "Key-1","Key-3"

2 : "Key-1","Key-2"

3 : “Key-1”、“Key-2”、“Key-3”

4 : “Key-1”、“Key-2”、“Key-3”

5 : "Key-2", "Key-3"

我知道使用 for 循环我可以实现这一点,但我需要知道这可以通过 java8 中的流/lamda 来完成。

-谢谢。

【问题讨论】:

    标签: java java-8 java-stream


    【解决方案1】:

    一个想法可能是从原始映射生成所有值-键对,然后按这些值对键进行分组:

    import java.util.AbstractMap.SimpleEntry;
    
    import static java.util.stream.Collectors.groupingBy;
    import static java.util.stream.Collectors.mapping;
    import static java.util.stream.Collectors.toList;
    
    ...
    
    Map<Integer, List<String>> transposeMap =
        map.entrySet()
           .stream()
           .flatMap(e -> e.getValue().stream().map(i -> new SimpleEntry<>(i, e.getKey())))
           .collect(groupingBy(Map.Entry::getKey, mapping(Map.Entry::getValue, toList())));
    

    【讨论】:

      【解决方案2】:

      Alexis’ answer 包含此类任务的通用解决方案,使用flatMap 和临时持有者用于组合键和展平值。避免创建临时持有者对象的唯一替代方法是重新实现groupingBy 收集器的逻辑并将值列表上的循环 逻辑插入累加器函数:

      Map<Integer, List<String>> mapT = map.entrySet().stream().collect(
          HashMap::new,
          (m,e) -> e.getValue().forEach(
                       i -> m.computeIfAbsent(i,x -> new ArrayList<>()).add(e.getKey())),
          (m1,m2) -> m2.forEach((k,v) -> m1.merge(k, v, (l1,l2)->{l1.addAll(l2); return l1;})));
      

      【讨论】:

        【解决方案3】:

        这有点吓人(我通常会尝试将其分解以使其更具可读性)但您可以这样做:

        Map<Integer, List<String>> transposeMap = new HashMap<>();
        
        map.forEach((key, list) -> list.stream().forEach(
            elm -> transposeMap.put(elm,
                transposeMap.get(elm) == null ? Arrays.asList(key) : (Stream.concat(transposeMap.get(elm).stream(),
                    Arrays.asList(key).stream()).collect(Collectors.toList())))));
        

        假设Map&lt;String, List&lt;Integer&gt;&gt; map 是您想要转置的原始地图。 transposeMap 会有你需要的转置地图。

        【讨论】:

        • 流更适合无状态操作。虽然这个答案是正确的,但它更适合命令式风格。这基本上是 OP 的循环策略,但用流的 forEach 重新标记。
        • forEach 不属于streams。在Iterable中声明,list.stream().forEach应该是list.forEach
        • @Jean-François Savard:流也有一个 forEach 方法,尽管语义略有不同。
        • @Holger 我知道,但我一直强调在stream 中使用 forEach 的目的是在经过漫长的管道后获得一些 潜在 性能(并行性)您已经流式传输的位置。如果您只想循环播放Collection,是否真的有流媒体的意义? (我不这么认为)
        • @Jean-François Savard:使用这种更复杂的变体(除了测试)没有意义,但这与“应该是[更简单的形式]”不同。
        【解决方案4】:

        你可以这样实现

        假设我有带有 Gender 和 Age 的 Person 类。我想以这种形式获得它

        Map<SEX,List<Person>> 
        

        我会简单地写

        Map<SEX,List<Person>> map =  personList.stream()
        
                                   .collect(Collectors.groupingBy(Person::getGender));
        

        它会让我得到类似下面的东西(一个键对抗多个值)

        key:MALE
        age31sexMALE
        age28sexMALE
        
        key:FEMALE
        age40sexFEMALE
        age44sexFEMALE
        

        【讨论】:

          【解决方案5】:

          with teeing 您可以分别处理 2 个流中的键和值
          从 Java 12 开始

          Map<Integer, List<String>> to = from.entrySet().stream()
            .collect(teeing(flatMapping(e -> e.getValue().stream(), toList()),
                flatMapping(e -> (Stream<String>)e.getValue().stream().map(i -> e.getKey()), toList()),
                (k, v) -> {
                  return IntStream.range(0, k.size()).boxed().collect(
                      groupingBy(i -> k.get(i), mapping(i -> v.get(i), toList())));
                }));
          

          【讨论】:

            猜你喜欢
            • 2021-06-29
            • 1970-01-01
            • 2018-07-10
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2018-11-13
            • 1970-01-01
            相关资源
            最近更新 更多