【问题标题】:Flatten a Map<Integer, List<String>> to Map<String, Integer> with stream and lambda使用流和 lambda 将 Map<Integer, List<String>> 展平为 Map<String, Integer>
【发布时间】:2017-03-21 23:10:31
【问题描述】:

我想展平一个Map,它将Integer 键与String 列表相关联,而不会丢失键映射。 我很好奇,好像使用 streamlambda 这样做是可能和有用的。

我们从这样的开始:

Map<Integer, List<String>> mapFrom = new HashMap<>();

让我们假设 mapFrom 填充在某个地方,看起来像:

1: a,b,c
2: d,e,f
etc.

我们还假设列表中的值是唯一的。

现在,我想“展开”它以获得第二张地图,例如:

a: 1
b: 1
c: 1
d: 2
e: 2
f: 2
etc.

我可以这样做(或者非常类似,使用foreach):

Map<String, Integer> mapTo = new HashMap<>();
for (Map.Entry<Integer, List<String>> entry: mapFrom.entrySet()) {
    for (String s: entry.getValue()) {
        mapTo.put(s, entry.getKey());
    }
}

现在让我们假设我想使用 lambda 而不是嵌套的 for 循环。我可能会这样做:

Map<String, Integer> mapTo = mapFrom.entrySet().stream().map(e -> {
    e.getValue().stream().?
    // Here I can iterate on each List, 
    // but my best try would only give me a flat map for each key, 
    // that I wouldn't know how to flatten.
}).collect(Collectors.toMap(/*A String value*/,/*An Integer key*/))

我也尝试了flatMap,但我认为这不是正确的方法,因为虽然它帮助我摆脱了维度问题,但我在这个过程中失去了关键。

简而言之,我的两个问题是:

  • 是否可以使用streamslambda 来实现这一点?
  • 这样做是否有用(性能、可读性)?

【问题讨论】:

标签: java lambda java-8 java-stream


【解决方案1】:

与 Java 9 的先前答案相同:

Map<String, Integer> mapTo = mapFrom.entrySet()
        .stream()
        .flatMap(entry -> entry.getValue()
                .stream()
                .map(s -> Map.entry(s, entry.getKey())))
        .collect(toMap(Entry::getKey, Entry::getValue));

【讨论】:

    【解决方案2】:

    您应该使用flatMap,如下所示:

    entrySet.stream()
            .flatMap(e -> e.getValue().stream()
                           .map(s -> new SimpleImmutableEntry(e.getKey(), s)));
    

    SimpleImmutableEntryAbstractMap 中的一个嵌套类。

    【讨论】:

    • 感谢您的快速(和良好)回答。请注意,末尾缺少右括号。如果能对我的第二个问题有所了解,我将不胜感激。
    【解决方案3】:

    您需要使用flatMap 将值展平到一个新流中,但是由于您仍然需要将原始键收集到Map 中,因此您必须映射到一个保存键和值的临时对象,例如

    Map<String, Integer> mapTo = mapFrom.entrySet().stream()
           .flatMap(e->e.getValue().stream()
                        .map(v->new AbstractMap.SimpleImmutableEntry<>(e.getKey(), v)))
           .collect(Collectors.toMap(Map.Entry::getValue, Map.Entry::getKey));
    

    Map.Entry 是不存在的元组类型的替代,任何其他能够容纳两个不同类型对象的类型就足够了。

    另一种不需要这些临时对象的方法是自定义收集器:

    Map<String, Integer> mapTo = mapFrom.entrySet().stream().collect(
        HashMap::new, (m,e)->e.getValue().forEach(v->m.put(v, e.getKey())), Map::putAll);
    

    这与toMap 的不同之处在于以静默方式覆盖重复键,而没有合并功能的toMap 如果存在重复键,则会抛出异常。基本上,这个自定义收集器是一个具有并行能力的变体

    Map<String, Integer> mapTo = new HashMap<>();
    mapFrom.forEach((k, l) -> l.forEach(v -> mapTo.put(v, k)));
    

    但请注意,即使输入图非常大,此任务也不会从并行处理中受益。只有当流管道中有额外的计算密集型任务可以从 SMP 中受益时,才有机会从并行流中受益。因此,简洁、顺序的 Collection API 解决方案或许更可取。

    【讨论】:

    • 非常感谢您提供信息丰富且完整的答案。
    • 涵盖 360 个视角的好答案 :))
    【解决方案4】:

    希望这会以最简单的方式完成。 :))

    mapFrom.forEach((key, values) -> values.forEach(value -> mapTo.put(value, key)));
    

    【讨论】:

    • 这行得通,谢谢。不幸的是,这不是我想要的。也许我应该在问题中更清楚地说明我已经知道如何使用foreach,并且我想使用stream
    • @Vongo 你提到的更好:))
    【解决方案5】:

    这应该有效。请注意,您丢失了 List 中的一些密钥。

    Map<Integer, List<String>> mapFrom = new HashMap<>();
    Map<String, Integer> mapTo = mapFrom.entrySet().stream()
            .flatMap(integerListEntry -> integerListEntry.getValue()
                    .stream()
                    .map(listItem -> new AbstractMap.SimpleEntry<>(listItem, integerListEntry.getKey())))
            .collect(Collectors.toMap(AbstractMap.SimpleEntry::getKey, AbstractMap.SimpleEntry::getValue));
    

    【讨论】:

    • 这行得通,谢谢。请注意,在我的情况下,我们不会丢失 List 中的键,因为正如我在问题中指定的那样,List 值是唯一的(通过唯一,我的意思是两个 List 之间永远不会有重复的值,也许这个陈述不清楚)。不过,关于第二个问题的见解也将不胜感激!
    猜你喜欢
    • 2019-12-06
    • 1970-01-01
    • 2022-09-30
    • 2023-03-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-01-28
    • 1970-01-01
    相关资源
    最近更新 更多